/*
* 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.geode.internal;
import org.apache.geode.distributed.internal.DistributionConfig;
import java.io.*;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
/**
* This class contains static methods for manipulating files and directories, such as recursively
* copying or deleting files.
*
* TODO A lot of this functionality is probably duplicating apache commons io, maybe we should
* switch to that.
*
*
*/
public class FileUtil {
public static final long MAX_TRANSFER_SIZE =
Long.getLong(DistributionConfig.GEMFIRE_PREFIX + "FileUtil.MAX_TRANSFER_SIZE", 1024 * 1024)
.longValue();
public static final boolean USE_NIO =
!Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "FileUtil.USE_OLD_IO");
public static final String extSeparator = ".";
/**
* Copy a file from the source file to the destination file. If the source is a directory, it will
* be copied recursively.
*
* Note that unlike unix cp, if the destination is directory, the source *contents* will be copied
* to the destination *contents*, not as a subdirectory of dest.
*
* @param source the source file or directory
* @param dest the destination file or directory.
* @throws IOException
*/
public static void copy(File source, File dest) throws IOException {
if (source.isDirectory()) {
dest.mkdir();
for (File child : listFiles(source)) {
copy(child, new File(dest, child.getName()));
}
} else {
if (source.exists()) {
long lm = source.lastModified();
if (dest.isDirectory()) {
dest = new File(dest, source.getName());
}
FileOutputStream fos = new FileOutputStream(dest);
try {
FileInputStream fis = new FileInputStream(source);
try {
if (USE_NIO) {
nioCopy(fos, fis);
} else {
oioCopy(source, fos, fis);
}
} finally {
fis.close();
}
} finally {
fos.close();
}
dest.setExecutable(source.canExecute(), true);
dest.setLastModified(lm);
}
}
}
/**
* Basically just like {@link File#listFiles()} but instead of returning null returns an empty
* array. This fixes bug 43729
*/
public static File[] listFiles(File dir) {
File[] result = dir.listFiles();
if (result == null) {
result = new File[0];
}
return result;
}
/**
* Basically just like {@link File#listFiles(FilenameFilter)} but instead of returning null
* returns an empty array. This fixes bug 43729
*/
public static File[] listFiles(File dir, FilenameFilter filter) {
File[] result = dir.listFiles(filter);
if (result == null) {
result = new File[0];
}
return result;
}
/**
* Copy a single file using NIO.
*
* @throws IOException
*/
private static void nioCopy(FileOutputStream fos, FileInputStream fis) throws IOException {
FileChannel outChannel = fos.getChannel();
FileChannel inChannel = fis.getChannel();
long length = inChannel.size();
long offset = 0;
while (true) {
long remaining = length - offset;
long toTransfer = remaining < MAX_TRANSFER_SIZE ? remaining : MAX_TRANSFER_SIZE;
long transferredBytes = inChannel.transferTo(offset, toTransfer, outChannel);
offset += transferredBytes;
length = inChannel.size();
if (offset >= length) {
break;
}
}
}
/**
* Copy a single file using the java.io.
*
* @throws IOException
*/
private static void oioCopy(File source, FileOutputStream fos, FileInputStream fis)
throws IOException {
int size = (int) (source.length() < MAX_TRANSFER_SIZE ? source.length() : MAX_TRANSFER_SIZE);
byte[] buffer = new byte[size];
int read;
while ((read = fis.read(buffer)) > 0) {
fos.write(buffer, 0, read);
}
}
/**
* Recursively delete a file or directory.
*
* @throws IOException if the file or directory couldn't be deleted. Unlike File.delete, which
* just returns false.
*/
public static void delete(File file) throws IOException {
if (!file.exists())
return;
if (file.isDirectory()) {
for (File child : listFiles(file)) {
delete(child);
}
}
Files.delete(file.toPath());
}
/**
* Recursively delete a file or directory. A description of any files or directories that can not
* be deleted will be added to failures if failures is non-null. This method tries to delete as
* much as possible.
*/
public static void delete(File file, StringBuilder failures) {
if (!file.exists())
return;
if (file.isDirectory()) {
for (File child : listFiles(file)) {
delete(child, failures);
}
}
try {
Files.delete(file.toPath());
} catch (IOException e) {
if (failures != null) {
failures.append("Could not delete ").append(file).append(" due to ").append(e.getMessage())
.append('\n');
}
}
}
/**
* Find the file whose name matches the given regular expression. The regex is matched against the
* absolute path of the file.
*
* This could probably use a lot of optimization!
*/
public static File find(File baseFile, String regex) {
if (baseFile.getAbsolutePath().matches(regex)) {
return baseFile;
}
if (baseFile.exists() && baseFile.isDirectory()) {
for (File child : listFiles(baseFile)) {
File foundFile = find(child, regex);
if (foundFile != null) {
return foundFile;
}
}
}
return null;
}
/**
* Find a files in a given base directory that match a the given regex. The regex is matched
* against the full path of the file.
*/
public static List<File> findAll(File baseFile, String regex) {
ArrayList<File> found = new ArrayList<File>();
findAll(baseFile, regex, found);
return found;
}
/**
* Destroys all files that match the given regex that are in the given directory. If a destroy
* fails it is ignored and an attempt is made to destroy any other files that match.
*/
public static void deleteMatching(File baseFile, String regex) {
if (baseFile.exists() && baseFile.isDirectory()) {
for (File child : listFiles(baseFile)) {
if (child.getName().matches(regex)) {
try {
delete(child);
} catch (IOException ignore) {
}
}
}
}
}
/** Implementation of findAll. */
private static void findAll(File baseFile, String regex, List<File> found) {
if (baseFile.getAbsolutePath().matches(regex)) {
found.add(baseFile);
}
if (baseFile.exists() && baseFile.isDirectory()) {
for (File child : listFiles(baseFile)) {
findAll(child, regex, found);
}
}
}
/**
* Convert a file into a relative path from a given parent. This is useful if you want to write
* out the file name into that parent directory.
*
* @param parent The parent directory.
* @param file The file we want to covert to a relative file.
* @return A file, such that new File(parent, returnValue) == file. Note that if file does not
* have the parent in it's path, an the absolute version if the file is returned.
*/
public static File removeParent(File parent, File file) {
String absolutePath = file.getAbsolutePath();
String parentAbsolutePath = parent.getAbsolutePath();
String newPath = absolutePath.replace(parentAbsolutePath + "/", "");
return new File(newPath);
}
/**
* Copy a URL to a file.
*
* @throws IOException
*/
public static void copy(URL url, File file) throws IOException {
InputStream is = url.openStream();
try {
OutputStream os = new FileOutputStream(file);
try {
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) > 0) {
os.write(buffer, 0, read);
}
} finally {
os.close();
}
} finally {
is.close();
}
}
/**
* A safer version of File.mkdirs, which works around a race in the 1.5 JDK where two VMs creating
* the same directory chain at the same time could end up in one VM failing to create a
* subdirectory.
*
* @param file
*/
public static boolean mkdirs(File file) {
final File parentFile = file.getAbsoluteFile().getParentFile();
if (!parentFile.exists()) {
mkdirs(parentFile);
}
// As long as someone successfully created the parent file
// go ahead and create the child directory.
if (parentFile.exists()) {
return file.mkdir();
} else {
return false;
}
}
/**
* Returns the file name with the extension stripped off (if it has one).
*
* @param fileName the file name
* @return the file name with the extension stripped off (if it had one)
*/
public static String stripOffExtension(final String fileName) {
if (fileName.contains(extSeparator)) {
// strip off the extension and right-most "."
return fileName.substring(0, fileName.lastIndexOf(extSeparator));
}
return fileName;
}
}