Main.java revision 1e4c4bebc1feb4b68155e9c2e7e6f2c056ef8e3b
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 private static class ArgumentsParser { 803 804 /** The arguments to process. */ 805 private final String[] arguments; 806 /** The index of the next argument to process. */ 807 private int index; 808 /** The current argument being processed after a {@link #getNext()} call. */ 809 private String current; 810 /** The last value of an argument processed by {@link #isArg(String)}. */ 811 private String lastValue; 812 813 public ArgumentsParser(String[] arguments) { 814 this.arguments = arguments; 815 index = 0; 816 } 817 818 public String getCurrent() { 819 return current; 820 } 821 822 public String getLastValue() { 823 return lastValue; 824 } 825 826 /** 827 * Moves on to the next argument. 828 * Returns false when we ran out of arguments that start with --. 829 */ 830 public boolean getNext() { 831 if (index >= arguments.length) { 832 return false; 833 } 834 current = arguments[index]; 835 if (current.equals("--") || !current.startsWith("--")) { 836 return false; 837 } 838 index++; 839 return true; 840 } 841 842 /** 843 * Similar to {@link #getNext()}, this moves on the to next argument. 844 * It does not check however whether the argument starts with -- 845 * and thus can be used to retrieve values. 846 */ 847 private boolean getNextValue() { 848 if (index >= arguments.length) { 849 return false; 850 } 851 current = arguments[index]; 852 index++; 853 return true; 854 } 855 856 /** 857 * Returns all the arguments that have not been processed yet. 858 */ 859 public String[] getRemaining() { 860 int n = arguments.length - index; 861 String[] remaining = new String[n]; 862 if (n > 0) { 863 System.arraycopy(arguments, index, remaining, 0, n); 864 } 865 return remaining; 866 } 867 868 /** 869 * Checks the current argument against the given prefix. 870 * If prefix is in the form '--name=', an extra value is expected. 871 * The argument can then be in the form '--name=value' or as a 2-argument 872 * form '--name value'. 873 */ 874 public boolean isArg(String prefix) { 875 int n = prefix.length(); 876 if (n > 0 && prefix.charAt(n-1) == '=') { 877 // Argument accepts a value. Capture it. 878 if (current.startsWith(prefix)) { 879 // Argument is in the form --name=value, split the value out 880 lastValue = current.substring(n); 881 return true; 882 } else { 883 // Check whether we have "--name value" as 2 arguments 884 prefix = prefix.substring(0, n-1); 885 if (current.equals(prefix)) { 886 if (getNextValue()) { 887 lastValue = current; 888 return true; 889 } else { 890 System.err.println("Missing value after parameter " + prefix); 891 throw new UsageException(); 892 } 893 } 894 return false; 895 } 896 } else { 897 // Argument does not accept a value. 898 return current.equals(prefix); 899 } 900 } 901 } 902 903 /** 904 * Parses the given command-line arguments. 905 * 906 * @param args {@code non-null;} the arguments 907 */ 908 public void parse(String[] args) { 909 ArgumentsParser parser = new ArgumentsParser(args); 910 911 while(parser.getNext()) { 912 if (parser.isArg("--debug")) { 913 debug = true; 914 } else if (parser.isArg("--verbose")) { 915 verbose = true; 916 } else if (parser.isArg("--verbose-dump")) { 917 verboseDump = true; 918 } else if (parser.isArg("--no-files")) { 919 emptyOk = true; 920 } else if (parser.isArg("--no-optimize")) { 921 optimize = false; 922 } else if (parser.isArg("--no-strict")) { 923 strictNameCheck = false; 924 } else if (parser.isArg("--core-library")) { 925 coreLibrary = true; 926 } else if (parser.isArg("--statistics")) { 927 statistics = true; 928 } else if (parser.isArg("--optimize-list=")) { 929 if (dontOptimizeListFile != null) { 930 System.err.println("--optimize-list and " 931 + "--no-optimize-list are incompatible."); 932 throw new UsageException(); 933 } 934 optimize = true; 935 optimizeListFile = parser.getLastValue(); 936 } else if (parser.isArg("--no-optimize-list=")) { 937 if (dontOptimizeListFile != null) { 938 System.err.println("--optimize-list and " 939 + "--no-optimize-list are incompatible."); 940 throw new UsageException(); 941 } 942 optimize = true; 943 dontOptimizeListFile = parser.getLastValue(); 944 } else if (parser.isArg("--keep-classes")) { 945 keepClassesInJar = true; 946 } else if (parser.isArg("--output=")) { 947 outName = parser.getLastValue(); 948 if (outName.endsWith(".zip") || 949 outName.endsWith(".jar") || 950 outName.endsWith(".apk")) { 951 jarOutput = true; 952 } else if (outName.endsWith(".dex") || 953 outName.equals("-")) { 954 jarOutput = false; 955 } else { 956 System.err.println("unknown output extension: " + 957 outName); 958 throw new UsageException(); 959 } 960 } else if (parser.isArg("--dump-to=")) { 961 humanOutName = parser.getLastValue(); 962 } else if (parser.isArg("--dump-width=")) { 963 dumpWidth = Integer.parseInt(parser.getLastValue()); 964 } else if (parser.isArg("--dump-method=")) { 965 methodToDump = parser.getLastValue(); 966 jarOutput = false; 967 } else if (parser.isArg("--positions=")) { 968 String pstr = parser.getLastValue().intern(); 969 if (pstr == "none") { 970 positionInfo = PositionList.NONE; 971 } else if (pstr == "important") { 972 positionInfo = PositionList.IMPORTANT; 973 } else if (pstr == "lines") { 974 positionInfo = PositionList.LINES; 975 } else { 976 System.err.println("unknown positions option: " + 977 pstr); 978 throw new UsageException(); 979 } 980 } else if (parser.isArg("--no-locals")) { 981 localInfo = false; 982 } else { 983 System.err.println("unknown option: " + parser.getCurrent()); 984 throw new UsageException(); 985 } 986 } 987 988 fileNames = parser.getRemaining(); 989 if (fileNames.length == 0) { 990 if (!emptyOk) { 991 System.err.println("no input files specified"); 992 throw new UsageException(); 993 } 994 } else if (emptyOk) { 995 System.out.println("ignoring input files"); 996 } 997 998 if ((humanOutName == null) && (methodToDump != null)) { 999 humanOutName = "-"; 1000 } 1001 1002 makeCfOptions(); 1003 } 1004 1005 /** 1006 * Copies relevent arguments over into a CfOptions instance. 1007 */ 1008 private void makeCfOptions() { 1009 cfOptions = new CfOptions(); 1010 1011 cfOptions.positionInfo = positionInfo; 1012 cfOptions.localInfo = localInfo; 1013 cfOptions.strictNameCheck = strictNameCheck; 1014 cfOptions.optimize = optimize; 1015 cfOptions.optimizeListFile = optimizeListFile; 1016 cfOptions.dontOptimizeListFile = dontOptimizeListFile; 1017 cfOptions.statistics = statistics; 1018 cfOptions.warn = DxConsole.err; 1019 } 1020 } 1021} 1022