blob: 7cd718c90126e0b962325f0100932eaeb38fa4ec [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 com.android.utils;
import static com.google.common.base.Preconditions.checkArgument;
import com.android.annotations.NonNull;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.Closeables;
import com.google.common.io.Files;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.regex.Pattern;
public final class FileUtils {
private FileUtils() {}
private static final Joiner PATH_JOINER = Joiner.on(File.separatorChar);
private static final Joiner COMMA_SEPARATED_JOINER = Joiner.on(", ");
private static final Joiner UNIX_NEW_LINE_JOINER = Joiner.on('\n');
public static final Function<File, String> GET_NAME = new Function<File, String>() {
@Override
public String apply(File file) {
return file.getName();
}
};
public static final Function<File, String> GET_PATH = new Function<File, String>() {
@Override
public String apply(File file) {
return file.getPath();
}
};
@NonNull
public static Predicate<File> withExtension(@NonNull final String extension) {
checkArgument(extension.charAt(0) != '.', "Extension should not start with a dot.");
return new Predicate<File>() {
@Override
public boolean apply(File input) {
return Files.getFileExtension(input.getName()).equals(extension);
}
};
}
public static void deleteFolder(@NonNull final File folder) throws IOException {
if (!folder.exists()) {
return;
}
File[] files = folder.listFiles();
if (files != null) { // i.e. is a directory.
for (final File file : files) {
deleteFolder(file);
}
}
if (!folder.delete()) {
throw new IOException(String.format("Could not delete folder %s", folder));
}
}
public static void emptyFolder(@NonNull final File folder) throws IOException {
deleteFolder(folder);
if (!folder.mkdirs()) {
throw new IOException(String.format("Could not create empty folder %s", folder));
}
}
public static void copy(@NonNull final File from, @NonNull final File toDir)
throws IOException {
File to = new File(toDir, from.getName());
if (from.isDirectory()) {
mkdirs(to);
File[] children = from.listFiles();
if (children != null) {
for (File child : children) {
copy(child, to);
}
}
} else if (from.isFile()) {
Files.copy(from, to);
}
}
public static void mkdirs(@NonNull File folder) {
// attempt to create first.
// if failure only throw if folder does not exist.
// This makes this method able to create the same folder(s) from different thread
if (!folder.mkdirs() && !folder.exists()) {
throw new RuntimeException("Cannot create directory " + folder);
}
}
public static void delete(@NonNull File file) throws IOException {
boolean result = file.delete();
if (!result) {
throw new IOException("Failed to delete " + file.getAbsolutePath());
}
}
public static void deleteIfExists(@NonNull File file) throws IOException {
boolean result = file.delete();
if (!result && file.exists()) {
throw new IOException("Failed to delete " + file.getAbsolutePath());
}
}
public static void renameTo(@NonNull File file, @NonNull File to) throws IOException {
boolean result = file.renameTo(to);
if (!result) {
throw new IOException("Failed to rename " + file.getAbsolutePath() + " to " + to);
}
}
/**
* Joins a list of path segments to a given File object.
*
* @param dir the file object.
* @param paths the segments.
* @return a new File object.
*/
@NonNull
public static File join(@NonNull File dir, @NonNull String... paths) {
if (paths.length == 0) {
return dir;
}
return new File(dir, PATH_JOINER.join(paths));
}
/**
* Joins a list of path segments to a given File object.
*
* @param dir the file object.
* @param paths the segments.
* @return a new File object.
*/
@NonNull
public static File join(@NonNull File dir, @NonNull Iterable<String> paths) {
return new File(dir, PATH_JOINER.join(paths));
}
/**
* Joins a set of segment into a string, separating each segments with a host-specific
* path separator.
*
* @param paths the segments.
* @return a string with the segments.
*/
@NonNull
public static String join(@NonNull String... paths) {
return PATH_JOINER.join(paths);
}
/**
* Joins a set of segment into a string, separating each segments with a host-specific
* path separator.
*
* @param paths the segments.
* @return a string with the segments.
*/
@NonNull
public static String join(@NonNull Iterable<String> paths) {
return PATH_JOINER.join(paths);
}
/**
* Loads a text file forcing the line separator to be of Unix style '\n' rather than being
* Windows style '\r\n'.
*/
@NonNull
public static String loadFileWithUnixLineSeparators(@NonNull File file) throws IOException {
return UNIX_NEW_LINE_JOINER.join(Files.readLines(file, Charsets.UTF_8));
}
/**
* Computes the relative of a file or directory with respect to a directory.
*
* @param file the file or directory, which must exist in the filesystem
* @param dir the directory to compute the path relative to
* @return the relative path from {@code dir} to {@code file}; if {@code file} is a directory
* the path comes appended with the file separator (see documentation on {@code relativize}
* on java's {@code URI} class)
*/
@NonNull
public static String relativePath(@NonNull File file, @NonNull File dir) {
checkArgument(file.isFile() || file.isDirectory(), "%s is not a file nor a directory.",
file.getPath());
checkArgument(dir.isDirectory(), "%s is not a directory.", dir.getPath());
return relativePossiblyNonExistingPath(file, dir);
}
/**
* Computes the relative of a file or directory with respect to a directory.
* For example, if the file's absolute path is {@code /a/b/c} and the directory
* is {@code /a}, this method returns {@code b/c}.
*
* @param file the path that may not correspond to any existing path in the filesystem
* @param dir the directory to compute the path relative to
* @return the relative path from {@code dir} to {@code file}; if {@code file} is a directory
* the path comes appended with the file separator (see documentation on {@code relativize}
* on java's {@code URI} class)
*/
@NonNull
public static String relativePossiblyNonExistingPath(@NonNull File file, @NonNull File dir) {
String path = dir.toURI().relativize(file.toURI()).getPath();
return toSystemDependentPath(path);
}
/**
* Converts a /-based path into a path using the system dependent separator.
* @param path the system independent path to convert
* @return the system dependent path
*/
@NonNull
public static String toSystemDependentPath(@NonNull String path) {
if (File.separatorChar != '/') {
path = path.replace('/', File.separatorChar);
}
return path;
}
/**
* Converts a system-dependent path into a /-based path.
* @param path the system dependent path
* @return the system independent path
*/
@NonNull
public static String toSystemIndependentPath(@NonNull String path) {
if (File.separatorChar != '/') {
path = path.replace(File.separatorChar, '/');
}
return path;
}
@NonNull
public static String sha1(@NonNull File file) throws IOException {
return Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
}
@NonNull
public static FluentIterable<File> getAllFiles(@NonNull File dir) {
return Files.fileTreeTraverser().preOrderTraversal(dir).filter(Files.isFile());
}
@NonNull
public static String getNamesAsCommaSeparatedList(@NonNull Iterable<File> files) {
return COMMA_SEPARATED_JOINER.join(Iterables.transform(files, GET_NAME));
}
/**
* Chooses a directory name, based on a JAR file name, considering exploded-aar and classes.jar.
*/
@NonNull
public static String getDirectoryNameForJar(@NonNull File inputFile) {
// add a hash of the original file path.
HashFunction hashFunction = Hashing.sha1();
HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath(), Charsets.UTF_16LE);
String name = Files.getNameWithoutExtension(inputFile.getName());
if (name.equals("classes") && inputFile.getAbsolutePath().contains("exploded-aar")) {
// This naming scheme is coming from DependencyManager#computeArtifactPath.
File versionDir = inputFile.getParentFile().getParentFile();
File artifactDir = versionDir.getParentFile();
File groupDir = artifactDir.getParentFile();
name = Joiner.on('-').join(
groupDir.getName(), artifactDir.getName(), versionDir.getName());
}
name = name + "_" + hashCode.toString();
return name;
}
public static void createFile(@NonNull File file, @NonNull String content) throws IOException {
checkArgument(!file.exists(), "%s exists already.", file);
Files.createParentDirs(file);
Files.write(content, file, Charsets.UTF_8);
}
/**
* Find a list of files in a directory, using a specified path pattern.
*/
public static List<File> find(@NonNull File base, @NonNull final Pattern pattern) {
checkArgument(base.isDirectory(), "'base' must be a directory.");
return Files.fileTreeTraverser()
.preOrderTraversal(base)
.filter(Predicates.compose(Predicates.contains(pattern), GET_PATH))
.toList();
}
/**
* Find a file with the specified name in a given directory .
*/
public static Optional<File> find(@NonNull File base, @NonNull final String name) {
checkArgument(base.isDirectory(), "'base' must be a directory.");
return Files.fileTreeTraverser()
.preOrderTraversal(base)
.filter(Predicates.compose(Predicates.equalTo(name), GET_NAME))
.last();
}
/**
* Reads a portion of a file to memory.
*
* @param file the file to read data from
* @param start the offset in the file to start reading
* @param length the number of bytes to read
* @return the bytes read
* @throws Exception failed to read the file
*/
@NonNull
public static byte[] readSegment(@NonNull File file, long start, int length) throws Exception {
Preconditions.checkArgument(start >= 0, "start < 0");
Preconditions.checkArgument(length >= 0, "length < 0");
byte data[];
boolean threw = true;
RandomAccessFile raf = new RandomAccessFile(file, "r");
try {
raf.seek(start);
data = new byte[length];
int tot = 0;
while (tot < length) {
int r = raf.read(data, tot, length - tot);
if (r < 0) {
throw new EOFException();
}
tot += r;
}
threw = false;
} finally {
Closeables.close(raf, threw);
}
return data;
}
}