| // 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.io.UncheckedIOException; |
| 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 String GLOBAL_SYNTHETIC_EXTENSION = ".global"; |
| |
| public static final boolean isAndroid = |
| System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik"); |
| |
| public static boolean isDexFile(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(DEX_EXTENSION); |
| } |
| |
| public static boolean isVDexFile(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(VDEX_EXTENSION); |
| } |
| |
| public static boolean isClassFile(String path) { |
| String name = StringUtils.toLowerCase(path); |
| // 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 isClassFile(Path path) { |
| return isClassFile(path.getFileName().toString()); |
| } |
| |
| public static boolean isJarFile(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(JAR_EXTENSION); |
| } |
| |
| public static boolean isZipFile(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(ZIP_EXTENSION); |
| } |
| |
| public static boolean isApkFile(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(APK_EXTENSION); |
| } |
| |
| public static boolean isAarFile(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(AAR_EXTENSION); |
| } |
| |
| public static boolean isJavaFile(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(JAVA_EXTENSION); |
| } |
| |
| public static boolean isArchive(Path path) { |
| String name = StringUtils.toLowerCase(path.getFileName().toString()); |
| return name.endsWith(APK_EXTENSION) |
| || name.endsWith(JAR_EXTENSION) |
| || name.endsWith(ZIP_EXTENSION) |
| || name.endsWith(AAR_EXTENSION); |
| } |
| |
| public static String readTextFile(Path file) throws IOException { |
| return readTextFile(file, StandardCharsets.UTF_8); |
| } |
| |
| public static String readTextFile(Path file, Charset charset) throws IOException { |
| return new String(Files.readAllBytes(file), charset); |
| } |
| |
| public static byte[] uncheckedReadAllBytes(Path file) { |
| try { |
| return Files.readAllBytes(file); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| public static List<String> readAllLines(Path file) throws IOException { |
| return Files.readAllLines(file); |
| } |
| |
| public static Path writeTextFile(Path file, String text) throws IOException { |
| Files.writeString(file, text, StandardCharsets.UTF_8); |
| return file; |
| } |
| |
| public static Path writeTextFile(Path file, List<String> lines) throws IOException { |
| Files.write(file, lines); |
| return file; |
| } |
| |
| public static Path writeTextFile(Path file, String... lines) throws IOException { |
| Files.write(file, Arrays.asList(lines)); |
| return file; |
| } |
| |
| 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 = StringUtils.toLowerCase(file.getFileName().toString()); |
| 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('/', '\\'); |
| } |
| } |
| |
| @SuppressWarnings("ReferenceEquality") |
| 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, but the charset used is " |
| + Charset.defaultCharset()); |
| } |
| } |