FileDocCategorySizeDatePackage
Main.javaAPI DocAndroid 5.1 API57858Thu Mar 12 22:18:30 GMT 2015com.android.dx.command.dexer

Main

public class Main extends Object
Main class for the class file translator.

Fields Summary
private static final String
DEX_EXTENSION
File extension of a {@code .dex} file.
private static final String
DEX_PREFIX
File name prefix of a {@code .dex} file automatically loaded in an archive.
private static final String
IN_RE_CORE_CLASSES
{@code non-null;} the lengthy message that tries to discourage people from defining core classes in applications
private static final String
MANIFEST_NAME
{@code non-null;} name of the standard manifest file in {@code .jar} files
private static final Attributes$Name
CREATED_BY
{@code non-null;} attribute name for the (quasi-standard?) {@code Created-By} attribute
private static final String[]
JAVAX_CORE
{@code non-null;} list of {@code javax} subpackages that are considered to be "core". Note:: This list must be sorted, since it is binary-searched.
private static final int
MAX_METHOD_ADDED_DURING_DEX_CREATION
private static final int
MAX_FIELD_ADDED_DURING_DEX_CREATION
private static AtomicInteger
errors
number of errors during processing
private static Arguments
args
{@code non-null;} parsed command-line arguments
private static com.android.dx.dex.file.DexFile
outputDex
{@code non-null;} output file in-progress
private static TreeMap
outputResources
{@code null-ok;} map of resources to include in the output, or {@code null} if resources are being ignored
private static final List
libraryDexBuffers
Library .dex files to merge into the output .dex.
private static ExecutorService
threadPool
thread pool object used for multi-threaded file processing
private static List
parallelProcessorFutures
used to handle Errors for multi-threaded file processing
private static volatile boolean
anyFilesProcessed
true if any files are successfully processed
private static long
minimumFileAge
class files older than this must be defined in the target dex file.
private static Set
classesInMainDex
private static List
dexOutputArrays
private static OutputStreamWriter
humanOutWriter
Constructors Summary
private Main()
This class is uninstantiable.


             
      
        // This space intentionally left blank.
    
Methods Summary
private static voidcheckClassName(java.lang.String name)
Check the class name to make sure it's not a "core library" class. If there is a problem, this updates the error count and throws an exception to stop processing.

param
name {@code non-null;} the fully-qualified internal-form class name

        boolean bogus = false;

        if (name.startsWith("java/")) {
            bogus = true;
        } else if (name.startsWith("javax/")) {
            int slashAt = name.indexOf('/", 6);
            if (slashAt == -1) {
                // Top-level javax classes are verboten.
                bogus = true;
            } else {
                String pkg = name.substring(6, slashAt);
                bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0);
            }
        }

        if (! bogus) {
            return;
        }

        /*
         * The user is probably trying to include an entire desktop
         * core library in a misguided attempt to get their application
         * working. Try to help them understand what's happening.
         */

        DxConsole.err.println("\ntrouble processing \"" + name + "\":\n\n" +
                IN_RE_CORE_CLASSES);
        errors.incrementAndGet();
        throw new StopProcessing();
    
