aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
diff options
context:
space:
mode:
authorGravatar Chengnian Sun <cnsun@google.com>2017-03-25 04:12:43 +0000
committerGravatar Philipp Wollermann <philwo@google.com>2017-03-27 11:36:59 +0000
commit507de14c75ae5eff6069887cf7d4f1b900885488 (patch)
treedf934d94507b4a6c26164981643cbd8fa342dbcb /src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
parentebebec3fd5ddc89a89bda5684e3c3e1c1d5cd376 (diff)
split the long method Desugar.desugar() into smaller pieces.
RELNOTES: n/a -- PiperOrigin-RevId: 151203625 MOS_MIGRATED_REVID=151203625
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java304
1 files changed, 184 insertions, 120 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
index ab68839b7c..b114c32696 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -21,9 +21,11 @@ import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.io.Closer;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.desugar.CoreLibraryRewriter.UnprefixingClassWriter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
@@ -163,6 +165,8 @@ class Desugar {
private final LambdaClassMaker lambdas;
private final boolean allowDefaultMethods;
private final boolean allowCallsToObjectsNonNull;
+ /** An instance of Desugar is expected to be used ONLY ONCE */
+ private boolean used;
private Desugar(Options options, Path dumpDirectory) {
this.options = options;
@@ -171,9 +175,13 @@ class Desugar {
this.lambdas = new LambdaClassMaker(dumpDirectory);
this.allowDefaultMethods = options.minSdkVersion >= 24;
this.allowCallsToObjectsNonNull = options.minSdkVersion >= 19;
+ this.used = false;
}
- public void desugar() throws Exception {
+ private void desugar() throws Exception {
+ checkState(!this.used, "This Desugar instance has been used. Please create another one.");
+ this.used = true;
+
try (Closer closer = Closer.create()) {
IndexedInputs indexedClasspath =
new IndexedInputs(toRegisteredInputFileProvider(closer, options.classpath));
@@ -189,132 +197,188 @@ class Desugar {
// Process each input separately
for (InputOutputPair inputOutputPair : toInputOutputPairs(options)) {
- Path inputPath = inputOutputPair.getInput();
- Path outputPath = inputOutputPair.getOutput();
- checkState(
- Files.isDirectory(inputPath) || !Files.isDirectory(outputPath),
- "Input jar file requires an output jar file");
-
- try (OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath);
- InputFileProvider inputFiles = toInputFileProvider(inputPath)) {
- IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles));
- // Prepend classpath with input file itself so LambdaDesugaring can load classes with
- // lambdas.
- IndexedInputs indexedClasspathAndInputFiles =
- indexedClasspath.withParent(indexedInputFiles);
- // Note that input file and classpath need to be in the same classloader because
- // we typically get the header Jar for inputJar on the classpath and having the header
- // Jar in a parent loader means the header version is preferred over the real thing.
- ClassLoader loader =
- new HeaderClassLoader(indexedClasspathAndInputFiles, rewriter, bootclassloader);
-
- ClassReaderFactory readerFactory =
- new ClassReaderFactory(
- (options.copyBridgesFromClasspath && !allowDefaultMethods)
- ? indexedClasspathAndInputFiles
- : indexedInputFiles,
- rewriter);
-
- ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder();
-
- // Process inputs, desugaring as we go
- for (String filename : inputFiles) {
- try (InputStream content = inputFiles.getInputStream(filename)) {
- // We can write classes uncompressed since they need to be converted to .dex format
- // for Android anyways. Resources are written as they were in the input jar to avoid
- // any danger of accidentally uncompressed resources ending up in an .apk.
- if (filename.endsWith(".class")) {
- ClassReader reader = rewriter.reader(content);
- CoreLibraryRewriter.UnprefixingClassWriter writer =
- rewriter.writer(ClassWriter.COMPUTE_MAXS /*for bridge methods*/);
- ClassVisitor visitor = writer;
-
- if (!options.onlyDesugarJavac9ForLint) {
- if (!allowDefaultMethods) {
- visitor = new Java7Compatibility(visitor, readerFactory);
- }
-
- visitor =
- new LambdaDesugaring(
- visitor,
- loader,
- lambdas,
- interfaceLambdaMethodCollector,
- allowDefaultMethods);
- }
-
- if (!allowCallsToObjectsNonNull) {
- visitor = new ObjectsRequireNonNullMethodRewriter(visitor);
- }
- if (options.enableRewritingOfLongCompare) {
- visitor = new LongCompareMethodRewriter(visitor);
- }
- reader.accept(visitor, 0);
-
- outputFileProvider.write(filename, writer.toByteArray());
- } else {
- outputFileProvider.copyFrom(filename, inputFiles);
- }
- }
- }
+ desugarOneInput(inputOutputPair, indexedClasspath, bootclassloader);
+ }
+ }
+ }
- ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build();
- checkState(
- !allowDefaultMethods || interfaceLambdaMethods.isEmpty(),
- "Desugaring with default methods enabled moved interface lambdas");
-
- // Write out the lambda classes we generated along the way
- ImmutableMap<Path, LambdaInfo> lambdaClasses = lambdas.drain();
- checkState(
- !options.onlyDesugarJavac9ForLint || lambdaClasses.isEmpty(),
- "There should be no lambda classes generated: %s",
- lambdaClasses.keySet());
-
- for (Map.Entry<Path, LambdaInfo> lambdaClass : lambdaClasses.entrySet()) {
- try (InputStream bytecode =
- Files.newInputStream(dumpDirectory.resolve(lambdaClass.getKey()))) {
- ClassReader reader = rewriter.reader(bytecode);
- CoreLibraryRewriter.UnprefixingClassWriter writer =
- rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/);
- ClassVisitor visitor = writer;
-
- if (!allowDefaultMethods) {
- // null ClassReaderFactory b/c we don't expect to need it for lambda classes
- visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null);
- }
-
- visitor =
- new LambdaClassFixer(
- visitor,
- lambdaClass.getValue(),
- readerFactory,
- interfaceLambdaMethods,
- allowDefaultMethods);
- // Send lambda classes through desugaring to make sure there's no invokedynamic
- // instructions in generated lambda classes (checkState below will fail)
- visitor = new LambdaDesugaring(visitor, loader, lambdas, null, allowDefaultMethods);
- if (!allowCallsToObjectsNonNull) {
- // Not sure whether there will be implicit null check emitted by javac, so we rerun
- // the inliner again
- visitor = new ObjectsRequireNonNullMethodRewriter(visitor);
- }
- if (options.enableRewritingOfLongCompare) {
- visitor = new LongCompareMethodRewriter(visitor);
- }
- reader.accept(visitor, 0);
- String filename =
- rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class";
- outputFileProvider.write(filename, writer.toByteArray());
- }
- }
+ private void desugarOneInput(
+ InputOutputPair inputOutputPair, IndexedInputs indexedClasspath, ClassLoader bootclassloader)
+ throws Exception {
+ Path inputPath = inputOutputPair.getInput();
+ Path outputPath = inputOutputPair.getOutput();
+ checkArgument(
+ Files.isDirectory(inputPath) || !Files.isDirectory(outputPath),
+ "Input jar file requires an output jar file");
+
+ try (OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath);
+ InputFileProvider inputFiles = toInputFileProvider(inputPath)) {
+ IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles));
+ // Prepend classpath with input file itself so LambdaDesugaring can load classes with
+ // lambdas.
+ IndexedInputs indexedClasspathAndInputFiles = indexedClasspath.withParent(indexedInputFiles);
+ // Note that input file and classpath need to be in the same classloader because
+ // we typically get the header Jar for inputJar on the classpath and having the header
+ // Jar in a parent loader means the header version is preferred over the real thing.
+ ClassLoader loader =
+ new HeaderClassLoader(indexedClasspathAndInputFiles, rewriter, bootclassloader);
+
+ ClassReaderFactory readerFactory =
+ new ClassReaderFactory(
+ (options.copyBridgesFromClasspath && !allowDefaultMethods)
+ ? indexedClasspathAndInputFiles
+ : indexedInputFiles,
+ rewriter);
+
+ ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder();
+
+ desugarClassesInInput(
+ inputFiles, outputFileProvider, loader, readerFactory, interfaceLambdaMethodCollector);
+
+ desugarAndWriteDumpedLambdaClassesToOutput(
+ outputFileProvider, loader, readerFactory, interfaceLambdaMethodCollector);
+ }
+
+ ImmutableMap<Path, LambdaInfo> leftBehind = lambdas.drain();
+ checkState(leftBehind.isEmpty(), "Didn't process %s", leftBehind);
+ }
+
+ /** Desugar the classes that are in the inputs specified in the command line arguments. */
+ private void desugarClassesInInput(
+ InputFileProvider inputFiles,
+ OutputFileProvider outputFileProvider,
+ ClassLoader loader,
+ ClassReaderFactory readerFactory,
+ Builder<String> interfaceLambdaMethodCollector)
+ throws IOException {
+ for (String filename : inputFiles) {
+ try (InputStream content = inputFiles.getInputStream(filename)) {
+ // We can write classes uncompressed since they need to be converted to .dex format
+ // for Android anyways. Resources are written as they were in the input jar to avoid
+ // any danger of accidentally uncompressed resources ending up in an .apk.
+ if (filename.endsWith(".class")) {
+ ClassReader reader = rewriter.reader(content);
+ UnprefixingClassWriter writer =
+ rewriter.writer(ClassWriter.COMPUTE_MAXS /*for bridge methods*/);
+ ClassVisitor visitor =
+ createClassVisitorsForClassesInInputs(
+ loader, readerFactory, interfaceLambdaMethodCollector, writer);
+ reader.accept(visitor, 0);
+
+ outputFileProvider.write(filename, writer.toByteArray());
+ } else {
+ outputFileProvider.copyFrom(filename, inputFiles);
}
+ }
+ }
+ }
- ImmutableMap<Path, LambdaInfo> leftBehind = lambdas.drain();
- checkState(leftBehind.isEmpty(), "Didn't process %s", leftBehind);
+ /**
+ * Desugar the classes that are generated on the fly when we are desugaring the classes in the
+ * specified inputs.
+ */
+ private void desugarAndWriteDumpedLambdaClassesToOutput(
+ OutputFileProvider outputFileProvider,
+ ClassLoader loader,
+ ClassReaderFactory readerFactory,
+ Builder<String> interfaceLambdaMethodCollector)
+ throws IOException {
+ ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build();
+ checkState(
+ !allowDefaultMethods || interfaceLambdaMethods.isEmpty(),
+ "Desugaring with default methods enabled moved interface lambdas");
+
+ // Write out the lambda classes we generated along the way
+ ImmutableMap<Path, LambdaInfo> lambdaClasses = lambdas.drain();
+ checkState(
+ !options.onlyDesugarJavac9ForLint || lambdaClasses.isEmpty(),
+ "There should be no lambda classes generated: %s",
+ lambdaClasses.keySet());
+
+ for (Map.Entry<Path, LambdaInfo> lambdaClass : lambdaClasses.entrySet()) {
+ try (InputStream bytecode =
+ Files.newInputStream(dumpDirectory.resolve(lambdaClass.getKey()))) {
+ ClassReader reader = rewriter.reader(bytecode);
+ UnprefixingClassWriter writer =
+ rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/);
+ ClassVisitor visitor =
+ createClassVisitorsForDumpedLambdaClasses(
+ loader, readerFactory, interfaceLambdaMethods, lambdaClass.getValue(), writer);
+ reader.accept(visitor, 0);
+ String filename =
+ rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class";
+ outputFileProvider.write(filename, writer.toByteArray());
}
}
}
+ /**
+ * Create the class visitors for the lambda classes that are generated on the fly. If no new class
+ * visitors are not generated, then the passed-in {@code writer} will be returned.
+ */
+ private ClassVisitor createClassVisitorsForDumpedLambdaClasses(
+ ClassLoader loader,
+ ClassReaderFactory readerFactory,
+ ImmutableSet<String> interfaceLambdaMethods,
+ LambdaInfo lambdaClass,
+ UnprefixingClassWriter writer) {
+ ClassVisitor visitor = writer;
+
+ if (!allowDefaultMethods) {
+ // null ClassReaderFactory b/c we don't expect to need it for lambda classes
+ visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null);
+ }
+
+ visitor =
+ new LambdaClassFixer(
+ visitor, lambdaClass, readerFactory, interfaceLambdaMethods, allowDefaultMethods);
+ // Send lambda classes through desugaring to make sure there's no invokedynamic
+ // instructions in generated lambda classes (checkState below will fail)
+ visitor = new LambdaDesugaring(visitor, loader, lambdas, null, allowDefaultMethods);
+ if (!allowCallsToObjectsNonNull) {
+ // Not sure whether there will be implicit null check emitted by javac, so we rerun
+ // the inliner again
+ visitor = new ObjectsRequireNonNullMethodRewriter(visitor);
+ }
+ if (options.enableRewritingOfLongCompare) {
+ visitor = new LongCompareMethodRewriter(visitor);
+ }
+ return visitor;
+ }
+
+ /**
+ * Create the class visitors for the classes which are in the inputs. If new visitors are created,
+ * then all these visitors and the passed-in writer will be chained together. If no new visitor is
+ * created, then the passed-in {@code writer} will be returned.
+ */
+ private ClassVisitor createClassVisitorsForClassesInInputs(
+ ClassLoader loader,
+ ClassReaderFactory readerFactory,
+ Builder<String> interfaceLambdaMethodCollector,
+ UnprefixingClassWriter writer) {
+ checkArgument(writer != null, "The class writer cannot be null");
+ ClassVisitor visitor = writer;
+
+ if (!options.onlyDesugarJavac9ForLint) {
+ if (!allowDefaultMethods) {
+ visitor = new Java7Compatibility(visitor, readerFactory);
+ }
+
+ visitor =
+ new LambdaDesugaring(
+ visitor, loader, lambdas, interfaceLambdaMethodCollector, allowDefaultMethods);
+ }
+
+ if (!allowCallsToObjectsNonNull) {
+ visitor = new ObjectsRequireNonNullMethodRewriter(visitor);
+ }
+ if (options.enableRewritingOfLongCompare) {
+ visitor = new LongCompareMethodRewriter(visitor);
+ }
+ return visitor;
+ }
+
+
public static void main(String[] args) throws Exception {
// It is important that this method is called first. See its javadoc.
Path dumpDirectory = createAndRegisterLambdaDumpDirectory();