blob: e363630b1a4a0460736a831dbab044fd1c662cdd [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.Locale;
import java.util.Random;
import java.util.Stack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* File utilities
*/
public final class FileUtil {
public static final int BUFFER_SIZE = 128 * 1024;
private static final transient Logger LOG = LoggerFactory.getLogger(FileUtil.class);
private static final int RETRY_SLEEP_MILLIS = 10;
private static File defaultTempDir;
private FileUtil() {
}
/**
* Normalizes the path to cater for Windows and other platforms
*/
public static String normalizePath(String path) {
if (path == null) {
return null;
}
if (isWindows()) {
// special handling for Windows where we need to convert / to \\
return path.replace('/', '\\');
} else {
// for other systems make sure we use / as separators
return path.replace('\\', '/');
}
}
public static boolean isWindows() {
String osName = System.getProperty("os.name").toLowerCase(Locale.US);
return osName.indexOf("windows") > -1;
}
public static File createTempFile(String prefix, String suffix) throws IOException {
return createTempFile(prefix, suffix, null);
}
public static File createTempFile(String prefix, String suffix, File parentDir) throws IOException {
File parent = (parentDir == null) ? getDefaultTempDir() : parentDir;
if (suffix == null) {
suffix = ".tmp";
}
if (prefix == null) {
prefix = "camel";
} else if (prefix.length() < 3) {
prefix = prefix + "camel";
}
// create parent folder
parent.mkdirs();
return File.createTempFile(prefix, suffix, parent);
}
/**
* Strip any leading separators
*/
public static String stripLeadingSeparator(String name) {
if (name == null) {
return null;
}
while (name.startsWith("/") || name.startsWith(File.separator)) {
name = name.substring(1);
}
return name;
}
/**
* Does the name start with a leading separator
*/
public static boolean hasLeadingSeparator(String name) {
if (name == null) {
return false;
}
if (name.startsWith("/") || name.startsWith(File.separator)) {
return true;
}
return false;
}
/**
* Strip first leading separator
*/
public static String stripFirstLeadingSeparator(String name) {
if (name == null) {
return null;
}
if (name.startsWith("/") || name.startsWith(File.separator)) {
name = name.substring(1);
}
return name;
}
/**
* Strip any trailing separators
*/
public static String stripTrailingSeparator(String name) {
if (name == null) {
return null;
}
while (name.endsWith("/") || name.endsWith(File.separator)) {
name = name.substring(0, name.length() - 1);
}
return name;
}
/**
* Strips any leading paths
*/
public static String stripPath(String name) {
if (name == null) {
return null;
}
int posUnix = name.lastIndexOf('/');
int posWin = name.lastIndexOf('\\');
int pos = Math.max(posUnix, posWin);
if (pos != -1) {
return name.substring(pos + 1);
}
return name;
}
public static String stripExt(String name) {
if (name == null) {
return null;
}
int pos = name.lastIndexOf('.');
if (pos != -1) {
return name.substring(0, pos);
}
return name;
}
/**
* Returns only the leading path (returns <tt>null</tt> if no path)
*/
public static String onlyPath(String name) {
if (name == null) {
return null;
}
int posUnix = name.lastIndexOf('/');
int posWin = name.lastIndexOf('\\');
int pos = Math.max(posUnix, posWin);
if (pos > 0) {
return name.substring(0, pos);
} else if (pos == 0) {
// name is in the root path, so extract the path as the first char
return name.substring(0, 1);
}
// no path in name
return null;
}
/**
* Compacts a path by stacking it and reducing <tt>..</tt>
*/
public static String compactPath(String path) {
if (path == null) {
return null;
}
// only normalize path if it contains .. as we want to avoid: path/../sub/../sub2 as this can leads to trouble
if (path.indexOf("..") == -1) {
return path;
}
// only normalize if contains a path separator
if (path.indexOf(File.separator) == -1) {
return path;
}
Stack<String> stack = new Stack<String>();
String separatorRegex = File.separator;
if (FileUtil.isWindows()) {
separatorRegex = "\\\\";
}
String[] parts = path.split(separatorRegex);
for (String part : parts) {
if (part.equals("..") && !stack.isEmpty()) {
// only pop if there is a previous path
stack.pop();
} else {
stack.push(part);
}
}
// build path based on stack
StringBuilder sb = new StringBuilder();
for (Iterator<String> it = stack.iterator(); it.hasNext();) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(File.separator);
}
}
return sb.toString();
}
private static synchronized File getDefaultTempDir() {
if (defaultTempDir != null && defaultTempDir.exists()) {
return defaultTempDir;
}
String s = System.getProperty("java.io.tmpdir");
File checkExists = new File(s);
if (!checkExists.exists()) {
throw new RuntimeException("The directory "
+ checkExists.getAbsolutePath()
+ " does not exist, please set java.io.tempdir"
+ " to an existing directory");
}
// create a sub folder with a random number
Random ran = new Random();
int x = ran.nextInt(1000000);
File f = new File(s, "camel-tmp-" + x);
while (!f.mkdir()) {
x = ran.nextInt(1000000);
f = new File(s, "camel-tmp-" + x);
}
defaultTempDir = f;
// create shutdown hook to remove the temp dir
Thread hook = new Thread() {
@Override
public void run() {
removeDir(defaultTempDir);
}
};
Runtime.getRuntime().addShutdownHook(hook);
return defaultTempDir;
}
private static void removeDir(File d) {
String[] list = d.list();
if (list == null) {
list = new String[0];
}
for (String s : list) {
File f = new File(d, s);
if (f.isDirectory()) {
removeDir(f);
} else {
delete(f);
}
}
delete(d);
}
private static void delete(File f) {
if (!f.delete()) {
if (isWindows()) {
System.gc();
}
try {
Thread.sleep(RETRY_SLEEP_MILLIS);
} catch (InterruptedException ex) {
// Ignore Exception
}
if (!f.delete()) {
f.deleteOnExit();
}
}
}
public static boolean renameFile(File from, File to) {
// do not try to rename non existing files
if (!from.exists()) {
return false;
}
// some OS such as Windows can have problem doing rename IO operations so we may need to
// retry a couple of times to let it work
boolean renamed = false;
int count = 0;
while (!renamed && count < 3) {
if (LOG.isDebugEnabled() && count > 0) {
LOG.debug("Retrying attempt {} to rename file from: {} to: {}", new Object[]{count, from, to});
}
renamed = from.renameTo(to);
if (!renamed && count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
}
}
count++;
}
// we could not rename using renameTo, so lets fallback and do a copy/delete approach.
// for example if you move files between different file systems (linux -> windows etc.)
if (!renamed) {
// now do a copy and delete as all rename attempts failed
try {
LOG.debug("Cannot rename file from: {} to: {}, will now use a copy/delete approach instead", from, to);
copyFile(from, to);
if (!deleteFile(from)) {
LOG.warn("Renaming file from: {} to: {} failed due cannot delete from file: {} after copy succeeded", new Object[]{from, to, from});
renamed = false;
}
renamed = true;
} catch (IOException e) {
LOG.debug("Error renaming file from: " + from + " to: " + to + " using copy/delete", e);
}
}
if (LOG.isDebugEnabled() && count > 0) {
LOG.debug("Tried {} to rename file: {} to: {} with result: {}", new Object[]{count, from, to, renamed});
}
return renamed;
}
public static void copyFile(File from, File to) throws IOException {
FileChannel in = new FileInputStream(from).getChannel();
FileChannel out = new FileOutputStream(to).getChannel();
try {
if (LOG.isTraceEnabled()) {
LOG.trace("Using FileChannel to copy from: " + in + " to: " + out);
}
long size = in.size();
long position = 0;
while (position < size) {
position += in.transferTo(position, BUFFER_SIZE, out);
}
} finally {
IOHelper.close(in, from.getName(), LOG);
IOHelper.close(out, to.getName(), LOG);
}
}
public static boolean deleteFile(File file) {
// do not try to delete non existing files
if (!file.exists()) {
return false;
}
// some OS such as Windows can have problem doing delete IO operations so we may need to
// retry a couple of times to let it work
boolean deleted = false;
int count = 0;
while (!deleted && count < 3) {
LOG.debug("Retrying attempt {} to delete file: {}", count, file);
deleted = file.delete();
if (!deleted && count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
}
}
count++;
}
if (LOG.isDebugEnabled() && count > 0) {
LOG.debug("Tried {} to delete file: {} with result: {}", new Object[]{count, file, deleted});
}
return deleted;
}
/**
* Is the given file an absolute file.
* <p/>
* Will also work around issue on Windows to consider files on Windows starting with a \
* as absolute files. This makes the logic consistent across all OS platforms.
*
* @param file the file
* @return <tt>true</ff> if its an absolute path, <tt>false</tt> otherwise.
*/
public static boolean isAbsolute(File file) {
if (isWindows()) {
// special for windows
String path = file.getPath();
if (path.startsWith(File.separator)) {
return true;
}
}
return file.isAbsolute();
}
}