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