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