diff options
4 files changed, 128 insertions, 54 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java index 6ec4e0d4c9..56f99a3d67 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java @@ -15,31 +15,39 @@ package com.google.devtools.build.android.desugar; import java.io.IOException; import java.io.InputStream; +import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.annotation.Nullable; import org.objectweb.asm.ClassReader; class ClassReaderFactory { - private final ZipFile jar; + private final IndexedJars indexedJars; private final CoreLibraryRewriter rewriter; - public ClassReaderFactory(ZipFile jar, CoreLibraryRewriter rewriter) { - this.jar = jar; + public ClassReaderFactory(IndexedJars indexedJars, CoreLibraryRewriter rewriter) { this.rewriter = rewriter; + this.indexedJars = indexedJars; } /** * Returns a reader for the given/internal/Class$Name if the class is defined in the wrapped Jar - * and {@code null} otherwise. For simplicity this method turns checked into runtime excpetions + * and {@code null} otherwise. For simplicity this method turns checked into runtime exceptions * under the assumption that all classes have already been read once when this method is called. */ @Nullable public ClassReader readIfKnown(String internalClassName) { - ZipEntry entry = jar.getEntry(rewriter.unprefix(internalClassName) + ".class"); - if (entry == null) { - return null; + String filename = rewriter.unprefix(internalClassName) + ".class"; + JarFile jarFile = indexedJars.getJarFile(filename); + + if (jarFile != null) { + return getClassReader(internalClassName, jarFile, jarFile.getEntry(filename)); } + + return null; + } + + private ClassReader getClassReader(String internalClassName, ZipFile jar, ZipEntry entry) { try (InputStream bytecode = jar.getInputStream(entry)) { // ClassReader doesn't take ownership and instead eagerly reads the stream's contents return rewriter.reader(bytecode); 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 36d9f5587d..116b2b5769 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 @@ -121,6 +121,14 @@ class Desugar { public int minSdkVersion; @Option( + name = "copy_bridges_from_classpath", + defaultValue = "false", + category = "misc", + help = "Copy bridges from classpath to desugared classes." + ) + public boolean copyBridgesFromClasspath; + + @Option( name = "core_library", defaultValue = "false", category = "undocumented", @@ -168,16 +176,22 @@ class Desugar { CoreLibraryRewriter rewriter = new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : ""); + IndexedJars appIndexedJar = new IndexedJars(ImmutableList.of(options.inputJar)); + IndexedJars appAndClasspathIndexedJars = new IndexedJars(options.classpath, appIndexedJar); ClassLoader loader = - createClassLoader( - rewriter, options.bootclasspath, options.inputJar, options.classpath, parent); + createClassLoader(rewriter, options.bootclasspath, appAndClasspathIndexedJars, parent); boolean allowCallsToObjectsNonNull = options.minSdkVersion >= 19; try (ZipFile in = new ZipFile(options.inputJar.toFile()); ZipOutputStream out = new ZipOutputStream( new BufferedOutputStream(Files.newOutputStream(options.outputJar)))) { LambdaClassMaker lambdas = new LambdaClassMaker(dumpDirectory); - ClassReaderFactory readerFactory = new ClassReaderFactory(in, rewriter); + ClassReaderFactory readerFactory = + new ClassReaderFactory( + (options.copyBridgesFromClasspath && !allowDefaultMethods) + ? appAndClasspathIndexedJars + : appIndexedJar, + rewriter); ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder(); @@ -288,21 +302,19 @@ class Desugar { private static ClassLoader createClassLoader( CoreLibraryRewriter rewriter, List<Path> bootclasspath, - Path inputJar, - List<Path> classpath, + IndexedJars appAndClasspathIndexedJars, ClassLoader parent) throws IOException { - // Prepend classpath with input jar itself so LambdaDesugaring can load classes with lambdas. - // Note that inputJar 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. - classpath = ImmutableList.<Path>builder().add(inputJar).addAll(classpath).build(); // Use a classloader that as much as possible uses the provided bootclasspath instead of // the tool's system classloader. Unfortunately we can't do that for java. classes. if (!bootclasspath.isEmpty()) { - parent = HeaderClassLoader.fromClassPath(bootclasspath, rewriter, parent); + parent = new HeaderClassLoader(new IndexedJars(bootclasspath), rewriter, parent); } - return HeaderClassLoader.fromClassPath(classpath, rewriter, parent); + // Prepend classpath with input jar itself so LambdaDesugaring can load classes with lambdas. + // Note that inputJar 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. + return new HeaderClassLoader(appAndClasspathIndexedJars, rewriter, parent); } private static class ThrowingClassLoader extends ClassLoader { diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java b/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java index 053d52d4b2..44c39325c1 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java @@ -16,12 +16,6 @@ package com.google.devtools.build.android.desugar; import java.io.IOError; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Path; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import org.objectweb.asm.ClassReader; @@ -41,44 +35,20 @@ import org.objectweb.asm.Opcodes; */ class HeaderClassLoader extends ClassLoader { - private final Map<String, JarFile> jarfiles; + private final IndexedJars indexedJars; private final CoreLibraryRewriter rewriter; - /** Creates a classloader from the given classpath with the given parent. */ - public static HeaderClassLoader fromClassPath( - List<Path> classpath, CoreLibraryRewriter rewriter, ClassLoader parent) throws IOException { - return new HeaderClassLoader(indexJars(classpath), rewriter, parent); - } - - /** - * Opens the given list of Jar files and returns an index of all classes in them, to avoid - * scanning all Jars over and over for each class in {@link #findClass}. - */ - private static Map<String, JarFile> indexJars(List<Path> classpath) throws IOException { - HashMap<String, JarFile> result = new HashMap<>(); - for (Path jarfile : classpath) { - JarFile jar = new JarFile(jarfile.toFile()); - for (Enumeration<JarEntry> cur = jar.entries(); cur.hasMoreElements(); ) { - JarEntry entry = cur.nextElement(); - if (entry.getName().endsWith(".class") && !result.containsKey(entry.getName())) { - result.put(entry.getName(), jar); - } - } - } - return result; - } - - private HeaderClassLoader( - Map<String, JarFile> jarfiles, CoreLibraryRewriter rewriter, ClassLoader parent) { + public HeaderClassLoader( + IndexedJars indexedJars, CoreLibraryRewriter rewriter, ClassLoader parent) { super(parent); this.rewriter = rewriter; - this.jarfiles = jarfiles; + this.indexedJars = indexedJars; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String filename = rewriter.unprefix(name.replace('.', '/') + ".class"); - JarFile jarfile = jarfiles.get(filename); + JarFile jarfile = indexedJars.getJarFile(filename); if (jarfile == null) { throw new ClassNotFoundException(); } diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/IndexedJars.java b/src/tools/android/java/com/google/devtools/build/android/desugar/IndexedJars.java new file mode 100644 index 0000000000..bdb5b47844 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/IndexedJars.java @@ -0,0 +1,84 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.android.desugar; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import javax.annotation.Nullable; + +/** + * Opens the given list of Jar files and compute an index of all classes in them, to avoid + * scanning all Jars over and over for each class to load. An indexed jars can have a parent + * that is firstly used when a file name is searched. + */ +class IndexedJars { + + private final Map<String, JarFile> jarfiles = new HashMap<>(); + + /** + * Parent indexed jars to use before to search a file name into this indexed jars. + */ + @Nullable + private final IndexedJars parentIndexedJar; + + /** + * Index a list of Jar files without a parent indexed jars. + */ + public IndexedJars(List<Path> jarFiles) throws IOException { + this(jarFiles, null); + } + + /** + * Index a list of Jar files and set a parent indexed jars that is firstly used during the search + * of a file name. + */ + public IndexedJars(List<Path> jarFiles, @Nullable IndexedJars parentIndexedJar) + throws IOException { + this.parentIndexedJar = parentIndexedJar; + for (Path jarfile : jarFiles) { + indexJar(jarfile); + } + } + + @Nullable + public JarFile getJarFile(String filename) { + Preconditions.checkArgument(filename.endsWith(".class")); + + if (parentIndexedJar != null) { + JarFile jarFile = parentIndexedJar.getJarFile(filename); + if (jarFile != null) { + return jarFile; + } + } + + return jarfiles.get(filename); + } + + private void indexJar(Path jarfile) throws IOException { + JarFile jar = new JarFile(jarfile.toFile()); + for (Enumeration<JarEntry> cur = jar.entries(); cur.hasMoreElements(); ) { + JarEntry entry = cur.nextElement(); + if (entry.getName().endsWith(".class") && !jarfiles.containsKey(entry.getName())) { + jarfiles.put(entry.getName(), jar); + } + } + } +} |