| // 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); |
| } |
| } |
| } |