Make KeepScanner tool search classpath for nearest definition of each member reference, instead of potentially referring to a subtype.
Refactor desugar's class loading machinery and related code into a separate package for easier reuse in this tool.
RELNOTES: None.

PiperOrigin-RevId: 188825305
GitOrigin-RevId: 2cbeb24a9c41c6b14ecbb26e2e198fbaf79aea64
Change-Id: Ie2969cb1e1c86aa68c5a6dc0be6b42b09dfaee70
diff --git a/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java b/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java
index 783069f..ce36071 100644
--- a/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java
+++ b/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java
@@ -19,6 +19,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import java.util.ArrayList;
 import java.util.Optional;
 import javax.annotation.Nullable;
diff --git a/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java b/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
index bae5251..aff9bab 100644
--- a/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
+++ b/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
@@ -13,6 +13,9 @@
 // limitations under the License.
 package com.google.devtools.build.android.desugar;
 
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
+import com.google.devtools.build.android.desugar.io.IndexedInputs;
+import com.google.devtools.build.android.desugar.io.InputFileProvider;
 import java.io.IOException;
 import java.io.InputStream;
 import javax.annotation.Nullable;
diff --git a/java/com/google/devtools/build/android/desugar/ClassVsInterface.java b/java/com/google/devtools/build/android/desugar/ClassVsInterface.java
index cb62deb..2724454 100644
--- a/java/com/google/devtools/build/android/desugar/ClassVsInterface.java
+++ b/java/com/google/devtools/build/android/desugar/ClassVsInterface.java
@@ -16,6 +16,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import java.util.HashMap;
 import javax.annotation.Nullable;
 import org.objectweb.asm.ClassReader;
diff --git a/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java b/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
index da23c12..fd10e5e 100644
--- a/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
+++ b/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
@@ -25,6 +25,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Multimap;
+import com.google.devtools.build.android.desugar.io.BitFlags;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
 import com.google.errorprone.annotations.Immutable;
 import java.lang.reflect.Method;
 import java.util.Collection;
diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
index 853ed09..960cfeb 100644
--- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
+++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
@@ -18,6 +18,7 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
diff --git a/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java
index 506a380..c176f9c 100644
--- a/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -27,14 +27,19 @@
 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.build.android.desugar.io.CoreLibraryRewriter;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter.UnprefixingClassWriter;
+import com.google.devtools.build.android.desugar.io.HeaderClassLoader;
+import com.google.devtools.build.android.desugar.io.IndexedInputs;
+import com.google.devtools.build.android.desugar.io.InputFileProvider;
+import com.google.devtools.build.android.desugar.io.OutputFileProvider;
+import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
 import com.google.devtools.common.options.OptionEffectTag;
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsParser;
 import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
-import com.google.errorprone.annotations.MustBeClosed;
 import java.io.IOError;
 import java.io.IOException;
 import java.io.InputStream;
@@ -386,8 +391,8 @@
         Files.isDirectory(inputPath) || !Files.isDirectory(outputPath),
         "Input jar file requires an output jar file");
 
-    try (OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath);
-        InputFileProvider inputFiles = toInputFileProvider(inputPath)) {
+    try (OutputFileProvider outputFileProvider = OutputFileProvider.create(outputPath);
+        InputFileProvider inputFiles = InputFileProvider.open(inputPath)) {
       DependencyCollector depsCollector = createDepsCollector();
       IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles));
       // Prepend classpath with input file itself so LambdaDesugaring can load classes with
@@ -942,19 +947,6 @@
     return ioPairListbuilder.build();
   }
 
-  @VisibleForTesting
-  static class ThrowingClassLoader extends ClassLoader {
-    @Override
-    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-      if (name.startsWith("java.")) {
-        // Use system class loader for java. classes, since ClassLoader.defineClass gets
-        // grumpy when those don't come from the standard place.
-        return super.loadClass(name, resolve);
-      }
-      throw new ClassNotFoundException();
-    }
-  }
-
   private static void deleteTreeOnExit(final Path directory) {
     Thread shutdownHook =
         new Thread() {
@@ -993,26 +985,6 @@
     }
   }
 