private static voidcloseOutput(java.io.OutputStream stream)
Flushes and closes the given output stream, except if it happens to be {@link System#out} in which case this method does the flush but not the close. This method will also silently do nothing if given a {@code null} argument.

param
stream {@code null-ok;} what to close

        if (stream == null) {
            return;
        }

        stream.flush();

        if (stream != System.out) {
            stream.close();
        }
    
private static voidcreateDexFile()

        if (outputDex != null) {
            dexOutputArrays.add(writeDex());
        }

        outputDex = new DexFile(args.dexOptions);

        if (args.dumpWidth != 0) {
            outputDex.setDumpWidth(args.dumpWidth);
        }
    
private static booleancreateJar(java.lang.String fileName)
Creates a jar file from the resources (including dex file arrays).

param
fileName {@code non-null;} name of the file
return
whether the creation was successful

        /*
         * Make or modify the manifest (as appropriate), put the dex
         * array into the resources map, and then process the entire
         * resources map in a uniform manner.
         */

        try {
            Manifest manifest = makeManifest();
            OutputStream out = openOutput(fileName);
            JarOutputStream jarOut = new JarOutputStream(out, manifest);

            try {
                for (Map.Entry<String, byte[]> e :
                         outputResources.entrySet()) {
                    String name = e.getKey();
                    byte[] contents = e.getValue();
                    JarEntry entry = new JarEntry(name);
                    int length = contents.length;

                    if (args.verbose) {
                        DxConsole.out.println("writing " + name + "; size " + length + "...");
                    }

                    entry.setSize(length);
                    jarOut.putNextEntry(entry);
                    jarOut.write(contents);
                    jarOut.closeEntry();
                }
            } finally {
                jarOut.finish();
                jarOut.flush();
                closeOutput(out);
            }
        } catch (Exception ex) {
            if (args.debug) {
                DxConsole.err.println("\ntrouble writing output:");
                ex.printStackTrace(DxConsole.err);
            } else {
                DxConsole.err.println("\ntrouble writing output: " +
                                   ex.getMessage());
            }
            return false;
        }

        return true;
    
private static voiddumpMethod(com.android.dx.dex.file.DexFile dex, java.lang.String fqName, java.io.OutputStreamWriter out)
Dumps any method with the given name in the given file.

param
dex {@code non-null;} the dex file
param
fqName {@code non-null;} the fully-qualified name of the method(s)
param
out {@code non-null;} where to dump to

        boolean wildcard = fqName.endsWith("*");
        int lastDot = fqName.lastIndexOf('.");

        if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) {
            DxConsole.err.println("bogus fully-qualified method name: " +
                               fqName);
            return;
        }

        String className = fqName.substring(0, lastDot).replace('.", '/");
        String methodName = fqName.substring(lastDot + 1);
        ClassDefItem clazz = dex.getClassOrNull(className);

        if (clazz == null) {
            DxConsole.err.println("no such class: " + className);
            return;
        }

        if (wildcard) {
            methodName = methodName.substring(0, methodName.length() - 1);
        }

        ArrayList<EncodedMethod> allMeths = clazz.getMethods();
        TreeMap<CstNat, EncodedMethod> meths =
            new TreeMap<CstNat, EncodedMethod>();

        /*
         * Figure out which methods to include in the output, and get them
         * all sorted, so that the printout code is robust with respect to
         * changes in the underlying order.
         */
        for (EncodedMethod meth : allMeths) {
            String methName = meth.getName().getString();
            if ((wildcard && methName.startsWith(methodName)) ||
                (!wildcard && methName.equals(methodName))) {
                meths.put(meth.getRef().getNat(), meth);
            }
        }

        if (meths.size() == 0) {
            DxConsole.err.println("no such method: " + fqName);
            return;
        }

        PrintWriter pw = new PrintWriter(out);

        for (EncodedMethod meth : meths.values()) {
            // TODO: Better stuff goes here, perhaps.
            meth.debugPrint(pw, args.verboseDump);

            /*
             * The (default) source file is an attribute of the class, but
             * it's useful to see it in method dumps.
             */
            CstString sourceFile = clazz.getSourceFile();
            if (sourceFile != null) {
                pw.println("  source file: " + sourceFile.toQuoted());
            }

            Annotations methodAnnotations =
                clazz.getMethodAnnotations(meth.getRef());
            AnnotationsList parameterAnnotations =
                clazz.getParameterAnnotations(meth.getRef());

            if (methodAnnotations != null) {
                pw.println("  method annotations:");
                for (Annotation a : methodAnnotations.getAnnotations()) {
                    pw.println("    " + a);
                }
            }

            if (parameterAnnotations != null) {
                pw.println("  parameter annotations:");
                int sz = parameterAnnotations.size();
                for (int i = 0; i < sz; i++) {
                    pw.println("    parameter " + i);
                    Annotations annotations = parameterAnnotations.get(i);
                    for (Annotation a : annotations.getAnnotations()) {
                        pw.println("      " + a);
                    }
                }
            }
        }

        pw.flush();
    
private static java.lang.StringfixPath(java.lang.String path)
Returns the "fixed" version of a given file path, suitable for use as a path within a {@code .jar} file and for checking against a classfile-internal "this class" name. This looks for the last instance of the substring {@code "/./"} within the path, and if it finds it, it takes the portion after to be the fixed path. If that isn't found but the path starts with {@code "./"}, then that prefix is removed and the rest is return. If neither of these is the case, this method returns its argument.

param
path {@code non-null;} the path to "fix"
return
{@code non-null;} the fixed version (which might be the same as the given {@code path})

        /*
         * If the path separator is \ (like on windows), we convert the
         * path to a standard '/' separated path.
         */
        if (File.separatorChar == '\\") {
            path = path.replace('\\", '/");
        }

        int index = path.lastIndexOf("/./");

        if (index != -1) {
            return path.substring(index + 3);
        }

        if (path.startsWith("./")) {
            return path.substring(2);
        }

        return path;
    
private static java.lang.StringgetDexFileName(int i)

        if (i == 0) {
            return DexFormat.DEX_IN_JAR_NAME;
        } else {
            return DEX_PREFIX + (i + 1) + DEX_EXTENSION;
        }
    
public static java.lang.StringgetTooManyIdsErrorMessage()
{@code non-null;} Error message for too many method/field/type ids.

        if (args.multiDex) {
            return "The list of classes given in " + Arguments.MAIN_DEX_LIST_OPTION +
                   " is too big and does not fit in the main dex.";
        } else {
            return "You may try using " + Arguments.MULTI_DEX_OPTION + " option.";
        }
    
public static voidmain(java.lang.String[] argArray)
Run and exit if something unexpected happened.

param
argArray the command line arguments

        Arguments arguments = new Arguments();
        arguments.parse(argArray);

        int result = run(arguments);
        if (result != 0) {
            System.exit(result);
        }
    
private static java.util.jar.ManifestmakeManifest()
Creates and returns the manifest to use for the output. This may modify {@link #outputResources} (removing the pre-existing manifest).

return
{@code non-null;} the manifest

        byte[] manifestBytes = outputResources.get(MANIFEST_NAME);
        Manifest manifest;
        Attributes attribs;

        if (manifestBytes == null) {
            // We need to construct an entirely new manifest.
            manifest = new Manifest();
            attribs = manifest.getMainAttributes();
            attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
        } else {
            manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
            attribs = manifest.getMainAttributes();
            outputResources.remove(MANIFEST_NAME);
        }

        String createdBy = attribs.getValue(CREATED_BY);
        if (createdBy == null) {
            createdBy = "";
        } else {
            createdBy += " + ";
        }
        createdBy += "dx " + Version.VERSION;

        attribs.put(CREATED_BY, createdBy);
        attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME);

        return manifest;
    
private static byte[]mergeIncremental(byte[] update, java.io.File base)
Merges the dex files {@code update} and {@code base}, preferring {@code update}'s definition for types defined in both dex files.

param
base a file to find the previous dex file. May be a .dex file, a jar file possibly containing a .dex file, or null.
return
the bytes of the merged dex file, or null if both the update and the base dex do not exist.

        Dex dexA = null;
        Dex dexB = null;

        if (update != null) {
            dexA = new Dex(update);
        }

        if (base.exists()) {
            dexB = new Dex(base);
        }

        Dex result;
        if (dexA == null && dexB == null) {
            return null;
        } else if (dexA == null) {
            result = dexB;
        } else if (dexB == null) {
            result = dexA;
        } else {
            result = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
        }

        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
        result.writeTo(bytesOut);
        return bytesOut.toByteArray();
    
private static byte[]mergeLibraryDexBuffers(byte[] outArray)
Merges the dex files in library jars. If multiple dex files define the same type, this fails with an exception.

        for (byte[] libraryDex : libraryDexBuffers) {
            if (outArray == null) {
                outArray = libraryDex;
                continue;
            }

            Dex a = new Dex(outArray);
            Dex b = new Dex(libraryDex);
            Dex ab = new DexMerger(a, b, CollisionPolicy.FAIL).merge();
            outArray = ab.getBytes();
        }

        return outArray;
    
private static java.io.OutputStreamopenOutput(java.lang.String name)
Opens and returns the named file for writing, treating "-" specially.

param
name {@code non-null;} the file name
return
{@code non-null;} the opened file

        if (name.equals("-") ||
                name.startsWith("-.")) {
            return System.out;
        }

        return new FileOutputStream(name);
    
private static booleanprocessAllFiles()
Constructs the output {@link DexFile}, fill it in with all the specified classes, and populate the resources map if required.

return
whether processing was successful

        createDexFile();

        if (args.jarOutput) {
            outputResources = new TreeMap<String, byte[]>();
        }

        anyFilesProcessed = false;
        String[] fileNames = args.fileNames;

        if (args.numThreads > 1) {
            threadPool = Executors.newFixedThreadPool(args.numThreads);
            parallelProcessorFutures = new ArrayList<Future<Void>>();
        }

        try {
            if (args.mainDexListFile != null) {
                // with --main-dex-list
                FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() :
                    new BestEffortMainDexListFilter();

                // forced in main dex
                for (int i = 0; i < fileNames.length; i++) {
                    processOne(fileNames[i], mainPassFilter);
                }

                if (dexOutputArrays.size() > 0) {
                    throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
                            + ", main dex capacity exceeded");
                }

                if (args.minimalMainDex) {
                    // start second pass directly in a secondary dex file.
                    createDexFile();
                }

                // remaining files
                for (int i = 0; i < fileNames.length; i++) {
                    processOne(fileNames[i], new NotFilter(mainPassFilter));
                }
            } else {
                // without --main-dex-list
                for (int i = 0; i < fileNames.length; i++) {
                    processOne(fileNames[i], ClassPathOpener.acceptAll);
                }
            }
        } catch (StopProcessing ex) {
            /*
             * Ignore it and just let the error reporting do
             * their things.
             */
        }

        if (args.numThreads > 1) {
            try {
                threadPool.shutdown();
                if (!threadPool.awaitTermination(600L, TimeUnit.SECONDS)) {
                    throw new RuntimeException("Timed out waiting for threads.");
                }
            } catch (InterruptedException ex) {
                threadPool.shutdownNow();
                throw new RuntimeException("A thread has been interrupted.");
            }

            try {
              for (Future<?> future : parallelProcessorFutures) {
                future.get();
              }
            } catch (ExecutionException e) {
                Throwable cause = e.getCause();
                // All Exceptions should have been handled in the ParallelProcessor, only Errors
                // should remain
                if (cause instanceof Error) {
                    throw (Error) e.getCause();
                } else {
                    throw new AssertionError(e.getCause());
                }
            } catch (InterruptedException e) {
              // If we're here, it means all threads have completed cleanly, so there should not be
              // any InterruptedException
              throw new AssertionError(e);
            }
        }

        int errorNum = errors.get();
        if (errorNum != 0) {
            DxConsole.err.println(errorNum + " error" +
                    ((errorNum == 1) ? "" : "s") + "; aborting");
            return false;
        }

        if (args.incremental && !anyFilesProcessed) {
            return true;
        }

        if (!(anyFilesProcessed || args.emptyOk)) {
            DxConsole.err.println("no classfiles specified");
            return false;
        }

        if (args.optimize && args.statistics) {
            CodeStatistics.dumpStatistics(DxConsole.out);
        }

        return true;
    
private static booleanprocessClass(java.lang.String name, byte[] bytes)
Processes one classfile.

param
name {@code non-null;} name of the file, clipped such that it should correspond to the name of the class it contains
param
bytes {@code non-null;} contents of the file
return
whether processing was successful

        if (! args.coreLibrary) {
            checkClassName(name);
        }

        DirectClassFile cf =
            new DirectClassFile(bytes, name, args.cfOptions.strictNameCheck);

        cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
        cf.getMagic();

        int numMethodIds = outputDex.getMethodIds().items().size();
        int numFieldIds = outputDex.getFieldIds().items().size();
        int constantPoolSize = cf.getConstantPool().size();

        int maxMethodIdsInDex = numMethodIds + constantPoolSize + cf.getMethods().size() +
                MAX_METHOD_ADDED_DURING_DEX_CREATION;
        int maxFieldIdsInDex = numFieldIds + constantPoolSize + cf.getFields().size() +
                MAX_FIELD_ADDED_DURING_DEX_CREATION;

        if (args.multiDex
            // Never switch to the next dex if current dex is already empty
            && (outputDex.getClassDefs().items().size() > 0)
            && ((maxMethodIdsInDex > args.maxNumberOfIdxPerDex) ||
                (maxFieldIdsInDex > args.maxNumberOfIdxPerDex))) {
            DexFile completeDex = outputDex;
            createDexFile();
            assert  (completeDex.getMethodIds().items().size() <= numMethodIds +
                    MAX_METHOD_ADDED_DURING_DEX_CREATION) &&
                    (completeDex.getFieldIds().items().size() <= numFieldIds +
                    MAX_FIELD_ADDED_DURING_DEX_CREATION);
        }

        try {
            ClassDefItem clazz =
                CfTranslator.translate(cf, bytes, args.cfOptions, args.dexOptions, outputDex);
            synchronized (outputDex) {
                outputDex.add(clazz);
            }
            return true;

        } catch (ParseException ex) {
            DxConsole.err.println("\ntrouble processing:");
            if (args.debug) {
                ex.printStackTrace(DxConsole.err);
            } else {
                ex.printContext(DxConsole.err);
            }
        }
        errors.incrementAndGet();
        return false;
    
private static booleanprocessFileBytes(java.lang.String name, long lastModified, byte[] bytes)
Processes one file, which may be either a class or a resource.

param
name {@code non-null;} name of the file
param
bytes {@code non-null;} contents of the file
return
whether processing was successful

        boolean isClass = name.endsWith(".class");
        boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
        boolean keepResources = (outputResources != null);

        if (!isClass && !isClassesDex && !keepResources) {
            if (args.verbose) {
                DxConsole.out.println("ignored resource " + name);
            }
            return false;
        }

        if (args.verbose) {
            DxConsole.out.println("processing " + name + "...");
        }

        String fixedName = fixPath(name);

        if (isClass) {

            if (keepResources && args.keepClassesInJar) {
                synchronized (outputResources) {
                    outputResources.put(fixedName, bytes);
                }
            }
            if (lastModified < minimumFileAge) {
                return true;
            }
            return processClass(fixedName, bytes);
        } else if (isClassesDex) {
            synchronized (libraryDexBuffers) {
                libraryDexBuffers.add(bytes);
            }
            return true;
        } else {
            synchronized (outputResources) {
                outputResources.put(fixedName, bytes);
            }
            return true;
        }
    
private static voidprocessOne(java.lang.String pathname, com.android.dx.cf.direct.ClassPathOpener.FileNameFilter filter)
Processes one pathname element.

param
pathname {@code non-null;} the pathname to process. May be the path of a class file, a jar file, or a directory containing class files.
param
filter {@code non-null;} A filter for excluding files.

        ClassPathOpener opener;

        opener = new ClassPathOpener(pathname, false, filter,
                new ClassPathOpener.Consumer() {

            @Override
            public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
                return Main.processFileBytes(name, lastModified, bytes);
            }

            @Override
            public void onException(Exception ex) {
                if (ex instanceof StopProcessing) {
                    throw (StopProcessing) ex;
                } else if (ex instanceof SimException) {
                    DxConsole.err.println("\nEXCEPTION FROM SIMULATION:");
                    DxConsole.err.println(ex.getMessage() + "\n");
                    DxConsole.err.println(((SimException) ex).getContext());
                } else {
                    DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
                    ex.printStackTrace(DxConsole.err);
                }
                errors.incrementAndGet();
            }

            @Override
            public void onProcessArchiveStart(File file) {
                if (args.verbose) {
                    DxConsole.out.println("processing archive " + file +
                            "...");
                }
            }
        });

        if (args.numThreads > 1) {
            parallelProcessorFutures.add(threadPool.submit(new ParallelProcessor(opener)));
        } else {
            if (opener.process()) {
                anyFilesProcessed = true;
            }
        }
    
private static voidreadPathsFromFile(java.lang.String fileName, java.util.Collection paths)

        BufferedReader bfr = null;
        try {
            FileReader fr = new FileReader(fileName);
            bfr = new BufferedReader(fr);

            String line;

            while (null != (line = bfr.readLine())) {
                paths.add(fixPath(line));
            }

        } finally {
            if (bfr != null) {
                bfr.close();
            }
        }
    
public static intrun(com.android.dx.command.dexer.Main$Arguments arguments)
Run and return a result code.

param
arguments the data + parameters for the conversion
return
0 if success > 0 otherwise.

        // Reset the error count to start fresh.
        errors.set(0);
        // empty the list, so that  tools that load dx and keep it around
        // for multiple runs don't reuse older buffers.
        libraryDexBuffers.clear();

        args = arguments;
        args.makeOptionsObjects();

        OutputStream humanOutRaw = null;
        if (args.humanOutName != null) {
            humanOutRaw = openOutput(args.humanOutName);
            humanOutWriter = new OutputStreamWriter(humanOutRaw);
        }

        try {
            if (args.multiDex) {
                return runMultiDex();
            } else {
                return runMonoDex();
            }
        } finally {
            closeOutput(humanOutRaw);
        }
    
private static intrunMonoDex()


        File incrementalOutFile = null;
        if (args.incremental) {
            if (args.outName == null) {
                System.err.println(
                        "error: no incremental output name specified");
                return -1;
            }
            incrementalOutFile = new File(args.outName);
            if (incrementalOutFile.exists()) {
                minimumFileAge = incrementalOutFile.lastModified();
            }
        }

        if (!processAllFiles()) {
            return 1;
        }

        if (args.incremental && !anyFilesProcessed) {
            return 0; // this was a no-op incremental build
        }

        // this array is null if no classes were defined
        byte[] outArray = null;

        if (!outputDex.isEmpty() || (args.humanOutName != null)) {
            outArray = writeDex();

            if (outArray == null) {
                return 2;
            }
        }

        if (args.incremental) {
            outArray = mergeIncremental(outArray, incrementalOutFile);
        }

        outArray = mergeLibraryDexBuffers(outArray);

        if (args.jarOutput) {
            // Effectively free up the (often massive) DexFile memory.
            outputDex = null;

            if (outArray != null) {
                outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
            }
            if (!createJar(args.outName)) {
                return 3;
            }
        } else if (outArray != null && args.outName != null) {
            OutputStream out = openOutput(args.outName);
            out.write(outArray);
            closeOutput(out);
        }

        return 0;
    
private static intrunMultiDex()


        assert !args.incremental;
        assert args.numThreads == 1;

        if (args.mainDexListFile != null) {
            classesInMainDex = new HashSet<String>();
            readPathsFromFile(args.mainDexListFile, classesInMainDex);
        }

        if (!processAllFiles()) {
            return 1;
        }

        if (!libraryDexBuffers.isEmpty()) {
            throw new DexException("Library dex files are not supported in multi-dex mode");
        }

        if (outputDex != null) {
            // this array is null if no classes were defined
            dexOutputArrays.add(writeDex());

            // Effectively free up the (often massive) DexFile memory.
            outputDex = null;
        }

        if (args.jarOutput) {

            for (int i = 0; i < dexOutputArrays.size(); i++) {
                outputResources.put(getDexFileName(i),
                        dexOutputArrays.get(i));
            }

            if (!createJar(args.outName)) {
                return 3;
            }
        } else if (args.outName != null) {
            File outDir = new File(args.outName);
            assert outDir.isDirectory();
            for (int i = 0; i < dexOutputArrays.size(); i++) {
                OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i)));
                try {
                    out.write(dexOutputArrays.get(i));
                } finally {
                    closeOutput(out);
                }
            }

        }

        return 0;
    
