blob: a231b0f760e57c30958eb12028780a8ef625a5d1 [file] [log] [blame]
// Copyright (c) 2018, 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.utils;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.isArchive;
import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
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;
/**
* Internal-only provider for providing class files when clients use the addClasspathFiles and
* addLibraryFiles API.
*
* <p>The purpose of the internal provider is that for the API use above we know it is safe to use
* the zip-file descriptor throughout compilation and close at the end of reading. It must also be
* safe to reopen it as currently our own tests reuse AndroidApp structures.
*/
class InternalArchiveClassFileProvider implements ClassFileResourceProvider, AutoCloseable {
private final Path path;
private final Origin origin;
private final Set<String> descriptors = new HashSet<>();
private ZipFile openedZipFile = null;
/**
* Creates a lazy class-file program-resource provider.
*
* @param archive Zip archive to provide resources from.
*/
public InternalArchiveClassFileProvider(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 InternalArchiveClassFileProvider(Path archive, Predicate<String> include)
throws IOException {
assert isArchive(archive);
path = archive;
origin = new PathOrigin(archive);
final Enumeration<? extends ZipEntry> entries = getOpenZipFile().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;
}
return new ProgramResource() {
private final Origin entryOrigin =
new ArchiveEntryOrigin(getZipEntryNameFromDescriptor(descriptor), origin);
@Override
public Origin getOrigin() {
return entryOrigin;
}
@Override
public Kind getKind() {
return Kind.CF;
}
@Override
public Set<String> getClassDescriptors() {
return Collections.singleton(descriptor);
}
@Override
public InputStream getByteStream() throws ResourceException {
try {
ZipEntry zipEntry = getZipEntryFromDescriptor(descriptor);
return getOpenZipFile().getInputStream(zipEntry);
} catch (IOException e) {
throw new ResourceException(getOrigin(), "Failed to read '" + descriptor + "'");
}
}
};
}
private ZipFile getOpenZipFile() throws IOException {
if (openedZipFile == null) {
try {
openedZipFile = FileUtils.createZipFile(path.toFile(), StandardCharsets.UTF_8);
} catch (IOException e) {
if (!Files.exists(path)) {
throw new NoSuchFileException(path.toString());
} else {
throw e;
}
}
}
return openedZipFile;
}
@Override
public void close() throws IOException {
openedZipFile.close();
openedZipFile = null;
}
private static String getZipEntryNameFromDescriptor(String descriptor) {
return descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION;
}
private ZipEntry getZipEntryFromDescriptor(String descriptor) throws IOException {
return getOpenZipFile().getEntry(getZipEntryNameFromDescriptor(descriptor));
}
}