blob: d3ee009814f119d7a03ea26a7c238fcd5f123664 [file] [log] [blame]
// 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.keepanno.annotations.KeepForApi;
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.
*/
@KeepForApi
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.
*/
@SuppressWarnings("RedundantThrows")
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);
}
}