FileDocCategorySizeDatePackage
Main.javaAPI DocAndroid 1.5 API31422Wed May 06 22:41:02 BST 2009com.android.dx.command.dexer

Main

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

Fields Summary
private static final String
DEX_IN_JAR_NAME
non-null; name for the .dex file that goes into .jar files
private static final String
MANIFEST_NAME
non-null; name of the standard manifest file in .jar files
private static final Attributes$Name
CREATED_BY
non-null; attribute name for the (quasi-standard?) Created-By attribute
private static final String[]
JAVAX_CORE
non-null; list of javax subpackages that are considered to be "core". Note:: This list must be sorted, since it is binary-searched.
private static int
warnings
number of warnings during processing
private static int
errors
number of errors during processing
private static Arguments
args
non-null; parsed command-line arguments
private static com.android.dx.dex.file.DexFile
outputDex
non-null; output file in-progress
private static TreeMap
outputResources
null-ok; map of resources to include in the output, or null if resources are being ignored
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 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 + "\":");
        DxConsole.err.println("\n" + 
                "Attempt to include a core VM class in something other " +
                "than a core library.\n" +
                "It is likely that you have attempted to include the " +
                "core library from a desktop\n" +
                "virtual machine into an application, which will most " +
                "assuredly not work. If\n" +
                "you really intend to build a core library -- which is "+
                "only appropriate as\n" +
                "part of creating a full virtual machine binary, as " +
                "opposed to compiling an\n" +
                "application -- then use the \"--core-library\" option " +
                "to suppress this error\n" +
                "message. If you go ahead and use \"--core-library\" " +
                "but are in fact building\n" +
                "an application, then please be aware that your build " +
                "will still fail at some\n" +
                "point; you will simply be denied the pleasure of " +
                "reading this helpful error\n" +
                "message.");
        errors++;
        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 null argument.

param
stream null-ok; what to close

        if (stream == null) {
            return;
        }

        stream.flush();

        if (stream != System.out) {
            stream.close();
        }
    
private static booleancreateJar(java.lang.String fileName, byte[] dexArray)
Creates a jar file from the resources and given dex file array.

param
fileName non-null; name of the file
param
dexArray non-null; array containing the dex file to include
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);

            outputResources.put(DEX_IN_JAR_NAME, dexArray);

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

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

                    entry.setSize(contents.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 non-null; the dex file
param
fqName non-null; the fully-qualified name of the method(s)
param
out 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.
             */
            CstUtf8 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 .jar file and for checking against a classfile-internal "this class" name. This looks for the last instance of the substring "/./" 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 "./", then that prefix is removed and the rest is return. If neither of these is the case, this method returns its argument.

param
path non-null; the path to "fix"
return
non-null; the fixed version (which might be the same as the given 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;
    
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
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", DEX_IN_JAR_NAME);

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

param
name non-null; the file name
return
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

        outputDex = new DexFile();

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

        if (args.dumpWidth != 0) {
            outputDex.setDumpWidth(args.dumpWidth);
        }

        boolean any = false;
        String[] fileNames = args.fileNames;

        try {
            for (int i = 0; i < fileNames.length; i++) {
                any |= processOne(fileNames[i]);
            }
        } catch (StopProcessing ex) {
            /*
             * Ignore it and just let the warning/error reporting do
             * their things.
             */
        }

        if (warnings != 0) {
            DxConsole.err.println(warnings + " warning" +
                               ((warnings == 1) ? "" : "s"));
        }

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

        if (!(any || 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 non-null; name of the file, clipped such that it should correspond to the name of the class it contains
param
bytes non-null; contents of the file
return
whether processing was successful

        if (! args.coreLibrary) {
            checkClassName(name);
        }
        
        try {
            ClassDefItem clazz =
                CfTranslator.translate(name, bytes, args.cfOptions);
            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);
            }
        }

        warnings++;
        return false;
    
private static booleanprocessFileBytes(java.lang.String name, byte[] bytes)
Processes one file, which may be either a class or a resource.

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

        boolean isClass = name.endsWith(".class");
        boolean keepResources = (outputResources != null);

        if (!isClass && !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) {
                outputResources.put(fixedName, bytes);
            }
            return processClass(fixedName, bytes);
        } else {
            outputResources.put(fixedName, bytes);
            return true;
        }
    
private static booleanprocessOne(java.lang.String pathname)
Processes one pathname element.

param
pathname non-null; the pathname to process. May be the path of a class file, a jar file, or a directory containing class files.
return
whether any processing actually happened

        ClassPathOpener opener;

        opener = new ClassPathOpener(pathname, false,
                new ClassPathOpener.Consumer() {
            public boolean processFileBytes(String name, byte[] bytes) {
                return Main.processFileBytes(name, bytes);
            }
            public void onException(Exception ex) {
                if (ex instanceof StopProcessing) {
                    throw (StopProcessing) ex;
                }
                DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
                ex.printStackTrace(DxConsole.err);
                errors++;
            }
            public void onProcessArchiveStart(File file) {
                if (args.verbose) {
                    DxConsole.out.println("processing archive " + file + "...");
                }
            }
        });

        return opener.process();     
    
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/warning count to start fresh.
        warnings = 0;
        errors = 0;

        args = arguments;
        args.makeCfOptions();

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

        byte[] outArray = writeDex();

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

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

            if (!createJar(args.outName, outArray)) {
                return 3;
            }
        }

        return 0;
    
private static byte[]writeDex()
Converts {@link #outputDex} into a byte[], write it out to the proper file (if any), and also do whatever human-oriented dumping is required.

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

        byte[] outArray = null;

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

                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, humanOut);
                } else {
                    /*
                     * This is the usual case: Create an output .dex file,
                     * and write it, dump it, etc.
                     */
                    outArray = outputDex.toDex(humanOut, args.verboseDump);

                    if ((args.outName != null) && !args.jarOutput) {
                        out = openOutput(args.outName);
                        out.write(outArray);
                    }
                }

                if (args.statistics) {
                    DxConsole.out.println(outputDex.getStatistics().toHuman());
                }
            } finally {
                if (humanOut != null) {
                    humanOut.flush();
                }
                closeOutput(out);
                closeOutput(humanOutRaw);
            }
        } 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;