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.dex.Dex;
20import com.android.dex.DexException;
21import com.android.dex.DexFormat;
22import com.android.dex.util.FileUtils;
23import com.android.dx.Version;
24import com.android.dx.cf.code.SimException;
25import com.android.dx.cf.direct.ClassPathOpener;
26import com.android.dx.cf.direct.ClassPathOpener.FileNameFilter;
27import com.android.dx.cf.direct.DirectClassFile;
28import com.android.dx.cf.direct.StdAttributeFactory;
29import com.android.dx.cf.iface.ParseException;
30import com.android.dx.command.DxConsole;
31import com.android.dx.command.UsageException;
32import com.android.dx.dex.DexOptions;
33import com.android.dx.dex.cf.CfOptions;
34import com.android.dx.dex.cf.CfTranslator;
35import com.android.dx.dex.cf.CodeStatistics;
36import com.android.dx.dex.code.PositionList;
37import com.android.dx.dex.file.ClassDefItem;
38import com.android.dx.dex.file.DexFile;
39import com.android.dx.dex.file.EncodedMethod;
40import com.android.dx.merge.CollisionPolicy;
41import com.android.dx.merge.DexMerger;
42import com.android.dx.rop.annotation.Annotation;
43import com.android.dx.rop.annotation.Annotations;
44import com.android.dx.rop.annotation.AnnotationsList;
45import com.android.dx.rop.cst.CstNat;
46import com.android.dx.rop.cst.CstString;
47
48import java.io.BufferedReader;
49import java.io.ByteArrayInputStream;
50import java.io.ByteArrayOutputStream;
51import java.io.File;
52import java.io.FileOutputStream;
53import java.io.FileReader;
54import java.io.IOException;
55import java.io.OutputStream;
56import java.io.OutputStreamWriter;
57import java.io.PrintWriter;
58import java.util.ArrayList;
59import java.util.Arrays;
60import java.util.Collection;
61import java.util.HashMap;
62import java.util.HashSet;
63import java.util.List;
64import java.util.Map;
65import java.util.Set;
66import java.util.TreeMap;
67import java.util.concurrent.Callable;
68import java.util.concurrent.ExecutionException;
69import java.util.concurrent.ExecutorService;
70import java.util.concurrent.Executors;
71import java.util.concurrent.Future;
72import java.util.concurrent.ThreadFactory;
73import java.util.concurrent.TimeUnit;
74import java.util.concurrent.atomic.AtomicInteger;
75import java.util.jar.Attributes;
76import java.util.jar.JarEntry;
77import java.util.jar.JarOutputStream;
78import java.util.jar.Manifest;
79
80/**
81 * Main class for the class file translator.
82 */
83public class Main {
84
85    /**
86     * File extension of a {@code .dex} file.
87     */
88    private static final String DEX_EXTENSION = ".dex";
89
90    /**
91     * File name prefix of a {@code .dex} file automatically loaded in an
92     * archive.
93     */
94    private static final String DEX_PREFIX = "classes";
95
96    /**
97     * {@code non-null;} the lengthy message that tries to discourage
98     * people from defining core classes in applications
99     */
100    private static final String IN_RE_CORE_CLASSES =
101        "Ill-advised or mistaken usage of a core class (java.* or javax.*)\n" +
102        "when not building a core library.\n\n" +
103        "This is often due to inadvertently including a core library file\n" +
104        "in your application's project, when using an IDE (such as\n" +
105        "Eclipse). If you are sure you're not intentionally defining a\n" +
106        "core class, then this is the most likely explanation of what's\n" +
107        "going on.\n\n" +
108        "However, you might actually be trying to define a class in a core\n" +
109        "namespace, the source of which you may have taken, for example,\n" +
110        "from a non-Android virtual machine project. This will most\n" +
111        "assuredly not work. At a minimum, it jeopardizes the\n" +
112        "compatibility of your app with future versions of the platform.\n" +
113        "It is also often of questionable legality.\n\n" +
114        "If you really intend to build a core library -- which is only\n" +
115        "appropriate as part of creating a full virtual machine\n" +
116        "distribution, as opposed to compiling an application -- then use\n" +
117        "the \"--core-library\" option to suppress this error message.\n\n" +
118        "If you go ahead and use \"--core-library\" but are in fact\n" +
119        "building an application, then be forewarned that your application\n" +
120        "will still fail to build or run, at some point. Please be\n" +
121        "prepared for angry customers who find, for example, that your\n" +
122        "application ceases to function once they upgrade their operating\n" +
123        "system. You will be to blame for this problem.\n\n" +
124        "If you are legitimately using some code that happens to be in a\n" +
125        "core package, then the easiest safe alternative you have is to\n" +
126        "repackage that code. That is, move the classes in question into\n" +
127        "your own package namespace. This means that they will never be in\n" +
128        "conflict with core system classes. JarJar is a tool that may help\n" +
129        "you in this endeavor. If you find that you cannot do this, then\n" +
130        "that is an indication that the path you are on will ultimately\n" +
131        "lead to pain, suffering, grief, and lamentation.\n";
132
133    /**
134     * {@code non-null;} name of the standard manifest file in {@code .jar}
135     * files
136     */
137    private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
138
139    /**
140     * {@code non-null;} attribute name for the (quasi-standard?)
141     * {@code Created-By} attribute
142     */
143    private static final Attributes.Name CREATED_BY =
144        new Attributes.Name("Created-By");
145
146    /**
147     * {@code non-null;} list of {@code javax} subpackages that are considered
148     * to be "core". <b>Note:</b>: This list must be sorted, since it
149     * is binary-searched.
150     */
151    private static final String[] JAVAX_CORE = {
152        "accessibility", "crypto", "imageio", "management", "naming", "net",
153        "print", "rmi", "security", "sip", "sound", "sql", "swing",
154        "transaction", "xml"
155    };
156
157    /* Array.newInstance may be added by RopperMachine,
158     * ArrayIndexOutOfBoundsException.<init> may be added by EscapeAnalysis */
159    private static final int MAX_METHOD_ADDED_DURING_DEX_CREATION = 2;
160
161    /* <primitive types box class>.TYPE */
162    private static final int MAX_FIELD_ADDED_DURING_DEX_CREATION = 9;
163
164    /** number of errors during processing */
165    private static AtomicInteger errors = new AtomicInteger(0);
166
167    /** {@code non-null;} parsed command-line arguments */
168    private static Arguments args;
169
170    /** {@code non-null;} output file in-progress */
171    private static DexFile outputDex;
172
173    /**
174     * {@code null-ok;} map of resources to include in the output, or
175     * {@code null} if resources are being ignored
176     */
177    private static TreeMap<String, byte[]> outputResources;
178
179    /** Library .dex files to merge into the output .dex. */
180    private static final List<byte[]> libraryDexBuffers = new ArrayList<byte[]>();
181
182    /** thread pool object used for multi-threaded file processing */
183    private static ExecutorService threadPool;
184
185    /** used to handle Errors for multi-threaded file processing */
186    private static List<Future<Void>> parallelProcessorFutures;
187
188    /** true if any files are successfully processed */
189    private static volatile boolean anyFilesProcessed;
190
191    /** class files older than this must be defined in the target dex file. */
192    private static long minimumFileAge = 0;
193
194    private static Set<String> classesInMainDex = null;
195
196    private static List<byte[]> dexOutputArrays = new ArrayList<byte[]>();
197
198    private static OutputStreamWriter humanOutWriter = null;
199
200    /**
201     * This class is uninstantiable.
202     */
203    private Main() {
204        // This space intentionally left blank.
205    }
206
207    /**
208     * Run and exit if something unexpected happened.
209     * @param argArray the command line arguments
210     */
211    public static void main(String[] argArray) throws IOException {
212        Arguments arguments = new Arguments();
213        arguments.parse(argArray);
214
215        int result = run(arguments);
216        if (result != 0) {
217            System.exit(result);
218        }
219    }
220
221    /**
222     * Run and return a result code.
223     * @param arguments the data + parameters for the conversion
224     * @return 0 if success > 0 otherwise.
225     */
226    public static int run(Arguments arguments) throws IOException {
227        // Reset the error count to start fresh.
228        errors.set(0);
229        // empty the list, so that  tools that load dx and keep it around
230        // for multiple runs don't reuse older buffers.
231        libraryDexBuffers.clear();
232
233        args = arguments;
234        args.makeOptionsObjects();
235
236        OutputStream humanOutRaw = null;
237        if (args.humanOutName != null) {
238            humanOutRaw = openOutput(args.humanOutName);
239            humanOutWriter = new OutputStreamWriter(humanOutRaw);
240        }
241
242        try {
243            if (args.multiDex) {
244                return runMultiDex();
245            } else {
246                return runMonoDex();
247            }
248        } finally {
249            closeOutput(humanOutRaw);
250        }
251    }
252
253    /**
254     * {@code non-null;} Error message for too many method/field/type ids.
255     */
256    public static String getTooManyIdsErrorMessage() {
257        if (args.multiDex) {
258            return "The list of classes given in " + Arguments.MAIN_DEX_LIST_OPTION +
259                   " is too big and does not fit in the main dex.";
260        } else {
261            return "You may try using " + Arguments.MULTI_DEX_OPTION + " option.";
262        }
263    }
264
265    private static int runMonoDex() throws IOException {
266
267        File incrementalOutFile = null;
268        if (args.incremental) {
269            if (args.outName == null) {
270                System.err.println(
271                        "error: no incremental output name specified");
272                return -1;
273            }
274            incrementalOutFile = new File(args.outName);
275            if (incrementalOutFile.exists()) {
276                minimumFileAge = incrementalOutFile.lastModified();
277            }
278        }
279
280        if (!processAllFiles()) {
281            return 1;
282        }
283
284        if (args.incremental && !anyFilesProcessed) {
285            return 0; // this was a no-op incremental build
286        }
287
288        // this array is null if no classes were defined
289        byte[] outArray = null;
290
291        if (!outputDex.isEmpty() || (args.humanOutName != null)) {
292            outArray = writeDex();
293
294            if (outArray == null) {
295                return 2;
296            }
297        }
298
299        if (args.incremental) {
300            outArray = mergeIncremental(outArray, incrementalOutFile);
301        }
302
303        outArray = mergeLibraryDexBuffers(outArray);
304
305        if (args.jarOutput) {
306            // Effectively free up the (often massive) DexFile memory.
307            outputDex = null;
308
309            if (outArray != null) {
310                outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
311            }
312            if (!createJar(args.outName)) {
313                return 3;
314            }
315        } else if (outArray != null && args.outName != null) {
316            OutputStream out = openOutput(args.outName);
317            out.write(outArray);
318            closeOutput(out);
319        }
320
321        return 0;
322    }
323
324    private static int runMultiDex() throws IOException {
325
326        assert !args.incremental;
327        assert args.numThreads == 1;
328
329        if (args.mainDexListFile != null) {
330            classesInMainDex = new HashSet<String>();
331            readPathsFromFile(args.mainDexListFile, classesInMainDex);
332        }
333
334        if (!processAllFiles()) {
335            return 1;
336        }
337
338        if (!libraryDexBuffers.isEmpty()) {
339            throw new DexException("Library dex files are not supported in multi-dex mode");
340        }
341
342        if (outputDex != null) {
343            // this array is null if no classes were defined
344            dexOutputArrays.add(writeDex());
345
346            // Effectively free up the (often massive) DexFile memory.
347            outputDex = null;
348        }
349
350        if (args.jarOutput) {
351
352            for (int i = 0; i < dexOutputArrays.size(); i++) {
353                outputResources.put(getDexFileName(i),
354                        dexOutputArrays.get(i));
355            }
356
357            if (!createJar(args.outName)) {
358                return 3;
359            }
360        } else if (args.outName != null) {
361            File outDir = new File(args.outName);
362            assert outDir.isDirectory();
363            for (int i = 0; i < dexOutputArrays.size(); i++) {
364                OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i)));
365                try {
366                    out.write(dexOutputArrays.get(i));
367                } finally {
368                    closeOutput(out);
369                }
370            }
371
372        }
373
374        return 0;
375    }
376
377    private static String getDexFileName(int i) {
378        if (i == 0) {
379            return DexFormat.DEX_IN_JAR_NAME;
380        } else {
381            return DEX_PREFIX + (i + 1) + DEX_EXTENSION;
382        }
383    }
384
385    private static void readPathsFromFile(String fileName, Collection<String> paths) throws IOException {
386        BufferedReader bfr = null;
387        try {
388            FileReader fr = new FileReader(fileName);
389            bfr = new BufferedReader(fr);
390
391            String line;
392
393            while (null != (line = bfr.readLine())) {
394                paths.add(fixPath(line));
395            }
396
397        } finally {
398            if (bfr != null) {
399                bfr.close();
400            }
401        }
402    }
403
404    /**
405     * Merges the dex files {@code update} and {@code base}, preferring
406     * {@code update}'s definition for types defined in both dex files.
407     *
408     * @param base a file to find the previous dex file. May be a .dex file, a
409     *     jar file possibly containing a .dex file, or null.
410     * @return the bytes of the merged dex file, or null if both the update
411     *     and the base dex do not exist.
412     */
413    private static byte[] mergeIncremental(byte[] update, File base) throws IOException {
414        Dex dexA = null;
415        Dex dexB = null;
416
417        if (update != null) {
418            dexA = new Dex(update);
419        }
420
421        if (base.exists()) {
422            dexB = new Dex(base);
423        }
424
425        Dex result;
426        if (dexA == null && dexB == null) {
427            return null;
428        } else if (dexA == null) {
429            result = dexB;
430        } else if (dexB == null) {
431            result = dexA;
432        } else {
433            result = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
434        }
435
436        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
437        result.writeTo(bytesOut);
438        return bytesOut.toByteArray();
439    }
440
441    /**
442     * Merges the dex files in library jars. If multiple dex files define the
443     * same type, this fails with an exception.
444     */
445    private static byte[] mergeLibraryDexBuffers(byte[] outArray) throws IOException {
446        for (byte[] libraryDex : libraryDexBuffers) {
447            if (outArray == null) {
448                outArray = libraryDex;
449                continue;
450            }
451
452            Dex a = new Dex(outArray);
453            Dex b = new Dex(libraryDex);
454            Dex ab = new DexMerger(a, b, CollisionPolicy.FAIL).merge();
455            outArray = ab.getBytes();
456        }
457
458        return outArray;
459    }
460
461    /**
462     * Constructs the output {@link DexFile}, fill it in with all the
463     * specified classes, and populate the resources map if required.
464     *
465     * @return whether processing was successful
466     */
467    private static boolean processAllFiles() {
468        createDexFile();
469
470        if (args.jarOutput) {
471            outputResources = new TreeMap<String, byte[]>();
472        }
473
474        anyFilesProcessed = false;
475        String[] fileNames = args.fileNames;
476
477        if (args.numThreads > 1) {
478            threadPool = Executors.newFixedThreadPool(args.numThreads);
479            parallelProcessorFutures = new ArrayList<Future<Void>>();
480        }
481
482        try {
483            if (args.mainDexListFile != null) {
484                // with --main-dex-list
485                FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() :
486                    new BestEffortMainDexListFilter();
487
488                // forced in main dex
489                for (int i = 0; i < fileNames.length; i++) {
490                    processOne(fileNames[i], mainPassFilter);
491                }
492
493                if (dexOutputArrays.size() > 0) {
494                    throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
495                            + ", main dex capacity exceeded");
496                }
497
498                if (args.minimalMainDex) {
499                    // start second pass directly in a secondary dex file.
500                    createDexFile();
501                }
502
503                // remaining files
504                for (int i = 0; i < fileNames.length; i++) {
505                    processOne(fileNames[i], new NotFilter(mainPassFilter));
506                }
507            } else {
508                // without --main-dex-list
509                for (int i = 0; i < fileNames.length; i++) {
510                    processOne(fileNames[i], ClassPathOpener.acceptAll);
511                }
512            }
513        } catch (StopProcessing ex) {
514            /*
515             * Ignore it and just let the error reporting do
516             * their things.
517             */
518        }
519
520        if (args.numThreads > 1) {
521            try {
522                threadPool.shutdown();
523                if (!threadPool.awaitTermination(600L, TimeUnit.SECONDS)) {
524                    throw new RuntimeException("Timed out waiting for threads.");
525                }
526            } catch (InterruptedException ex) {
527                threadPool.shutdownNow();
528                throw new RuntimeException("A thread has been interrupted.");
529            }
530
531            try {
532              for (Future<?> future : parallelProcessorFutures) {
533                future.get();
534              }
535            } catch (ExecutionException e) {
536                Throwable cause = e.getCause();
537                // All Exceptions should have been handled in the ParallelProcessor, only Errors
538                // should remain
539                if (cause instanceof Error) {
540                    throw (Error) e.getCause();
541                } else {
542                    throw new AssertionError(e.getCause());
543                }
544            } catch (InterruptedException e) {
545              // If we're here, it means all threads have completed cleanly, so there should not be
546              // any InterruptedException
547              throw new AssertionError(e);
548            }
549        }
550
551        int errorNum = errors.get();
552        if (errorNum != 0) {
553            DxConsole.err.println(errorNum + " error" +
554                    ((errorNum == 1) ? "" : "s") + "; aborting");
555            return false;
556        }
557
558        if (args.incremental && !anyFilesProcessed) {
559            return true;
560        }
561
562        if (!(anyFilesProcessed || args.emptyOk)) {
563            DxConsole.err.println("no classfiles specified");
564            return false;
565        }
566
567        if (args.optimize && args.statistics) {
568            CodeStatistics.dumpStatistics(DxConsole.out);
569        }
570
571        return true;
572    }
573
574    private static void createDexFile() {
575        if (outputDex != null) {
576            dexOutputArrays.add(writeDex());
577        }
578
579        outputDex = new DexFile(args.dexOptions);
580
581        if (args.dumpWidth != 0) {
582            outputDex.setDumpWidth(args.dumpWidth);
583        }
584    }
585
586    /**
587     * Processes one pathname element.
588     *
589     * @param pathname {@code non-null;} the pathname to process. May
590     * be the path of a class file, a jar file, or a directory
591     * containing class files.
592     * @param filter {@code non-null;} A filter for excluding files.
593     */
594    private static void processOne(String pathname, FileNameFilter filter) {
595        ClassPathOpener opener;
596
597        opener = new ClassPathOpener(pathname, false, filter,
598                new ClassPathOpener.Consumer() {
599
600            @Override
601            public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
602                return Main.processFileBytes(name, lastModified, bytes);
603            }
604
605            @Override
606            public void onException(Exception ex) {
607                if (ex instanceof StopProcessing) {
608                    throw (StopProcessing) ex;
609                } else if (ex instanceof SimException) {
610                    DxConsole.err.println("\nEXCEPTION FROM SIMULATION:");
611                    DxConsole.err.println(ex.getMessage() + "\n");
612                    DxConsole.err.println(((SimException) ex).getContext());
613                } else {
614                    DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
615                    ex.printStackTrace(DxConsole.err);
616                }
617                errors.incrementAndGet();
618            }
619
620            @Override
621            public void onProcessArchiveStart(File file) {
622                if (args.verbose) {
623                    DxConsole.out.println("processing archive " + file +
624                            "...");
625                }
626            }
627        });
628
629        if (args.numThreads > 1) {
630            parallelProcessorFutures.add(threadPool.submit(new ParallelProcessor(opener)));
631        } else {
632            if (opener.process()) {
633                anyFilesProcessed = true;
634            }
635        }
636    }
637
638    /**
639     * Processes one file, which may be either a class or a resource.
640     *
641     * @param name {@code non-null;} name of the file
642     * @param bytes {@code non-null;} contents of the file
643     * @return whether processing was successful
644     */
645    private static boolean processFileBytes(String name, long lastModified, byte[] bytes) {
646        boolean isClass = name.endsWith(".class");
647        boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
648        boolean keepResources = (outputResources != null);
649
650        if (!isClass && !isClassesDex && !keepResources) {
651            if (args.verbose) {
652                DxConsole.out.println("ignored resource " + name);
653            }
654            return false;
655        }
656
657        if (args.verbose) {
658            DxConsole.out.println("processing " + name + "...");
659        }
660
661        String fixedName = fixPath(name);
662
663        if (isClass) {
664
665            if (keepResources && args.keepClassesInJar) {
666                synchronized (outputResources) {
667                    outputResources.put(fixedName, bytes);
668                }
669            }
670            if (lastModified < minimumFileAge) {
671                return true;
672            }
673            return processClass(fixedName, bytes);
674        } else if (isClassesDex) {
675            synchronized (libraryDexBuffers) {
676                libraryDexBuffers.add(bytes);
677            }
678            return true;
679        } else {
680            synchronized (outputResources) {
681                outputResources.put(fixedName, bytes);
682            }
683            return true;
684        }
685    }
686
687    /**
688     * Processes one classfile.
689     *
690     * @param name {@code non-null;} name of the file, clipped such that it
691     * <i>should</i> correspond to the name of the class it contains
692     * @param bytes {@code non-null;} contents of the file
693     * @return whether processing was successful
694     */
695    private static boolean processClass(String name, byte[] bytes) {
696        if (! args.coreLibrary) {
697            checkClassName(name);
698        }
699
700        DirectClassFile cf =
701            new DirectClassFile(bytes, name, args.cfOptions.strictNameCheck);
702
703        cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
704        cf.getMagic();
705
706        int numMethodIds = outputDex.getMethodIds().items().size();
707        int numFieldIds = outputDex.getFieldIds().items().size();
708        int constantPoolSize = cf.getConstantPool().size();
709
710        int maxMethodIdsInDex = numMethodIds + constantPoolSize + cf.getMethods().size() +
711                MAX_METHOD_ADDED_DURING_DEX_CREATION;
712        int maxFieldIdsInDex = numFieldIds + constantPoolSize + cf.getFields().size() +
713                MAX_FIELD_ADDED_DURING_DEX_CREATION;
714
715        if (args.multiDex
716            // Never switch to the next dex if current dex is already empty
717            && (outputDex.getClassDefs().items().size() > 0)
718            && ((maxMethodIdsInDex > args.maxNumberOfIdxPerDex) ||
719                (maxFieldIdsInDex > args.maxNumberOfIdxPerDex))) {
720            DexFile completeDex = outputDex;
721            createDexFile();
722            assert  (completeDex.getMethodIds().items().size() <= numMethodIds +
723                    MAX_METHOD_ADDED_DURING_DEX_CREATION) &&
724                    (completeDex.getFieldIds().items().size() <= numFieldIds +
725                    MAX_FIELD_ADDED_DURING_DEX_CREATION);
726        }
727
728        try {
729            ClassDefItem clazz =
730                CfTranslator.translate(cf, bytes, args.cfOptions, args.dexOptions, outputDex);
731            synchronized (outputDex) {
732                outputDex.add(clazz);
733            }
734            return true;
735
736        } catch (ParseException ex) {
737            DxConsole.err.println("\ntrouble processing:");
738            if (args.debug) {
739                ex.printStackTrace(DxConsole.err);
740            } else {
741                ex.printContext(DxConsole.err);
742            }
743        }
744        errors.incrementAndGet();
745        return false;
746    }
747
748    /**
749     * Check the class name to make sure it's not a "core library"
750     * class. If there is a problem, this updates the error count and
751     * throws an exception to stop processing.
752     *
753     * @param name {@code non-null;} the fully-qualified internal-form
754     * class name
755     */
756    private static void checkClassName(String name) {
757        boolean bogus = false;
758
759        if (name.startsWith("java/")) {
760            bogus = true;
761        } else if (name.startsWith("javax/")) {
762            int slashAt = name.indexOf('/', 6);
763            if (slashAt == -1) {
764                // Top-level javax classes are verboten.
765                bogus = true;
766            } else {
767                String pkg = name.substring(6, slashAt);
768                bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0);
769            }
770        }
771
772        if (! bogus) {
773            return;
774        }
775
776        /*
777         * The user is probably trying to include an entire desktop
778         * core library in a misguided attempt to get their application
779         * working. Try to help them understand what's happening.
780         */
781
782        DxConsole.err.println("\ntrouble processing \"" + name + "\":\n\n" +
783                IN_RE_CORE_CLASSES);
784        errors.incrementAndGet();
785        throw new StopProcessing();
786    }
787
788    /**
789     * Converts {@link #outputDex} into a {@code byte[]} and do whatever
790     * human-oriented dumping is required.
791     *
792     * @return {@code null-ok;} the converted {@code byte[]} or {@code null}
793     * if there was a problem
794     */
795    private static byte[] writeDex() {
796        byte[] outArray = null;
797
798        try {
799            try {
800                if (args.methodToDump != null) {
801                    /*
802                     * Simply dump the requested method. Note: The call
803                     * to toDex() is required just to get the underlying
804                     * structures ready.
805                     */
806                    outputDex.toDex(null, false);
807                    dumpMethod(outputDex, args.methodToDump, humanOutWriter);
808                } else {
809                    /*
810                     * This is the usual case: Create an output .dex file,
811                     * and write it, dump it, etc.
812                     */
813                    outArray = outputDex.toDex(humanOutWriter, args.verboseDump);
814                }
815
816                if (args.statistics) {
817                    DxConsole.out.println(outputDex.getStatistics().toHuman());
818                }
819            } finally {
820                if (humanOutWriter != null) {
821                    humanOutWriter.flush();
822                }
823            }
824        } catch (Exception ex) {
825            if (args.debug) {
826                DxConsole.err.println("\ntrouble writing output:");
827                ex.printStackTrace(DxConsole.err);
828            } else {
829                DxConsole.err.println("\ntrouble writing output: " +
830                                   ex.getMessage());
831            }
832            return null;
833        }
834
835        return outArray;
836    }
837
838    /**
839     * Creates a jar file from the resources (including dex file arrays).
840     *
841     * @param fileName {@code non-null;} name of the file
842     * @return whether the creation was successful
843     */
844    private static boolean createJar(String fileName) {
845        /*
846         * Make or modify the manifest (as appropriate), put the dex
847         * array into the resources map, and then process the entire
848         * resources map in a uniform manner.
849         */
850
851        try {
852            Manifest manifest = makeManifest();
853            OutputStream out = openOutput(fileName);
854            JarOutputStream jarOut = new JarOutputStream(out, manifest);
855
856            try {
857                for (Map.Entry<String, byte[]> e :
858                         outputResources.entrySet()) {
859                    String name = e.getKey();
860                    byte[] contents = e.getValue();
861                    JarEntry entry = new JarEntry(name);
862                    int length = contents.length;
863
864                    if (args.verbose) {
865                        DxConsole.out.println("writing " + name + "; size " + length + "...");
866                    }
867
868                    entry.setSize(length);
869                    jarOut.putNextEntry(entry);
870                    jarOut.write(contents);
871                    jarOut.closeEntry();
872                }
873            } finally {
874                jarOut.finish();
875                jarOut.flush();
876                closeOutput(out);
877            }
878        } catch (Exception ex) {
879            if (args.debug) {
880                DxConsole.err.println("\ntrouble writing output:");
881                ex.printStackTrace(DxConsole.err);
882            } else {
883                DxConsole.err.println("\ntrouble writing output: " +
884                                   ex.getMessage());
885            }
886            return false;
887        }
888
889        return true;
890    }
891
892    /**
893     * Creates and returns the manifest to use for the output. This may
894     * modify {@link #outputResources} (removing the pre-existing manifest).
895     *
896     * @return {@code non-null;} the manifest
897     */
898    private static Manifest makeManifest() throws IOException {
899        byte[] manifestBytes = outputResources.get(MANIFEST_NAME);
900        Manifest manifest;
901        Attributes attribs;
902
903        if (manifestBytes == null) {
904            // We need to construct an entirely new manifest.
905            manifest = new Manifest();
906            attribs = manifest.getMainAttributes();
907            attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
908        } else {
909            manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
910            attribs = manifest.getMainAttributes();
911            outputResources.remove(MANIFEST_NAME);
912        }
913
914        String createdBy = attribs.getValue(CREATED_BY);
915        if (createdBy == null) {
916            createdBy = "";
917        } else {
918            createdBy += " + ";
919        }
920        createdBy += "dx " + Version.VERSION;
921
922        attribs.put(CREATED_BY, createdBy);
923        attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME);
924
925        return manifest;
926    }
927
928    /**
929     * Opens and returns the named file for writing, treating "-" specially.
930     *
931     * @param name {@code non-null;} the file name
932     * @return {@code non-null;} the opened file
933     */
934    private static OutputStream openOutput(String name) throws IOException {
935        if (name.equals("-") ||
936                name.startsWith("-.")) {
937            return System.out;
938        }
939
940        return new FileOutputStream(name);
941    }
942
943    /**
944     * Flushes and closes the given output stream, except if it happens to be
945     * {@link System#out} in which case this method does the flush but not
946     * the close. This method will also silently do nothing if given a
947     * {@code null} argument.
948     *
949     * @param stream {@code null-ok;} what to close
950     */
951    private static void closeOutput(OutputStream stream) throws IOException {
952        if (stream == null) {
953            return;
954        }
955
956        stream.flush();
957
958        if (stream != System.out) {
959            stream.close();
960        }
961    }
962
963    /**
964     * Returns the "fixed" version of a given file path, suitable for
965     * use as a path within a {@code .jar} file and for checking
966     * against a classfile-internal "this class" name. This looks for
967     * the last instance of the substring {@code "/./"} within
968     * the path, and if it finds it, it takes the portion after to be
969     * the fixed path. If that isn't found but the path starts with
970     * {@code "./"}, then that prefix is removed and the rest is
971     * return. If neither of these is the case, this method returns
972     * its argument.
973     *
974     * @param path {@code non-null;} the path to "fix"
975     * @return {@code non-null;} the fixed version (which might be the same as
976     * the given {@code path})
977     */
978    private static String fixPath(String path) {
979        /*
980         * If the path separator is \ (like on windows), we convert the
981         * path to a standard '/' separated path.
982         */
983        if (File.separatorChar == '\\') {
984            path = path.replace('\\', '/');
985        }
986
987        int index = path.lastIndexOf("/./");
988
989        if (index != -1) {
990            return path.substring(index + 3);
991        }
992
993        if (path.startsWith("./")) {
994            return path.substring(2);
995        }
996
997        return path;
998    }
999
1000    /**
1001     * Dumps any method with the given name in the given file.
1002     *
1003     * @param dex {@code non-null;} the dex file
1004     * @param fqName {@code non-null;} the fully-qualified name of the
1005     * method(s)
1006     * @param out {@code non-null;} where to dump to
1007     */
1008    private static void dumpMethod(DexFile dex, String fqName,
1009            OutputStreamWriter out) {
1010        boolean wildcard = fqName.endsWith("*");
1011        int lastDot = fqName.lastIndexOf('.');
1012
1013        if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) {
1014            DxConsole.err.println("bogus fully-qualified method name: " +
1015                               fqName);
1016            return;
1017        }
1018
1019        String className = fqName.substring(0, lastDot).replace('.', '/');
1020        String methodName = fqName.substring(lastDot + 1);
1021        ClassDefItem clazz = dex.getClassOrNull(className);
1022
1023        if (clazz == null) {
1024            DxConsole.err.println("no such class: " + className);
1025            return;
1026        }
1027
1028        if (wildcard) {
1029            methodName = methodName.substring(0, methodName.length() - 1);
1030        }
1031
1032        ArrayList<EncodedMethod> allMeths = clazz.getMethods();
1033        TreeMap<CstNat, EncodedMethod> meths =
1034            new TreeMap<CstNat, EncodedMethod>();
1035
1036        /*
1037         * Figure out which methods to include in the output, and get them
1038         * all sorted, so that the printout code is robust with respect to
1039         * changes in the underlying order.
1040         */
1041        for (EncodedMethod meth : allMeths) {
1042            String methName = meth.getName().getString();
1043            if ((wildcard && methName.startsWith(methodName)) ||
1044                (!wildcard && methName.equals(methodName))) {
1045                meths.put(meth.getRef().getNat(), meth);
1046            }
1047        }
1048
1049        if (meths.size() == 0) {
1050            DxConsole.err.println("no such method: " + fqName);
1051            return;
1052        }
1053
1054        PrintWriter pw = new PrintWriter(out);
1055
1056        for (EncodedMethod meth : meths.values()) {
1057            // TODO: Better stuff goes here, perhaps.
1058            meth.debugPrint(pw, args.verboseDump);
1059
1060            /*
1061             * The (default) source file is an attribute of the class, but
1062             * it's useful to see it in method dumps.
1063             */
1064            CstString sourceFile = clazz.getSourceFile();
1065            if (sourceFile != null) {
1066                pw.println("  source file: " + sourceFile.toQuoted());
1067            }
1068
1069            Annotations methodAnnotations =
1070                clazz.getMethodAnnotations(meth.getRef());
1071            AnnotationsList parameterAnnotations =
1072                clazz.getParameterAnnotations(meth.getRef());
1073
1074            if (methodAnnotations != null) {
1075                pw.println("  method annotations:");
1076                for (Annotation a : methodAnnotations.getAnnotations()) {
1077                    pw.println("    " + a);
1078                }
1079            }
1080
1081            if (parameterAnnotations != null) {
1082                pw.println("  parameter annotations:");
1083                int sz = parameterAnnotations.size();
1084                for (int i = 0; i < sz; i++) {
1085                    pw.println("    parameter " + i);
1086                    Annotations annotations = parameterAnnotations.get(i);
1087                    for (Annotation a : annotations.getAnnotations()) {
1088                        pw.println("      " + a);
1089                    }
1090                }
1091            }
1092        }
1093
1094        pw.flush();
1095    }
1096
1097    private static class NotFilter implements FileNameFilter {
1098        private final FileNameFilter filter;
1099
1100        private NotFilter(FileNameFilter filter) {
1101            this.filter = filter;
1102        }
1103
1104        @Override
1105        public boolean accept(String path) {
1106            return !filter.accept(path);
1107        }
1108    }
1109
1110    /**
1111     * A quick and accurate filter for when file path can be trusted.
1112     */
1113    private static class MainDexListFilter implements FileNameFilter {
1114
1115        @Override
1116        public boolean accept(String fullPath) {
1117            if (fullPath.endsWith(".class")) {
1118                String path = fixPath(fullPath);
1119                return classesInMainDex.contains(path);
1120            } else {
1121                return true;
1122            }
1123        }
1124    }
1125
1126    /**
1127     * A best effort conservative filter for when file path can <b>not</b> be trusted.
1128     */
1129    private static class BestEffortMainDexListFilter implements FileNameFilter {
1130
1131       Map<String, List<String>> map = new HashMap<String, List<String>>();
1132
1133       public BestEffortMainDexListFilter() {
1134           for (String pathOfClass : classesInMainDex) {
1135               String normalized = fixPath(pathOfClass);
1136               String simple = getSimpleName(normalized);
1137               List<String> fullPath = map.get(simple);
1138               if (fullPath == null) {
1139                   fullPath = new ArrayList<String>(1);
1140                   map.put(simple, fullPath);
1141               }
1142               fullPath.add(normalized);
1143           }
1144        }
1145
1146        @Override
1147        public boolean accept(String path) {
1148            if (path.endsWith(".class")) {
1149                String normalized = fixPath(path);
1150                String simple = getSimpleName(normalized);
1151                List<String> fullPaths = map.get(simple);
1152                if (fullPaths != null) {
1153                    for (String fullPath : fullPaths) {
1154                        if (normalized.endsWith(fullPath)) {
1155                            return true;
1156                        }
1157                    }
1158                }
1159                return false;
1160            } else {
1161                return true;
1162            }
1163        }
1164
1165        private static String getSimpleName(String path) {
1166            int index = path.lastIndexOf('/');
1167            if (index >= 0) {
1168                return path.substring(index + 1);
1169            } else {
1170                return path;
1171            }
1172        }
1173    }
1174
1175    /**
1176     * Exception class used to halt processing prematurely.
1177     */
1178    private static class StopProcessing extends RuntimeException {
1179        // This space intentionally left blank.
1180    }
1181
1182    /**
1183     * Command-line argument parser and access.
1184     */
1185    public static class Arguments {
1186
1187        private static final String MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex";
1188
1189        private static final String MAIN_DEX_LIST_OPTION = "--main-dex-list";
1190
1191        private static final String MULTI_DEX_OPTION = "--multi-dex";
1192
1193        private static final String NUM_THREADS_OPTION = "--num-threads";
1194
1195        private static final String INCREMENTAL_OPTION = "--incremental";
1196
1197        private static final String INPUT_LIST_OPTION = "--input-list";
1198
1199        /** whether to run in debug mode */
1200        public boolean debug = false;
1201
1202        /** whether to emit high-level verbose human-oriented output */
1203        public boolean verbose = false;
1204
1205        /** whether to emit verbose human-oriented output in the dump file */
1206        public boolean verboseDump = false;
1207
1208        /** whether we are constructing a core library */
1209        public boolean coreLibrary = false;
1210
1211        /** {@code null-ok;} particular method to dump */
1212        public String methodToDump = null;
1213
1214        /** max width for columnar output */
1215        public int dumpWidth = 0;
1216
1217        /** {@code null-ok;} output file name for binary file */
1218        public String outName = null;
1219
1220        /** {@code null-ok;} output file name for human-oriented dump */
1221        public String humanOutName = null;
1222
1223        /** whether strict file-name-vs-class-name checking should be done */
1224        public boolean strictNameCheck = true;
1225
1226        /**
1227         * whether it is okay for there to be no {@code .class} files
1228         * to process
1229         */
1230        public boolean emptyOk = false;
1231
1232        /**
1233         * whether the binary output is to be a {@code .jar} file
1234         * instead of a plain {@code .dex}
1235         */
1236        public boolean jarOutput = false;
1237
1238        /**
1239         * when writing a {@code .jar} file, whether to still
1240         * keep the {@code .class} files
1241         */
1242        public boolean keepClassesInJar = false;
1243
1244        /** how much source position info to preserve */
1245        public int positionInfo = PositionList.LINES;
1246
1247        /** whether to keep local variable information */
1248        public boolean localInfo = true;
1249
1250        /** whether to merge with the output dex file if it exists. */
1251        public boolean incremental = false;
1252
1253        /** whether to force generation of const-string/jumbo for all indexes,
1254         *  to allow merges between dex files with many strings. */
1255        public boolean forceJumbo = false;
1256
1257        /** {@code non-null} after {@link #parse}; file name arguments */
1258        public String[] fileNames;
1259
1260        /** whether to do SSA/register optimization */
1261        public boolean optimize = true;
1262
1263        /** Filename containg list of methods to optimize */
1264        public String optimizeListFile = null;
1265
1266        /** Filename containing list of methods to NOT optimize */
1267        public String dontOptimizeListFile = null;
1268
1269        /** Whether to print statistics to stdout at end of compile cycle */
1270        public boolean statistics;
1271
1272        /** Options for class file transformation */
1273        public CfOptions cfOptions;
1274
1275        /** Options for dex file output */
1276        public DexOptions dexOptions;
1277
1278        /** number of threads to run with */
1279        public int numThreads = 1;
1280
1281        /** generation of multiple dex is allowed */
1282        public boolean multiDex = false;
1283
1284        /** Optional file containing a list of class files containing classes to be forced in main
1285         * dex */
1286        public String mainDexListFile = null;
1287
1288        /** Produce the smallest possible main dex. Ignored unless multiDex is true and
1289         * mainDexListFile is specified and non empty. */
1290        public boolean minimalMainDex = false;
1291
1292        /** Optional list containing inputs read in from a file. */
1293        private List<String> inputList = null;
1294
1295        private int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1;
1296
1297        private static class ArgumentsParser {
1298
1299            /** The arguments to process. */
1300            private final String[] arguments;
1301            /** The index of the next argument to process. */
1302            private int index;
1303            /** The current argument being processed after a {@link #getNext()} call. */
1304            private String current;
1305            /** The last value of an argument processed by {@link #isArg(String)}. */
1306            private String lastValue;
1307
1308            public ArgumentsParser(String[] arguments) {
1309                this.arguments = arguments;
1310                index = 0;
1311            }
1312
1313            public String getCurrent() {
1314                return current;
1315            }
1316
1317            public String getLastValue() {
1318                return lastValue;
1319            }
1320
1321            /**
1322             * Moves on to the next argument.
1323             * Returns false when we ran out of arguments that start with --.
1324             */
1325            public boolean getNext() {
1326                if (index >= arguments.length) {
1327                    return false;
1328                }
1329                current = arguments[index];
1330                if (current.equals("--") || !current.startsWith("--")) {
1331                    return false;
1332                }
1333                index++;
1334                return true;
1335            }
1336
1337            /**
1338             * Similar to {@link #getNext()}, this moves on the to next argument.
1339             * It does not check however whether the argument starts with --
1340             * and thus can be used to retrieve values.
1341             */
1342            private boolean getNextValue() {
1343                if (index >= arguments.length) {
1344                    return false;
1345                }
1346                current = arguments[index];
1347                index++;
1348                return true;
1349            }
1350
1351            /**
1352             * Returns all the arguments that have not been processed yet.
1353             */
1354            public String[] getRemaining() {
1355                int n = arguments.length - index;
1356                String[] remaining = new String[n];
1357                if (n > 0) {
1358                    System.arraycopy(arguments, index, remaining, 0, n);
1359                }
1360                return remaining;
1361            }
1362
1363            /**
1364             * Checks the current argument against the given prefix.
1365             * If prefix is in the form '--name=', an extra value is expected.
1366             * The argument can then be in the form '--name=value' or as a 2-argument
1367             * form '--name value'.
1368             */
1369            public boolean isArg(String prefix) {
1370                int n = prefix.length();
1371                if (n > 0 && prefix.charAt(n-1) == '=') {
1372                    // Argument accepts a value. Capture it.
1373                    if (current.startsWith(prefix)) {
1374                        // Argument is in the form --name=value, split the value out
1375                        lastValue = current.substring(n);
1376                        return true;
1377                    } else {
1378                        // Check whether we have "--name value" as 2 arguments
1379                        prefix = prefix.substring(0, n-1);
1380                        if (current.equals(prefix)) {
1381                            if (getNextValue()) {
1382                                lastValue = current;
1383                                return true;
1384                            } else {
1385                                System.err.println("Missing value after parameter " + prefix);
1386                                throw new UsageException();
1387                            }
1388                        }
1389                        return false;
1390                    }
1391                } else {
1392                    // Argument does not accept a value.
1393                    return current.equals(prefix);
1394                }
1395            }
1396        }
1397
1398        /**
1399         * Parses the given command-line arguments.
1400         *
1401         * @param args {@code non-null;} the arguments
1402         */
1403        public void parse(String[] args) {
1404            ArgumentsParser parser = new ArgumentsParser(args);
1405
1406            boolean outputIsDirectory = false;
1407            boolean outputIsDirectDex = false;
1408
1409            while(parser.getNext()) {
1410                if (parser.isArg("--debug")) {
1411                    debug = true;
1412                } else if (parser.isArg("--verbose")) {
1413                    verbose = true;
1414                } else if (parser.isArg("--verbose-dump")) {
1415                    verboseDump = true;
1416                } else if (parser.isArg("--no-files")) {
1417                    emptyOk = true;
1418                } else if (parser.isArg("--no-optimize")) {
1419                    optimize = false;
1420                } else if (parser.isArg("--no-strict")) {
1421                    strictNameCheck = false;
1422                } else if (parser.isArg("--core-library")) {
1423                    coreLibrary = true;
1424                } else if (parser.isArg("--statistics")) {
1425                    statistics = true;
1426                } else if (parser.isArg("--optimize-list=")) {
1427                    if (dontOptimizeListFile != null) {
1428                        System.err.println("--optimize-list and "
1429                                + "--no-optimize-list are incompatible.");
1430                        throw new UsageException();
1431                    }
1432                    optimize = true;
1433                    optimizeListFile = parser.getLastValue();
1434                } else if (parser.isArg("--no-optimize-list=")) {
1435                    if (dontOptimizeListFile != null) {
1436                        System.err.println("--optimize-list and "
1437                                + "--no-optimize-list are incompatible.");
1438                        throw new UsageException();
1439                    }
1440                    optimize = true;
1441                    dontOptimizeListFile = parser.getLastValue();
1442                } else if (parser.isArg("--keep-classes")) {
1443                    keepClassesInJar = true;
1444                } else if (parser.isArg("--output=")) {
1445                    outName = parser.getLastValue();
1446                    if (new File(outName).isDirectory()) {
1447                        jarOutput = false;
1448                        outputIsDirectory = true;
1449                    } else if (FileUtils.hasArchiveSuffix(outName)) {
1450                        jarOutput = true;
1451                    } else if (outName.endsWith(".dex") ||
1452                               outName.equals("-")) {
1453                        jarOutput = false;
1454                        outputIsDirectDex = true;
1455                    } else {
1456                        System.err.println("unknown output extension: " +
1457                                           outName);
1458                        throw new UsageException();
1459                    }
1460                } else if (parser.isArg("--dump-to=")) {
1461                    humanOutName = parser.getLastValue();
1462                } else if (parser.isArg("--dump-width=")) {
1463                    dumpWidth = Integer.parseInt(parser.getLastValue());
1464                } else if (parser.isArg("--dump-method=")) {
1465                    methodToDump = parser.getLastValue();
1466                    jarOutput = false;
1467                } else if (parser.isArg("--positions=")) {
1468                    String pstr = parser.getLastValue().intern();
1469                    if (pstr == "none") {
1470                        positionInfo = PositionList.NONE;
1471                    } else if (pstr == "important") {
1472                        positionInfo = PositionList.IMPORTANT;
1473                    } else if (pstr == "lines") {
1474                        positionInfo = PositionList.LINES;
1475                    } else {
1476                        System.err.println("unknown positions option: " +
1477                                           pstr);
1478                        throw new UsageException();
1479                    }
1480                } else if (parser.isArg("--no-locals")) {
1481                    localInfo = false;
1482                } else if (parser.isArg(NUM_THREADS_OPTION + "=")) {
1483                    numThreads = Integer.parseInt(parser.getLastValue());
1484                } else if (parser.isArg(INCREMENTAL_OPTION)) {
1485                    incremental = true;
1486                } else if (parser.isArg("--force-jumbo")) {
1487                    forceJumbo = true;
1488                } else if (parser.isArg(MULTI_DEX_OPTION)) {
1489                    multiDex = true;
1490                } else if (parser.isArg(MAIN_DEX_LIST_OPTION + "=")) {
1491                    mainDexListFile = parser.getLastValue();
1492                } else if (parser.isArg(MINIMAL_MAIN_DEX_OPTION)) {
1493                    minimalMainDex = true;
1494                } else if (parser.isArg("--set-max-idx-number=")) { // undocumented test option
1495                    maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue());
1496                } else if(parser.isArg(INPUT_LIST_OPTION + "=")) {
1497                    File inputListFile = new File(parser.getLastValue());
1498                    try{
1499                        inputList = new ArrayList<String>();
1500                        readPathsFromFile(inputListFile.getAbsolutePath(), inputList);
1501                    } catch(IOException e) {
1502                        System.err.println(
1503                            "Unable to read input list file: " + inputListFile.getName());
1504                        // problem reading the file so we should halt execution
1505                        throw new UsageException();
1506                    }
1507                } else {
1508                    System.err.println("unknown option: " + parser.getCurrent());
1509                    throw new UsageException();
1510                }
1511            }
1512
1513            fileNames = parser.getRemaining();
1514            if(inputList != null && !inputList.isEmpty()) {
1515                // append the file names to the end of the input list
1516                inputList.addAll(Arrays.asList(fileNames));
1517                fileNames = inputList.toArray(new String[inputList.size()]);
1518            }
1519
1520            if (fileNames.length == 0) {
1521                if (!emptyOk) {
1522                    System.err.println("no input files specified");
1523                    throw new UsageException();
1524                }
1525            } else if (emptyOk) {
1526                System.out.println("ignoring input files");
1527            }
1528
1529            if ((humanOutName == null) && (methodToDump != null)) {
1530                humanOutName = "-";
1531            }
1532
1533            if (mainDexListFile != null && !multiDex) {
1534                System.err.println(MAIN_DEX_LIST_OPTION + " is only supported in combination with "
1535                    + MULTI_DEX_OPTION);
1536                throw new UsageException();
1537            }
1538
1539            if (minimalMainDex && (mainDexListFile == null || !multiDex)) {
1540                System.err.println(MINIMAL_MAIN_DEX_OPTION + " is only supported in combination with "
1541                    + MULTI_DEX_OPTION + " and " + MAIN_DEX_LIST_OPTION);
1542                throw new UsageException();
1543            }
1544
1545            if (multiDex && numThreads != 1) {
1546                System.out.println(NUM_THREADS_OPTION + " is ignored when used with "
1547                    + MULTI_DEX_OPTION);
1548                numThreads = 1;
1549            }
1550
1551            if (multiDex && incremental) {
1552                System.err.println(INCREMENTAL_OPTION + " is not supported with "
1553                    + MULTI_DEX_OPTION);
1554                throw new UsageException();
1555            }
1556
1557            if (multiDex && outputIsDirectDex) {
1558                System.err.println("Unsupported output \"" + outName +"\". " + MULTI_DEX_OPTION +
1559                        " supports only archive or directory output");
1560                throw new UsageException();
1561            }
1562
1563            if (outputIsDirectory && !multiDex) {
1564                outName = new File(outName, DexFormat.DEX_IN_JAR_NAME).getPath();
1565            }
1566
1567            makeOptionsObjects();
1568        }
1569
1570        /**
1571         * Copies relevent arguments over into CfOptions and
1572         * DexOptions instances.
1573         */
1574        private void makeOptionsObjects() {
1575            cfOptions = new CfOptions();
1576            cfOptions.positionInfo = positionInfo;
1577            cfOptions.localInfo = localInfo;
1578            cfOptions.strictNameCheck = strictNameCheck;
1579            cfOptions.optimize = optimize;
1580            cfOptions.optimizeListFile = optimizeListFile;
1581            cfOptions.dontOptimizeListFile = dontOptimizeListFile;
1582            cfOptions.statistics = statistics;
1583            cfOptions.warn = DxConsole.err;
1584
1585            dexOptions = new DexOptions();
1586            dexOptions.forceJumbo = forceJumbo;
1587        }
1588    }
1589
1590    /** Callable helper class to process files in multiple threads */
1591    private static class ParallelProcessor implements Callable<Void> {
1592
1593        ClassPathOpener classPathOpener;
1594
1595        private ParallelProcessor(ClassPathOpener classPathOpener) {
1596            this.classPathOpener = classPathOpener;
1597        }
1598
1599        @Override
1600        public Void call() throws Exception {
1601            if (classPathOpener.process()) {
1602                anyFilesProcessed = true;
1603            }
1604            return null;
1605        }
1606    }
1607}
1608