|  | // Copyright (c) 2019, 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; | 
|  |  | 
|  | import com.android.tools.r8.errors.CompilationError; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.origin.PathOrigin; | 
|  | import com.android.tools.r8.utils.DescriptorUtils; | 
|  | import com.android.tools.r8.utils.FileUtils; | 
|  | import java.io.Closeable; | 
|  | import java.io.IOException; | 
|  | import java.net.URI; | 
|  | import java.net.URL; | 
|  | import java.net.URLClassLoader; | 
|  | import java.nio.file.FileSystem; | 
|  | import java.nio.file.FileSystems; | 
|  | import java.nio.file.Files; | 
|  | import java.nio.file.NoSuchFileException; | 
|  | import java.nio.file.Path; | 
|  | import java.util.Collections; | 
|  | import java.util.HashMap; | 
|  | import java.util.HashSet; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  |  | 
|  | /** | 
|  | * Lazy Java class file resource provider loading class files from a JDK. | 
|  | * | 
|  | * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for | 
|  | * resources in the descriptor set will then force the read of JDK content. | 
|  | * | 
|  | * <p>This supports all JDK versions. For JDK's of version 8 or lower classes in | 
|  | * <code>lib/rt.jar</code> will be loaded. JDK's of version 9 or higher system module classes will | 
|  | * be loaded using <code>lib/jrt-fs.jar/<code>. | 
|  | */ | 
|  | @Keep | 
|  | public class JdkClassFileProvider implements ClassFileResourceProvider, Closeable { | 
|  | private Origin origin; | 
|  | private final Set<String> descriptors = new HashSet<>(); | 
|  | private final Map<String, String> descriptorToModule = new HashMap<>(); | 
|  | private URLClassLoader jrtFsJarLoader; | 
|  | private FileSystem jrtFs; | 
|  |  | 
|  | /** | 
|  | * Creates a lazy class-file program-resource provider for the system modules of the running Java | 
|  | * VM. | 
|  | * | 
|  | * <p>Only supported if the current VM is of version 9 or higher. | 
|  | */ | 
|  | public static ClassFileResourceProvider fromSystemJdk() throws IOException { | 
|  | return new JdkClassFileProvider(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a lazy class-file program-resource provider for the system modules of a JDK. | 
|  | * | 
|  | * @param home Location of the JDK to read the program-resources from. | 
|  | *     <p>Only supported if the JDK to read is of version 9 or higher. | 
|  | */ | 
|  | public static ClassFileResourceProvider fromSystemModulesJdk(Path home) throws IOException { | 
|  | Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar"); | 
|  | if (!Files.exists(jrtFsJar)) { | 
|  | throw new NoSuchFileException(jrtFsJar.toString()); | 
|  | } | 
|  | return new JdkClassFileProvider(home); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a lazy class-file program-resource provider for a JDK. | 
|  | * | 
|  | * <p>This will load the program resources form the system modules for JDK of version 9 or higher. | 
|  | * | 
|  | * <p>This will load <code>lib/rt.jar</code> for JDK of version 8 and lower. | 
|  | * | 
|  | * @param home Location of the JDK to read the program-resources from. | 
|  | */ | 
|  | public static ClassFileResourceProvider fromJdkHome(Path home) throws IOException { | 
|  | Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar"); | 
|  | if (Files.exists(jrtFsJar)) { | 
|  | return JdkClassFileProvider.fromSystemModulesJdk(home); | 
|  | } | 
|  | // JDK has rt.jar in jre/lib/rt.jar. | 
|  | Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar"); | 
|  | if (Files.exists(rtJar)) { | 
|  | return fromJavaRuntimeJar(rtJar); | 
|  | } | 
|  | // JRE has rt.jar in lib/rt.jar. | 
|  | rtJar = home.resolve("lib").resolve("rt.jar"); | 
|  | if (Files.exists(rtJar)) { | 
|  | return fromJavaRuntimeJar(rtJar); | 
|  | } | 
|  | throw new IOException("Path " + home + " does not look like a Java home"); | 
|  | } | 
|  |  | 
|  | public static ClassFileResourceProvider fromJavaRuntimeJar(Path archive) throws IOException { | 
|  | return new ArchiveClassFileProvider(archive); | 
|  | } | 
|  |  | 
|  | private JdkClassFileProvider() throws IOException { | 
|  | origin = Origin.unknown(); | 
|  | collectDescriptors(FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap())); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a lazy class-file program-resource provider for the system modules of a JDK. | 
|  | * | 
|  | * @param home Location of the JDK to read the program-resources from. | 
|  | *     <p>Only supported if the JDK to read is of version 9 or higher. | 
|  | */ | 
|  | private JdkClassFileProvider(Path home) throws IOException { | 
|  | origin = new PathOrigin(home); | 
|  | Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar"); | 
|  | assert Files.exists(jrtFsJar); | 
|  | jrtFsJarLoader = new URLClassLoader(new URL[] {jrtFsJar.toUri().toURL()}); | 
|  | FileSystem jrtFs = | 
|  | FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap(), jrtFsJarLoader); | 
|  | collectDescriptors(jrtFs); | 
|  | } | 
|  |  | 
|  | private void collectDescriptors(FileSystem jrtFs) throws IOException { | 
|  | this.jrtFs = jrtFs; | 
|  | Files.walk(jrtFs.getPath("/modules")) | 
|  | .forEach( | 
|  | path -> { | 
|  | if (FileUtils.isClassFile(path)) { | 
|  | DescriptorUtils.ModuleAndDescriptor moduleAndDescriptor = | 
|  | DescriptorUtils.guessJrtModuleAndTypeDescriptor(path.toString()); | 
|  | descriptorToModule.put( | 
|  | moduleAndDescriptor.getDescriptor(), moduleAndDescriptor.getModule()); | 
|  | descriptors.add(moduleAndDescriptor.getDescriptor()); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Set<String> getClassDescriptors() { | 
|  | return Collections.unmodifiableSet(descriptors); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ProgramResource getProgramResource(String descriptor) { | 
|  | if (!descriptors.contains(descriptor)) { | 
|  | return null; | 
|  | } | 
|  | try { | 
|  | return ProgramResource.fromBytes( | 
|  | Origin.unknown(), | 
|  | ProgramResource.Kind.CF, | 
|  | Files.readAllBytes( | 
|  | jrtFs.getPath( | 
|  | "modules", | 
|  | descriptorToModule.get(descriptor), | 
|  | DescriptorUtils.getPathFromDescriptor(descriptor))), | 
|  | Collections.singleton(descriptor)); | 
|  | } catch (IOException e) { | 
|  | throw new CompilationError("Failed to read '" + descriptor, origin); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void finalize() throws Throwable { | 
|  | close(); | 
|  | super.finalize(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void close() throws IOException { | 
|  | jrtFs.close(); | 
|  | if (jrtFsJarLoader != null) { | 
|  | jrtFsJarLoader.close(); | 
|  | } | 
|  | } | 
|  | } |