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.FileInputStream;
021 import java.io.FileOutputStream;
022 import java.io.IOException;
023 import java.nio.channels.FileChannel;
024 import java.util.Iterator;
025 import java.util.Locale;
026 import java.util.Random;
027 import java.util.Stack;
028
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
031
032 /**
033 * File utilities
034 */
035 public final class FileUtil {
036
037 public static final int BUFFER_SIZE = 128 * 1024;
038
039 private static final transient Logger LOG = LoggerFactory.getLogger(FileUtil.class);
040 private static final int RETRY_SLEEP_MILLIS = 10;
041 private static File defaultTempDir;
042
043 private FileUtil() {
044 }
045
046 /**
047 * Normalizes the path to cater for Windows and other platforms
048 */
049 public static String normalizePath(String path) {
050 if (path == null) {
051 return null;
052 }
053
054 if (isWindows()) {
055 // special handling for Windows where we need to convert / to \\
056 return path.replace('/', '\\');
057 } else {
058 // for other systems make sure we use / as separators
059 return path.replace('\\', '/');
060 }
061 }
062
063 public static boolean isWindows() {
064 String osName = System.getProperty("os.name").toLowerCase(Locale.US);
065 return osName.indexOf("windows") > -1;
066 }
067
068 public static File createTempFile(String prefix, String suffix) throws IOException {
069 return createTempFile(prefix, suffix, null);
070 }
071
072 public static File createTempFile(String prefix, String suffix, File parentDir) throws IOException {
073 File parent = (parentDir == null) ? getDefaultTempDir() : parentDir;
074
075 if (suffix == null) {
076 suffix = ".tmp";
077 }
078 if (prefix == null) {
079 prefix = "camel";
080 } else if (prefix.length() < 3) {
081 prefix = prefix + "camel";
082 }
083
084 // create parent folder
085 parent.mkdirs();
086
087 return File.createTempFile(prefix, suffix, parent);
088 }
089
090 /**
091 * Strip any leading separators
092 */
093 public static String stripLeadingSeparator(String name) {
094 if (name == null) {
095 return null;
096 }
097 while (name.startsWith("/") || name.startsWith(File.separator)) {
098 name = name.substring(1);
099 }
100 return name;
101 }
102
103 /**
104 * Does the name start with a leading separator
105 */
106 public static boolean hasLeadingSeparator(String name) {
107 if (name == null) {
108 return false;
109 }
110 if (name.startsWith("/") || name.startsWith(File.separator)) {
111 return true;
112 }
113 return false;
114 }
115
116 /**
117 * Strip first leading separator
118 */
119 public static String stripFirstLeadingSeparator(String name) {
120 if (name == null) {
121 return null;
122 }
123 if (name.startsWith("/") || name.startsWith(File.separator)) {
124 name = name.substring(1);
125 }
126 return name;
127 }
128
129 /**
130 * Strip any trailing separators
131 */
132 public static String stripTrailingSeparator(String name) {
133 if (ObjectHelper.isEmpty(name)) {
134 return name;
135 }
136
137 String s = name;
138
139 // there must be some leading text, as we should only remove trailing separators
140 while (s.endsWith("/") || s.endsWith(File.separator)) {
141 s = s.substring(0, s.length() - 1);
142 }
143
144 // if the string is empty, that means there was only trailing slashes, and no leading text
145 // and so we should then return the original name as is
146 if (ObjectHelper.isEmpty(s)) {
147 return name;
148 } else {
149 // return without trailing slashes
150 return s;
151 }
152 }
153
154 /**
155 * Strips any leading paths
156 */
157 public static String stripPath(String name) {
158 if (name == null) {
159 return null;
160 }
161 int posUnix = name.lastIndexOf('/');
162 int posWin = name.lastIndexOf('\\');
163 int pos = Math.max(posUnix, posWin);
164
165 if (pos != -1) {
166 return name.substring(pos + 1);
167 }
168 return name;
169 }
170
171 public static String stripExt(String name) {
172 if (name == null) {
173 return null;
174 }
175 int pos = name.lastIndexOf('.');
176 if (pos != -1) {
177 return name.substring(0, pos);
178 }
179 return name;
180 }
181
182 /**
183 * Returns only the leading path (returns <tt>null</tt> if no path)
184 */
185 public static String onlyPath(String name) {
186 if (name == null) {
187 return null;
188 }
189
190 int posUnix = name.lastIndexOf('/');
191 int posWin = name.lastIndexOf('\\');
192 int pos = Math.max(posUnix, posWin);
193
194 if (pos > 0) {
195 return name.substring(0, pos);
196 } else if (pos == 0) {
197 // name is in the root path, so extract the path as the first char
198 return name.substring(0, 1);
199 }
200 // no path in name
201 return null;
202 }
203
204 /**
205 * Compacts a path by stacking it and reducing <tt>..</tt>
206 */
207 public static String compactPath(String path) {
208 if (path == null) {
209 return null;
210 }
211
212 // only normalize path if it contains .. as we want to avoid: path/../sub/../sub2 as this can leads to trouble
213 if (path.indexOf("..") == -1) {
214 return path;
215 }
216
217 // only normalize if contains a path separator
218 if (path.indexOf(File.separator) == -1) {
219 return path;
220 }
221
222 Stack<String> stack = new Stack<String>();
223
224 String separatorRegex = File.separator;
225 if (FileUtil.isWindows()) {
226 separatorRegex = "\\\\";
227 }
228 String[] parts = path.split(separatorRegex);
229 for (String part : parts) {
230 if (part.equals("..") && !stack.isEmpty()) {
231 // only pop if there is a previous path
232 stack.pop();
233 } else {
234 stack.push(part);
235 }
236 }
237
238 // build path based on stack
239 StringBuilder sb = new StringBuilder();
240 for (Iterator<String> it = stack.iterator(); it.hasNext();) {
241 sb.append(it.next());
242 if (it.hasNext()) {
243 sb.append(File.separator);
244 }
245 }
246
247 return sb.toString();
248 }
249
250 private static synchronized File getDefaultTempDir() {
251 if (defaultTempDir != null && defaultTempDir.exists()) {
252 return defaultTempDir;
253 }
254
255 String s = System.getProperty("java.io.tmpdir");
256 File checkExists = new File(s);
257 if (!checkExists.exists()) {
258 throw new RuntimeException("The directory "
259 + checkExists.getAbsolutePath()
260 + " does not exist, please set java.io.tempdir"
261 + " to an existing directory");
262 }
263
264 // create a sub folder with a random number
265 Random ran = new Random();
266 int x = ran.nextInt(1000000);
267
268 File f = new File(s, "camel-tmp-" + x);
269 while (!f.mkdir()) {
270 x = ran.nextInt(1000000);
271 f = new File(s, "camel-tmp-" + x);
272 }
273
274 defaultTempDir = f;
275
276 // create shutdown hook to remove the temp dir
277 Thread hook = new Thread() {
278 @Override
279 public void run() {
280 removeDir(defaultTempDir);
281 }
282 };
283 Runtime.getRuntime().addShutdownHook(hook);
284
285 return defaultTempDir;
286 }
287
288 private static void removeDir(File d) {
289 String[] list = d.list();
290 if (list == null) {
291 list = new String[0];
292 }
293 for (String s : list) {
294 File f = new File(d, s);
295 if (f.isDirectory()) {
296 removeDir(f);
297 } else {
298 delete(f);
299 }
300 }
301 delete(d);
302 }
303
304 private static void delete(File f) {
305 if (!f.delete()) {
306 if (isWindows()) {
307 System.gc();
308 }
309 try {
310 Thread.sleep(RETRY_SLEEP_MILLIS);
311 } catch (InterruptedException ex) {
312 // Ignore Exception
313 }
314 if (!f.delete()) {
315 f.deleteOnExit();
316 }
317 }
318 }
319
320 /**
321 * Renames a file.
322 *
323 * @param from the from file
324 * @param to the to file
325 * @param copyAndDeleteOnRenameFail whether to fallback and do copy and delete, if renameTo fails
326 * @return <tt>true</tt> if the file was renamed, otherwise <tt>false</tt>
327 * @throws java.io.IOException is thrown if error renaming file
328 */
329 public static boolean renameFile(File from, File to, boolean copyAndDeleteOnRenameFail) throws IOException {
330 // do not try to rename non existing files
331 if (!from.exists()) {
332 return false;
333 }
334
335 // some OS such as Windows can have problem doing rename IO operations so we may need to
336 // retry a couple of times to let it work
337 boolean renamed = false;
338 int count = 0;
339 while (!renamed && count < 3) {
340 if (LOG.isDebugEnabled() && count > 0) {
341 LOG.debug("Retrying attempt {} to rename file from: {} to: {}", new Object[]{count, from, to});
342 }
343
344 renamed = from.renameTo(to);
345 if (!renamed && count > 0) {
346 try {
347 Thread.sleep(1000);
348 } catch (InterruptedException e) {
349 // ignore
350 }
351 }
352 count++;
353 }
354
355 // we could not rename using renameTo, so lets fallback and do a copy/delete approach.
356 // for example if you move files between different file systems (linux -> windows etc.)
357 if (!renamed && copyAndDeleteOnRenameFail) {
358 // now do a copy and delete as all rename attempts failed
359 LOG.debug("Cannot rename file from: {} to: {}, will now use a copy/delete approach instead", from, to);
360 copyFile(from, to);
361 if (!deleteFile(from)) {
362 throw new IOException("Renaming file from: " + from + " to: " + to + " failed due cannot delete from file: " + from + " after copy succeeded");
363 } else {
364 renamed = true;
365 }
366 }
367
368 if (LOG.isDebugEnabled() && count > 0) {
369 LOG.debug("Tried {} to rename file: {} to: {} with result: {}", new Object[]{count, from, to, renamed});
370 }
371 return renamed;
372 }
373
374 public static void copyFile(File from, File to) throws IOException {
375 FileChannel in = new FileInputStream(from).getChannel();
376 FileChannel out = new FileOutputStream(to).getChannel();
377 try {
378 if (LOG.isTraceEnabled()) {
379 LOG.trace("Using FileChannel to copy from: " + in + " to: " + out);
380 }
381
382 long size = in.size();
383 long position = 0;
384 while (position < size) {
385 position += in.transferTo(position, BUFFER_SIZE, out);
386 }
387 } finally {
388 IOHelper.close(in, from.getName(), LOG);
389 IOHelper.close(out, to.getName(), LOG);
390 }
391 }
392
393 public static boolean deleteFile(File file) {
394 // do not try to delete non existing files
395 if (!file.exists()) {
396 return false;
397 }
398
399 // some OS such as Windows can have problem doing delete IO operations so we may need to
400 // retry a couple of times to let it work
401 boolean deleted = false;
402 int count = 0;
403 while (!deleted && count < 3) {
404 LOG.debug("Retrying attempt {} to delete file: {}", count, file);
405
406 deleted = file.delete();
407 if (!deleted && count > 0) {
408 try {
409 Thread.sleep(1000);
410 } catch (InterruptedException e) {
411 // ignore
412 }
413 }
414 count++;
415 }
416
417
418 if (LOG.isDebugEnabled() && count > 0) {
419 LOG.debug("Tried {} to delete file: {} with result: {}", new Object[]{count, file, deleted});
420 }
421 return deleted;
422 }
423
424 /**
425 * Is the given file an absolute file.
426 * <p/>
427 * Will also work around issue on Windows to consider files on Windows starting with a \
428 * as absolute files. This makes the logic consistent across all OS platforms.
429 *
430 * @param file the file
431 * @return <tt>true</ff> if its an absolute path, <tt>false</tt> otherwise.
432 */
433 public static boolean isAbsolute(File file) {
434 if (isWindows()) {
435 // special for windows
436 String path = file.getPath();
437 if (path.startsWith(File.separator)) {
438 return true;
439 }
440 }
441 return file.isAbsolute();
442 }
443
444 }