blob: 19fc10a3d1eca50f2a0489e205e46ddbedd04951 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
import com.android.tools.r8.ByteDataView;
import com.google.common.io.Closer;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipFile;
public class FileUtils {
public static final String AAR_EXTENSION = ".aar";
public static final String APK_EXTENSION = ".apk";
public static final String CLASS_EXTENSION = ".class";
public static final String DEX_EXTENSION = ".dex";
public static final String VDEX_EXTENSION = ".vdex";
public static final String JAR_EXTENSION = ".jar";
public static final String ZIP_EXTENSION = ".zip";
public static final String JAVA_EXTENSION = ".java";
public static final String KT_EXTENSION = ".kt";
public static final String MODULE_INFO_CLASS = "module-info.class";
public static final String MODULES_PREFIX = "/modules";
public static final boolean isAndroid =
System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
public static boolean isDexFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(DEX_EXTENSION);
}
public static boolean isVDexFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(VDEX_EXTENSION);
}
public static boolean isClassFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
// Android does not support Java 9 module, thus skip module-info.
if (name.equals(MODULE_INFO_CLASS)) {
return false;
}
return name.endsWith(CLASS_EXTENSION);
}
public static boolean isJarFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(JAR_EXTENSION);
}
public static boolean isZipFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(ZIP_EXTENSION);
}
public static boolean isApkFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(APK_EXTENSION);
}
public static boolean isAarFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(AAR_EXTENSION);
}
public static boolean isArchive(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(APK_EXTENSION)
|| name.endsWith(JAR_EXTENSION)
|| name.endsWith(ZIP_EXTENSION)
|| name.endsWith(AAR_EXTENSION);
}
public static String readTextFile(Path file, Charset charset) throws IOException {
return new String(Files.readAllBytes(file), charset);
}
public static List<String> readAllLines(Path file) throws IOException {
return Files.readAllLines(file);
}
public static void writeTextFile(Path file, List<String> lines) throws IOException {
Files.write(file, lines);
}
public static void writeTextFile(Path file, String... lines) throws IOException {
Files.write(file, Arrays.asList(lines));
}
public static Path validateOutputFile(Path path, Reporter reporter) {
if (path != null) {
boolean isJarOrZip = isZipFile(path) || isJarFile(path);
if (!isJarOrZip && !(Files.exists(path) && Files.isDirectory(path))) {
reporter.error(new StringDiagnostic(
"Invalid output: "
+ path
+ "\nOutput must be a .zip or .jar archive or an existing directory"));
}
}
return path;
}
public static OutputStream openPath(
Closer closer,
Path file,
OpenOption... openOptions)
throws IOException {
assert file != null;
return openPathWithDefault(closer, file, null, openOptions);
}
public static OutputStream openPathWithDefault(
Closer closer,
Path file,
OutputStream defaultOutput,
OpenOption... openOptions)
throws IOException {
OutputStream mapOut;
if (file == null) {
assert defaultOutput != null;
mapOut = defaultOutput;
} else {
mapOut = Files.newOutputStream(file, openOptions);
closer.register(mapOut);
}
return mapOut;
}
public static boolean isClassesDexFile(Path file) {
String name = file.getFileName().toString().toLowerCase();
if (!name.startsWith("classes") || !name.endsWith(DEX_EXTENSION)) {
return false;
}
String numeral = name.substring("classes".length(), name.length() - DEX_EXTENSION.length());
if (numeral.isEmpty()) {
return true;
}
char c0 = numeral.charAt(0);
if (numeral.length() == 1) {
return '2' <= c0 && c0 <= '9';
}
if (c0 < '1' || '9' < c0) {
return false;
}
for (int i = 1; i < numeral.length(); i++) {
char c = numeral.charAt(i);
if (c < '0' || '9' < c) {
return false;
}
}
return true;
}
public static void writeToFile(Path output, OutputStream defValue, byte[] contents)
throws IOException {
writeToFile(output, defValue, ByteDataView.of(contents));
}
public static void writeToFile(Path output, OutputStream defValue, ByteDataView contents)
throws IOException {
try (Closer closer = Closer.create()) {
OutputStream outputStream =
openPathWithDefault(
closer,
output,
defValue,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE);
outputStream.write(contents.getBuffer(), contents.getOffset(), contents.getLength());
}
}
public static String withNativeFileSeparators(String path) {
char fileSeparator = File.separatorChar;
if (fileSeparator == '/') {
return path.replace('\\', '/');
} else {
assert fileSeparator == '\\';
return path.replace('/', '\\');
}
}
public static ZipFile createZipFile(File file, Charset charset) throws IOException {
if (!isAndroid) {
return new ZipFile(file, charset);
}
// On Android pre-26 we cannot use the constructor ZipFile(file, charset).
// By default Android use UTF_8 as the charset, so we can use the default constructor.
if (Charset.defaultCharset() == StandardCharsets.UTF_8) {
return new ZipFile(file);
}
// If the Android runtime is started with a different default charset, the default constructor
// won't work. It is possible to support this case if we read/write the ZipFile not with it's
// own Input/OutputStream, but an external one which one can define on a different Charset than
// default. We do not support this at the moment since R8 on dex is used only in tests, and
// UTF_8 is the default charset used in tests.
throw new RuntimeException("R8 can run on dex only with UTF_8 as the default charset.");
}
}