| /* |
| * 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; |
| } |
| } |