diff options
author | 2017-03-25 04:12:43 +0000 | |
---|---|---|
committer | 2017-03-27 11:36:59 +0000 | |
commit | 507de14c75ae5eff6069887cf7d4f1b900885488 (patch) | |
tree | df934d94507b4a6c26164981643cbd8fa342dbcb /src/tools/android/java/com/google | |
parent | ebebec3fd5ddc89a89bda5684e3c3e1c1d5cd376 (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')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java | 304 |
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(); |