blob: 31e0dbdddd1c23f49b7c743ca8dc5bde5c9382e7 [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.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 Origin origin;
private final ZipFile zipFile;
private final Set<String> descriptors = new HashSet<>();
/**
* 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);
origin = new PathOrigin(archive);
try {
zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8);
} catch (IOException e) {
if (!Files.exists(archive)) {
throw new NoSuchFileException(archive.toString());
} else {
throw e;
}
}
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (ZipUtils.isClassFile(name) && include.test(name)) {
descriptors.add(DescriptorUtils.guessTypeDescriptor(name));
}
}
}
@Override
public Set<String> getClassDescriptors() {
return Collections.unmodifiableSet(descriptors);
}
@Override
public ProgramResource getProgramResource(String descriptor) {
if (!descriptors.contains(descriptor)) {
return null;
}
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
protected void finalize() throws Throwable {
close();
super.finalize();
}
@Override
public void close() throws IOException {
zipFile.close();
}
private ZipEntry getZipEntryFromDescriptor(String descriptor) {
return zipFile.getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
}
}