Mirror of the JODE repository
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
jode/jode/src/net/sf/jode/decompiler/Main.java

442 lines
14 KiB

/* Main Copyright (C) 1998-2002 Jochen Hoenicke.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; see the file COPYING.LESSER. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package net.sf.jode.decompiler;
import net.sf.jode.bytecode.ClassInfo;
import net.sf.jode.bytecode.ClassPath;
import net.sf.jode.bytecode.ClassFormatException;
import net.sf.jode.GlobalOptions;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.Enumeration;
import gnu.getopt.LongOpt;
import gnu.getopt.Getopt;
public class Main extends Options {
private static final int OPTION_START=0x10000;
private static final int OPTION_END =0x20000;
private static final LongOpt[] longOptions = new LongOpt[] {
new LongOpt("cp", LongOpt.REQUIRED_ARGUMENT, null, 'c'),
new LongOpt("classpath", LongOpt.REQUIRED_ARGUMENT, null, 'c'),
new LongOpt("dest", LongOpt.REQUIRED_ARGUMENT, null, 'd'),
new LongOpt("keep-going", LongOpt.NO_ARGUMENT, null, 'k'),
new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'),
new LongOpt("version", LongOpt.NO_ARGUMENT, null, 'V'),
new LongOpt("verbose", LongOpt.OPTIONAL_ARGUMENT, null, 'v'),
new LongOpt("debug", LongOpt.OPTIONAL_ARGUMENT, null, 'D'),
new LongOpt("import", LongOpt.REQUIRED_ARGUMENT, null, 'i'),
new LongOpt("style", LongOpt.REQUIRED_ARGUMENT, null, 's'),
new LongOpt("chars-per-line", LongOpt.REQUIRED_ARGUMENT, null, 'l'),
new LongOpt("lvt", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+0),
new LongOpt("inner", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+1),
new LongOpt("anonymous", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+2),
new LongOpt("push", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+3),
new LongOpt("pretty", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+4),
new LongOpt("decrypt", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+5),
new LongOpt("onetime", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+6),
new LongOpt("immediate", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+7),
new LongOpt("verify", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+8),
new LongOpt("contrafo", LongOpt.OPTIONAL_ARGUMENT, null,
OPTION_START+9)
};
public static void usage() {
PrintWriter err = GlobalOptions.err;
err.println("Version: " + GlobalOptions.version);
err.println("Usage: java net.sf.jode.decompiler.Main [OPTION]* {CLASS|JAR}*");
err.println("Give a fully qualified CLASS name, e.g. net.sf.jode.decompiler.Main, if you want to");
err.println("decompile a single class, or a JAR file containing many classes.");
err.println("OPTION is any of these:");
err.println(" -h, --help "+
"show this information.");
err.println(" -V, --version "+
"output version information and exit.");
err.println(" -v, --verbose "+
"be verbose (multiple times means more verbose).");
err.println(" -c, --classpath <path> "+
"search for classes in specified classpath.");
err.println(" "+
"The directories should be separated by ','.");
err.println(" -d, --dest <dir> "+
"write decompiled files to disk into directory destdir.");
err.println(" -s, --style {sun|gnu|pascal|python} "+
"specify indentation style");
err.println(" -l, --chars-per-line <number> "+
"specify line length");
err.println(" -i, --import <pkglimit>,<clslimit>");
err.println(" "+
"import classes used more than clslimit times");
err.println(" "+
"and packages with more then pkglimit used classes.");
err.println(" "+
"Limit 0 means never import. Default is 0,1.");
err.println(" -k, --keep-going "+
"After an error continue to decompile the other classes.");
err.println(" "+
"after an error decompiling one of them.");
}
public static boolean handleOption(int option, int longind, String arg) {
if (arg == null)
options ^= 1 << option;
else if ("yes".startsWith(arg) || arg.equals("on"))
options |= 1 << option;
else if ("no".startsWith(arg) || arg.equals("off"))
options &= ~(1 << option);
else {
GlobalOptions.err.println
("net.sf.jode.decompiler.Main: option --"+longOptions[longind].getName()
+" takes one of `yes', `no', `on', `off' as parameter");
return false;
}
return true;
}
public static boolean decompileClass
(String className, ClassPath classPath,
String classPathStr,
ZipOutputStream destZip, String destDir,
TabbedPrintWriter writer, ImportHandler imports) {
try {
ClassInfo clazz;
try {
clazz = classPath.getClassInfo(className);
} catch (IllegalArgumentException ex) {
GlobalOptions.err.println
("`"+className+"' is not a class name");
return false;
}
if (skipClass(clazz))
return true;
String filename =
className.replace('.', File.separatorChar)+".java";
if (destZip != null) {
writer.flush();
destZip.putNextEntry(new ZipEntry(filename));
} else if (destDir != null) {
File file = new File (destDir, filename);
File directory = new File(file.getParent());
if (!directory.exists() && !directory.mkdirs()) {
GlobalOptions.err.println
("Could not create directory "
+ directory.getPath() + ", check permissions.");
}
writer = new TabbedPrintWriter
(new BufferedOutputStream(new FileOutputStream(file)),
imports, false);
}
GlobalOptions.err.println(className);
ClassAnalyzer clazzAna = new ClassAnalyzer(clazz, imports);
clazzAna.dumpJavaFile(writer);
if (destZip != null) {
writer.flush();
destZip.closeEntry();
} else if (destDir != null)
writer.close();
/* Now is a good time to clean up */
System.gc();
return true;
} catch (FileNotFoundException ex) {
GlobalOptions.err.println
("Can't read "+ex.getMessage()+".");
GlobalOptions.err.println
("Check the class path ("+classPathStr+
") and check that you use the java class name.");
return false;
} catch (ClassFormatException ex) {
GlobalOptions.err.println
("Error while reading "+className+".");
ex.printStackTrace(GlobalOptions.err);
return false;
} catch (IOException ex) {
GlobalOptions.err.println
("Can't write source of "+className+".");
GlobalOptions.err.println("Check the permissions.");
ex.printStackTrace(GlobalOptions.err);
return false;
} catch(RuntimeException ex) {
GlobalOptions.err.println
("Error whilst decompiling " + className + ".");
ex.printStackTrace(GlobalOptions.err);
return false;
} catch(InternalError ex) {
/* InternalError should not normally be
* caught, however we catch it here because
* some parts of JODE
* (e.g. TransformExceptionHandlers.analyze())
* throw it.
* TODO: Replace InternalError with something else in
* places they can actually be thrown
*/
GlobalOptions.err.println
("Internal error whilst decompiling " + className + ".");
ex.printStackTrace(GlobalOptions.err);
return false;
}
}
public static void main(String[] params) throws Throwable{
decompile(params);
/* When AWT applications are compiled with insufficient
* classpath the type guessing by reflection code can
* generate an awt thread that will prevent normal
* exiting.
*/
System.exit(0);
}
public static void decompile(String[] params) {
if (params.length == 0) {
usage();
return;
}
ClassPath classPath;
String classPathStr = System.getProperty("java.class.path")
.replace(File.pathSeparatorChar, ClassPath.altPathSeparatorChar);
String bootClassPath = System.getProperty("sun.boot.class.path");
if (bootClassPath != null)
classPathStr = classPathStr + ClassPath.altPathSeparatorChar
+ bootClassPath.replace(File.pathSeparatorChar,
ClassPath.altPathSeparatorChar);
String destDir = null;
int importPackageLimit = ImportHandler.DEFAULT_PACKAGE_LIMIT;
int importClassLimit = ImportHandler.DEFAULT_CLASS_LIMIT;;
int outputStyle = TabbedPrintWriter.BRACE_AT_EOL;
int indentSize = 4;
int outputLineLength = 79;
boolean keepGoing = false;
GlobalOptions.err.println(GlobalOptions.copyright);
boolean errorInParams = false;
Getopt g = new Getopt("net.sf.jode.decompiler.Main", params, "hVvkc:d:D:i:s:l:",
longOptions, true);
for (int opt = g.getopt(); opt != -1; opt = g.getopt()) {
switch(opt) {
case 0:
break;
case 'h':
usage();
errorInParams = true;
break;
case 'V':
GlobalOptions.err.println(GlobalOptions.version);
break;
case 'c':
classPathStr = g.getOptarg();
break;
case 'd':
destDir = g.getOptarg();
break;
case 'k':
keepGoing = true;
break;
case 'v': {
String arg = g.getOptarg();
if (arg == null)
GlobalOptions.verboseLevel++;
else {
try {
GlobalOptions.verboseLevel = Integer.parseInt(arg);
} catch (NumberFormatException ex) {
GlobalOptions.err.println
("net.sf.jode.decompiler.Main: Argument `"
+arg+"' to --verbose must be numeric:");
errorInParams = true;
}
}
break;
}
case 'D': {
String arg = g.getOptarg();
if (arg == null)
arg = "help";
errorInParams |= !GlobalOptions.setDebugging(arg);
break;
}
case 's': {
String arg = g.getOptarg();
if (arg.equals("gnu")) {
outputStyle = TabbedPrintWriter.GNU_SPACING
| TabbedPrintWriter.INDENT_BRACES;
indentSize = 2;
} else if (arg.equals("sun")) {
outputStyle = TabbedPrintWriter.BRACE_AT_EOL;
indentSize = 4;
} else if (arg.equals("pascal")) {
outputStyle = 0;
indentSize = 4;
} else if (arg.equals("python") || arg.equals("codd")) {
outputStyle = TabbedPrintWriter.BRACE_AT_EOL|TabbedPrintWriter.CODD_FORMATTING;
indentSize = 4;
} else {
GlobalOptions.err.println
("net.sf.jode.decompiler.Main: Unknown style `"+arg+"'.");
errorInParams = true;
}
break;
}
case 'l': {
String arg = g.getOptarg();
try {
outputLineLength = Integer.parseInt(arg.trim());
}
catch (RuntimeException rte) {
GlobalOptions.err.println(
"net.sf.jode.decompiler.Main: Invalid Linelength " + arg);
errorInParams = true;
}
break;
}
case 'i': {
String arg = g.getOptarg();
int comma = arg.indexOf(',');
try {
int packLimit = Integer.parseInt(arg.substring(0, comma));
if (packLimit == 0)
packLimit = Integer.MAX_VALUE;
if (packLimit < 0)
throw new IllegalArgumentException();
int clazzLimit = Integer.parseInt(arg.substring(comma+1));
if (clazzLimit == 0)
clazzLimit = Integer.MAX_VALUE;
if (clazzLimit < 0)
throw new IllegalArgumentException();
importPackageLimit = packLimit;
importClassLimit = clazzLimit;
} catch (RuntimeException ex) {
GlobalOptions.err.println
("net.sf.jode.decompiler.Main: Invalid argument for -i option.");
errorInParams = true;
}
break;
}
default:
if (opt >= OPTION_START && opt <= OPTION_END) {
errorInParams |= !handleOption(opt-OPTION_START,
g.getLongind(),
g.getOptarg());
} else
errorInParams = true;
break;
}
}
if (errorInParams)
return;
classPath = new ClassPath(classPathStr);
ImportHandler imports = new ImportHandler(classPath,
importPackageLimit,
importClassLimit);
ZipOutputStream destZip = null;
TabbedPrintWriter writer = null;
if (destDir == null)
writer = new TabbedPrintWriter(System.out, imports, true,
outputStyle, indentSize, 0, outputLineLength);
else if (destDir.toLowerCase().endsWith(".zip")
|| destDir.toLowerCase().endsWith(".jar")) {
try {
destZip = new ZipOutputStream(new FileOutputStream(destDir));
} catch (IOException ex) {
GlobalOptions.err.println("Can't open zip file "+destDir);
ex.printStackTrace(GlobalOptions.err);
return;
}
writer = new TabbedPrintWriter(new BufferedOutputStream(destZip),
imports, false,
outputStyle, indentSize, 0, outputLineLength);
}
for (int i= g.getOptind(); i< params.length; i++) {
try {
if ((params[i].endsWith(".jar") || params[i].endsWith(".zip"))
&& new File(params[i]).isFile()) {
/* The user obviously wants to decompile a jar/zip file.
* Lets do him a pleasure and allow this.
*/
ClassPath zipClassPath
= new ClassPath(params[i], classPath);
Enumeration enumeration = new ZipFile(params[i]).entries();
while (enumeration.hasMoreElements()) {
String entry
= ((ZipEntry) enumeration.nextElement()).getName();
if (entry.endsWith(".class")) {
entry = entry.substring(0, entry.length() - 6)
.replace('/', '.');
if (!decompileClass(entry, zipClassPath,
classPathStr,
destZip, destDir,
writer, imports)
&& !keepGoing)
break;
}
}
} else {
if (!decompileClass(params[i], classPath,
classPathStr,
destZip, destDir,
writer, imports)
&& !keepGoing)
break;
}
} catch (IOException ex) {
GlobalOptions.err.println
("Can't read zip file " + params[i] + ".");
ex.printStackTrace(GlobalOptions.err);
}
}
if (destZip != null) {
try {
destZip.close();
} catch (IOException ex) {
GlobalOptions.err.println("Can't close Zipfile");
ex.printStackTrace(GlobalOptions.err);
}
}
}
}