-  /** Transform a Path to an {@link OutputFileProvider} */
-  @MustBeClosed
-  private static OutputFileProvider toOutputFileProvider(Path path) throws IOException {
-    if (Files.isDirectory(path)) {
-      return new DirectoryOutputFileProvider(path);
-    } else {
-      return new ZipOutputFileProvider(path);
-    }
-  }
-
-  /** Transform a Path to an InputFileProvider that needs to be closed by the caller. */
-  @MustBeClosed
-  private static InputFileProvider toInputFileProvider(Path path) throws IOException {
-    if (Files.isDirectory(path)) {
-      return new DirectoryInputFileProvider(path);
-    } else {
-      return new ZipInputFileProvider(path);
-    }
-  }
-
   /**
    * Transform a list of Path to a list of InputFileProvider and register them with the given
    * closer.
@@ -1023,7 +995,7 @@
       Closer closer, List<Path> paths) throws IOException {
     ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>();
     for (Path path : paths) {
-      builder.add(closer.register(toInputFileProvider(path)));
+      builder.add(closer.register(InputFileProvider.open(path)));
     }
     return builder.build();
   }
diff --git a/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java b/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java
index f066f2a..355dd97 100644
--- a/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java
+++ b/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.android.desugar;
 
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import org.objectweb.asm.ClassVisitor;
diff --git a/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java b/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
index 0a10df1..e9e3199 100644
--- a/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
+++ b/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
@@ -17,6 +17,8 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
+import com.google.devtools.build.android.desugar.io.BitFlags;
+import com.google.devtools.build.android.desugar.io.FieldInfo;
 import java.lang.reflect.Method;
 import javax.annotation.Nullable;
 import org.objectweb.asm.AnnotationVisitor;
diff --git a/java/com/google/devtools/build/android/desugar/Java7Compatibility.java b/java/com/google/devtools/build/android/desugar/Java7Compatibility.java
index 37a45dd..2090d5c 100644
--- a/java/com/google/devtools/build/android/desugar/Java7Compatibility.java
+++ b/java/com/google/devtools/build/android/desugar/Java7Compatibility.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import javax.annotation.Nullable;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.Attribute;
diff --git a/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java b/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java
index fb05bcb..6b0a921 100644
--- a/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java
+++ b/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java
@@ -19,6 +19,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import org.objectweb.asm.AnnotationVisitor;
diff --git a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
index 5f41347..f9b5316 100644
--- a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
+++ b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
@@ -21,6 +21,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import java.io.IOException;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles.Lookup;
diff --git a/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java b/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java
index 6ac415d..7f2f355 100644
--- a/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java
+++ b/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java
@@ -17,6 +17,7 @@
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 import static org.objectweb.asm.Opcodes.LCMP;
 
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
 
diff --git a/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java b/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java
index 5e0a344..931459a 100644
--- a/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java
+++ b/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java
@@ -19,6 +19,7 @@
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 import static org.objectweb.asm.Opcodes.POP;
 
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
 
diff --git a/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java b/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
index e8509e7..818585f 100644
--- a/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
+++ b/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
@@ -29,6 +29,7 @@
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType;
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Optional;
diff --git a/java/com/google/devtools/build/android/desugar/BitFlags.java b/java/com/google/devtools/build/android/desugar/io/BitFlags.java
similarity index 95%
rename from java/com/google/devtools/build/android/desugar/BitFlags.java
rename to java/com/google/devtools/build/android/desugar/io/BitFlags.java
index 8be2288..af6f481 100644
--- a/java/com/google/devtools/build/android/desugar/BitFlags.java
+++ b/java/com/google/devtools/build/android/desugar/io/BitFlags.java
@@ -11,12 +11,12 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import org.objectweb.asm.Opcodes;
 
 /** Convenience method for working with {@code int} bitwise flags. */
