• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.dx.command.dexer;
18 
19 import com.android.dx.Version;
20 import com.android.dx.cf.iface.ParseException;
21 import com.android.dx.cf.direct.ClassPathOpener;
22 import com.android.dx.command.DxConsole;
23 import com.android.dx.command.UsageException;
24 import com.android.dx.dex.cf.CfOptions;
25 import com.android.dx.dex.cf.CfTranslator;
26 import com.android.dx.dex.cf.CodeStatistics;
27 import com.android.dx.dex.code.PositionList;
28 import com.android.dx.dex.file.ClassDefItem;
29 import com.android.dx.dex.file.DexFile;
30 import com.android.dx.dex.file.EncodedMethod;
31 import com.android.dx.rop.annotation.Annotation;
32 import com.android.dx.rop.annotation.Annotations;
33 import com.android.dx.rop.annotation.AnnotationsList;
34 import com.android.dx.rop.cst.CstNat;
35 import com.android.dx.rop.cst.CstUtf8;
36 
37 import java.io.ByteArrayInputStream;
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.OutputStream;
42 import java.io.OutputStreamWriter;
43 import java.io.PrintWriter;
44 import java.util.Arrays;
45 import java.util.ArrayList;
46 import java.util.Map;
47 import java.util.TreeMap;
48 import java.util.jar.Attributes;
49 import java.util.jar.JarEntry;
50 import java.util.jar.JarOutputStream;
51 import java.util.jar.Manifest;
52 
53 /**
54  * Main class for the class file translator.
55  */
56 public class Main {
57     /**
58      * non-null; name for the <code>.dex</code> file that goes into
59      * <code>.jar</code> files
60      */
61     private static final String DEX_IN_JAR_NAME = "classes.dex";
62 
63     /**
64      * non-null; name of the standard manifest file in <code>.jar</code>
65      * files
66      */
67     private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
68 
69     /**
70      * non-null; attribute name for the (quasi-standard?)
71      * <code>Created-By</code> attribute
72      */
73     private static final Attributes.Name CREATED_BY =
74         new Attributes.Name("Created-By");
75 
76     /**
77      * non-null; list of <code>javax</code> 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     /** non-null; parsed command-line arguments */
94     private static Arguments args;
95 
96     /** non-null; output file in-progress */
97     private static DexFile outputDex;
98 
99     /**
100      * null-ok; map of resources to include in the output, or
101      * <code>null</code> if resources are being ignored
102      */
103     private static TreeMap<String, byte[]> outputResources;
104 
105     /**
106      * This class is uninstantiable.
107      */
Main()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      */
main(String[] argArray)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      */
run(Arguments arguments)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      */
processAllFiles()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 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      */
processOne(String pathname)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 non-null; name of the file
252      * @param bytes non-null; contents of the file
253      * @return whether processing was successful
254      */
processFileBytes(String name, byte[] bytes)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 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 non-null; contents of the file
289      * @return whether processing was successful
290      */
processClass(String name, byte[] bytes)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 non-null; the fully-qualified internal-form class name
320      */
checkClassName(String name)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 VM class in something other " +
350                 "than a core library.\n" +
351                 "It is likely that you have attempted to include the " +
352                 "core library from a desktop\n" +
353                 "virtual machine into an application, which will most " +
354                 "assuredly not work. If\n" +
355                 "you really intend to build a core library -- which is "+
356                 "only appropriate as\n" +
357                 "part of creating a full virtual machine binary, as " +
358                 "opposed to compiling an\n" +
359                 "application -- then use the \"--core-library\" option " +
360                 "to suppress this error\n" +
361                 "message. If you go ahead and use \"--core-library\" " +
362                 "but are in fact building\n" +
363                 "an application, then please be aware that your build " +
364                 "will still fail at some\n" +
365                 "point; you will simply be denied the pleasure of " +
366                 "reading this helpful error\n" +
367                 "message.");
368         errors++;
369         throw new StopProcessing();
370     }
371 
372     /**
373      * Converts {@link #outputDex} into a <code>byte[]</code>, write
374      * it out to the proper file (if any), and also do whatever human-oriented
375      * dumping is required.
376      *
377      * @return null-ok; the converted <code>byte[]</code> or <code>null</code>
378      * if there was a problem
379      */
writeDex()380     private static byte[] writeDex() {
381         byte[] outArray = null;
382 
383         try {
384             OutputStream out = null;
385             OutputStream humanOutRaw = null;
386             OutputStreamWriter humanOut = null;
387             try {
388                 if (args.humanOutName != null) {
389                     humanOutRaw = openOutput(args.humanOutName);
390                     humanOut = new OutputStreamWriter(humanOutRaw);
391                 }
392 
393                 if (args.methodToDump != null) {
394                     /*
395                      * Simply dump the requested method. Note: The call
396                      * to toDex() is required just to get the underlying
397                      * structures ready.
398                      */
399                     outputDex.toDex(null, false);
400                     dumpMethod(outputDex, args.methodToDump, humanOut);
401                 } else {
402                     /*
403                      * This is the usual case: Create an output .dex file,
404                      * and write it, dump it, etc.
405                      */
406                     outArray = outputDex.toDex(humanOut, args.verboseDump);
407 
408                     if ((args.outName != null) && !args.jarOutput) {
409                         out = openOutput(args.outName);
410                         out.write(outArray);
411                     }
412                 }
413 
414                 if (args.statistics) {
415                     DxConsole.out.println(outputDex.getStatistics().toHuman());
416                 }
417             } finally {
418                 if (humanOut != null) {
419                     humanOut.flush();
420                 }
421                 closeOutput(out);
422                 closeOutput(humanOutRaw);
423             }
424         } catch (Exception ex) {
425             if (args.debug) {
426                 DxConsole.err.println("\ntrouble writing output:");
427                 ex.printStackTrace(DxConsole.err);
428             } else {
429                 DxConsole.err.println("\ntrouble writing output: " +
430                                    ex.getMessage());
431             }
432             return null;
433         }
434 
435         return outArray;
436     }
437 
438     /**
439      * Creates a jar file from the resources and given dex file array.
440      *
441      * @param fileName non-null; name of the file
442      * @param dexArray non-null; array containing the dex file to include
443      * @return whether the creation was successful
444      */
createJar(String fileName, byte[] dexArray)445     private static boolean createJar(String fileName, byte[] dexArray) {
446         /*
447          * Make or modify the manifest (as appropriate), put the dex
448          * array into the resources map, and then process the entire
449          * resources map in a uniform manner.
450          */
451 
452         try {
453             Manifest manifest = makeManifest();
454             OutputStream out = openOutput(fileName);
455             JarOutputStream jarOut = new JarOutputStream(out, manifest);
456 
457             outputResources.put(DEX_IN_JAR_NAME, dexArray);
458 
459             try {
460                 for (Map.Entry<String, byte[]> e :
461                          outputResources.entrySet()) {
462                     String name = e.getKey();
463                     byte[] contents = e.getValue();
464                     JarEntry entry = new JarEntry(name);
465 
466                     if (args.verbose) {
467                         DxConsole.out.println("writing " + name + "; size " +
468                                            contents.length + "...");
469                     }
470 
471                     entry.setSize(contents.length);
472                     jarOut.putNextEntry(entry);
473                     jarOut.write(contents);
474                     jarOut.closeEntry();
475                 }
476             } finally {
477                 jarOut.finish();
478                 jarOut.flush();
479                 closeOutput(out);
480             }
481         } catch (Exception ex) {
482             if (args.debug) {
483                 DxConsole.err.println("\ntrouble writing output:");
484                 ex.printStackTrace(DxConsole.err);
485             } else {
486                 DxConsole.err.println("\ntrouble writing output: " +
487                                    ex.getMessage());
488             }
489             return false;
490         }
491 
492         return true;
493     }
494 
495     /**
496      * Creates and returns the manifest to use for the output. This may
497      * modify {@link #outputResources} (removing the pre-existing manifest).
498      *
499      * @return non-null; the manifest
500      */
makeManifest()501     private static Manifest makeManifest() throws IOException {
502         byte[] manifestBytes = outputResources.get(MANIFEST_NAME);
503         Manifest manifest;
504         Attributes attribs;
505 
506         if (manifestBytes == null) {
507             // We need to construct an entirely new manifest.
508             manifest = new Manifest();
509             attribs = manifest.getMainAttributes();
510             attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
511         } else {
512             manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
513             attribs = manifest.getMainAttributes();
514             outputResources.remove(MANIFEST_NAME);
515         }
516 
517         String createdBy = attribs.getValue(CREATED_BY);
518         if (createdBy == null) {
519             createdBy = "";
520         } else {
521             createdBy += " + ";
522         }
523         createdBy += "dx " + Version.VERSION;
524 
525         attribs.put(CREATED_BY, createdBy);
526         attribs.putValue("Dex-Location", DEX_IN_JAR_NAME);
527 
528         return manifest;
529     }
530 
531     /**
532      * Opens and returns the named file for writing, treating "-" specially.
533      *
534      * @param name non-null; the file name
535      * @return non-null; the opened file
536      */
openOutput(String name)537     private static OutputStream openOutput(String name) throws IOException {
538         if (name.equals("-") ||
539                 name.startsWith("-.")) {
540             return System.out;
541         }
542 
543         return new FileOutputStream(name);
544     }
545 
546     /**
547      * Flushes and closes the given output stream, except if it happens to be
548      * {@link System#out} in which case this method does the flush but not
549      * the close. This method will also silently do nothing if given a
550      * <code>null</code> argument.
551      *
552      * @param stream null-ok; what to close
553      */
closeOutput(OutputStream stream)554     private static void closeOutput(OutputStream stream) throws IOException {
555         if (stream == null) {
556             return;
557         }
558 
559         stream.flush();
560 
561         if (stream != System.out) {
562             stream.close();
563         }
564     }
565 
566     /**
567      * Returns the "fixed" version of a given file path, suitable for
568      * use as a path within a <code>.jar</code> file and for checking
569      * against a classfile-internal "this class" name. This looks for
570      * the last instance of the substring <code>"/./"</code> within
571      * the path, and if it finds it, it takes the portion after to be
572      * the fixed path. If that isn't found but the path starts with
573      * <code>"./"</code>, then that prefix is removed and the rest is
574      * return. If neither of these is the case, this method returns
575      * its argument.
576      *
577      * @param path non-null; the path to "fix"
578      * @return non-null; the fixed version (which might be the same as
579      * the given <code>path</code>)
580      */
fixPath(String path)581     private static String fixPath(String path) {
582         /*
583          * If the path separator is \ (like on windows), we convert the
584          * path to a standard '/' separated path.
585          */
586         if (File.separatorChar == '\\') {
587             path = path.replace('\\', '/');
588         }
589 
590         int index = path.lastIndexOf("/./");
591 
592         if (index != -1) {
593             return path.substring(index + 3);
594         }
595 
596         if (path.startsWith("./")) {
597             return path.substring(2);
598         }
599 
600         return path;
601     }
602 
603     /**
604      * Dumps any method with the given name in the given file.
605      *
606      * @param dex non-null; the dex file
607      * @param fqName non-null; the fully-qualified name of the method(s)
608      * @param out non-null; where to dump to
609      */
dumpMethod(DexFile dex, String fqName, OutputStreamWriter out)610     private static void dumpMethod(DexFile dex, String fqName,
611             OutputStreamWriter out) {
612         boolean wildcard = fqName.endsWith("*");
613         int lastDot = fqName.lastIndexOf('.');
614 
615         if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) {
616             DxConsole.err.println("bogus fully-qualified method name: " +
617                                fqName);
618             return;
619         }
620 
621         String className = fqName.substring(0, lastDot).replace('.', '/');
622         String methodName = fqName.substring(lastDot + 1);
623         ClassDefItem clazz = dex.getClassOrNull(className);
624 
625         if (clazz == null) {
626             DxConsole.err.println("no such class: " + className);
627             return;
628         }
629 
630         if (wildcard) {
631             methodName = methodName.substring(0, methodName.length() - 1);
632         }
633 
634         ArrayList<EncodedMethod> allMeths = clazz.getMethods();
635         TreeMap<CstNat, EncodedMethod> meths =
636             new TreeMap<CstNat, EncodedMethod>();
637 
638         /*
639          * Figure out which methods to include in the output, and get them
640          * all sorted, so that the printout code is robust with respect to
641          * changes in the underlying order.
642          */
643         for (EncodedMethod meth : allMeths) {
644             String methName = meth.getName().getString();
645             if ((wildcard && methName.startsWith(methodName)) ||
646                 (!wildcard && methName.equals(methodName))) {
647                 meths.put(meth.getRef().getNat(), meth);
648             }
649         }
650 
651         if (meths.size() == 0) {
652             DxConsole.err.println("no such method: " + fqName);
653             return;
654         }
655 
656         PrintWriter pw = new PrintWriter(out);
657 
658         for (EncodedMethod meth : meths.values()) {
659             // TODO: Better stuff goes here, perhaps.
660             meth.debugPrint(pw, args.verboseDump);
661 
662             /*
663              * The (default) source file is an attribute of the class, but
664              * it's useful to see it in method dumps.
665              */
666             CstUtf8 sourceFile = clazz.getSourceFile();
667             if (sourceFile != null) {
668                 pw.println("  source file: " + sourceFile.toQuoted());
669             }
670 
671             Annotations methodAnnotations =
672                 clazz.getMethodAnnotations(meth.getRef());
673             AnnotationsList parameterAnnotations =
674                 clazz.getParameterAnnotations(meth.getRef());
675 
676             if (methodAnnotations != null) {
677                 pw.println("  method annotations:");
678                 for (Annotation a : methodAnnotations.getAnnotations()) {
679                     pw.println("    " + a);
680                 }
681             }
682 
683             if (parameterAnnotations != null) {
684                 pw.println("  parameter annotations:");
685                 int sz = parameterAnnotations.size();
686                 for (int i = 0; i < sz; i++) {
687                     pw.println("    parameter " + i);
688                     Annotations annotations = parameterAnnotations.get(i);
689                     for (Annotation a : annotations.getAnnotations()) {
690                         pw.println("      " + a);
691                     }
692                 }
693             }
694         }
695 
696         pw.flush();
697     }
698 
699     /**
700      * Exception class used to halt processing prematurely.
701      */
702     private static class StopProcessing extends RuntimeException {
703         // This space intentionally left blank.
704     }
705 
706     /**
707      * Command-line argument parser and access.
708      */
709     public static class Arguments {
710         /** whether to run in debug mode */
711         public boolean debug = false;
712 
713         /** whether to emit high-level verbose human-oriented output */
714         public boolean verbose = false;
715 
716         /** whether to emit verbose human-oriented output in the dump file */
717         public boolean verboseDump = false;
718 
719         /** whether we are constructing a core library */
720         public boolean coreLibrary = false;
721 
722         /** null-ok; particular method to dump */
723         public String methodToDump = null;
724 
725         /** max width for columnar output */
726         public int dumpWidth = 0;
727 
728         /** null-ok; output file name for binary file */
729         public String outName = null;
730 
731         /** null-ok; output file name for human-oriented dump */
732         public String humanOutName = null;
733 
734         /** whether strict file-name-vs-class-name checking should be done */
735         public boolean strictNameCheck = true;
736 
737         /**
738          * whether it is okay for there to be no <code>.class</code> files
739          * to process
740          */
741         public boolean emptyOk = false;
742 
743         /**
744          * whether the binary output is to be a <code>.jar</code> file
745          * instead of a plain <code>.dex</code>
746          */
747         public boolean jarOutput = false;
748 
749         /**
750          * when writing a <code>.jar</code> file, whether to still
751          * keep the <code>.class</code> files
752          */
753         public boolean keepClassesInJar = false;
754 
755         /** how much source position info to preserve */
756         public int positionInfo = PositionList.LINES;
757 
758         /** whether to keep local variable information */
759         public boolean localInfo = true;
760 
761         /** non-null after {@link #parse}; file name arguments */
762         public String[] fileNames;
763 
764         /** whether to do SSA/register optimization */
765         public boolean optimize = true;
766 
767         /** Filename containg list of methods to optimize */
768         public String optimizeListFile = null;
769 
770         /** Filename containing list of methods to NOT optimize */
771         public String dontOptimizeListFile = null;
772 
773         /** Whether to print statistics to stdout at end of compile cycle */
774         public boolean statistics;
775 
776         /** Options for dex.cf.* */
777         public CfOptions cfOptions;
778 
779         /**
780          * Parses the given command-line arguments.
781          *
782          * @param args non-null; the arguments
783          */
parse(String[] args)784         public void parse(String[] args) {
785             int at = 0;
786 
787             for (/*at*/; at < args.length; at++) {
788                 String arg = args[at];
789                 if (arg.equals("--") || !arg.startsWith("--")) {
790                     break;
791                 } else if (arg.equals("--debug")) {
792                     debug = true;
793                 } else if (arg.equals("--verbose")) {
794                     verbose = true;
795                 } else if (arg.equals("--verbose-dump")) {
796                     verboseDump = true;
797                 } else if (arg.equals("--no-files")) {
798                     emptyOk = true;
799                 } else if (arg.equals("--no-optimize")) {
800                     optimize = false;
801                 } else if (arg.equals("--no-strict")) {
802                     strictNameCheck = false;
803                 } else if (arg.equals("--core-library")) {
804                     coreLibrary = true;
805                 } else if (arg.equals("--statistics")) {
806                     statistics = true;
807                 } else if (arg.startsWith("--optimize-list=")) {
808                     if (dontOptimizeListFile != null) {
809                         System.err.println("--optimize-list and "
810                                 + "--no-optimize-list are incompatible.");
811                         throw new UsageException();
812                     }
813                     optimize = true;
814                     optimizeListFile = arg.substring(arg.indexOf('=') + 1);
815                 } else if (arg.startsWith("--no-optimize-list=")) {
816                     if (dontOptimizeListFile != null) {
817                         System.err.println("--optimize-list and "
818                                 + "--no-optimize-list are incompatible.");
819                         throw new UsageException();
820                     }
821                     optimize = true;
822                     dontOptimizeListFile = arg.substring(arg.indexOf('=') + 1);
823                 } else if (arg.equals("--keep-classes")) {
824                     keepClassesInJar = true;
825                 } else if (arg.startsWith("--output=")) {
826                     outName = arg.substring(arg.indexOf('=') + 1);
827                     if (outName.endsWith(".zip") ||
828                             outName.endsWith(".jar") ||
829                             outName.endsWith(".apk")) {
830                         jarOutput = true;
831                     } else if (outName.endsWith(".dex") ||
832                                outName.equals("-")) {
833                         jarOutput = false;
834                     } else {
835                         System.err.println("unknown output extension: " +
836                                            outName);
837                         throw new UsageException();
838                     }
839                 } else if (arg.startsWith("--dump-to=")) {
840                     humanOutName = arg.substring(arg.indexOf('=') + 1);
841                 } else if (arg.startsWith("--dump-width=")) {
842                     arg = arg.substring(arg.indexOf('=') + 1);
843                     dumpWidth = Integer.parseInt(arg);
844                 } else if (arg.startsWith("--dump-method=")) {
845                     methodToDump = arg.substring(arg.indexOf('=') + 1);
846                     jarOutput = false;
847                 } else if (arg.startsWith("--positions=")) {
848                     String pstr = arg.substring(arg.indexOf('=') + 1).intern();
849                     if (pstr == "none") {
850                         positionInfo = PositionList.NONE;
851                     } else if (pstr == "important") {
852                         positionInfo = PositionList.IMPORTANT;
853                     } else if (pstr == "lines") {
854                         positionInfo = PositionList.LINES;
855                     } else {
856                         System.err.println("unknown positions option: " +
857                                            pstr);
858                         throw new UsageException();
859                     }
860                 } else if (arg.equals("--no-locals")) {
861                     localInfo = false;
862                 } else {
863                     System.err.println("unknown option: " + arg);
864                     throw new UsageException();
865                 }
866             }
867 
868             int fileCount = args.length - at;
869             fileNames = new String[fileCount];
870             System.arraycopy(args, at, fileNames, 0, fileCount);
871 
872             if (fileCount == 0) {
873                 if (!emptyOk) {
874                     System.err.println("no input files specified");
875                     throw new UsageException();
876                 }
877             } else if (emptyOk) {
878                 System.out.println("ignoring input files");
879                 at = args.length;
880             }
881 
882             if ((humanOutName == null) && (methodToDump != null)) {
883                 humanOutName = "-";
884             }
885 
886             makeCfOptions();
887         }
888 
889         /**
890          * Copies relevent arguments over into a CfOptions instance.
891          */
makeCfOptions()892         private void makeCfOptions() {
893             cfOptions = new CfOptions();
894 
895             cfOptions.positionInfo = positionInfo;
896             cfOptions.localInfo = localInfo;
897             cfOptions.strictNameCheck = strictNameCheck;
898             cfOptions.optimize = optimize;
899             cfOptions.optimizeListFile = optimizeListFile;
900             cfOptions.dontOptimizeListFile = dontOptimizeListFile;
901             cfOptions.statistics = statistics;
902             cfOptions.warn = DxConsole.err;
903         }
904     }
905 }
906