001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.util;
018
019 import java.io.File;
020 import java.io.IOException;
021 import java.util.Iterator;
022 import java.util.Locale;
023 import java.util.Random;
024 import java.util.Stack;
025
026 import org.apache.commons.logging.Log;
027 import org.apache.commons.logging.LogFactory;
028
029 /**
030 * File utilities
031 */
032 public final class FileUtil {
033
034 private static final transient Log LOG = LogFactory.getLog(FileUtil.class);
035 private static final int RETRY_SLEEP_MILLIS = 10;
036 private static File defaultTempDir;
037
038 private FileUtil() {
039 }
040
041 /**
042 * Normalizes the path to cater for Windows and other platforms
043 */
044 public static String normalizePath(String path) {
045 // special handling for Windows where we need to convert / to \\
046 if (path != null && isWindows() && path.indexOf('/') >= 0) {
047 return path.replace('/', '\\');
048 }
049 return path;
050 }
051
052 public static boolean isWindows() {
053 String osName = System.getProperty("os.name").toLowerCase(Locale.US);
054 return osName.indexOf("windows") > -1;
055 }
056
057 public static File createTempFile(String prefix, String suffix) throws IOException {
058 return createTempFile(prefix, suffix, null);
059 }
060
061 public static File createTempFile(String prefix, String suffix, File parentDir) throws IOException {
062 File parent = (parentDir == null) ? getDefaultTempDir() : parentDir;
063
064 if (suffix == null) {
065 suffix = ".tmp";
066 }
067 if (prefix == null) {
068 prefix = "camel";
069 } else if (prefix.length() < 3) {
070 prefix = prefix + "camel";
071 }
072
073 // create parent folder
074 parent.mkdirs();
075
076 return File.createTempFile(prefix, suffix, parent);
077 }
078
079 /**
080 * Strip any leading separators
081 */
082 public static String stripLeadingSeparator(String name) {
083 if (name == null) {
084 return null;
085 }
086 while (name.startsWith("/") || name.startsWith(File.separator)) {
087 name = name.substring(1);
088 }
089 return name;
090 }
091
092 /**
093 * Strip first leading separator
094 */
095 public static String stripFirstLeadingSeparator(String name) {
096 if (name == null) {
097 return null;
098 }
099 if (name.startsWith("/") || name.startsWith(File.separator)) {
100 name = name.substring(1);
101 }
102 return name;
103 }
104
105 /**
106 * Strip any trailing separators
107 */
108 public static String stripTrailingSeparator(String name) {
109 if (name == null) {
110 return null;
111 }
112 while (name.endsWith("/") || name.endsWith(File.separator)) {
113 name = name.substring(0, name.length() - 1);
114 }
115 return name;
116 }
117
118 /**
119 * Strips any leading paths
120 */
121 public static String stripPath(String name) {
122 if (name == null) {
123 return null;
124 }
125 int pos = name.lastIndexOf('/');
126 if (pos == -1) {
127 pos = name.lastIndexOf(File.separator);
128 }
129 if (pos != -1) {
130 return name.substring(pos + 1);
131 }
132 return name;
133 }
134
135 public static String stripExt(String name) {
136 if (name == null) {
137 return null;
138 }
139 int pos = name.lastIndexOf('.');
140 if (pos != -1) {
141 return name.substring(0, pos);
142 }
143 return name;
144 }
145
146 /**
147 * Returns only the leading path (returns <tt>null</tt> if no path)
148 */
149 public static String onlyPath(String name) {
150 if (name == null) {
151 return null;
152 }
153 int pos = name.lastIndexOf('/');
154 if (pos == -1) {
155 pos = name.lastIndexOf(File.separator);
156 }
157 if (pos != -1) {
158 return name.substring(0, pos);
159 }
160 // no path
161 return null;
162 }
163
164 /**
165 * Compacts a path by stacking it and reducing <tt>..</tt>
166 */
167 public static String compactPath(String path) {
168 if (path == null) {
169 return null;
170 }
171
172 // only normalize path if it contains .. as we want to avoid: path/../sub/../sub2 as this can leads to trouble
173 if (path.indexOf("..") == -1) {
174 return path;
175 }
176
177 // only normalize if contains a path separator
178 if (path.indexOf(File.separator) == -1) {
179 return path;
180 }
181
182 Stack<String> stack = new Stack<String>();
183
184 String separatorRegex = File.separator;
185 if (FileUtil.isWindows()) {
186 separatorRegex = "\\\\";
187 }
188 String[] parts = path.split(separatorRegex);
189 for (String part : parts) {
190 if (part.equals("..") && !stack.isEmpty()) {
191 // only pop if there is a previous path
192 stack.pop();
193 } else {
194 stack.push(part);
195 }
196 }
197
198 // build path based on stack
199 StringBuilder sb = new StringBuilder();
200 for (Iterator<String> it = stack.iterator(); it.hasNext();) {
201 sb.append(it.next());
202 if (it.hasNext()) {
203 sb.append(File.separator);
204 }
205 }
206
207 return sb.toString();
208 }
209
210 private static synchronized File getDefaultTempDir() {
211 if (defaultTempDir != null && defaultTempDir.exists()) {
212 return defaultTempDir;
213 }
214
215 String s = System.getProperty("java.io.tmpdir");
216 File checkExists = new File(s);
217 if (!checkExists.exists()) {
218 throw new RuntimeException("The directory "
219 + checkExists.getAbsolutePath()
220 + " does not exist, please set java.io.tempdir"
221 + " to an existing directory");
222 }
223
224 // create a sub folder with a random number
225 Random ran = new Random();
226 int x = ran.nextInt(1000000);
227
228 File f = new File(s, "camel-tmp-" + x);
229 while (!f.mkdir()) {
230 x = ran.nextInt(1000000);
231 f = new File(s, "camel-tmp-" + x);
232 }
233
234 defaultTempDir = f;
235
236 // create shutdown hook to remove the temp dir
237 Thread hook = new Thread() {
238 @Override
239 public void run() {
240 removeDir(defaultTempDir);
241 }
242 };
243 Runtime.getRuntime().addShutdownHook(hook);
244
245 return defaultTempDir;
246 }
247
248 private static void removeDir(File d) {
249 String[] list = d.list();
250 if (list == null) {
251 list = new String[0];
252 }
253 for (String s : list) {
254 File f = new File(d, s);
255 if (f.isDirectory()) {
256 removeDir(f);
257 } else {
258 delete(f);
259 }
260 }
261 delete(d);
262 }
263
264 private static void delete(File f) {
265 if (!f.delete()) {
266 if (isWindows()) {
267 System.gc();
268 }
269 try {
270 Thread.sleep(RETRY_SLEEP_MILLIS);
271 } catch (InterruptedException ex) {
272 // Ignore Exception
273 }
274 if (!f.delete()) {
275 f.deleteOnExit();
276 }
277 }
278 }
279
280 public static boolean renameFile(File from, File to) {
281 // do not try to rename non existing files
282 if (!from.exists()) {
283 return false;
284 }
285
286 // some OS such as Windows can have problem doing rename IO operations so we may need to
287 // retry a couple of times to let it work
288 boolean renamed = false;
289 int count = 0;
290 while (!renamed && count < 3) {
291 if (LOG.isDebugEnabled() && count > 0) {
292 LOG.debug("Retrying attempt " + count + " to rename file from: " + from + " to: " + to);
293 }
294
295 renamed = from.renameTo(to);
296 if (!renamed && count > 0) {
297 try {
298 Thread.sleep(1000);
299 } catch (InterruptedException e) {
300 // ignore
301 }
302 }
303 count++;
304 }
305
306 if (LOG.isDebugEnabled() && count > 0) {
307 LOG.debug("Tried " + count + " to rename file: " + from + " to: " + to + " with result: " + renamed);
308 }
309 return renamed;
310 }
311
312 public static boolean deleteFile(File file) {
313 // do not try to delete non existing files
314 if (!file.exists()) {
315 return false;
316 }
317
318 // some OS such as Windows can have problem doing delete IO operations so we may need to
319 // retry a couple of times to let it work
320 boolean deleted = false;
321 int count = 0;
322 while (!deleted && count < 3) {
323 if (LOG.isDebugEnabled() && count > 0) {
324 LOG.debug("Retrying attempt " + count + " to delete file: " + file);
325 }
326
327 deleted = file.delete();
328 if (!deleted && count > 0) {
329 try {
330 Thread.sleep(1000);
331 } catch (InterruptedException e) {
332 // ignore
333 }
334 }
335 count++;
336 }
337
338
339 if (LOG.isDebugEnabled() && count > 0) {
340 LOG.debug("Tried " + count + " to delete file: " + file + " with result: " + deleted);
341 }
342 return deleted;
343 }
344
345 /**
346 * Is the given file an absolute file.
347 * <p/>
348 * Will also work around issue on Windows to consider files on Windows starting with a \
349 * as absolute files. This makes the logic consistent across all OS platforms.
350 *
351 * @param file the file
352 * @return <tt>true</ff> if its an absolute path, <tt>false</tt> otherwise.
353 */
354 public static boolean isAbsolute(File file) {
355 if (isWindows()) {
356 // special for windows
357 String path = file.getPath();
358 if (path.startsWith(File.separator)) {
359 return true;
360 }
361 }
362 return file.isAbsolute();
363 }
364
365 }