blob: 7af6e19c0afb7b82833d0dadf3048fb33ef5d33c [file] [log] [blame]
// Copyright (c) 2023, 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.AndroidResourceInput.Kind;
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.utils.FileUtils;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayInputStream;
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.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Provides android resources from an 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.
*/
@KeepForApi
public class ArchiveProtoAndroidResourceProvider implements AndroidResourceProvider {
private final Path archive;
private final Origin origin;
private static final String MANIFEST_NAME = "AndroidManifest.xml";
private static final String RESOURCE_TABLE = "resources.pb";
private static final String RES_FOLDER = "res/";
private static final String XML_SUFFIX = ".xml";
/**
* Creates an android resource provider from an archive.
*
* @param archive Zip archive to provide resources from.
* @param origin Origin of the archive.
*/
public ArchiveProtoAndroidResourceProvider(Path archive, Origin origin) {
this.archive = archive;
this.origin = origin;
}
@Override
public Collection<AndroidResourceInput> getAndroidResources() throws ResourceException {
try (ZipFile zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8)) {
List<AndroidResourceInput> resources = new ArrayList<>();
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
ByteAndroidResourceInput resource =
new ByteAndroidResourceInput(
name,
getKindFromName(name),
ByteStreams.toByteArray(zipFile.getInputStream(entry)),
new ArchiveEntryOrigin(name, origin));
resources.add(resource);
}
return resources;
} catch (IOException e) {
throw new ResourceException(origin, e);
}
}
private Kind getKindFromName(String name) {
if (name.equals(MANIFEST_NAME)) {
return Kind.MANIFEST;
}
if (name.equals(RESOURCE_TABLE)) {
return Kind.RESOURCE_TABLE;
}
if (!name.startsWith(RES_FOLDER)) {
return Kind.UNKNOWN;
}
if (name.endsWith(XML_SUFFIX)) {
return Kind.XML_FILE;
}
return Kind.RES_FOLDER_FILE;
}
private static class ByteAndroidResourceInput implements AndroidResourceInput {
private final String name;
private final Kind kind;
private final byte[] bytes;
private final Origin origin;
public ByteAndroidResourceInput(String name, Kind kind, byte[] bytes, Origin origin) {
this.name = name;
this.kind = kind;
this.bytes = bytes;
this.origin = origin;
}
@Override
public ResourcePath getPath() {
return () -> name;
}
@Override
public Kind getKind() {
return kind;
}
@Override
public InputStream getByteStream() throws ResourceException {
return new ByteArrayInputStream(bytes);
}
@Override
public Origin getOrigin() {
return origin;
}
}
}