2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.dx.command.dexer;
19 import com.android.dx.Version;
20 import com.android.dx.cf.code.SimException;
21 import com.android.dx.cf.direct.ClassPathOpener;
22 import com.android.dx.cf.iface.ParseException;
23 import com.android.dx.command.DxConsole;
24 import com.android.dx.command.UsageException;
25 import com.android.dx.dex.DexFormat;
26 import com.android.dx.dex.DexOptions;
27 import com.android.dx.dex.cf.CfOptions;
28 import com.android.dx.dex.cf.CfTranslator;
29 import com.android.dx.dex.cf.CodeStatistics;
30 import com.android.dx.dex.code.PositionList;
31 import com.android.dx.dex.file.ClassDefItem;
32 import com.android.dx.dex.file.DexFile;
33 import com.android.dx.dex.file.EncodedMethod;
34 import com.android.dx.io.DexBuffer;
35 import com.android.dx.merge.CollisionPolicy;
36 import com.android.dx.merge.DexMerger;
37 import com.android.dx.rop.annotation.Annotation;
38 import com.android.dx.rop.annotation.Annotations;
39 import com.android.dx.rop.annotation.AnnotationsList;
40 import com.android.dx.rop.cst.CstNat;
41 import com.android.dx.rop.cst.CstString;
42 import com.android.dx.util.FileUtils;
43 import java.io.ByteArrayInputStream;
44 import java.io.ByteArrayOutputStream;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.io.OutputStream;
49 import java.io.OutputStreamWriter;
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.List;
55 import java.util.TreeMap;
56 import java.util.concurrent.ExecutorService;
57 import java.util.concurrent.Executors;
58 import java.util.concurrent.TimeUnit;
59 import java.util.jar.Attributes;
60 import java.util.jar.JarEntry;
61 import java.util.jar.JarOutputStream;
62 import java.util.jar.Manifest;
65 * Main class for the class file translator.
69 * {@code non-null;} the lengthy message that tries to discourage
70 * people from defining core classes in applications
72 private static final String IN_RE_CORE_CLASSES =
73 "Ill-advised or mistaken usage of a core class (java.* or javax.*)\n" +
74 "when not building a core library.\n\n" +
75 "This is often due to inadvertently including a core library file\n" +
76 "in your application's project, when using an IDE (such as\n" +
77 "Eclipse). If you are sure you're not intentionally defining a\n" +
78 "core class, then this is the most likely explanation of what's\n" +
80 "However, you might actually be trying to define a class in a core\n" +
81 "namespace, the source of which you may have taken, for example,\n" +
82 "from a non-Android virtual machine project. This will most\n" +
83 "assuredly not work. At a minimum, it jeopardizes the\n" +
84 "compatibility of your app with future versions of the platform.\n" +
85 "It is also often of questionable legality.\n\n" +
86 "If you really intend to build a core library -- which is only\n" +
87 "appropriate as part of creating a full virtual machine\n" +
88 "distribution, as opposed to compiling an application -- then use\n" +
89 "the \"--core-library\" option to suppress this error message.\n\n" +
90 "If you go ahead and use \"--core-library\" but are in fact\n" +
91 "building an application, then be forewarned that your application\n" +
92 "will still fail to build or run, at some point. Please be\n" +
93 "prepared for angry customers who find, for example, that your\n" +
94 "application ceases to function once they upgrade their operating\n" +
95 "system. You will be to blame for this problem.\n\n" +
96 "If you are legitimately using some code that happens to be in a\n" +
97 "core package, then the easiest safe alternative you have is to\n" +
98 "repackage that code. That is, move the classes in question into\n" +
99 "your own package namespace. This means that they will never be in\n" +
100 "conflict with core system classes. JarJar is a tool that may help\n" +
101 "you in this endeavor. If you find that you cannot do this, then\n" +
102 "that is an indication that the path you are on will ultimately\n" +
103 "lead to pain, suffering, grief, and lamentation.\n";
106 * {@code non-null;} name of the standard manifest file in {@code .jar}
109 private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
112 * {@code non-null;} attribute name for the (quasi-standard?)
113 * {@code Created-By} attribute
115 private static final Attributes.Name CREATED_BY =
116 new Attributes.Name("Created-By");
119 * {@code non-null;} list of {@code javax} subpackages that are considered
120 * to be "core". <b>Note:</b>: This list must be sorted, since it
121 * is binary-searched.
123 private static final String[] JAVAX_CORE = {
124 "accessibility", "crypto", "imageio", "management", "naming", "net",
125 "print", "rmi", "security", "sip", "sound", "sql", "swing",
129 /** number of warnings during processing */
130 private static int warnings = 0;
132 /** number of errors during processing */
133 private static int errors = 0;
135 /** {@code non-null;} parsed command-line arguments */
136 private static Arguments args;
138 /** {@code non-null;} output file in-progress */
139 private static DexFile outputDex;
142 * {@code null-ok;} map of resources to include in the output, or
143 * {@code null} if resources are being ignored
145 private static TreeMap<String, byte[]> outputResources;
147 /** Library .dex files to merge into the output .dex. */
148 private static final List<byte[]> libraryDexBuffers = new ArrayList<byte[]>();
150 /** thread pool object used for multi-threaded file processing */
151 private static ExecutorService threadPool;
153 /** true if any files are successfully processed */
154 private static boolean anyFilesProcessed;
156 /** class files older than this must be defined in the target dex file. */
157 private static long minimumFileAge = 0;
160 * This class is uninstantiable.
163 // This space intentionally left blank.
167 * Run and exit if something unexpected happened.
168 * @param argArray the command line arguments
170 public static void main(String[] argArray) throws IOException {
171 Arguments arguments = new Arguments();
172 arguments.parse(argArray);
174 int result = run(arguments);
181 * Run and return a result code.
182 * @param arguments the data + parameters for the conversion
183 * @return 0 if success > 0 otherwise.
185 public static int run(Arguments arguments) throws IOException {
186 // Reset the error/warning count to start fresh.
191 args.makeOptionsObjects();
193 File incrementalOutFile = null;
194 if (args.incremental) {
195 if (args.outName == null) {
197 "error: no incremental output name specified");
200 incrementalOutFile = new File(args.outName);
201 if (incrementalOutFile.exists()) {
202 minimumFileAge = incrementalOutFile.lastModified();
206 if (!processAllFiles()) {
210 if (args.incremental && !anyFilesProcessed) {
211 return 0; // this was a no-op incremental build
214 // this array is null if no classes were defined
215 byte[] outArray = null;
217 if (!outputDex.isEmpty()) {
218 outArray = writeDex();
220 if (outArray == null) {
225 if (args.incremental) {
226 outArray = mergeIncremental(outArray, incrementalOutFile);
229 outArray = mergeLibraryDexBuffers(outArray);
231 if (args.jarOutput) {
232 // Effectively free up the (often massive) DexFile memory.
235 if (!createJar(args.outName, outArray)) {
238 } else if (outArray != null && args.outName != null) {
239 OutputStream out = openOutput(args.outName);
248 * Merges the dex files {@code update} and {@code base}, preferring
249 * {@code update}'s definition for types defined in both dex files.
251 * @param base a file to find the previous dex file. May be a .dex file, a
252 * jar file possibly containing a .dex file, or null.
253 * @return the bytes of the merged dex file, or null if both the update
254 * and the base dex do not exist.
256 private static byte[] mergeIncremental(byte[] update, File base) throws IOException {
257 DexBuffer dexA = null;
258 DexBuffer dexB = null;
260 if (update != null) {
261 dexA = new DexBuffer(update);
265 dexB = new DexBuffer(base);
269 if (dexA == null && dexB == null) {
271 } else if (dexA == null) {
273 } else if (dexB == null) {
276 result = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
279 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
280 result.writeTo(bytesOut);
281 return bytesOut.toByteArray();
285 * Merges the dex files in library jars. If multiple dex files define the
286 * same type, this fails with an exception.
288 private static byte[] mergeLibraryDexBuffers(byte[] outArray) throws IOException {
289 for (byte[] libraryDexBuffer : libraryDexBuffers) {
290 if (outArray == null) {
291 outArray = libraryDexBuffer;
295 DexBuffer a = new DexBuffer(outArray);
296 DexBuffer b = new DexBuffer(libraryDexBuffer);
297 DexBuffer ab = new DexMerger(a, b, CollisionPolicy.FAIL).merge();
298 outArray = ab.getBytes();
304 * Constructs the output {@link DexFile}, fill it in with all the
305 * specified classes, and populate the resources map if required.
307 * @return whether processing was successful
309 private static boolean processAllFiles() {
310 outputDex = new DexFile(args.dexOptions);
312 if (args.jarOutput) {
313 outputResources = new TreeMap<String, byte[]>();
316 if (args.dumpWidth != 0) {
317 outputDex.setDumpWidth(args.dumpWidth);
320 anyFilesProcessed = false;
321 String[] fileNames = args.fileNames;
323 if (args.numThreads > 1) {
324 threadPool = Executors.newFixedThreadPool(args.numThreads);
328 for (int i = 0; i < fileNames.length; i++) {
329 if (processOne(fileNames[i])) {
330 anyFilesProcessed = true;
333 } catch (StopProcessing ex) {
335 * Ignore it and just let the warning/error reporting do
340 if (args.numThreads > 1) {
342 threadPool.shutdown();
343 threadPool.awaitTermination(600L, TimeUnit.SECONDS);
344 } catch (InterruptedException ex) {
345 throw new RuntimeException("Timed out waiting for threads.");
350 DxConsole.err.println(warnings + " warning" +
351 ((warnings == 1) ? "" : "s"));
355 DxConsole.err.println(errors + " error" +
356 ((errors == 1) ? "" : "s") + "; aborting");
360 if (args.incremental && !anyFilesProcessed) {
364 if (!(anyFilesProcessed || args.emptyOk)) {
365 DxConsole.err.println("no classfiles specified");
369 if (args.optimize && args.statistics) {
370 CodeStatistics.dumpStatistics(DxConsole.out);
377 * Processes one pathname element.
379 * @param pathname {@code non-null;} the pathname to process. May
380 * be the path of a class file, a jar file, or a directory
381 * containing class files.
382 * @return whether any processing actually happened
384 private static boolean processOne(String pathname) {
385 ClassPathOpener opener;
387 opener = new ClassPathOpener(pathname, false,
388 new ClassPathOpener.Consumer() {
389 public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
390 if (args.numThreads > 1) {
391 threadPool.execute(new ParallelProcessor(name, lastModified, bytes));
394 return Main.processFileBytes(name, lastModified, bytes);
397 public void onException(Exception ex) {
398 if (ex instanceof StopProcessing) {
399 throw (StopProcessing) ex;
400 } else if (ex instanceof SimException) {
401 DxConsole.err.println("\nEXCEPTION FROM SIMULATION:");
402 DxConsole.err.println(ex.getMessage() + "\n");
403 DxConsole.err.println(((SimException) ex).getContext());
405 DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
406 ex.printStackTrace(DxConsole.err);
410 public void onProcessArchiveStart(File file) {
412 DxConsole.out.println("processing archive " + file +
418 return opener.process();
422 * Processes one file, which may be either a class or a resource.
424 * @param name {@code non-null;} name of the file
425 * @param bytes {@code non-null;} contents of the file
426 * @return whether processing was successful
428 private static boolean processFileBytes(String name, long lastModified, byte[] bytes) {
429 boolean isClass = name.endsWith(".class");
430 boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
431 boolean keepResources = (outputResources != null);
433 if (!isClass && !isClassesDex && !keepResources) {
435 DxConsole.out.println("ignored resource " + name);
441 DxConsole.out.println("processing " + name + "...");
444 String fixedName = fixPath(name);
447 if (keepResources && args.keepClassesInJar) {
448 synchronized (outputResources) {
449 outputResources.put(fixedName, bytes);
452 if (lastModified < minimumFileAge) {
455 return processClass(fixedName, bytes);
456 } else if (isClassesDex) {
457 synchronized (libraryDexBuffers) {
458 libraryDexBuffers.add(bytes);
462 synchronized (outputResources) {
463 outputResources.put(fixedName, bytes);
470 * Processes one classfile.
472 * @param name {@code non-null;} name of the file, clipped such that it
473 * <i>should</i> correspond to the name of the class it contains
474 * @param bytes {@code non-null;} contents of the file
475 * @return whether processing was successful
477 private static boolean processClass(String name, byte[] bytes) {
478 if (! args.coreLibrary) {
479 checkClassName(name);
484 CfTranslator.translate(name, bytes, args.cfOptions, args.dexOptions);
485 synchronized (outputDex) {
486 outputDex.add(clazz);
489 } catch (ParseException ex) {
490 DxConsole.err.println("\ntrouble processing:");
492 ex.printStackTrace(DxConsole.err);
494 ex.printContext(DxConsole.err);
503 * Check the class name to make sure it's not a "core library"
504 * class. If there is a problem, this updates the error count and
505 * throws an exception to stop processing.
507 * @param name {@code non-null;} the fully-qualified internal-form
510 private static void checkClassName(String name) {
511 boolean bogus = false;
513 if (name.startsWith("java/")) {
515 } else if (name.startsWith("javax/")) {
516 int slashAt = name.indexOf('/', 6);
518 // Top-level javax classes are verboten.
521 String pkg = name.substring(6, slashAt);
522 bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0);
531 * The user is probably trying to include an entire desktop
532 * core library in a misguided attempt to get their application
533 * working. Try to help them understand what's happening.
536 DxConsole.err.println("\ntrouble processing \"" + name + "\":\n\n" +
539 throw new StopProcessing();
543 * Converts {@link #outputDex} into a {@code byte[]} and do whatever
544 * human-oriented dumping is required.
546 * @return {@code null-ok;} the converted {@code byte[]} or {@code null}
547 * if there was a problem
549 private static byte[] writeDex() {
550 byte[] outArray = null;
553 OutputStream humanOutRaw = null;
554 OutputStreamWriter humanOut = null;
556 if (args.humanOutName != null) {
557 humanOutRaw = openOutput(args.humanOutName);
558 humanOut = new OutputStreamWriter(humanOutRaw);
561 if (args.methodToDump != null) {
563 * Simply dump the requested method. Note: The call
564 * to toDex() is required just to get the underlying
567 outputDex.toDex(null, false);
568 dumpMethod(outputDex, args.methodToDump, humanOut);
571 * This is the usual case: Create an output .dex file,
572 * and write it, dump it, etc.
574 outArray = outputDex.toDex(humanOut, args.verboseDump);
577 if (args.statistics) {
578 DxConsole.out.println(outputDex.getStatistics().toHuman());
581 if (humanOut != null) {
584 closeOutput(humanOutRaw);
586 } catch (Exception ex) {
588 DxConsole.err.println("\ntrouble writing output:");
589 ex.printStackTrace(DxConsole.err);
591 DxConsole.err.println("\ntrouble writing output: " +
601 * Creates a jar file from the resources and given dex file array.
603 * @param fileName {@code non-null;} name of the file
604 * @param dexArray array containing the dex file to include, or null if the
605 * output contains no class defs.
606 * @return whether the creation was successful
608 private static boolean createJar(String fileName, byte[] dexArray) {
610 * Make or modify the manifest (as appropriate), put the dex
611 * array into the resources map, and then process the entire
612 * resources map in a uniform manner.
616 Manifest manifest = makeManifest();
617 OutputStream out = openOutput(fileName);
618 JarOutputStream jarOut = new JarOutputStream(out, manifest);
620 if (dexArray != null) {
621 outputResources.put(DexFormat.DEX_IN_JAR_NAME, dexArray);
625 for (Map.Entry<String, byte[]> e :
626 outputResources.entrySet()) {
627 String name = e.getKey();
628 byte[] contents = e.getValue();
629 JarEntry entry = new JarEntry(name);
632 DxConsole.out.println("writing " + name + "; size " +
633 contents.length + "...");
636 entry.setSize(contents.length);
637 jarOut.putNextEntry(entry);
638 jarOut.write(contents);
646 } catch (Exception ex) {
648 DxConsole.err.println("\ntrouble writing output:");
649 ex.printStackTrace(DxConsole.err);
651 DxConsole.err.println("\ntrouble writing output: " +
661 * Creates and returns the manifest to use for the output. This may
662 * modify {@link #outputResources} (removing the pre-existing manifest).
664 * @return {@code non-null;} the manifest
666 private static Manifest makeManifest() throws IOException {
667 byte[] manifestBytes = outputResources.get(MANIFEST_NAME);
671 if (manifestBytes == null) {
672 // We need to construct an entirely new manifest.
673 manifest = new Manifest();
674 attribs = manifest.getMainAttributes();
675 attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
677 manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
678 attribs = manifest.getMainAttributes();
679 outputResources.remove(MANIFEST_NAME);
682 String createdBy = attribs.getValue(CREATED_BY);
683 if (createdBy == null) {
688 createdBy += "dx " + Version.VERSION;
690 attribs.put(CREATED_BY, createdBy);
691 attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME);
697 * Opens and returns the named file for writing, treating "-" specially.
699 * @param name {@code non-null;} the file name
700 * @return {@code non-null;} the opened file
702 private static OutputStream openOutput(String name) throws IOException {
703 if (name.equals("-") ||
704 name.startsWith("-.")) {
708 return new FileOutputStream(name);
712 * Flushes and closes the given output stream, except if it happens to be
713 * {@link System#out} in which case this method does the flush but not
714 * the close. This method will also silently do nothing if given a
715 * {@code null} argument.
717 * @param stream {@code null-ok;} what to close
719 private static void closeOutput(OutputStream stream) throws IOException {
720 if (stream == null) {
726 if (stream != System.out) {
732 * Returns the "fixed" version of a given file path, suitable for
733 * use as a path within a {@code .jar} file and for checking
734 * against a classfile-internal "this class" name. This looks for
735 * the last instance of the substring {@code "/./"} within
736 * the path, and if it finds it, it takes the portion after to be
737 * the fixed path. If that isn't found but the path starts with
738 * {@code "./"}, then that prefix is removed and the rest is
739 * return. If neither of these is the case, this method returns
742 * @param path {@code non-null;} the path to "fix"
743 * @return {@code non-null;} the fixed version (which might be the same as
744 * the given {@code path})
746 private static String fixPath(String path) {
748 * If the path separator is \ (like on windows), we convert the
749 * path to a standard '/' separated path.
751 if (File.separatorChar == '\\') {
752 path = path.replace('\\', '/');
755 int index = path.lastIndexOf("/./");
758 return path.substring(index + 3);
761 if (path.startsWith("./")) {
762 return path.substring(2);
769 * Dumps any method with the given name in the given file.
771 * @param dex {@code non-null;} the dex file
772 * @param fqName {@code non-null;} the fully-qualified name of the
774 * @param out {@code non-null;} where to dump to
776 private static void dumpMethod(DexFile dex, String fqName,
777 OutputStreamWriter out) {
778 boolean wildcard = fqName.endsWith("*");
779 int lastDot = fqName.lastIndexOf('.');
781 if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) {
782 DxConsole.err.println("bogus fully-qualified method name: " +
787 String className = fqName.substring(0, lastDot).replace('.', '/');
788 String methodName = fqName.substring(lastDot + 1);
789 ClassDefItem clazz = dex.getClassOrNull(className);
792 DxConsole.err.println("no such class: " + className);
797 methodName = methodName.substring(0, methodName.length() - 1);
800 ArrayList<EncodedMethod> allMeths = clazz.getMethods();
801 TreeMap<CstNat, EncodedMethod> meths =
802 new TreeMap<CstNat, EncodedMethod>();
805 * Figure out which methods to include in the output, and get them
806 * all sorted, so that the printout code is robust with respect to
807 * changes in the underlying order.
809 for (EncodedMethod meth : allMeths) {
810 String methName = meth.getName().getString();
811 if ((wildcard && methName.startsWith(methodName)) ||
812 (!wildcard && methName.equals(methodName))) {
813 meths.put(meth.getRef().getNat(), meth);
817 if (meths.size() == 0) {
818 DxConsole.err.println("no such method: " + fqName);
822 PrintWriter pw = new PrintWriter(out);
824 for (EncodedMethod meth : meths.values()) {
825 // TODO: Better stuff goes here, perhaps.
826 meth.debugPrint(pw, args.verboseDump);
829 * The (default) source file is an attribute of the class, but
830 * it's useful to see it in method dumps.
832 CstString sourceFile = clazz.getSourceFile();
833 if (sourceFile != null) {
834 pw.println(" source file: " + sourceFile.toQuoted());
837 Annotations methodAnnotations =
838 clazz.getMethodAnnotations(meth.getRef());
839 AnnotationsList parameterAnnotations =
840 clazz.getParameterAnnotations(meth.getRef());
842 if (methodAnnotations != null) {
843 pw.println(" method annotations:");
844 for (Annotation a : methodAnnotations.getAnnotations()) {
849 if (parameterAnnotations != null) {
850 pw.println(" parameter annotations:");
851 int sz = parameterAnnotations.size();
852 for (int i = 0; i < sz; i++) {
853 pw.println(" parameter " + i);
854 Annotations annotations = parameterAnnotations.get(i);
855 for (Annotation a : annotations.getAnnotations()) {
866 * Exception class used to halt processing prematurely.
868 private static class StopProcessing extends RuntimeException {
869 // This space intentionally left blank.
873 * Command-line argument parser and access.
875 public static class Arguments {
876 /** whether to run in debug mode */
877 public boolean debug = false;
879 /** whether to emit high-level verbose human-oriented output */
880 public boolean verbose = false;
882 /** whether to emit verbose human-oriented output in the dump file */
883 public boolean verboseDump = false;
885 /** whether we are constructing a core library */
886 public boolean coreLibrary = false;
888 /** {@code null-ok;} particular method to dump */
889 public String methodToDump = null;
891 /** max width for columnar output */
892 public int dumpWidth = 0;
894 /** {@code null-ok;} output file name for binary file */
895 public String outName = null;
897 /** {@code null-ok;} output file name for human-oriented dump */
898 public String humanOutName = null;
900 /** whether strict file-name-vs-class-name checking should be done */
901 public boolean strictNameCheck = true;
904 * whether it is okay for there to be no {@code .class} files
907 public boolean emptyOk = false;
910 * whether the binary output is to be a {@code .jar} file
911 * instead of a plain {@code .dex}
913 public boolean jarOutput = false;
916 * when writing a {@code .jar} file, whether to still
917 * keep the {@code .class} files
919 public boolean keepClassesInJar = false;
921 /** what API level to target */
922 public int targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
924 /** how much source position info to preserve */
925 public int positionInfo = PositionList.LINES;
927 /** whether to keep local variable information */
928 public boolean localInfo = true;
930 /** whether to merge with the output dex file if it exists. */
931 public boolean incremental = false;
933 /** {@code non-null} after {@link #parse}; file name arguments */
934 public String[] fileNames;
936 /** whether to do SSA/register optimization */
937 public boolean optimize = true;
939 /** Filename containg list of methods to optimize */
940 public String optimizeListFile = null;
942 /** Filename containing list of methods to NOT optimize */
943 public String dontOptimizeListFile = null;
945 /** Whether to print statistics to stdout at end of compile cycle */
946 public boolean statistics;
948 /** Options for class file transformation */
949 public CfOptions cfOptions;
951 /** Options for dex file output */
952 public DexOptions dexOptions;
954 /** number of threads to run with */
955 public int numThreads = 1;
957 private static class ArgumentsParser {
959 /** The arguments to process. */
960 private final String[] arguments;
961 /** The index of the next argument to process. */
963 /** The current argument being processed after a {@link #getNext()} call. */
964 private String current;
965 /** The last value of an argument processed by {@link #isArg(String)}. */
966 private String lastValue;
968 public ArgumentsParser(String[] arguments) {
969 this.arguments = arguments;
973 public String getCurrent() {
977 public String getLastValue() {
982 * Moves on to the next argument.
983 * Returns false when we ran out of arguments that start with --.
985 public boolean getNext() {
986 if (index >= arguments.length) {
989 current = arguments[index];
990 if (current.equals("--") || !current.startsWith("--")) {
998 * Similar to {@link #getNext()}, this moves on the to next argument.
999 * It does not check however whether the argument starts with --
1000 * and thus can be used to retrieve values.
1002 private boolean getNextValue() {
1003 if (index >= arguments.length) {
1006 current = arguments[index];
1012 * Returns all the arguments that have not been processed yet.
1014 public String[] getRemaining() {
1015 int n = arguments.length - index;
1016 String[] remaining = new String[n];
1018 System.arraycopy(arguments, index, remaining, 0, n);
1024 * Checks the current argument against the given prefix.
1025 * If prefix is in the form '--name=', an extra value is expected.
1026 * The argument can then be in the form '--name=value' or as a 2-argument
1027 * form '--name value'.
1029 public boolean isArg(String prefix) {
1030 int n = prefix.length();
1031 if (n > 0 && prefix.charAt(n-1) == '=') {
1032 // Argument accepts a value. Capture it.
1033 if (current.startsWith(prefix)) {
1034 // Argument is in the form --name=value, split the value out
1035 lastValue = current.substring(n);
1038 // Check whether we have "--name value" as 2 arguments
1039 prefix = prefix.substring(0, n-1);
1040 if (current.equals(prefix)) {
1041 if (getNextValue()) {
1042 lastValue = current;
1045 System.err.println("Missing value after parameter " + prefix);
1046 throw new UsageException();
1052 // Argument does not accept a value.
1053 return current.equals(prefix);
1059 * Parses the given command-line arguments.
1061 * @param args {@code non-null;} the arguments
1063 public void parse(String[] args) {
1064 ArgumentsParser parser = new ArgumentsParser(args);
1066 while(parser.getNext()) {
1067 if (parser.isArg("--debug")) {
1069 } else if (parser.isArg("--verbose")) {
1071 } else if (parser.isArg("--verbose-dump")) {
1073 } else if (parser.isArg("--no-files")) {
1075 } else if (parser.isArg("--no-optimize")) {
1077 } else if (parser.isArg("--no-strict")) {
1078 strictNameCheck = false;
1079 } else if (parser.isArg("--core-library")) {
1081 } else if (parser.isArg("--statistics")) {
1083 } else if (parser.isArg("--optimize-list=")) {
1084 if (dontOptimizeListFile != null) {
1085 System.err.println("--optimize-list and "
1086 + "--no-optimize-list are incompatible.");
1087 throw new UsageException();
1090 optimizeListFile = parser.getLastValue();
1091 } else if (parser.isArg("--no-optimize-list=")) {
1092 if (dontOptimizeListFile != null) {
1093 System.err.println("--optimize-list and "
1094 + "--no-optimize-list are incompatible.");
1095 throw new UsageException();
1098 dontOptimizeListFile = parser.getLastValue();
1099 } else if (parser.isArg("--keep-classes")) {
1100 keepClassesInJar = true;
1101 } else if (parser.isArg("--output=")) {
1102 outName = parser.getLastValue();
1103 if (FileUtils.hasArchiveSuffix(outName)) {
1105 } else if (outName.endsWith(".dex") ||
1106 outName.equals("-")) {
1109 System.err.println("unknown output extension: " +
1111 throw new UsageException();
1113 } else if (parser.isArg("--dump-to=")) {
1114 humanOutName = parser.getLastValue();
1115 } else if (parser.isArg("--dump-width=")) {
1116 dumpWidth = Integer.parseInt(parser.getLastValue());
1117 } else if (parser.isArg("--dump-method=")) {
1118 methodToDump = parser.getLastValue();
1120 } else if (parser.isArg("--positions=")) {
1121 String pstr = parser.getLastValue().intern();
1122 if (pstr == "none") {
1123 positionInfo = PositionList.NONE;
1124 } else if (pstr == "important") {
1125 positionInfo = PositionList.IMPORTANT;
1126 } else if (pstr == "lines") {
1127 positionInfo = PositionList.LINES;
1129 System.err.println("unknown positions option: " +
1131 throw new UsageException();
1133 } else if (parser.isArg("--no-locals")) {
1135 } else if (parser.isArg("--num-threads=")) {
1136 numThreads = Integer.parseInt(parser.getLastValue());
1137 } else if (parser.isArg("--incremental")) {
1140 System.err.println("unknown option: " + parser.getCurrent());
1141 throw new UsageException();
1145 fileNames = parser.getRemaining();
1146 if (fileNames.length == 0) {
1148 System.err.println("no input files specified");
1149 throw new UsageException();
1151 } else if (emptyOk) {
1152 System.out.println("ignoring input files");
1155 if ((humanOutName == null) && (methodToDump != null)) {
1159 makeOptionsObjects();
1163 * Copies relevent arguments over into CfOptions and
1164 * DexOptions instances.
1166 private void makeOptionsObjects() {
1167 cfOptions = new CfOptions();
1168 cfOptions.positionInfo = positionInfo;
1169 cfOptions.localInfo = localInfo;
1170 cfOptions.strictNameCheck = strictNameCheck;
1171 cfOptions.optimize = optimize;
1172 cfOptions.optimizeListFile = optimizeListFile;
1173 cfOptions.dontOptimizeListFile = dontOptimizeListFile;
1174 cfOptions.statistics = statistics;
1175 cfOptions.warn = DxConsole.err;
1177 dexOptions = new DexOptions();
1178 dexOptions.targetApiLevel = targetApiLevel;
1182 /** Runnable helper class to process files in multiple threads */
1183 private static class ParallelProcessor implements Runnable {
1190 * Constructs an instance.
1192 * @param path {@code non-null;} filename of element. May not be a valid
1194 * @param bytes {@code non-null;} file data
1196 private ParallelProcessor(String path, long lastModified, byte bytes[]) {
1198 this.lastModified = lastModified;
1203 * Task run by each thread in the thread pool. Runs processFileBytes
1204 * with the given path and bytes.
1207 if (Main.processFileBytes(path, lastModified, bytes)) {
1208 anyFilesProcessed = true;