| // Copyright (c) 2017, 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 static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; | |
| import static com.android.tools.r8.utils.FileUtils.isArchive; | |
| import com.android.tools.r8.ProgramResource.Kind; | |
| import com.android.tools.r8.errors.CompilationError; | |
| import com.android.tools.r8.origin.ArchiveEntryOrigin; | |
| 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 com.android.tools.r8.utils.ZipUtils; | |
| import com.google.common.io.ByteStreams; | |
| import java.io.Closeable; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.nio.charset.StandardCharsets; | |
| import java.nio.file.Files; | |
| import java.nio.file.NoSuchFileException; | |
| import java.nio.file.Path; | |
| import java.util.Collections; | |
| import java.util.Enumeration; | |
| import java.util.HashSet; | |
| import java.util.Set; | |
| import java.util.function.Predicate; | |
| import java.util.zip.ZipEntry; | |
| import java.util.zip.ZipFile; | |
| /** | |
| * Lazy Java class file resource provider loading class files from a zip archive. | |
| * | |
| * <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 zip entry contents. | |
| */ | |
| @Keep | |
| public class ArchiveClassFileProvider implements ClassFileResourceProvider, Closeable { | |
| private final Path archive; | |
| private final Origin origin; | |
| private final Predicate<String> include; | |
| private ZipFile lazyZipFile = null; | |
| private Set<String> lazyDescriptors = null; | |
| /** | |
| * Creates a lazy class-file program-resource provider. | |
| * | |
| * @param archive Zip archive to provide resources from. | |
| */ | |
| public ArchiveClassFileProvider(Path archive) throws IOException { | |
| this(archive, entry -> true); | |
| } | |
| /** | |
| * Creates a lazy class-file program-resource provider with an include filter. | |
| * | |
| * @param archive Zip archive to provide resources from. | |
| * @param include Predicate deciding if a given class-file entry should be provided. | |
| */ | |
| public ArchiveClassFileProvider(Path archive, Predicate<String> include) throws IOException { | |
| assert isArchive(archive); | |
| this.archive = archive; | |
| this.include = include; | |
| origin = new PathOrigin(archive); | |
| ensureZipFile(); | |
| } | |
| @Override | |
| public Set<String> getClassDescriptors() { | |
| return ensureDescriptors(); | |
| } | |
| @Override | |
| public ProgramResource getProgramResource(String descriptor) { | |
| if (!ensureDescriptors().contains(descriptor)) { | |
| return null; | |
| } | |
| ZipFile zipFile = ensureZipFile(); | |
| ZipEntry zipEntry = getZipEntryFromDescriptor(descriptor); | |
| try (InputStream inputStream = zipFile.getInputStream(zipEntry)) { | |
| return ProgramResource.fromBytes( | |
| new ArchiveEntryOrigin(zipEntry.getName(), origin), | |
| Kind.CF, | |
| ByteStreams.toByteArray(inputStream), | |
| Collections.singleton(descriptor)); | |
| } catch (IOException e) { | |
| throw new CompilationError("Failed to read '" + descriptor, origin); | |
| } | |
| } | |
| @Override | |
| public void finished(DiagnosticsHandler handler) throws IOException { | |
| close(); | |
| } | |
| @Override | |
| public void close() throws IOException { | |
| if (lazyZipFile != null) { | |
| lazyZipFile.close(); | |
| } | |
| lazyZipFile = null; | |
| lazyDescriptors = null; | |
| } | |
| private void reopenZipFile() throws IOException { | |
| assert lazyZipFile == null; | |
| assert lazyDescriptors == null; | |
| try { | |
| lazyZipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8); | |
| } catch (IOException e) { | |
| if (!Files.exists(archive)) { | |
| throw new NoSuchFileException(archive.toString()); | |
| } else { | |
| throw e; | |
| } | |
| } | |
| lazyDescriptors = new HashSet<>(); | |
| final Enumeration<? extends ZipEntry> entries = lazyZipFile.entries(); | |
| while (entries.hasMoreElements()) { | |
| ZipEntry entry = entries.nextElement(); | |
| String name = entry.getName(); | |
| if (ZipUtils.isClassFile(name) && include.test(name)) { | |
| lazyDescriptors.add(DescriptorUtils.guessTypeDescriptor(name)); | |
| } | |
| } | |
| } | |
| private ZipFile ensureZipFile() { | |
| if (lazyZipFile == null) { | |
| try { | |
| reopenZipFile(); | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| return lazyZipFile; | |
| } | |
| private Set<String> ensureDescriptors() { | |
| ensureZipFile(); | |
| return Collections.unmodifiableSet(lazyDescriptors); | |
| } | |
| private ZipEntry getZipEntryFromDescriptor(String descriptor) { | |
| return ensureZipFile() | |
| .getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION); | |
| } | |
| } |