Mainpublic class Main extends Object Main class for the class file translator. |
Fields Summary |
---|
private static final String | DEX_IN_JAR_NAMEnon-null; name for the .dex file that goes into
.jar files | private static final String | MANIFEST_NAMEnon-null; name of the standard manifest file in .jar
files | private static final Attributes$Name | CREATED_BYnon-null; attribute name for the (quasi-standard?)
Created-By attribute | private static final String[] | JAVAX_COREnon-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 | warningsnumber of warnings during processing | private static int | errorsnumber of errors during processing | private static Arguments | argsnon-null; parsed command-line arguments | private static com.android.dx.dex.file.DexFile | outputDexnon-null; output file in-progress | private static TreeMap | outputResourcesnull-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 void | checkClassName(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.
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 void | closeOutput(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.
if (stream == null) {
return;
}
stream.flush();
if (stream != System.out) {
stream.close();
}
| private static boolean | createJar(java.lang.String fileName, byte[] dexArray)Creates a jar file from the resources and given dex file array.
/*
* 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 void | dumpMethod(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.
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.String | fixPath(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.
/*
* 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 void | main(java.lang.String[] argArray)Run and exit if something unexpected happened.
Arguments arguments = new Arguments();
arguments.parse(argArray);
int result = run(arguments);
if (result != 0) {
System.exit(result);
}
| private static java.util.jar.Manifest | makeManifest()Creates and returns the manifest to use for the output. This may
modify {@link #outputResources} (removing the pre-existing 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.OutputStream | openOutput(java.lang.String name)Opens and returns the named file for writing, treating "-" specially.
if (name.equals("-") ||
name.startsWith("-.")) {
return System.out;
}
return new FileOutputStream(name);
| private static boolean | processAllFiles()Constructs the output {@link DexFile}, fill it in with all the
specified classes, and populate the resources map if required.
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 boolean | processClass(java.lang.String name, byte[] bytes)Processes one classfile.
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 boolean | processFileBytes(java.lang.String name, byte[] bytes)Processes one file, which may be either a class or a resource.
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 boolean | processOne(java.lang.String pathname)Processes one pathname element.
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 int | run(com.android.dx.command.dexer.Main$Arguments arguments)Run and return a result code.
// 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.
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;
|
|