| // 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."); | |
| } | |
| } |