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