blob: e5557d996ecb2d81305efce4c4d33d2beb645716 [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.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.google.common.io.ByteStreams;
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;
}
try {
ZipEntry zipEntry = getZipEntryFromDescriptor(descriptor);
try (InputStream inputStream = getOpenZipFile().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);
}
}
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 ZipEntry getZipEntryFromDescriptor(String descriptor) throws IOException {
return getOpenZipFile()
.getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
}
}