blob: d5dd24027301099d13f9b6b7fe0ff8c7212531fa [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 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.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/** Provider for archives of program resources. */
@KeepForSubclassing
public class ArchiveProgramResourceProvider implements ProgramResourceProvider {
interface ArchiveEntryConsumer {
void accept(ArchiveEntryOrigin entry, InputStream stream) throws IOException;
}
@KeepForSubclassing
public interface ZipFileSupplier {
ZipFile open() throws IOException;
}
public static boolean includeClassFileEntries(String entry) {
return ZipUtils.isClassFile(entry);
}
public static boolean includeDexEntries(String entry) {
return ZipUtils.isDexFile(entry);
}
public static boolean includeClassFileOrDexEntries(String entry) {
return ZipUtils.isClassFile(entry) || ZipUtils.isDexFile(entry);
}
private final Origin origin;
private final ZipFileSupplier supplier;
private final Predicate<String> include;
public static ArchiveProgramResourceProvider fromArchive(Path archive) {
return fromArchive(archive, ArchiveProgramResourceProvider::includeClassFileOrDexEntries);
}
public static ArchiveProgramResourceProvider fromArchive(
Path archive, Predicate<String> include) {
return fromSupplier(
new PathOrigin(archive),
() -> FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8),
include);
}
public static ArchiveProgramResourceProvider fromSupplier(
Origin origin, ZipFileSupplier supplier) {
return fromSupplier(
origin, supplier, ArchiveProgramResourceProvider::includeClassFileOrDexEntries);
}
public static ArchiveProgramResourceProvider fromSupplier(
Origin origin, ZipFileSupplier supplier, Predicate<String> include) {
return new ArchiveProgramResourceProvider(origin, supplier, include);
}
private ArchiveProgramResourceProvider(
Origin origin, ZipFileSupplier supplier, Predicate<String> include) {
assert origin != null;
assert supplier != null;
assert include != null;
this.origin = origin;
this.supplier = supplier;
this.include = include;
}
void readArchive(ArchiveEntryConsumer consumer) throws IOException {
try (ZipFile zipFile = supplier.open()) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
try (InputStream stream = zipFile.getInputStream(entry)) {
consumer.accept(new ArchiveEntryOrigin(entry.getName(), origin), stream);
}
}
} catch (ZipException e) {
throw new CompilationError("Zip error while reading archive" + e.getMessage(), e, origin);
}
}
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
try {
List<ProgramResource> dexResources = new ArrayList<>();
List<ProgramResource> classResources = new ArrayList<>();
readArchive(
(entry, stream) -> {
String name = entry.getEntryName();
if (include.test(name)) {
if (ZipUtils.isDexFile(name)) {
dexResources.add(
ProgramResource.fromBytes(
entry, Kind.DEX, ByteStreams.toByteArray(stream), null));
} else if (ZipUtils.isClassFile(name)) {
String descriptor = DescriptorUtils.guessTypeDescriptor(name);
classResources.add(
ProgramResource.fromBytes(
entry,
Kind.CF,
ByteStreams.toByteArray(stream),
Collections.singleton(descriptor)));
}
}
});
if (!dexResources.isEmpty() && !classResources.isEmpty()) {
throw new CompilationError(
"Cannot create android app from an archive containing both DEX and Java-bytecode "
+ "content.",
origin);
}
return !dexResources.isEmpty() ? dexResources : classResources;
} catch (IOException e) {
throw new ResourceException(origin, e);
}
}
}