org.apache.camel.util.FileUtil Maven / Gradle / Ivy
The newest version!
/*
* 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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Locale;
import java.util.Objects;
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 Logger LOG = LoggerFactory.getLogger(FileUtil.class);
private static final int RETRY_SLEEP_MILLIS = 10;
/**
* The System property key for the user directory.
*/
private static final String USER_DIR_KEY = "user.dir";
private static final File USER_DIR = new File(System.getProperty(USER_DIR_KEY));
private static final boolean IS_WINDOWS = initWindowsOs();
private FileUtil() {
// Utils method
}
private static boolean initWindowsOs() {
// initialize once as System.getProperty is not fast
String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
return osName.contains("windows");
}
public static File getUserDir() {
return USER_DIR;
}
/**
* 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('\\', '/');
}
}
/**
* Returns true, if the OS is windows
*/
public static boolean isWindows() {
return IS_WINDOWS;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static File createTempFile(String prefix, String suffix, File parentDir) throws IOException {
Objects.requireNonNull(parentDir);
if (suffix == null) {
suffix = ".tmp";
}
if (prefix == null) {
prefix = "camel";
} else if (prefix.length() < 3) {
prefix = prefix + "camel";
}
// create parent folder
parentDir.mkdirs();
return Files.createTempFile(parentDir.toPath(), prefix, suffix).toFile();
}
/**
* 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 (ObjectHelper.isEmpty(name)) {
return name;
}
String s = name;
// there must be some leading text, as we should only remove trailing separators
while (s.endsWith("/") || s.endsWith(File.separator)) {
s = s.substring(0, s.length() - 1);
}
// if the string is empty, that means there was only trailing slashes, and no leading text
// and so we should then return the original name as is
if (ObjectHelper.isEmpty(s)) {
return name;
} else {
// return without trailing slashes
return s;
}
}
/**
* 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) {
return stripExt(name, false);
}
public static String stripExt(String name, boolean singleMode) {
if (name == null) {
return null;
}
// the name may have a leading path
int posUnix = name.lastIndexOf('/');
int posWin = name.lastIndexOf('\\');
int pos = Math.max(posUnix, posWin);
if (pos > 0) {
String onlyName = name.substring(pos + 1);
int pos2 = singleMode ? onlyName.lastIndexOf('.') : onlyName.indexOf('.');
if (pos2 > 0) {
return name.substring(0, pos + pos2 + 1);
}
} else {
// if single ext mode, then only return last extension
int pos2 = singleMode ? name.lastIndexOf('.') : name.indexOf('.');
if (pos2 > 0) {
return name.substring(0, pos2);
}
}
return name;
}
public static String onlyExt(String name) {
return onlyExt(name, false);
}
public static String onlyExt(String name, boolean singleMode) {
if (name == null) {
return null;
}
name = stripPath(name);
// extension is the first dot, as a file may have double extension such as .tar.gz
// if single ext mode, then only return last extension
int pos = singleMode ? name.lastIndexOf('.') : name.indexOf('.');
if (pos != -1) {
return name.substring(pos + 1);
}
return null;
}
/**
* Returns only the leading path (returns null 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;
}
public static String onlyName(String name) {
return onlyName(name, false);
}
public static String onlyName(String name, boolean singleMode) {
name = FileUtil.stripPath(name);
name = FileUtil.stripExt(name, singleMode);
return name;
}
/**
* Compacts a path by stacking it and reducing .., and uses OS specific file separators (eg
* {@link java.io.File#separator}).
*/
public static String compactPath(String path) {
return compactPath(path, String.valueOf(File.separatorChar));
}
/**
* Compacts a path by stacking it and reducing .., and uses the given separator.
*/
public static String compactPath(String path, char separator) {
return compactPath(path, String.valueOf(separator));
}
/**
* Compacts a file path by stacking it and reducing .., and uses the given separator.
*/
public static String compactPath(String path, String separator) {
if (path == null) {
return null;
}
if (path.startsWith("http:") || path.startsWith("https:")) {
return path;
}
// only normalize if contains a path separator
if (path.indexOf('/') == -1 && path.indexOf('\\') == -1) {
return path;
}
// need to normalize path before compacting
path = normalizePath(path);
// preserve scheme
String scheme = null;
if (hasScheme(path)) {
int pos = path.indexOf(':');
scheme = path.substring(0, pos);
path = path.substring(pos + 1);
}
// preserve ending slash if given in input path
boolean endsWithSlash = path.endsWith("/") || path.endsWith("\\");
// preserve starting slash if given in input path
int cntSlashsAtStart = 0;
if (path.startsWith("/") || path.startsWith("\\")) {
cntSlashsAtStart++;
// for Windows, preserve up to 2 starting slashes, which is necessary for UNC paths.
if (isWindows() && path.length() > 1 && (path.charAt(1) == '/' || path.charAt(1) == '\\')) {
cntSlashsAtStart++;
}
}
Deque stack = new ArrayDeque<>();
// separator can either be windows or unix style
String separatorRegex = "[\\\\/]";
String[] parts = path.split(separatorRegex);
for (String part : parts) {
if (part.equals("..") && !stack.isEmpty() && !"..".equals(stack.peek())) {
// only pop if there is a previous path, which is not a ".." path either
stack.pop();
} else if (!part.equals(".") && !part.isEmpty()) {
stack.push(part);
}
// else do nothing because we don't want a path like foo/./bar or foo//bar
}
// build path based on stack
StringBuilder sb = new StringBuilder(256);
if (scheme != null) {
sb.append(scheme);
sb.append(":");
}
sb.append(String.valueOf(separator).repeat(cntSlashsAtStart));
// now we build back using FIFO so need to use descending
for (Iterator it = stack.descendingIterator(); it.hasNext();) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(separator);
}
}
if (endsWithSlash && !stack.isEmpty()) {
sb.append(separator);
}
return sb.toString();
}
public 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) {
try {
Files.delete(f.toPath());
} catch (IOException e) {
try {
Thread.sleep(RETRY_SLEEP_MILLIS);
} catch (InterruptedException ex) {
LOG.info("Interrupted while trying to delete file {}", f, e);
Thread.currentThread().interrupt();
}
try {
Files.delete(f.toPath());
} catch (IOException ex) {
f.deleteOnExit();
}
}
}
/**
* Renames a file.
*
* @param from the from file
* @param to the to file
* @param copyAndDeleteOnRenameFail whether to fallback and do copy and delete, if renameTo fails
* @return true if the file was renamed, otherwise false
* @throws java.io.IOException is thrown if error renaming file
*/
public static boolean renameFile(File from, File to, boolean copyAndDeleteOnRenameFail) throws IOException {
return renameFile(from.toPath(), to.toPath(), copyAndDeleteOnRenameFail);
}
/**
* Renames a file.
*
* @param from the from file
* @param to the to file
* @param copyAndDeleteOnRenameFail whether to fallback and do copy and delete, if renameTo fails
* @return true if the file was renamed, otherwise false
* @throws java.io.IOException is thrown if error renaming file
*/
public static boolean renameFile(Path from, Path to, boolean copyAndDeleteOnRenameFail) throws IOException {
// do not try to rename non existing files
if (!Files.exists(from)) {
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: {}", count, from, to);
}
try {
Files.move(from, to, StandardCopyOption.ATOMIC_MOVE);
renamed = true;
} catch (IOException e) {
// failed
}
if (!renamed && count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOG.info("Interrupted while trying to rename file from {} to {}", from, to, e);
Thread.currentThread().interrupt();
}
}
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 && copyAndDeleteOnRenameFail) {
// now do a copy and delete as all rename attempts failed
LOG.debug("Cannot rename file from: {} to: {}, will now use a copy/delete approach instead", from, to);
renamed = renameFileUsingCopy(from, to);
}
if (LOG.isDebugEnabled() && count > 0) {
LOG.debug("Tried {} to rename file: {} to: {} with result: {}", count, from, to, renamed);
}
return renamed;
}
/**
* Rename file using copy and delete strategy. This is primarily used in environments where the regular rename
* operation is unreliable.
*
* @param from the file to be renamed
* @param to the new target file
* @return true if the file was renamed successfully, otherwise false
* @throws IOException If an I/O error occurs during copy or delete operations.
*/
public static boolean renameFileUsingCopy(File from, File to) throws IOException {
return renameFileUsingCopy(from.toPath(), to.toPath());
}
/**
* Rename file using copy and delete strategy. This is primarily used in environments where the regular rename
* operation is unreliable.
*
* @param from the file to be renamed
* @param to the new target file
* @return true if the file was renamed successfully, otherwise false
* @throws IOException If an I/O error occurs during copy or delete operations.
*/
public static boolean renameFileUsingCopy(Path from, Path to) throws IOException {
// do not try to rename non existing files
if (!Files.exists(from)) {
return false;
}
LOG.debug("Rename file '{}' to '{}' using copy/delete strategy.", from, to);
copyFile(from, to);
if (!deleteFile(from)) {
throw new IOException(
"Renaming file from '" + from + "' to '" + to + "' failed: Cannot delete file '" + from
+ "' after copy succeeded");
}
return true;
}
/**
* Copies the file
*
* @param from the source file
* @param to the destination file
* @throws IOException If an I/O error occurs during copy operation
*/
public static void copyFile(File from, File to) throws IOException {
copyFile(from.toPath(), to.toPath());
}
/**
* Copies the file
*
* @param from the source file
* @param to the destination file
* @throws IOException If an I/O error occurs during copy operation
*/
public static void copyFile(Path from, Path to) throws IOException {
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
}
/**
* Deletes the file.
*
* This implementation will attempt to delete the file up till three times with one second delay, which can mitigate
* problems on deleting files on some platforms such as Windows.
*
* @param file the file to delete
*/
public static boolean deleteFile(File file) {
return deleteFile(file.toPath());
}
/**
* Deletes the file.
*
* This implementation will attempt to delete the file up till three times with one second delay, which can mitigate
* problems on deleting files on some platforms such as Windows.
*
* @param file the file to delete
*/
public static boolean deleteFile(Path file) {
// do not try to delete non existing files
if (!Files.exists(file)) {
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);
try {
Files.delete(file);
deleted = true;
} catch (IOException e) {
if (count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
LOG.info("Interrupted while trying to delete file {}", file, e);
Thread.currentThread().interrupt();
}
}
}
count++;
}
if (LOG.isDebugEnabled() && count > 0) {
LOG.debug("Tried {} to delete file: {} with result: {}", count, file, deleted);
}
return deleted;
}
/**
* Is the given file an absolute file.
*
* 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 true if it's an absolute path, false 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();
}
/**
* Creates a new file.
*
* @param file the file
* @return true if created a new file, false otherwise
* @throws IOException is thrown if error creating the new file
*/
public static boolean createNewFile(File file) throws IOException {
// need to check first
if (file.exists()) {
return false;
}
try {
return file.createNewFile();
} catch (IOException e) {
// and check again if the file was created as createNewFile may create the file
// but throw a permission error afterwards when using some NAS
if (file.exists()) {
return true;
} else {
throw e;
}
}
}
/**
* Determines whether the URI has a scheme (e.g. file:, classpath: or http:)
*
* @param uri the URI
* @return true if the URI starts with a scheme
*/
private static boolean hasScheme(String uri) {
if (uri == null) {
return false;
}
return uri.startsWith("file:") || uri.startsWith("classpath:") || uri.startsWith("http:");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy