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