-class BitFlags {
+public class BitFlags {
 
   /**
    * Returns {@code true} iff <b>all</b> bits in {@code bitmask} are set in {@code flags}. Trivially
diff --git a/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java b/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
similarity index 97%
rename from java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java
rename to java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
index 698fc53..f3c546c 100644
--- a/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java
+++ b/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -25,7 +25,7 @@
 import org.objectweb.asm.commons.Remapper;
 
 /** Utility class to prefix or unprefix class names of core library classes */
-class CoreLibraryRewriter {
+public class CoreLibraryRewriter {
   private final String prefix;
 
   public CoreLibraryRewriter(String prefix) {
@@ -178,11 +178,11 @@
 
     /** Returns the (unprefixed) name of the class once written. */
     @Nullable
-    String getClassName() {
+    public String getClassName() {
       return finalClassName;
     }
 
-    byte[] toByteArray() {
+    public byte[] toByteArray() {
       return writer.toByteArray();
     }
 
diff --git a/java/com/google/devtools/build/android/desugar/DirectoryInputFileProvider.java b/java/com/google/devtools/build/android/desugar/io/DirectoryInputFileProvider.java
similarity index 97%
rename from java/com/google/devtools/build/android/desugar/DirectoryInputFileProvider.java
rename to java/com/google/devtools/build/android/desugar/io/DirectoryInputFileProvider.java
index 1c5abc9..c607b42 100644
--- a/java/com/google/devtools/build/android/desugar/DirectoryInputFileProvider.java
+++ b/java/com/google/devtools/build/android/desugar/io/DirectoryInputFileProvider.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import java.io.File;
 import java.io.FileInputStream;
diff --git a/java/com/google/devtools/build/android/desugar/DirectoryOutputFileProvider.java b/java/com/google/devtools/build/android/desugar/io/DirectoryOutputFileProvider.java
similarity index 93%
rename from java/com/google/devtools/build/android/desugar/DirectoryOutputFileProvider.java
rename to java/com/google/devtools/build/android/desugar/io/DirectoryOutputFileProvider.java
index 782a81e..f8e87cb 100644
--- a/java/com/google/devtools/build/android/desugar/DirectoryOutputFileProvider.java
+++ b/java/com/google/devtools/build/android/desugar/io/DirectoryOutputFileProvider.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
@@ -21,7 +21,7 @@
 import java.nio.file.Path;
 
 /** Output provider is a directory. */
-public class DirectoryOutputFileProvider implements OutputFileProvider {
+class DirectoryOutputFileProvider implements OutputFileProvider {
 
   private final Path root;
 
diff --git a/java/com/google/devtools/build/android/desugar/FieldInfo.java b/java/com/google/devtools/build/android/desugar/io/FieldInfo.java
similarity index 87%
rename from java/com/google/devtools/build/android/desugar/FieldInfo.java
rename to java/com/google/devtools/build/android/desugar/io/FieldInfo.java
index c281039..0b4f634 100644
--- a/java/com/google/devtools/build/android/desugar/FieldInfo.java
+++ b/java/com/google/devtools/build/android/desugar/io/FieldInfo.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import com.google.auto.value.AutoValue;
 
@@ -19,7 +19,7 @@
 @AutoValue
 public abstract class FieldInfo {
 
-  static FieldInfo create(String owner, String name, String desc) {
+  public static FieldInfo create(String owner, String name, String desc) {
     return new AutoValue_FieldInfo(owner, name, desc);
   }
 
diff --git a/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java b/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java
similarity index 98%
rename from java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
rename to java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java
index 77d99bb..f70dc0e 100644
--- a/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
+++ b/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import com.google.common.collect.ImmutableList;
 import java.io.IOError;
@@ -33,7 +33,7 @@
  *
  * @see java.net.URLClassLoader
  */
-class HeaderClassLoader extends ClassLoader {
+public class HeaderClassLoader extends ClassLoader {
 
   private final IndexedInputs indexedInputs;
   private final CoreLibraryRewriter rewriter;
diff --git a/java/com/google/devtools/build/android/desugar/IndexedInputs.java b/java/com/google/devtools/build/android/desugar/io/IndexedInputs.java
similarity index 97%
rename from java/com/google/devtools/build/android/desugar/IndexedInputs.java
rename to java/com/google/devtools/build/android/desugar/io/IndexedInputs.java
index 33c6132..8ce4b62 100644
--- a/java/com/google/devtools/build/android/desugar/IndexedInputs.java
+++ b/java/com/google/devtools/build/android/desugar/io/IndexedInputs.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
@@ -28,7 +28,7 @@
  * scanning all inputs over and over for each class to load. An indexed inputs can have a parent
  * that is firstly used when a file name is searched.
  */
-class IndexedInputs {
+public class IndexedInputs {
 
   private final ImmutableMap<String, InputFileProvider> inputFiles;
 
diff --git a/java/com/google/devtools/build/android/desugar/InputFileProvider.java b/java/com/google/devtools/build/android/desugar/io/InputFileProvider.java
similarity index 70%
rename from java/com/google/devtools/build/android/desugar/InputFileProvider.java
rename to java/com/google/devtools/build/android/desugar/io/InputFileProvider.java
index c2b6353..c41d018 100644
--- a/java/com/google/devtools/build/android/desugar/InputFileProvider.java
+++ b/java/com/google/devtools/build/android/desugar/io/InputFileProvider.java
@@ -11,15 +11,18 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
+import com.google.errorprone.annotations.MustBeClosed;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.zip.ZipEntry;
 
 /** Input file provider allows to iterate on relative path filename of a directory or a jar file. */
-interface InputFileProvider extends Closeable, Iterable<String> {
+public interface InputFileProvider extends Closeable, Iterable<String> {
 
   /**
    * Return a ZipEntry for {@code filename}. If the provider is a {@link ZipInputFileProvider}, the
@@ -33,4 +36,14 @@
    * responsibility of the caller to close this stream.
    */
   InputStream getInputStream(String filename) throws IOException;
+
+  /** Transform a Path to an InputFileProvider that needs to be closed by the caller. */
+  @MustBeClosed
+  public static InputFileProvider open(Path path) throws IOException {
+    if (Files.isDirectory(path)) {
+      return new DirectoryInputFileProvider(path);
+    } else {
+      return new ZipInputFileProvider(path);
+    }
+  }
 }
diff --git a/java/com/google/devtools/build/android/desugar/OutputFileProvider.java b/java/com/google/devtools/build/android/desugar/io/OutputFileProvider.java
similarity index 71%
rename from java/com/google/devtools/build/android/desugar/OutputFileProvider.java
rename to java/com/google/devtools/build/android/desugar/io/OutputFileProvider.java
index 7a590ef..e693786 100644
--- a/java/com/google/devtools/build/android/desugar/OutputFileProvider.java
+++ b/java/com/google/devtools/build/android/desugar/io/OutputFileProvider.java
@@ -11,12 +11,15 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
+import com.google.errorprone.annotations.MustBeClosed;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 
 /** Output file provider allows to write files in directory or jar files. */
-interface OutputFileProvider extends AutoCloseable {
+public interface OutputFileProvider extends AutoCloseable {
 
   /** Filename to use to write out dependency metadata for later consistency checking. */
   public static final String DESUGAR_DEPS_FILENAME = "META-INF/desugar_deps";
@@ -29,4 +32,14 @@
 
   /** Write {@code content} in {@code filename} to this output */
   void write(String filename, byte[] content) throws IOException;
+
+  /** Transform a Path to an {@link OutputFileProvider} */
+  @MustBeClosed
+  public static OutputFileProvider create(Path path) throws IOException {
+    if (Files.isDirectory(path)) {
+      return new DirectoryOutputFileProvider(path);
+    } else {
+      return new ZipOutputFileProvider(path);
+    }
+  }
 }
diff --git a/java/com/google/devtools/build/android/desugar/io/ThrowingClassLoader.java b/java/com/google/devtools/build/android/desugar/io/ThrowingClassLoader.java
new file mode 100644
index 0000000..16f83f2
--- /dev/null
+++ b/java/com/google/devtools/build/android/desugar/io/ThrowingClassLoader.java
@@ -0,0 +1,27 @@
+// Copyright 2016 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.io;
+
+/** Class loader that throws whenever it can, for use the parent of a class loader hierarchy. */
+public class ThrowingClassLoader extends ClassLoader {
+  @Override
+  protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+    if (name.startsWith("java.")) {
+      // Use system class loader for java. classes, since ClassLoader.defineClass gets
+      // grumpy when those don't come from the standard place.
+      return super.loadClass(name, resolve);
+    }
+    throw new ClassNotFoundException();
+  }
+}
\ No newline at end of file
diff --git a/java/com/google/devtools/build/android/desugar/ZipInputFileProvider.java b/java/com/google/devtools/build/android/desugar/io/ZipInputFileProvider.java
similarity index 96%
rename from java/com/google/devtools/build/android/desugar/ZipInputFileProvider.java
rename to java/com/google/devtools/build/android/desugar/io/ZipInputFileProvider.java
index 307c8b8..9bd7758 100644
--- a/java/com/google/devtools/build/android/desugar/ZipInputFileProvider.java
+++ b/java/com/google/devtools/build/android/desugar/io/ZipInputFileProvider.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import com.google.common.base.Functions;
 import com.google.common.collect.Iterators;
diff --git a/java/com/google/devtools/build/android/desugar/ZipOutputFileProvider.java b/java/com/google/devtools/build/android/desugar/io/ZipOutputFileProvider.java
similarity index 95%
rename from java/com/google/devtools/build/android/desugar/ZipOutputFileProvider.java
rename to java/com/google/devtools/build/android/desugar/io/ZipOutputFileProvider.java
index 8d6501d..36cb26d 100644
--- a/java/com/google/devtools/build/android/desugar/ZipOutputFileProvider.java
+++ b/java/com/google/devtools/build/android/desugar/io/ZipOutputFileProvider.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
@@ -26,7 +26,7 @@
 import java.util.zip.ZipOutputStream;
 
 /** Output provider is a zip file. */
-public class ZipOutputFileProvider implements OutputFileProvider {
+class ZipOutputFileProvider implements OutputFileProvider {
 
   private final ZipOutputStream out;
 
diff --git a/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java b/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java
index b347c7a..4924f7c 100644
--- a/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java
+++ b/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java
@@ -15,13 +15,21 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 import static java.nio.file.StandardOpenOption.CREATE;
 import static java.util.Comparator.comparing;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.ByteStreams;
+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.io.CoreLibraryRewriter;
+import com.google.devtools.build.android.desugar.io.HeaderClassLoader;
+import com.google.devtools.build.android.desugar.io.IndexedInputs;
+import com.google.devtools.build.android.desugar.io.InputFileProvider;
+import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
 import com.google.devtools.common.options.OptionEffectTag;
@@ -32,9 +40,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
+import java.lang.reflect.Method;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
@@ -57,6 +67,32 @@
     public Path inputJars;
 
     @Option(
+      name = "classpath_entry",
+      allowMultiple = true,
+      defaultValue = "",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      converter = ExistingPathConverter.class,
+      help =
+          "Ordered classpath (Jar or directory) to resolve symbols in the --input Jar, like "
+              + "javac's -cp flag."
+    )
+    public List<Path> classpath;
+
+    @Option(
+      name = "bootclasspath_entry",
+      allowMultiple = true,
+      defaultValue = "",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      converter = ExistingPathConverter.class,
+      help =
+          "Bootclasspath that was used to compile the --input Jar with, like javac's "
+              + "-bootclasspath flag (required)."
+    )
+    public List<Path> bootclasspath;
+
+    @Option(
       name = "keep_file",
       defaultValue = "null",
       documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
@@ -81,10 +117,25 @@
     parser.setAllowResidue(false);
     parser.enableParamsFileSupport(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()));
     parser.parseAndExitUponError(args);
-
     KeepScannerOptions options = parser.getOptions(KeepScannerOptions.class);
-    Map<String, ImmutableSet<KeepReference>> seeds =
-        scan(checkNotNull(options.inputJars), options.prefix);
+
+    Map<String, ImmutableSet<KeepReference>> seeds;
+    try (Closer closer = Closer.create()) {
+      // TODO(kmb): Try to share more of this code with Desugar binary
+      IndexedInputs classpath =
+          new IndexedInputs(toRegisteredInputFileProvider(closer, options.classpath));
+      IndexedInputs bootclasspath =
+          new IndexedInputs(toRegisteredInputFileProvider(closer, options.bootclasspath));
+
+      // Construct classloader from classpath.  Since we're assuming the prefix we're looking for
+      // isn't part of the input itself we shouldn't need to include the input in the classloader.
+      CoreLibraryRewriter noopRewriter = new CoreLibraryRewriter("");
+      ClassLoader classloader =
+          new HeaderClassLoader(classpath, noopRewriter,
+              new HeaderClassLoader(bootclasspath, noopRewriter,
+                  new ThrowingClassLoader()));
+      seeds = scan(checkNotNull(options.inputJars), options.prefix, classloader);
+    }
 
     try (PrintStream out =
         new PrintStream(
@@ -117,11 +168,9 @@
             });
   }
 
-  /**
-   * Scans for and returns references with owners matching the given prefix grouped by owner.
-   */
-  private static Map<String, ImmutableSet<KeepReference>> scan(Path jarFile, String prefix)
-      throws IOException {
+  /** Scans for and returns references with owners matching the given prefix grouped by owner. */
+  private static Map<String, ImmutableSet<KeepReference>> scan(
+      Path jarFile, String prefix, ClassLoader classpath) throws IOException {
     // We read the Jar sequentially since ZipFile uses locks anyway but then allow scanning each
     // class in parallel.
     try (ZipFile zip = new ZipFile(jarFile.toFile())) {
@@ -131,6 +180,8 @@
           .parallel()
           .flatMap(
               content -> PrefixReferenceScanner.scan(new ClassReader(content), prefix).stream())
+          .distinct() // so we don't process the same reference multiple times next
+          .map(ref -> nearestDeclaration(ref, classpath))
           .collect(
               Collectors.groupingByConcurrent(
                   KeepReference::internalName, ImmutableSet.toImmutableSet()));
@@ -147,6 +198,68 @@
     }
   }
 
+  /**
+   * Find the nearest definition of the given reference in the class hierarchy and return the
+   * modified reference.  This is needed b/c bytecode sometimes refers to a method or field using
+   * an owner type that inherits the method or field instead of defining the member itself.
+   * In that case we need to find and keep the inherited definition.
+   */
+  private static KeepReference nearestDeclaration(KeepReference ref, ClassLoader classpath) {
+    if (!ref.isMemberReference() || "<init>".equals(ref.name())) {
+      return ref; // class and constructor references don't need any further work
+    }
+
+    Class<?> clazz;
+    try {
+      clazz = classpath.loadClass(ref.internalName().replace('/', '.'));
+    } catch (ClassNotFoundException e) {
+      throw (NoClassDefFoundError) new NoClassDefFoundError("Couldn't load " + ref).initCause(e);
+    }
+
+    Class<?> owner = findDeclaringClass(clazz, ref);
+    if (owner == clazz) {
+      return ref;
+    }
+    String parent = checkNotNull(owner, "Can't resolve: %s", ref).getName().replace('.', '/');
+    return KeepReference.memberReference(parent, ref.name(), ref.desc());
+  }
+
+  private static Class<?> findDeclaringClass(Class<?> clazz, KeepReference ref) {
+    if (ref.isFieldReference()) {
+      try {
+        return clazz.getField(ref.name()).getDeclaringClass();
+      } catch (NoSuchFieldException e) {
+        // field must be non-public, so search class hierarchy
+        do {
+          try {
+            return clazz.getDeclaredField(ref.name()).getDeclaringClass();
+          } catch (NoSuchFieldException ignored) {
+            // fall through for clarity
+          }
+          clazz = clazz.getSuperclass();
+        } while (clazz != null);
+      }
+    } else {
+      checkState(ref.isMethodReference());
+      Type descriptor = Type.getMethodType(ref.desc());
+      for (Method m : clazz.getMethods()) {
+        if (m.getName().equals(ref.name()) && Type.getType(m).equals(descriptor)) {
+          return m.getDeclaringClass();
+        }
+      }
+      do {
+        // Method must be non-public, so search class hierarchy
+        for (Method m : clazz.getDeclaredMethods()) {
+          if (m.getName().equals(ref.name()) && Type.getType(m).equals(descriptor)) {
+            return m.getDeclaringClass();
+          }
+        }
+        clazz = clazz.getSuperclass();
+      } while (clazz != null);
+    }
+    return null;
+  }
+
   private static CharSequence toKeepDescriptor(KeepReference member) {
     StringBuilder result = new StringBuilder();
     if (member.isMethodReference()) {
@@ -172,5 +285,19 @@
     return result;
   }
 
+  /**
+   * Transform a list of Path to a list of InputFileProvider and register them with the given
+   * closer.
+   */
+  @SuppressWarnings("MustBeClosedChecker")
+  private static ImmutableList<InputFileProvider> toRegisteredInputFileProvider(
+      Closer closer, List<Path> paths) throws IOException {
+    ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>();
+    for (Path path : paths) {
+      builder.add(closer.register(InputFileProvider.open(path)));
+    }
+    return builder.build();
+  }
+
   private KeepScanner() {}
 }
diff --git a/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java b/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java
index ec4e16d..42f1f78 100644
--- a/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java
+++ b/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java
@@ -16,6 +16,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Map;
diff --git a/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java b/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java
index 2bdd58b..5220ed6 100644
--- a/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java
+++ b/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java
@@ -16,6 +16,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
diff --git a/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java b/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java
index faa6dda..406a36f 100644
--- a/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java
+++ b/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java
@@ -19,7 +19,10 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.Closer;
-import com.google.devtools.build.android.desugar.Desugar.ThrowingClassLoader;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
+import com.google.devtools.build.android.desugar.io.HeaderClassLoader;
+import com.google.devtools.build.android.desugar.io.IndexedInputs;
+import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
 import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
diff --git a/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java b/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java
index 2eab943..99e51c1 100644
--- a/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java
+++ b/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java
@@ -16,6 +16,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
 
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
diff --git a/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java b/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
index 37afae7..dc0da22 100644
--- a/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
+++ b/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
@@ -25,6 +25,7 @@
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
+import com.google.devtools.build.android.desugar.io.BitFlags;
 import com.google.devtools.build.android.desugar.runtime.ThrowableExtension;
 import com.google.devtools.build.android.desugar.testdata.ClassUsingTryWithResources;
 import java.io.IOException;
diff --git a/test/java/com/google/devtools/build/android/desugar/FieldInfoTest.java b/test/java/com/google/devtools/build/android/desugar/io/FieldInfoTest.java
similarity index 95%
rename from test/java/com/google/devtools/build/android/desugar/FieldInfoTest.java
rename to test/java/com/google/devtools/build/android/desugar/io/FieldInfoTest.java
index afb2bea..0579822 100644
--- a/test/java/com/google/devtools/build/android/desugar/FieldInfoTest.java
+++ b/test/java/com/google/devtools/build/android/desugar/io/FieldInfoTest.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/test/java/com/google/devtools/build/android/desugar/IndexedInputsTest.java b/test/java/com/google/devtools/build/android/desugar/io/IndexedInputsTest.java
similarity index 98%
rename from test/java/com/google/devtools/build/android/desugar/IndexedInputsTest.java
rename to test/java/com/google/devtools/build/android/desugar/io/IndexedInputsTest.java
index bac3fc9..81a4b31 100644
--- a/test/java/com/google/devtools/build/android/desugar/IndexedInputsTest.java
+++ b/test/java/com/google/devtools/build/android/desugar/io/IndexedInputsTest.java
@@ -11,7 +11,7 @@
 // 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;
+package com.google.devtools.build.android.desugar.io;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java b/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java
index 482c32a..830364c 100644
--- a/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java
+++ b/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java
@@ -54,6 +54,10 @@
     return result;
   }
 
+  public void expire(long before) {
+    dates.removeIf(d -> d.getTime() < before);
+  }
+
   static {
     System.out.println("Hello!");
   }
diff --git a/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt b/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt
index 35744ce..6082576 100644
--- a/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt
+++ b/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt
@@ -18,6 +18,19 @@
 -keep class java.lang.System {
   *** out;
 }
+-keep class java.lang.invoke.CallSite {
+}
+-keep class java.lang.invoke.LambdaMetafactory {
+  *** metafactory(java.lang.invoke.MethodHandles$Lookup, java.lang.String, java.lang.invoke.MethodType, java.lang.invoke.MethodType, java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
+}
+-keep class java.lang.invoke.MethodHandle {
+}
+-keep class java.lang.invoke.MethodHandles {
+}
+-keep class java.lang.invoke.MethodHandles$Lookup {
+}
+-keep class java.lang.invoke.MethodType {
+}
 -keep class java.util.AbstractList {
 }
 -keep class java.util.ArrayList {
@@ -28,6 +41,7 @@
   *** iterator();
 }
 -keep class java.util.Collection {
+  *** removeIf(java.util.function.Predicate);
 }
 -keep class java.util.Date {
   <init>(long);
@@ -44,3 +58,5 @@
   *** get(int);
   *** iterator();
 }
+-keep class java.util.function.Predicate {
+}