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