blob: ae022a51a148699656dba12ef5e7f3815ce28587 [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 static com.android.tools.r8.utils.FileUtils.isClassFile;
import com.android.tools.r8.Resource.Origin;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.shaking.FilteredClassPath;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.io.ByteStreams;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/** Lazy Java class file resource provider loading class files form a zip archive. */
public class ArchiveClassFileProvider implements ClassFileResourceProvider, Closeable {
private static class ArchiveEntryOrigin extends Origin {
final String descriptor;
public ArchiveEntryOrigin(String descriptor, Origin parent) {
super(parent);
this.descriptor = descriptor;
}
@Override
public String part() {
return descriptor;
}
}
private final Origin origin;
private final Set<String> descriptors = new HashSet<>();
private final ZipFile zipFile;
public static ClassFileResourceProvider fromArchive(FilteredClassPath archive)
throws IOException {
return new ArchiveClassFileProvider(archive);
}
protected ArchiveClassFileProvider(Path archive) throws IOException {
this(FilteredClassPath.unfiltered(archive));
}
private ArchiveClassFileProvider(FilteredClassPath archive) throws IOException {
assert isArchive(archive.getPath());
origin = new Resource.PathOrigin(archive.getPath(), Origin.root());
zipFile = new ZipFile(archive.getPath().toFile());
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
Path entryPath = Paths.get(name);
if (isClassFile(entryPath) && archive.matchesFile(entryPath)) {
descriptors.add(DescriptorUtils.guessTypeDescriptor(name));
}
}
}
@Override
public Set<String> getClassDescriptors() {
return Collections.unmodifiableSet(descriptors);
}
@Override
public Resource getResource(String descriptor) {
if (!descriptors.contains(descriptor)) {
return null;
}
try (InputStream inputStream = zipFile.getInputStream(getZipEntryFromDescriptor(descriptor))) {
return Resource.fromBytes(
new ArchiveEntryOrigin(descriptor, origin),
ByteStreams.toByteArray(inputStream),
Collections.singleton(descriptor));
} catch (IOException e) {
throw new CompilationError(
"Failed to read '" + descriptor + "' from '" + zipFile.getName() + "'");
}
}
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
@Override
public String toString() {
return descriptors.size() + " resources from '" + zipFile.getName() +"'";
}
@Override
public void close() throws IOException {
zipFile.close();
}
private ZipEntry getZipEntryFromDescriptor(String descriptor) {
return zipFile.getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
}
}