blob: 947b02e321f7dc7b6e160b8beafd582978a2e70b [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.utils;
import static com.android.tools.r8.utils.FileUtils.isArchive;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.DataDirectoryResource;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
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.shaking.FilteredClassPath;
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.Consumer;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
@KeepForApi
public class ArchiveResourceProvider implements ProgramResourceProvider, DataResourceProvider {
final Origin origin;
final FilteredClassPath archive;
final boolean ignoreDexInArchive;
public static ArchiveResourceProvider fromArchive(Path archive, boolean ignoreDexInArchive) {
return new ArchiveResourceProvider(FilteredClassPath.unfiltered(archive), ignoreDexInArchive);
}
ArchiveResourceProvider(FilteredClassPath archive, boolean ignoreDexInArchive) {
this(archive, ignoreDexInArchive, new PathOrigin(archive.getPath()));
}
ArchiveResourceProvider(FilteredClassPath archive, boolean ignoreDexInArchive, Origin origin) {
assert isArchive(archive.getPath());
this.archive = archive;
this.ignoreDexInArchive = ignoreDexInArchive;
this.origin = origin;
}
public Origin getOrigin() {
return origin;
}
private void readArchive(Consumer<ProgramResource> consumer) throws ResourceException {
List<Kind> seenKinds;
try {
seenKinds =
ArchiveResourceProviderUtils.readArchive(
archive.getPath(),
origin,
(name, kind) -> archive.matchesFile(name) && (kind == Kind.CF || !ignoreDexInArchive),
consumer);
} catch (ZipException e) {
throw new CompilationError(
"Zip error while reading '" + archive + "': " + e.getMessage(), e);
} catch (IOException e) {
throw new ResourceException(origin, e);
}
if (seenKinds.size() == 2) {
assert seenKinds.contains(Kind.CF);
assert seenKinds.contains(Kind.DEX);
throw new CompilationError(
"Cannot create android app from an archive '"
+ archive
+ "' containing both DEX and Java-bytecode content",
origin);
}
}
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
List<ProgramResource> programResources = new ArrayList<>();
readArchive(programResources::add);
return programResources;
}
@Override
public void getProgramResources(Consumer<ProgramResource> consumer) throws ResourceException {
readArchive(consumer);
}
@Override
public DataResourceProvider getDataResourceProvider() {
return this;
}
@Override
public void accept(Visitor resourceBrowser) throws ResourceException {
try (ZipFile zipFile =
FileUtils.createZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (archive.matchesFile(name) && !isProgramResourceName(name)) {
if (entry.isDirectory()) {
resourceBrowser.visit(DataDirectoryResource.fromZip(zipFile, entry));
} else {
resourceBrowser.visit(DataEntryResource.fromZip(zipFile, entry));
}
}
}
} catch (ZipException e) {
throw new ResourceException(origin, new CompilationError(
"Zip error while reading '" + archive + "': " + e.getMessage(), e));
} catch (IOException e) {
throw new ResourceException(origin, new CompilationError(
"I/O exception while reading '" + archive + "': " + e.getMessage(), e));
}
}
boolean isProgramResourceName(String name) {
return ZipUtils.isClassFile(name) || (ZipUtils.isDexFile(name) && !ignoreDexInArchive);
}
@Deprecated
public void accept(Consumer<ProgramResource> visitor) throws ResourceException {
new ArchiveResourceProviderHelper(this).accept(visitor, alwaysTrue());
}
public static class ArchiveResourceProviderHelper extends ArchiveResourceProvider {
ArchiveResourceProviderHelper(ArchiveResourceProvider provider) {
super(provider.archive, provider.ignoreDexInArchive, provider.origin);
}
public void accept(Consumer<ProgramResource> consumer, Predicate<ZipEntry> predicate)
throws ResourceException {
try (ZipFile zipFile =
FileUtils.createZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (archive.matchesFile(name) && isProgramResourceName(name) && predicate.test(entry)) {
Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
try (InputStream stream = zipFile.getInputStream(entry)) {
if (ZipUtils.isDexFile(name)) {
OneShotByteResource resource =
OneShotByteResource.create(
entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null);
consumer.accept(resource);
} else if (ZipUtils.isClassFile(name)) {
OneShotByteResource resource =
OneShotByteResource.create(
entryOrigin,
Kind.CF,
ByteStreams.toByteArray(stream),
Collections.singleton(DescriptorUtils.guessTypeDescriptor(name)));
consumer.accept(resource);
}
}
}
}
} catch (ZipException e) {
throw new ResourceException(
origin,
new CompilationError(
"Zip error while reading '" + archive + "': " + e.getMessage(), e));
} catch (IOException e) {
throw new ResourceException(
origin,
new CompilationError(
"I/O exception while reading '" + archive + "': " + e.getMessage(), e));
}
}
}
}