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