/*
* Copyright © 2014 Cask Data, Inc.
*
* Licensed 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 co.cask.cdap.common.utils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import javax.annotation.Nullable;
/**
* Provides utility methods for operating on directories.
*/
public final class DirUtils {
private static final int TEMP_DIR_ATTEMPTS = 10000;
/**
* Utility classes should have a public constructor or a default constructor
* hence made it private.
*/
private DirUtils(){}
/**
* Same as calling {@link #deleteDirectoryContents(File, boolean) deleteDirectoryContents(directory, false)}.
*/
public static void deleteDirectoryContents(File directory) throws IOException {
deleteDirectoryContents(directory, false);
}
/**
* Wipes out content of a directory starting from a given directory.
*
* @param directory to be cleaned
* @param retain if true, the given directory will be retained.
* @throws IOException
*/
public static void deleteDirectoryContents(File directory, boolean retain) throws IOException {
if (!directory.isDirectory()) {
throw new IOException("Not a directory: " + directory);
}
// avoid using guava's Queues.newArrayDeque() since this is a utility class that can be used in all sorts of
// contexts, some of which may use clashing guava versions... For example, when explore launches a Hive query,
// it includes hive-exec.jar which bundles guava 11 in its jar...
Deque<File> stack = new ArrayDeque<>();
stack.addAll(listFiles(directory));
while (!stack.isEmpty()) {
File file = stack.peekLast();
List<File> files = listFiles(file);
if (files.isEmpty()) {
if (!file.delete()) {
throw new IOException("Failed to delete file " + file);
}
stack.pollLast();
} else {
stack.addAll(files);
}
}
if (!retain) {
if (!directory.delete()) {
throw new IOException("Failed to delete directory " + directory);
}
}
}
/**
* Creates a temp directory inside the given base directory.
*
* @return the newly-created directory
* @throws IllegalStateException if the directory could not be created
*/
public static File createTempDir(File baseDir) {
String baseName = System.currentTimeMillis() + "-";
for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
File tempDir = new File(baseDir, baseName + counter);
if (tempDir.mkdirs()) {
return tempDir;
}
}
throw new IllegalStateException("Failed to create directory within "
+ TEMP_DIR_ATTEMPTS + " attempts (tried "
+ baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
}
/**
* Creates a directory if the directory doesn't exists.
*
* @param dir The directory to create
* @return {@code true} if the directory exists or successfully created the directory.
*/
public static boolean mkdirs(File dir) {
// The last clause is needed so that if there are multiple threads trying to create the same directory
// this method will still return true.
return dir.isDirectory() || dir.mkdirs() || dir.isDirectory();
}
/**
* Returns list of file names under the given directory. An empty list will be returned if the given file is
* not a directory.
*/
public static List<String> list(File directory) {
return listOf(directory.list());
}
/**
* Returns list of file names under the given directory that are accepted by the given filter.
* An empty list will be returned if the given file is not a directory.
*/
public static List<String> list(File directory, FilenameFilter filenameFilter) {
return listOf(directory.list(filenameFilter));
}
/**
* Returns list of file names under the given directory that matches the give set of file name extension.
* An empty list will be returned if the given file is not a directory.
*/
public static List<String> list(File directory, String...extensions) {
return list(directory, ImmutableSet.copyOf(extensions));
}
/**
* Returns list of file names under the given directory that matches the give set of file name extension.
* An empty list will be returned if the given file is not a directory.
*/
public static List<String> list(File directory, Iterable<String> extensions) {
final ImmutableSet<String> allowedExtensions = ImmutableSet.copyOf(extensions);
return list(directory, new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return allowedExtensions.contains(Files.getFileExtension(name));
}
});
}
/**
* Returns list of files under the given directory. An empty list will be returned if the
* given file is not a directory.
*/
public static List<File> listFiles(File directory) {
return listOf(directory.listFiles());
}
/**
* Returns list of files under the given directory that are accepted by the given filter.
* An empty list will be returned if the given file is not a directory.
*/
public static List<File> listFiles(File directory, FileFilter fileFilter) {
return listOf(directory.listFiles(fileFilter));
}
/**
* Returns list of files under the given directory that are accepted by the given filter.
* An empty list will be returned if the given file is not a directory.
*/
public static List<File> listFiles(File directory, FilenameFilter filenameFilter) {
return listOf(directory.listFiles(filenameFilter));
}
/**
* Returns list of files under the given directory that matches the give set of file name extension.
* An empty list will be returned if the given file is not a directory.
*/
public static List<File> listFiles(File directory, String...extensions) {
return listFiles(directory, ImmutableSet.copyOf(extensions));
}
/**
* Returns list of files under the given directory that matches the give set of file name extension.
* An empty list will be returned if the given file is not a directory.
*/
public static List<File> listFiles(File directory, Iterable<String> extensions) {
final ImmutableSet<String> allowedExtensions = ImmutableSet.copyOf(extensions);
return listFiles(directory, new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return allowedExtensions.contains(Files.getFileExtension(name));
}
});
}
/**
* Converts the given array into a list. An empty list will be returned if the given array is {@code null}.
* (Note: This method might worth to be in some other common class, which we don't have now).
*
* @param elements array to convert
* @param <T> type of elements in the array
* @return a new immutable list.
*/
private static <T> List<T> listOf(@Nullable T[] elements) {
return elements == null ? ImmutableList.<T>of() : ImmutableList.copyOf(elements);
}
}