private static byte[]writeDex()
Converts {@link #outputDex} into a {@code byte[]} and do whatever human-oriented dumping is required.

return
{@code null-ok;} the converted {@code byte[]} or {@code null} if there was a problem

        byte[] outArray = null;

        try {
            try {
                if (args.methodToDump != null) {
                    /*
                     * Simply dump the requested method. Note: The call
                     * to toDex() is required just to get the underlying
                     * structures ready.
                     */
                    outputDex.toDex(null, false);
                    dumpMethod(outputDex, args.methodToDump, humanOutWriter);
                } else {
                    /*
                     * This is the usual case: Create an output .dex file,
                     * and write it, dump it, etc.
                     */
                    outArray = outputDex.toDex(humanOutWriter, args.verboseDump);
                }

                if (args.statistics) {
                    DxConsole.out.println(outputDex.getStatistics().toHuman());
                }
            } finally {
                if (humanOutWriter != null) {
                    humanOutWriter.flush();
                }
            }
        } catch (Exception ex) {
            if (args.debug) {
                DxConsole.err.println("\ntrouble writing output:");
                ex.printStackTrace(DxConsole.err);
            } else {
                DxConsole.err.println("\ntrouble writing output: " +
                                   ex.getMessage());
            }
            return null;
        }

        return outArray;