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