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