• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 package com.android.tools.r8.utils;
5 
6 import static com.android.tools.r8.utils.FileUtils.isArchive;
7 import static com.android.tools.r8.utils.FileUtils.isClassFile;
8 import static com.android.tools.r8.utils.FileUtils.isDexFile;
9 
10 import com.android.tools.r8.ClassFileResourceProvider;
11 import com.android.tools.r8.Resource;
12 import com.android.tools.r8.errors.CompilationError;
13 import com.android.tools.r8.errors.Unreachable;
14 import com.android.tools.r8.graph.ClassKind;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.io.ByteStreams;
17 import com.google.common.io.Closer;
18 import java.io.ByteArrayOutputStream;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.nio.charset.StandardCharsets;
26 import java.nio.file.CopyOption;
27 import java.nio.file.Files;
28 import java.nio.file.OpenOption;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.nio.file.StandardCopyOption;
32 import java.nio.file.StandardOpenOption;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.stream.Collectors;
40 import java.util.zip.ZipEntry;
41 import java.util.zip.ZipException;
42 import java.util.zip.ZipInputStream;
43 import java.util.zip.ZipOutputStream;
44 
45 /**
46  * Collection of program files needed for processing.
47  *
48  * <p>This abstraction is the main input and output container for a given application.
49  */
50 public class AndroidApp {
51 
52   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
53 
54   private final ImmutableList<Resource> programResources;
55   private final ImmutableList<Resource> classpathResources;
56   private final ImmutableList<Resource> libraryResources;
57   private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
58   private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
59   private final Resource proguardMap;
60   private final Resource proguardSeeds;
61   private final Resource packageDistribution;
62   private final Resource mainDexList;
63 
64   // See factory methods and AndroidApp.Builder below.
AndroidApp( ImmutableList<Resource> programResources, ImmutableList<Resource> classpathResources, ImmutableList<Resource> libraryResources, ImmutableList<ClassFileResourceProvider> classpathResourceProviders, ImmutableList<ClassFileResourceProvider> libraryResourceProviders, Resource proguardMap, Resource proguardSeeds, Resource packageDistribution, Resource mainDexList)65   private AndroidApp(
66       ImmutableList<Resource> programResources,
67       ImmutableList<Resource> classpathResources,
68       ImmutableList<Resource> libraryResources,
69       ImmutableList<ClassFileResourceProvider> classpathResourceProviders,
70       ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
71       Resource proguardMap,
72       Resource proguardSeeds,
73       Resource packageDistribution,
74       Resource mainDexList) {
75     this.programResources = programResources;
76     this.classpathResources = classpathResources;
77     this.libraryResources = libraryResources;
78     this.classpathResourceProviders = classpathResourceProviders;
79     this.libraryResourceProviders = libraryResourceProviders;
80     this.proguardMap = proguardMap;
81     this.proguardSeeds = proguardSeeds;
82     this.packageDistribution = packageDistribution;
83     this.mainDexList = mainDexList;
84   }
85 
86   /**
87    * Create a new empty builder.
88    */
builder()89   public static Builder builder() {
90     return new Builder();
91   }
92 
93   /**
94    * Create a new builder initialized with the resources from @code{app}.
95    */
builder(AndroidApp app)96   public static Builder builder(AndroidApp app) {
97     return new Builder(app);
98   }
99 
100   /**
101    * Create an app from program files @code{files}. See also Builder::addProgramFiles.
102    */
fromProgramFiles(Path... files)103   public static AndroidApp fromProgramFiles(Path... files) throws IOException {
104     return fromProgramFiles(Arrays.asList(files));
105   }
106 
107   /**
108    * Create an app from program files @code{files}. See also Builder::addProgramFiles.
109    */
fromProgramFiles(List<Path> files)110   public static AndroidApp fromProgramFiles(List<Path> files) throws IOException {
111     return builder().addProgramFiles(files, false).build();
112   }
113 
114   /**
115    * Create an app from files found in @code{directory}. See also Builder::addProgramDirectory.
116    */
fromProgramDirectory(Path directory)117   public static AndroidApp fromProgramDirectory(Path directory) throws IOException {
118     return builder().addProgramDirectory(directory).build();
119   }
120 
121   /**
122    * Create an app from dex program data. See also Builder::addDexProgramData.
123    */
fromDexProgramData(byte[]... data)124   public static AndroidApp fromDexProgramData(byte[]... data) {
125     return fromDexProgramData(Arrays.asList(data));
126   }
127 
128   /**
129    * Create an app from dex program data. See also Builder::addDexProgramData.
130    */
fromDexProgramData(List<byte[]> data)131   public static AndroidApp fromDexProgramData(List<byte[]> data) {
132     return builder().addDexProgramData(data).build();
133   }
134 
135   /**
136    * Create an app from Java-bytecode program data. See also Builder::addClassProgramData.
137    */
fromClassProgramData(byte[]... data)138   public static AndroidApp fromClassProgramData(byte[]... data) {
139     return fromClassProgramData(Arrays.asList(data));
140   }
141 
142   /**
143    * Create an app from Java-bytecode program data. See also Builder::addClassProgramData.
144    */
fromClassProgramData(List<byte[]> data)145   public static AndroidApp fromClassProgramData(List<byte[]> data) {
146     return builder().addClassProgramData(data).build();
147   }
148 
149   /** Get input streams for all dex program resources. */
getDexProgramResources()150   public List<Resource> getDexProgramResources() {
151     return filter(programResources, Resource.Kind.DEX);
152   }
153 
154   /** Get input streams for all Java-bytecode program resources. */
getClassProgramResources()155   public List<Resource> getClassProgramResources() {
156     return filter(programResources, Resource.Kind.CLASSFILE);
157   }
158 
159   /** Get input streams for all dex program classpath resources. */
getDexClasspathResources()160   public List<Resource> getDexClasspathResources() {
161     return filter(classpathResources, Resource.Kind.DEX);
162   }
163 
164   /** Get input streams for all Java-bytecode classpath resources. */
getClassClasspathResources()165   public List<Resource> getClassClasspathResources() {
166     return filter(classpathResources, Resource.Kind.CLASSFILE);
167   }
168 
169   /** Get input streams for all dex library resources. */
getDexLibraryResources()170   public List<Resource> getDexLibraryResources() {
171     return filter(libraryResources, Resource.Kind.DEX);
172   }
173 
174   /** Get input streams for all Java-bytecode library resources. */
getClassLibraryResources()175   public List<Resource> getClassLibraryResources() {
176     return filter(libraryResources, Resource.Kind.CLASSFILE);
177   }
178 
179   /** Get classpath resource providers. */
getClasspathResourceProviders()180   public List<ClassFileResourceProvider> getClasspathResourceProviders() {
181     return classpathResourceProviders;
182   }
183 
184   /** Get library resource providers. */
getLibraryResourceProviders()185   public List<ClassFileResourceProvider> getLibraryResourceProviders() {
186     return libraryResourceProviders;
187   }
188 
filter(List<Resource> resources, Resource.Kind kind)189   private List<Resource> filter(List<Resource> resources, Resource.Kind kind) {
190     List<Resource> out = new ArrayList<>(resources.size());
191     for (Resource resource : resources) {
192       if (kind == resource.kind) {
193         out.add(resource);
194       }
195     }
196     return out;
197   }
198 
199   /**
200    * True if the proguard-map resource exists.
201    */
hasProguardMap()202   public boolean hasProguardMap() {
203     return proguardMap != null;
204   }
205 
206   /**
207    * Get the input stream of the proguard-map resource if it exists.
208    */
getProguardMap(Closer closer)209   public InputStream getProguardMap(Closer closer) throws IOException {
210     return proguardMap == null ? null : proguardMap.getStream(closer);
211   }
212 
213   /**
214    * True if the proguard-seeds resource exists.
215    */
hasProguardSeeds()216   public boolean hasProguardSeeds() {
217     return proguardSeeds != null;
218   }
219 
220   /**
221    * Get the input stream of the proguard-seeds resource if it exists.
222    */
getProguardSeeds(Closer closer)223   public InputStream getProguardSeeds(Closer closer) throws IOException {
224     return proguardSeeds == null ? null : proguardSeeds.getStream(closer);
225   }
226 
227   /**
228    * True if the package distribution resource exists.
229    */
hasPackageDistribution()230   public boolean hasPackageDistribution() {
231     return packageDistribution != null;
232   }
233 
234   /**
235    * Get the input stream of the package distribution resource if it exists.
236    */
getPackageDistribution(Closer closer)237   public InputStream getPackageDistribution(Closer closer) throws IOException {
238     return packageDistribution == null ? null : packageDistribution.getStream(closer);
239   }
240 
241   /**
242    * True if the main dex list resource exists.
243    */
hasMainDexList()244   public boolean hasMainDexList() {
245     return mainDexList != null;
246   }
247 
248   /**
249    * Get the input stream of the main dex list resource if it exists.
250    */
getMainDexList(Closer closer)251   public InputStream getMainDexList(Closer closer) throws IOException {
252     return mainDexList == null ? null : mainDexList.getStream(closer);
253   }
254 
255   /**
256    * Write the dex program resources and proguard resource to @code{output}.
257    */
write(Path output, OutputMode outputMode)258   public void write(Path output, OutputMode outputMode) throws IOException {
259     if (isArchive(output)) {
260       writeToZip(output, outputMode);
261     } else {
262       writeToDirectory(output, outputMode);
263     }
264   }
265 
266   /**
267    * Write the dex program resources and proguard resource to @code{directory}.
268    */
writeToDirectory(Path directory, OutputMode outputMode)269   public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException {
270     if (outputMode == OutputMode.Indexed) {
271       for (Path path : Files.list(directory).collect(Collectors.toList())) {
272         if (isClassesDexFile(path)) {
273           Files.delete(path);
274         }
275       }
276     }
277     CopyOption[] options = new CopyOption[] {StandardCopyOption.REPLACE_EXISTING};
278     try (Closer closer = Closer.create()) {
279       List<Resource> dexProgramSources = getDexProgramResources();
280       for (int i = 0; i < dexProgramSources.size(); i++) {
281         Path filePath = directory.resolve(outputMode.getOutputPath(dexProgramSources.get(i), i));
282         if (!Files.exists(filePath.getParent())) {
283           Files.createDirectories(filePath.getParent());
284         }
285         Files.copy(dexProgramSources.get(i).getStream(closer), filePath, options);
286       }
287     }
288   }
289 
isClassesDexFile(Path file)290   private static boolean isClassesDexFile(Path file) {
291     String name = file.getFileName().toString().toLowerCase();
292     if (!name.startsWith("classes") || !name.endsWith(".dex")) {
293       return false;
294     }
295     String numeral = name.substring("classes".length(), name.length() - ".dex".length());
296     if (numeral.isEmpty()) {
297       return true;
298     }
299     char c0 = numeral.charAt(0);
300     if (numeral.length() == 1) {
301       return '2' <= c0 && c0 <= '9';
302     }
303     if (c0 < '1' || '9' < c0) {
304       return false;
305     }
306     for (int i = 1; i < numeral.length(); i++) {
307       char c = numeral.charAt(i);
308       if (c < '0' || '9' < c) {
309         return false;
310       }
311     }
312     return true;
313   }
314 
writeToMemory()315   public List<byte[]> writeToMemory() throws IOException {
316     List<byte[]> dex = new ArrayList<>();
317     try (Closer closer = Closer.create()) {
318       List<Resource> dexProgramSources = getDexProgramResources();
319       for (int i = 0; i < dexProgramSources.size(); i++) {
320         ByteArrayOutputStream out = new ByteArrayOutputStream();
321         ByteStreams.copy(dexProgramSources.get(i).getStream(closer), out);
322         dex.add(out.toByteArray());
323       }
324       // TODO(sgjesse): Add Proguard map and seeds.
325     }
326     return dex;
327   }
328 
329   /**
330    * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
331    */
writeToZip(Path archive, OutputMode outputMode)332   public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
333     OpenOption[] options =
334         new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
335     try (Closer closer = Closer.create()) {
336       try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
337         List<Resource> dexProgramSources = getDexProgramResources();
338         for (int i = 0; i < dexProgramSources.size(); i++) {
339           ZipEntry zipEntry = new ZipEntry(outputMode.getOutputPath(dexProgramSources.get(i), i));
340           byte[] bytes = ByteStreams.toByteArray(dexProgramSources.get(i).getStream(closer));
341           zipEntry.setSize(bytes.length);
342           out.putNextEntry(zipEntry);
343           out.write(bytes);
344           out.closeEntry();
345         }
346       }
347     }
348   }
349 
writeProguardMap(Closer closer, OutputStream out)350   public void writeProguardMap(Closer closer, OutputStream out) throws IOException {
351     InputStream input = getProguardMap(closer);
352     assert input != null;
353     out.write(ByteStreams.toByteArray(input));
354   }
355 
writeProguardSeeds(Closer closer, OutputStream out)356   public void writeProguardSeeds(Closer closer, OutputStream out) throws IOException {
357     InputStream input = getProguardSeeds(closer);
358     assert input != null;
359     out.write(ByteStreams.toByteArray(input));
360   }
361 
writeMainDexList(Closer closer, OutputStream out)362   public void writeMainDexList(Closer closer, OutputStream out) throws IOException {
363     InputStream input = getMainDexList(closer);
364     assert input != null;
365     out.write(ByteStreams.toByteArray(input));
366   }
367 
368   /**
369    * Builder interface for constructing an AndroidApp.
370    */
371   public static class Builder {
372 
373     private final List<Resource> programResources = new ArrayList<>();
374     private final List<Resource> classpathResources = new ArrayList<>();
375     private final List<Resource> libraryResources = new ArrayList<>();
376     private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
377     private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
378     private Resource proguardMap;
379     private Resource proguardSeeds;
380     private Resource packageDistribution;
381     private Resource mainDexList;
382 
383     // See AndroidApp::builder().
Builder()384     private Builder() {
385     }
386 
387     // See AndroidApp::builder(AndroidApp).
Builder(AndroidApp app)388     private Builder(AndroidApp app) {
389       programResources.addAll(app.programResources);
390       classpathResources.addAll(app.classpathResources);
391       libraryResources.addAll(app.libraryResources);
392       classpathResourceProviders.addAll(app.classpathResourceProviders);
393       libraryResourceProviders.addAll(app.libraryResourceProviders);
394       proguardMap = app.proguardMap;
395       proguardSeeds = app.proguardSeeds;
396       packageDistribution = app.packageDistribution;
397       mainDexList = app.mainDexList;
398     }
399 
400     /**
401      * Add dex program files and proguard-map file located in @code{directory}.
402      *
403      * <p>The program files included are the top-level files ending in .dex and the proguard-map
404      * file should it exist (see @code{DEFAULT_PROGUARD_MAP_FILE} for its assumed name).
405      *
406      * <p>This method is mostly a utility for reading in the file content produces by some external
407      * tool, eg, dx.
408      *
409      * @param directory Directory containing dex program files and optional proguard-map file.
410      */
addProgramDirectory(Path directory)411     public Builder addProgramDirectory(Path directory) throws IOException {
412       File[] resources = directory.toFile().listFiles(file -> isDexFile(file.toPath()));
413       for (File source : resources) {
414         addFile(source.toPath(), ClassKind.PROGRAM, false);
415       }
416       File mapFile = new File(directory.toFile(), DEFAULT_PROGUARD_MAP_FILE);
417       if (mapFile.exists()) {
418         setProguardMapFile(mapFile.toPath());
419       }
420       return this;
421     }
422 
423     /**
424      * Add program file resources.
425      */
addProgramFiles(Path... files)426     public Builder addProgramFiles(Path... files) throws IOException {
427       return addProgramFiles(Arrays.asList(files), false);
428     }
429 
430     /**
431      * Add program file resources.
432      */
addProgramFiles(Collection<Path> files, boolean skipDex)433     public Builder addProgramFiles(Collection<Path> files, boolean skipDex) throws IOException {
434       for (Path file : files) {
435         addFile(file, ClassKind.PROGRAM, skipDex);
436       }
437       return this;
438     }
439 
440     /**
441      * Add classpath file resources.
442      */
addClasspathFiles(Path... files)443     public Builder addClasspathFiles(Path... files) throws IOException {
444       return addClasspathFiles(Arrays.asList(files));
445     }
446 
447     /**
448      * Add classpath file resources.
449      */
addClasspathFiles(Collection<Path> files)450     public Builder addClasspathFiles(Collection<Path> files) throws IOException {
451       for (Path file : files) {
452         addFile(file, ClassKind.CLASSPATH, false);
453       }
454       return this;
455     }
456 
457     /**
458      * Add classpath resource provider.
459      */
addClasspathResourceProvider(ClassFileResourceProvider provider)460     public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) {
461       classpathResourceProviders.add(provider);
462       return this;
463     }
464 
465     /**
466      * Add library file resources.
467      */
addLibraryFiles(Path... files)468     public Builder addLibraryFiles(Path... files) throws IOException {
469       return addLibraryFiles(Arrays.asList(files));
470     }
471 
472     /**
473      * Add library file resources.
474      */
addLibraryFiles(Collection<Path> files)475     public Builder addLibraryFiles(Collection<Path> files) throws IOException {
476       for (Path file : files) {
477         addFile(file, ClassKind.LIBRARY, false);
478       }
479       return this;
480     }
481 
482     /**
483      * Add library resource provider.
484      */
addLibraryResourceProvider(ClassFileResourceProvider provider)485     public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
486       libraryResourceProviders.add(provider);
487       return this;
488     }
489 
490     /**
491      * Add dex program-data with class descriptor.
492      */
addDexProgramData(byte[] data, Set<String> classDescriptors)493     public Builder addDexProgramData(byte[] data, Set<String> classDescriptors) {
494       resources(ClassKind.PROGRAM).add(
495           Resource.fromBytes(Resource.Kind.DEX, data, classDescriptors));
496       return this;
497     }
498 
499     /**
500      * Add dex program-data.
501      */
addDexProgramData(byte[]... data)502     public Builder addDexProgramData(byte[]... data) {
503       return addDexProgramData(Arrays.asList(data));
504     }
505 
506     /**
507      * Add dex program-data.
508      */
addDexProgramData(Collection<byte[]> data)509     public Builder addDexProgramData(Collection<byte[]> data) {
510       for (byte[] datum : data) {
511         resources(ClassKind.PROGRAM).add(Resource.fromBytes(Resource.Kind.DEX, datum));
512       }
513       return this;
514     }
515 
516     /**
517      * Add Java-bytecode program data.
518      */
addClassProgramData(byte[]... data)519     public Builder addClassProgramData(byte[]... data) {
520       return addClassProgramData(Arrays.asList(data));
521     }
522 
523     /**
524      * Add Java-bytecode program data.
525      */
addClassProgramData(Collection<byte[]> data)526     public Builder addClassProgramData(Collection<byte[]> data) {
527       for (byte[] datum : data) {
528         resources(ClassKind.PROGRAM).add(Resource.fromBytes(Resource.Kind.CLASSFILE, datum));
529       }
530       return this;
531     }
532 
533     /**
534      * Set proguard-map file.
535      */
setProguardMapFile(Path file)536     public Builder setProguardMapFile(Path file) {
537       proguardMap = file == null ? null : Resource.fromFile(null, file);
538       return this;
539     }
540 
541     /**
542      * Set proguard-map data.
543      */
setProguardMapData(String content)544     public Builder setProguardMapData(String content) {
545       return setProguardMapData(content == null ? null : content.getBytes(StandardCharsets.UTF_8));
546     }
547 
548     /**
549      * Set proguard-map data.
550      */
setProguardMapData(byte[] content)551     public Builder setProguardMapData(byte[] content) {
552       proguardMap = content == null ? null : Resource.fromBytes(null, content);
553       return this;
554     }
555 
556     /**
557      * Set proguard-seeds data.
558      */
setProguardSeedsData(byte[] content)559     public Builder setProguardSeedsData(byte[] content) {
560       proguardSeeds = content == null ? null : Resource.fromBytes(null, content);
561       return this;
562     }
563 
564     /**
565      * Set the package-distribution file.
566      */
setPackageDistributionFile(Path file)567     public Builder setPackageDistributionFile(Path file) {
568       packageDistribution = file == null ? null : Resource.fromFile(null, file);
569       return this;
570     }
571 
572     /**
573      * Set the main-dex list file.
574      */
setMainDexListFile(Path file)575     public Builder setMainDexListFile(Path file) {
576       mainDexList = file == null ? null : Resource.fromFile(null, file);
577       return this;
578     }
579 
580     /**
581      * Set the main-dex list data.
582      */
setMainDexListData(byte[] content)583     public Builder setMainDexListData(byte[] content) {
584       mainDexList = content == null ? null : Resource.fromBytes(null, content);
585       return this;
586     }
587 
588     /**
589      * Build final AndroidApp.
590      */
build()591     public AndroidApp build() {
592       return new AndroidApp(
593           ImmutableList.copyOf(programResources),
594           ImmutableList.copyOf(classpathResources),
595           ImmutableList.copyOf(libraryResources),
596           ImmutableList.copyOf(classpathResourceProviders),
597           ImmutableList.copyOf(libraryResourceProviders),
598           proguardMap,
599           proguardSeeds,
600           packageDistribution,
601           mainDexList);
602     }
603 
resources(ClassKind classKind)604     private List<Resource> resources(ClassKind classKind) {
605       switch (classKind) {
606         case PROGRAM:
607           return programResources;
608         case CLASSPATH:
609           return classpathResources;
610         case LIBRARY:
611           return libraryResources;
612       }
613       throw new Unreachable();
614     }
615 
addFile(Path file, ClassKind classKind, boolean skipDex)616     private void addFile(Path file, ClassKind classKind, boolean skipDex) throws IOException {
617       if (!Files.exists(file)) {
618         throw new FileNotFoundException("Non-existent input file: " + file);
619       }
620       if (isDexFile(file) && !skipDex) {
621         resources(classKind).add(Resource.fromFile(Resource.Kind.DEX, file));
622       } else if (isClassFile(file)) {
623         resources(classKind).add(Resource.fromFile(Resource.Kind.CLASSFILE, file));
624       } else if (isArchive(file)) {
625         addArchive(file, classKind, skipDex);
626       } else {
627         throw new CompilationError("Unsupported source file type for file: " + file);
628       }
629     }
630 
addArchive(Path archive, ClassKind classKind, boolean skipDex)631     private void addArchive(Path archive, ClassKind classKind, boolean skipDex) throws IOException {
632       assert isArchive(archive);
633       boolean containsDexData = false;
634       boolean containsClassData = false;
635       try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
636         ZipEntry entry;
637         while ((entry = stream.getNextEntry()) != null) {
638           Path name = Paths.get(entry.getName());
639           if (isDexFile(name) && !skipDex) {
640             containsDexData = true;
641             resources(classKind).add(Resource.fromBytes(
642                 Resource.Kind.DEX, ByteStreams.toByteArray(stream)));
643           } else if (isClassFile(name)) {
644             containsClassData = true;
645             String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
646             resources(classKind).add(Resource.fromBytes(Resource.Kind.CLASSFILE,
647                 ByteStreams.toByteArray(stream), Collections.singleton(descriptor)));
648           }
649         }
650       } catch (ZipException e) {
651         throw new CompilationError(
652             "Zip error while reading '" + archive + "': " + e.getMessage(), e);
653       }
654       if (containsDexData && containsClassData) {
655         throw new CompilationError(
656             "Cannot create android app from an archive '" + archive
657                 + "' containing both DEX and Java-bytecode content");
658       }
659     }
660   }
661 }
662