Main.java revision de1ab3724b284ab053322db7ff723f54d7826c96
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dx.command.dexer; 18 19import com.android.dx.Version; 20import com.android.dx.cf.iface.ParseException; 21import com.android.dx.cf.direct.ClassPathOpener; 22import com.android.dx.command.DxConsole; 23import com.android.dx.command.UsageException; 24import com.android.dx.dex.cf.CfOptions; 25import com.android.dx.dex.cf.CfTranslator; 26import com.android.dx.dex.cf.CodeStatistics; 27import com.android.dx.dex.code.PositionList; 28import com.android.dx.dex.file.ClassDefItem; 29import com.android.dx.dex.file.DexFile; 30import com.android.dx.dex.file.EncodedMethod; 31import com.android.dx.rop.annotation.Annotation; 32import com.android.dx.rop.annotation.Annotations; 33import com.android.dx.rop.annotation.AnnotationsList; 34import com.android.dx.rop.cst.CstNat; 35import com.android.dx.rop.cst.CstUtf8; 36 37import java.io.ByteArrayInputStream; 38import java.io.File; 39import java.io.FileOutputStream; 40import java.io.IOException; 41import java.io.OutputStream; 42import java.io.OutputStreamWriter; 43import java.io.PrintWriter; 44import java.util.Arrays; 45import java.util.ArrayList; 46import java.util.Map; 47import java.util.TreeMap; 48import java.util.jar.Attributes; 49import java.util.jar.JarEntry; 50import java.util.jar.JarOutputStream; 51import java.util.jar.Manifest; 52 53/** 54 * Main class for the class file translator. 55 */ 56public class Main { 57 /** 58 * {@code non-null;} name for the {@code .dex} file that goes into 59 * {@code .jar} files 60 */ 61 private static final String DEX_IN_JAR_NAME = "classes.dex"; 62 63 /** 64 * {@code non-null;} name of the standard manifest file in {@code .jar} 65 * files 66 */ 67 private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 68 69 /** 70 * {@code non-null;} attribute name for the (quasi-standard?) 71 * {@code Created-By} attribute 72 */ 73 private static final Attributes.Name CREATED_BY = 74 new Attributes.Name("Created-By"); 75 76 /** 77 * {@code non-null;} list of {@code javax} subpackages that are considered 78 * to be "core". <b>Note:</b>: This list must be sorted, since it 79 * is binary-searched. 80 */ 81 private static final String[] JAVAX_CORE = { 82 "accessibility", "crypto", "imageio", "management", "naming", "net", 83 "print", "rmi", "security", "sound", "sql", "swing", "transaction", 84 "xml" 85 }; 86 87 /** number of warnings during processing */ 88 private static int warnings = 0; 89 90 /** number of errors during processing */ 91 private static int errors = 0; 92 93 /** {@code non-null;} parsed command-line arguments */ 94 private static Arguments args; 95 96 /** {@code non-null;} output file in-progress */ 97 private static DexFile outputDex; 98 99 /** 100 * {@code null-ok;} map of resources to include in the output, or 101 * {@code null} if resources are being ignored 102 */ 103 private static TreeMap<String, byte[]> outputResources; 104 105 /** 106 * This class is uninstantiable. 107 */ 108 private Main() { 109 // This space intentionally left blank. 110 } 111 112 /** 113 * Run and exit if something unexpected happened. 114 * @param argArray the command line arguments 115 */ 116 public static void main(String[] argArray) { 117 Arguments arguments = new Arguments(); 118 arguments.parse(argArray); 119 120 int result = run(arguments); 121 if (result != 0) { 122 System.exit(result); 123 } 124 } 125 126 /** 127 * Run and return a result code. 128 * @param arguments the data + parameters for the conversion 129 * @return 0 if success > 0 otherwise. 130 */ 131 public static int run(Arguments arguments) { 132 // Reset the error/warning count to start fresh. 133 warnings = 0; 134 errors = 0; 135 136 args = arguments; 137 args.makeCfOptions(); 138 139 if (!processAllFiles()) { 140 return 1; 141 } 142 143 byte[] outArray = writeDex(); 144 145 if (outArray == null) { 146 return 2; 147 } 148 149 if (args.jarOutput) { 150 // Effectively free up the (often massive) DexFile memory. 151 outputDex = null; 152 153 if (!createJar(args.outName, outArray)) { 154 return 3; 155 } 156 } 157 158 return 0; 159 } 160 161 /** 162 * Constructs the output {@link DexFile}, fill it in with all the 163 * specified classes, and populate the resources map if required. 164 * 165 * @return whether processing was successful 166 */ 167 private static boolean processAllFiles() { 168 outputDex = new DexFile(); 169 170 if (args.jarOutput) { 171 outputResources = new TreeMap<String, byte[]>(); 172 } 173 174 if (args.dumpWidth != 0) { 175 outputDex.setDumpWidth(args.dumpWidth); 176 } 177 178 boolean any = false; 179 String[] fileNames = args.fileNames; 180 181 try { 182 for (int i = 0; i < fileNames.length; i++) { 183 any |= processOne(fileNames[i]); 184 } 185 } catch (StopProcessing ex) { 186 /* 187 * Ignore it and just let the warning/error reporting do 188 * their things. 189 */ 190 } 191 192 if (warnings != 0) { 193 DxConsole.err.println(warnings + " warning" + 194 ((warnings == 1) ? "" : "s")); 195 } 196 197 if (errors != 0) { 198 DxConsole.err.println(errors + " error" + 199 ((errors == 1) ? "" : "s") + "; aborting"); 200 return false; 201 } 202 203 if (!(any || args.emptyOk)) { 204 DxConsole.err.println("no classfiles specified"); 205 return false; 206 } 207 208 if (args.optimize && args.statistics) { 209 CodeStatistics.dumpStatistics(DxConsole.out); 210 } 211 212 return true; 213 } 214 215 /** 216 * Processes one pathname element. 217 * 218 * @param pathname {@code non-null;} the pathname to process. May be the path of 219 * a class file, a jar file, or a directory containing class files. 220 * @return whether any processing actually happened 221 */ 222 private static boolean processOne(String pathname) { 223 ClassPathOpener opener; 224 225 opener = new ClassPathOpener(pathname, false, 226 new ClassPathOpener.Consumer() { 227 public boolean processFileBytes(String name, byte[] bytes) { 228 return Main.processFileBytes(name, bytes); 229 } 230 public void onException(Exception ex) { 231 if (ex instanceof StopProcessing) { 232 throw (StopProcessing) ex; 233 } 234 DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 235 ex.printStackTrace(DxConsole.err); 236 errors++; 237 } 238 public void onProcessArchiveStart(File file) { 239 if (args.verbose) { 240 DxConsole.out.println("processing archive " + file + "..."); 241 } 242 } 243 }); 244 245 return opener.process(); 246 } 247 248 /** 249 * Processes one file, which may be either a class or a resource. 250 * 251 * @param name {@code non-null;} name of the file 252 * @param bytes {@code non-null;} contents of the file 253 * @return whether processing was successful 254 */ 255 private static boolean processFileBytes(String name, byte[] bytes) { 256 boolean isClass = name.endsWith(".class"); 257 boolean keepResources = (outputResources != null); 258 259 if (!isClass && !keepResources) { 260 if (args.verbose) { 261 DxConsole.out.println("ignored resource " + name); 262 } 263 return false; 264 } 265 266 if (args.verbose) { 267 DxConsole.out.println("processing " + name + "..."); 268 } 269 270 String fixedName = fixPath(name); 271 272 if (isClass) { 273 if (keepResources && args.keepClassesInJar) { 274 outputResources.put(fixedName, bytes); 275 } 276 return processClass(fixedName, bytes); 277 } else { 278 outputResources.put(fixedName, bytes); 279 return true; 280 } 281 } 282 283 /** 284 * Processes one classfile. 285 * 286 * @param name {@code non-null;} name of the file, clipped such that it 287 * <i>should</i> correspond to the name of the class it contains 288 * @param bytes {@code non-null;} contents of the file 289 * @return whether processing was successful 290 */ 291 private static boolean processClass(String name, byte[] bytes) { 292 if (! args.coreLibrary) { 293 checkClassName(name); 294 } 295 296 try { 297 ClassDefItem clazz = 298 CfTranslator.translate(name, bytes, args.cfOptions); 299 outputDex.add(clazz); 300 return true; 301 } catch (ParseException ex) { 302 DxConsole.err.println("\ntrouble processing:"); 303 if (args.debug) { 304 ex.printStackTrace(DxConsole.err); 305 } else { 306 ex.printContext(DxConsole.err); 307 } 308 } 309 310 warnings++; 311 return false; 312 } 313 314 /** 315 * Check the class name to make sure it's not a "core library" 316 * class. If there is a problem, this updates the error count and 317 * throws an exception to stop processing. 318 * 319 * @param name {@code non-null;} the fully-qualified internal-form class name 320 */ 321 private static void checkClassName(String name) { 322 boolean bogus = false; 323 324 if (name.startsWith("java/")) { 325 bogus = true; 326 } else if (name.startsWith("javax/")) { 327 int slashAt = name.indexOf('/', 6); 328 if (slashAt == -1) { 329 // Top-level javax classes are verboten. 330 bogus = true; 331 } else { 332 String pkg = name.substring(6, slashAt); 333 bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0); 334 } 335 } 336 337 if (! bogus) { 338 return; 339 } 340 341 /* 342 * The user is probably trying to include an entire desktop 343 * core library in a misguided attempt to get their application 344 * working. Try to help them understand what's happening. 345 */ 346 347 DxConsole.err.println("\ntrouble processing \"" + name + "\":"); 348 DxConsole.err.println("\n" + 349 "Attempt to include a core class (java.* or javax.*) in " + 350 "something other\n" + 351 "than a core library. It is likely that you have " + 352 "attempted to include\n" + 353 "in an application the core library (or a part thereof) " + 354 "from a desktop\n" + 355 "virtual machine. This will most assuredly not work. " + 356 "At a minimum, it\n" + 357 "jeopardizes the compatibility of your app with future " + 358 "versions of the\n" + 359 "platform. It is also often of questionable legality.\n" + 360 "\n" + 361 "If you really intend to build a core library -- which is " + 362 "only\n" + 363 "appropriate as part of creating a full virtual machine " + 364 "distribution,\n" + 365 "as opposed to compiling an application -- then use the\n" + 366 "\"--core-library\" option to suppress this error message.\n" + 367 "\n" + 368 "If you go ahead and use \"--core-library\" but are in " + 369 "fact building an\n" + 370 "application, then be forewarned that your application " + 371 "will still fail\n" + 372 "to build or run, at some point. Please be prepared for " + 373 "angry customers\n" + 374 "who find, for example, that your application ceases to " + 375 "function once\n" + 376 "they upgrade their operating system. You will be to " + 377 "blame for this\n" + 378 "problem.\n" + 379 "\n" + 380 "If you are legitimately using some code that happens to " + 381 "be in a core\n" + 382 "package, then the easiest safe alternative you have is " + 383 "to repackage\n" + 384 "that code. That is, move the classes in question into " + 385 "your own package\n" + 386 "namespace. This means that they will never be in " + 387 "conflict with core\n" + 388 "system classes. If you find that you cannot do this, " + 389 "then that is an\n" + 390 "indication that the path you are on will ultimately lead " + 391 "to pain,\n" + 392 "suffering, grief, and lamentation.\n"); 393 errors++; 394 throw new StopProcessing(); 395 } 396 397 /** 398 * Converts {@link #outputDex} into a {@code byte[]}, write 399 * it out to the proper file (if any), and also do whatever human-oriented 400 * dumping is required. 401 * 402 * @return {@code null-ok;} the converted {@code byte[]} or {@code null} 403 * if there was a problem 404 */ 405 private static byte[] writeDex() { 406 byte[] outArray = null; 407 408 try { 409 OutputStream out = null; 410 OutputStream humanOutRaw = null; 411 OutputStreamWriter humanOut = null; 412 try { 413 if (args.humanOutName != null) { 414 humanOutRaw = openOutput(args.humanOutName); 415 humanOut = new OutputStreamWriter(humanOutRaw); 416 } 417 418 if (args.methodToDump != null) { 419 /* 420 * Simply dump the requested method. Note: The call 421 * to toDex() is required just to get the underlying 422 * structures ready. 423 */ 424 outputDex.toDex(null, false); 425 dumpMethod(outputDex, args.methodToDump, humanOut); 426 } else { 427 /* 428 * This is the usual case: Create an output .dex file, 429 * and write it, dump it, etc. 430 */ 431 outArray = outputDex.toDex(humanOut, args.verboseDump); 432 433 if ((args.outName != null) && !args.jarOutput) { 434 out = openOutput(args.outName); 435 out.write(outArray); 436 } 437 } 438 439 if (args.statistics) { 440 DxConsole.out.println(outputDex.getStatistics().toHuman()); 441 } 442 } finally { 443 if (humanOut != null) { 444 humanOut.flush(); 445 } 446 closeOutput(out); 447 closeOutput(humanOutRaw); 448 } 449 } catch (Exception ex) { 450 if (args.debug) { 451 DxConsole.err.println("\ntrouble writing output:"); 452 ex.printStackTrace(DxConsole.err); 453 } else { 454 DxConsole.err.println("\ntrouble writing output: " + 455 ex.getMessage()); 456 } 457 return null; 458 } 459 460 return outArray; 461 } 462 463 /** 464 * Creates a jar file from the resources and given dex file array. 465 * 466 * @param fileName {@code non-null;} name of the file 467 * @param dexArray {@code non-null;} array containing the dex file to include 468 * @return whether the creation was successful 469 */ 470 private static boolean createJar(String fileName, byte[] dexArray) { 471 /* 472 * Make or modify the manifest (as appropriate), put the dex 473 * array into the resources map, and then process the entire 474 * resources map in a uniform manner. 475 */ 476 477 try { 478 Manifest manifest = makeManifest(); 479 OutputStream out = openOutput(fileName); 480 JarOutputStream jarOut = new JarOutputStream(out, manifest); 481 482 outputResources.put(DEX_IN_JAR_NAME, dexArray); 483 484 try { 485 for (Map.Entry<String, byte[]> e : 486 outputResources.entrySet()) { 487 String name = e.getKey(); 488 byte[] contents = e.getValue(); 489 JarEntry entry = new JarEntry(name); 490 491 if (args.verbose) { 492 DxConsole.out.println("writing " + name + "; size " + 493 contents.length + "..."); 494 } 495 496 entry.setSize(contents.length); 497 jarOut.putNextEntry(entry); 498 jarOut.write(contents); 499 jarOut.closeEntry(); 500 } 501 } finally { 502 jarOut.finish(); 503 jarOut.flush(); 504 closeOutput(out); 505 } 506 } catch (Exception ex) { 507 if (args.debug) { 508 DxConsole.err.println("\ntrouble writing output:"); 509 ex.printStackTrace(DxConsole.err); 510 } else { 511 DxConsole.err.println("\ntrouble writing output: " + 512 ex.getMessage()); 513 } 514 return false; 515 } 516 517 return true; 518 } 519 520 /** 521 * Creates and returns the manifest to use for the output. This may 522 * modify {@link #outputResources} (removing the pre-existing manifest). 523 * 524 * @return {@code non-null;} the manifest 525 */ 526 private static Manifest makeManifest() throws IOException { 527 byte[] manifestBytes = outputResources.get(MANIFEST_NAME); 528 Manifest manifest; 529 Attributes attribs; 530 531 if (manifestBytes == null) { 532 // We need to construct an entirely new manifest. 533 manifest = new Manifest(); 534 attribs = manifest.getMainAttributes(); 535 attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 536 } else { 537 manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); 538 attribs = manifest.getMainAttributes(); 539 outputResources.remove(MANIFEST_NAME); 540 } 541 542 String createdBy = attribs.getValue(CREATED_BY); 543 if (createdBy == null) { 544 createdBy = ""; 545 } else { 546 createdBy += " + "; 547 } 548 createdBy += "dx " + Version.VERSION; 549 550 attribs.put(CREATED_BY, createdBy); 551 attribs.putValue("Dex-Location", DEX_IN_JAR_NAME); 552 553 return manifest; 554 } 555 556 /** 557 * Opens and returns the named file for writing, treating "-" specially. 558 * 559 * @param name {@code non-null;} the file name 560 * @return {@code non-null;} the opened file 561 */ 562 private static OutputStream openOutput(String name) throws IOException { 563 if (name.equals("-") || 564 name.startsWith("-.")) { 565 return System.out; 566 } 567 568 return new FileOutputStream(name); 569 } 570 571 /** 572 * Flushes and closes the given output stream, except if it happens to be 573 * {@link System#out} in which case this method does the flush but not 574 * the close. This method will also silently do nothing if given a 575 * {@code null} argument. 576 * 577 * @param stream {@code null-ok;} what to close 578 */ 579 private static void closeOutput(OutputStream stream) throws IOException { 580 if (stream == null) { 581 return; 582 } 583 584 stream.flush(); 585 586 if (stream != System.out) { 587 stream.close(); 588 } 589 } 590 591 /** 592 * Returns the "fixed" version of a given file path, suitable for 593 * use as a path within a {@code .jar} file and for checking 594 * against a classfile-internal "this class" name. This looks for 595 * the last instance of the substring {@code "/./"} within 596 * the path, and if it finds it, it takes the portion after to be 597 * the fixed path. If that isn't found but the path starts with 598 * {@code "./"}, then that prefix is removed and the rest is 599 * return. If neither of these is the case, this method returns 600 * its argument. 601 * 602 * @param path {@code non-null;} the path to "fix" 603 * @return {@code non-null;} the fixed version (which might be the same as 604 * the given {@code path}) 605 */ 606 private static String fixPath(String path) { 607 /* 608 * If the path separator is \ (like on windows), we convert the 609 * path to a standard '/' separated path. 610 */ 611 if (File.separatorChar == '\\') { 612 path = path.replace('\\', '/'); 613 } 614 615 int index = path.lastIndexOf("/./"); 616 617 if (index != -1) { 618 return path.substring(index + 3); 619 } 620 621 if (path.startsWith("./")) { 622 return path.substring(2); 623 } 624 625 return path; 626 } 627 628 /** 629 * Dumps any method with the given name in the given file. 630 * 631 * @param dex {@code non-null;} the dex file 632 * @param fqName {@code non-null;} the fully-qualified name of the method(s) 633 * @param out {@code non-null;} where to dump to 634 */ 635 private static void dumpMethod(DexFile dex, String fqName, 636 OutputStreamWriter out) { 637 boolean wildcard = fqName.endsWith("*"); 638 int lastDot = fqName.lastIndexOf('.'); 639 640 if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) { 641 DxConsole.err.println("bogus fully-qualified method name: " + 642 fqName); 643 return; 644 } 645 646 String className = fqName.substring(0, lastDot).replace('.', '/'); 647 String methodName = fqName.substring(lastDot + 1); 648 ClassDefItem clazz = dex.getClassOrNull(className); 649 650 if (clazz == null) { 651 DxConsole.err.println("no such class: " + className); 652 return; 653 } 654 655 if (wildcard) { 656 methodName = methodName.substring(0, methodName.length() - 1); 657 } 658 659 ArrayList<EncodedMethod> allMeths = clazz.getMethods(); 660 TreeMap<CstNat, EncodedMethod> meths = 661 new TreeMap<CstNat, EncodedMethod>(); 662 663 /* 664 * Figure out which methods to include in the output, and get them 665 * all sorted, so that the printout code is robust with respect to 666 * changes in the underlying order. 667 */ 668 for (EncodedMethod meth : allMeths) { 669 String methName = meth.getName().getString(); 670 if ((wildcard && methName.startsWith(methodName)) || 671 (!wildcard && methName.equals(methodName))) { 672 meths.put(meth.getRef().getNat(), meth); 673 } 674 } 675 676 if (meths.size() == 0) { 677 DxConsole.err.println("no such method: " + fqName); 678 return; 679 } 680 681 PrintWriter pw = new PrintWriter(out); 682 683 for (EncodedMethod meth : meths.values()) { 684 // TODO: Better stuff goes here, perhaps. 685 meth.debugPrint(pw, args.verboseDump); 686 687 /* 688 * The (default) source file is an attribute of the class, but 689 * it's useful to see it in method dumps. 690 */ 691 CstUtf8 sourceFile = clazz.getSourceFile(); 692 if (sourceFile != null) { 693 pw.println(" source file: " + sourceFile.toQuoted()); 694 } 695 696 Annotations methodAnnotations = 697 clazz.getMethodAnnotations(meth.getRef()); 698 AnnotationsList parameterAnnotations = 699 clazz.getParameterAnnotations(meth.getRef()); 700 701 if (methodAnnotations != null) { 702 pw.println(" method annotations:"); 703 for (Annotation a : methodAnnotations.getAnnotations()) { 704 pw.println(" " + a); 705 } 706 } 707 708 if (parameterAnnotations != null) { 709 pw.println(" parameter annotations:"); 710 int sz = parameterAnnotations.size(); 711 for (int i = 0; i < sz; i++) { 712 pw.println(" parameter " + i); 713 Annotations annotations = parameterAnnotations.get(i); 714 for (Annotation a : annotations.getAnnotations()) { 715 pw.println(" " + a); 716 } 717 } 718 } 719 } 720 721 pw.flush(); 722 } 723 724 /** 725 * Exception class used to halt processing prematurely. 726 */ 727 private static class StopProcessing extends RuntimeException { 728 // This space intentionally left blank. 729 } 730 731 /** 732 * Command-line argument parser and access. 733 */ 734 public static class Arguments { 735 /** whether to run in debug mode */ 736 public boolean debug = false; 737 738 /** whether to emit high-level verbose human-oriented output */ 739 public boolean verbose = false; 740 741 /** whether to emit verbose human-oriented output in the dump file */ 742 public boolean verboseDump = false; 743 744 /** whether we are constructing a core library */ 745 public boolean coreLibrary = false; 746 747 /** {@code null-ok;} particular method to dump */ 748 public String methodToDump = null; 749 750 /** max width for columnar output */ 751 public int dumpWidth = 0; 752 753 /** {@code null-ok;} output file name for binary file */ 754 public String outName = null; 755 756 /** {@code null-ok;} output file name for human-oriented dump */ 757 public String humanOutName = null; 758 759 /** whether strict file-name-vs-class-name checking should be done */ 760 public boolean strictNameCheck = true; 761 762 /** 763 * whether it is okay for there to be no {@code .class} files 764 * to process 765 */ 766 public boolean emptyOk = false; 767 768 /** 769 * whether the binary output is to be a {@code .jar} file 770 * instead of a plain {@code .dex} 771 */ 772 public boolean jarOutput = false; 773 774 /** 775 * when writing a {@code .jar} file, whether to still 776 * keep the {@code .class} files 777 */ 778 public boolean keepClassesInJar = false; 779 780 /** how much source position info to preserve */ 781 public int positionInfo = PositionList.LINES; 782 783 /** whether to keep local variable information */ 784 public boolean localInfo = true; 785 786 /** {@code non-null after {@link #parse};} file name arguments */ 787 public String[] fileNames; 788 789 /** whether to do SSA/register optimization */ 790 public boolean optimize = true; 791 792 /** Filename containg list of methods to optimize */ 793 public String optimizeListFile = null; 794 795 /** Filename containing list of methods to NOT optimize */ 796 public String dontOptimizeListFile = null; 797 798 /** Whether to print statistics to stdout at end of compile cycle */ 799 public boolean statistics; 800 801 /** Options for dex.cf.* */ 802 public CfOptions cfOptions; 803 804 /** 805 * Parses the given command-line arguments. 806 * 807 * @param args {@code non-null;} the arguments 808 */ 809 public void parse(String[] args) { 810 int at = 0; 811 812 for (/*at*/; at < args.length; at++) { 813 String arg = args[at]; 814 if (arg.equals("--") || !arg.startsWith("--")) { 815 break; 816 } else if (arg.equals("--debug")) { 817 debug = true; 818 } else if (arg.equals("--verbose")) { 819 verbose = true; 820 } else if (arg.equals("--verbose-dump")) { 821 verboseDump = true; 822 } else if (arg.equals("--no-files")) { 823 emptyOk = true; 824 } else if (arg.equals("--no-optimize")) { 825 optimize = false; 826 } else if (arg.equals("--no-strict")) { 827 strictNameCheck = false; 828 } else if (arg.equals("--core-library")) { 829 coreLibrary = true; 830 } else if (arg.equals("--statistics")) { 831 statistics = true; 832 } else if (arg.startsWith("--optimize-list=")) { 833 if (dontOptimizeListFile != null) { 834 System.err.println("--optimize-list and " 835 + "--no-optimize-list are incompatible."); 836 throw new UsageException(); 837 } 838 optimize = true; 839 optimizeListFile = arg.substring(arg.indexOf('=') + 1); 840 } else if (arg.startsWith("--no-optimize-list=")) { 841 if (dontOptimizeListFile != null) { 842 System.err.println("--optimize-list and " 843 + "--no-optimize-list are incompatible."); 844 throw new UsageException(); 845 } 846 optimize = true; 847 dontOptimizeListFile = arg.substring(arg.indexOf('=') + 1); 848 } else if (arg.equals("--keep-classes")) { 849 keepClassesInJar = true; 850 } else if (arg.startsWith("--output=")) { 851 outName = arg.substring(arg.indexOf('=') + 1); 852 if (outName.endsWith(".zip") || 853 outName.endsWith(".jar") || 854 outName.endsWith(".apk")) { 855 jarOutput = true; 856 } else if (outName.endsWith(".dex") || 857 outName.equals("-")) { 858 jarOutput = false; 859 } else { 860 System.err.println("unknown output extension: " + 861 outName); 862 throw new UsageException(); 863 } 864 } else if (arg.startsWith("--dump-to=")) { 865 humanOutName = arg.substring(arg.indexOf('=') + 1); 866 } else if (arg.startsWith("--dump-width=")) { 867 arg = arg.substring(arg.indexOf('=') + 1); 868 dumpWidth = Integer.parseInt(arg); 869 } else if (arg.startsWith("--dump-method=")) { 870 methodToDump = arg.substring(arg.indexOf('=') + 1); 871 jarOutput = false; 872 } else if (arg.startsWith("--positions=")) { 873 String pstr = arg.substring(arg.indexOf('=') + 1).intern(); 874 if (pstr == "none") { 875 positionInfo = PositionList.NONE; 876 } else if (pstr == "important") { 877 positionInfo = PositionList.IMPORTANT; 878 } else if (pstr == "lines") { 879 positionInfo = PositionList.LINES; 880 } else { 881 System.err.println("unknown positions option: " + 882 pstr); 883 throw new UsageException(); 884 } 885 } else if (arg.equals("--no-locals")) { 886 localInfo = false; 887 } else { 888 System.err.println("unknown option: " + arg); 889 throw new UsageException(); 890 } 891 } 892 893 int fileCount = args.length - at; 894 fileNames = new String[fileCount]; 895 System.arraycopy(args, at, fileNames, 0, fileCount); 896 897 if (fileCount == 0) { 898 if (!emptyOk) { 899 System.err.println("no input files specified"); 900 throw new UsageException(); 901 } 902 } else if (emptyOk) { 903 System.out.println("ignoring input files"); 904 at = args.length; 905 } 906 907 if ((humanOutName == null) && (methodToDump != null)) { 908 humanOutName = "-"; 909 } 910 911 makeCfOptions(); 912 } 913 914 /** 915 * Copies relevent arguments over into a CfOptions instance. 916 */ 917 private void makeCfOptions() { 918 cfOptions = new CfOptions(); 919 920 cfOptions.positionInfo = positionInfo; 921 cfOptions.localInfo = localInfo; 922 cfOptions.strictNameCheck = strictNameCheck; 923 cfOptions.optimize = optimize; 924 cfOptions.optimizeListFile = optimizeListFile; 925 cfOptions.dontOptimizeListFile = dontOptimizeListFile; 926 cfOptions.statistics = statistics; 927 cfOptions.warn = DxConsole.err; 928 } 929 } 930} 931