/*
* Copyright 2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-15301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.tools.javap;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import com.sun.tools.classfile.*;
/**
* "Main" class for javap, normally accessed from the command line
* via Main, or from JSR199 via DisassemblerTool.
*
* <p><b>This is NOT part of any API supported by Sun Microsystems. If
* you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class JavapTask implements DisassemblerTool.DisassemblerTask {
public class BadArgs extends Exception {
static final long serialVersionUID = 8765093759964640721L;
BadArgs(String key, Object... args) {
super(JavapTask.this.getMessage(key, args));
this.key = key;
this.args = args;
}
BadArgs showUsage(boolean b) {
showUsage = b;
return this;
}
final String key;
final Object[] args;
boolean showUsage;
}
static abstract class Option {
Option(boolean hasArg, String... aliases) {
this.hasArg = hasArg;
this.aliases = aliases;
}
boolean matches(String opt) {
for (String a: aliases) {
if (a.equals(opt))
return true;
}
return false;
}
boolean ignoreRest() {
return false;
}
abstract void process(JavapTask task, String opt, String arg) throws BadArgs;
final boolean hasArg;
final String[] aliases;
}
static Option[] recognizedOptions = {
new Option(false, "-help", "--help", "-?") {
void process(JavapTask task, String opt, String arg) {
task.options.help = true;
}
},
new Option(false, "-version") {
void process(JavapTask task, String opt, String arg) {
task.options.version = true;
}
},
new Option(false, "-fullversion") {
void process(JavapTask task, String opt, String arg) {
task.options.fullVersion = true;
}
},
new Option(false, "-v", "-verbose", "-all") {
void process(JavapTask task, String opt, String arg) {
task.options.verbose = true;
task.options.showFlags = true;
task.options.showAllAttrs = true;
}
},
new Option(false, "-l") {
void process(JavapTask task, String opt, String arg) {
task.options.showLineAndLocalVariableTables = true;
}
},
new Option(false, "-public") {
void process(JavapTask task, String opt, String arg) {
task.options.showAccess = AccessFlags.ACC_PUBLIC;
}
},
new Option(false, "-protected") {
void process(JavapTask task, String opt, String arg) {
task.options.showAccess = AccessFlags.ACC_PROTECTED;
}
},
new Option(false, "-package") {
void process(JavapTask task, String opt, String arg) {
task.options.showAccess = 0;
}
},
new Option(false, "-p", "-private") {
void process(JavapTask task, String opt, String arg) {
task.options.showAccess = AccessFlags.ACC_PRIVATE;
}
},
new Option(false, "-c") {
void process(JavapTask task, String opt, String arg) {
task.options.showDisassembled = true;
}
},
new Option(false, "-s") {
void process(JavapTask task, String opt, String arg) {
task.options.showInternalSignatures = true;
}
},
// new Option(false, "-all") {
// void process(JavapTask task, String opt, String arg) {
// task.options.showAllAttrs = true;
// }
// },
new Option(false, "-h") {
void process(JavapTask task, String opt, String arg) throws BadArgs {
throw task.new BadArgs("err.h.not.supported");
}
},
new Option(false, "-verify", "-verify-verbose") {
void process(JavapTask task, String opt, String arg) throws BadArgs {
throw task.new BadArgs("err.verify.not.supported");
}
},
new Option(false, "-Xold") {
void process(JavapTask task, String opt, String arg) throws BadArgs {
// -Xold is only supported as first arg when invoked from
// command line; this is handled in Main,main
throw task.new BadArgs("err.Xold.not.supported.here");
}
},
new Option(false, "-Xnew") {
void process(JavapTask task, String opt, String arg) throws BadArgs {
// ignore: this _is_ the new version
}
},
new Option(false, "-XDcompat") {
void process(JavapTask task, String opt, String arg) {
task.options.compat = true;
}
},
new Option(false, "-XDjsr277") {
void process(JavapTask task, String opt, String arg) {
task.options.jsr277 = true;
}
},
new Option(false, "-XDignore.symbol.file") {
void process(JavapTask task, String opt, String arg) {
task.options.ignoreSymbolFile = true;
}
}
};
JavapTask() {
context = new Context();
options = Options.instance(context);
}
JavapTask(Writer out,
JavaFileManager fileManager,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
Iterable<String> options,
Iterable<String> classes) {
this();
this.log = getPrintWriterForWriter(out);
this.fileManager = fileManager;
this.diagnosticListener = diagnosticListener;
try {
handleOptions(options, false);
} catch (BadArgs e) {
throw new IllegalArgumentException(e.getMessage());
}
this.classes = new ArrayList<String>();
for (String classname: classes) {
classname.getClass(); // null-check
this.classes.add(classname);
}
}
public void setLocale(Locale locale) {
if (locale == null)
locale = Locale.getDefault();
task_locale = locale;
}
public void setLog(PrintWriter log) {
this.log = log;
}
public void setLog(OutputStream s) {
setLog(getPrintWriterForStream(s));
}
private static PrintWriter getPrintWriterForStream(OutputStream s) {
return new PrintWriter(s, true);
}
private static PrintWriter getPrintWriterForWriter(Writer w) {
if (w == null)
return getPrintWriterForStream(null);
else if (w instanceof PrintWriter)
return (PrintWriter) w;
else
return new PrintWriter(w, true);
}
public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
diagnosticListener = dl;
}
public void setDiagnosticListener(OutputStream s) {
setDiagnosticListener(getDiagnosticListenerForStream(s));
}
private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
}
private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) {
final PrintWriter pw = getPrintWriterForWriter(w);
return new DiagnosticListener<JavaFileObject> () {
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
pw.print(getMessage("err.prefix"));
pw.print(" ");
}
pw.println(diagnostic.getMessage(null));
}
};
}
int run(String[] args) {
try {
handleOptions(args);
boolean ok = run();
return ok ? 0 : 1;
} catch (BadArgs e) {
diagnosticListener.report(createDiagnostic(e.key, e.args));
return 1;
} catch (InternalError e) {
Object[] e_args;
if (e.getCause() == null)
e_args = e.args;
else {
e_args = new Object[e.args.length + 1];
e_args[0] = e.getCause();
System.arraycopy(e.args, 0, e_args, 1, e.args.length);
}
diagnosticListener.report(createDiagnostic("err.internal.error", e_args));
return 1;
} finally {
log.flush();
}
}
public void handleOptions(String[] args) throws BadArgs {
handleOptions(Arrays.asList(args), true);
}
private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs {
if (log == null) {
log = getPrintWriterForStream(System.out);
if (diagnosticListener == null)
diagnosticListener = getDiagnosticListenerForStream(System.err);
} else {
if (diagnosticListener == null)
diagnosticListener = getDiagnosticListenerForWriter(log);
}
if (fileManager == null)
fileManager = getDefaultFileManager(diagnosticListener, log);
Iterator<String> iter = args.iterator();
if (!iter.hasNext())
options.help = true;
while (iter.hasNext()) {
String arg = iter.next();
if (arg.startsWith("-"))
handleOption(arg, iter);
else if (allowClasses) {
if (classes == null)
classes = new ArrayList<String>();
classes.add(arg);
while (iter.hasNext())
classes.add(iter.next());
} else
throw new BadArgs("err.unknown.option", arg).showUsage(true);
}
if (options.ignoreSymbolFile && fileManager instanceof JavapFileManager)
((JavapFileManager) fileManager).setIgnoreSymbolFile(true);
if ((classes == null || classes.size() == 0) &&
!(options.help || options.version || options.fullVersion)) {
throw new BadArgs("err.no.classes.specified");
}
}
private void handleOption(String name, Iterator<String> rest) throws BadArgs {
for (Option o: recognizedOptions) {
if (o.matches(name)) {
if (o.hasArg) {
if (rest.hasNext())
o.process(this, name, rest.next());
else
throw new BadArgs("err.missing.arg", name).showUsage(true);
} else
o.process(this, name, null);
if (o.ignoreRest()) {
while (rest.hasNext())
rest.next();
}
return;
}
}
if (fileManager.handleOption(name, rest))
return;
throw new BadArgs("err.unknown.option", name).showUsage(true);
}
public Boolean call() {
return run();
}
public boolean run() {
if (options.help)
showHelp();
if (options.version || options.fullVersion)
showVersion(options.fullVersion);
if (classes == null || classes.size() == 0)
return true;
context.put(PrintWriter.class, log);
ClassWriter classWriter = ClassWriter.instance(context);
boolean ok = true;
for (String className: classes) {
JavaFileObject fo;
try {
if (className.endsWith(".class")) {
if (fileManager instanceof StandardJavaFileManager) {
StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
fo = sfm.getJavaFileObjects(className).iterator().next();
} else {
diagnosticListener.report(createDiagnostic("err.not.standard.file.manager", className));
ok = false;
continue;
}
} else {
fo = getClassFileObject(className);
if (fo == null) {
// see if it is an inner class, by replacing dots to $, starting from the right
String cn = className;
int lastDot;
while (fo == null && (lastDot = cn.lastIndexOf(".")) != -1) {
cn = cn.substring(0, lastDot) + "$" + cn.substring(lastDot + 1);
fo = getClassFileObject(cn);
}
}
if (fo == null) {
diagnosticListener.report(createDiagnostic("err.class.not.found", className));
ok = false;
continue;
}
}
Attribute.Factory attributeFactory = new Attribute.Factory();
attributeFactory.setCompat(options.compat);
attributeFactory.setJSR277(options.jsr277);
ClassFile cf = ClassFile.read(fo.openInputStream(), attributeFactory);
classWriter.write(cf);
} catch (ConstantPoolException e) {
diagnosticListener.report(createDiagnostic("err.bad.constant.pool", className, e.getLocalizedMessage()));
ok = false;
} catch (EOFException e) {
diagnosticListener.report(createDiagnostic("err.end.of.file", className));
ok = false;
} catch (FileNotFoundException e) {
diagnosticListener.report(createDiagnostic("err.file.not.found", e.getLocalizedMessage()));
ok = false;
} catch (IOException e) {
//e.printStackTrace();
Object msg = e.getLocalizedMessage();
if (msg == null)
msg = e;
diagnosticListener.report(createDiagnostic("err.ioerror", className, msg));
ok = false;
} catch (Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.close();
diagnosticListener.report(createDiagnostic("err.crash", t.toString(), sw.toString()));
ok = false;
}
}
return ok;
}
private JavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
return JavapFileManager.create(dl, log, options);
}
private JavaFileObject getClassFileObject(String className) throws IOException {
JavaFileObject fo;
fo = fileManager.getJavaFileForInput(StandardLocation.PLATFORM_CLASS_PATH, className, JavaFileObject.Kind.CLASS);
if (fo == null)
fo = fileManager.getJavaFileForInput(StandardLocation.CLASS_PATH, className, JavaFileObject.Kind.CLASS);
return fo;
}
private void showHelp() {
log.println(getMessage("main.usage", progname));
for (Option o: recognizedOptions) {
String name = o.aliases[0].substring(1); // there must always be at least one name
if (name.startsWith("X") || name.equals("fullversion") || name.equals("h") || name.equals("verify"))
continue;
log.println(getMessage("main.opt." + name));
}
String[] fmOptions = { "-classpath", "-bootclasspath" };
for (String o: fmOptions) {
if (fileManager.isSupportedOption(o) == -1)
continue;
String name = o.substring(1);
log.println(getMessage("main.opt." + name));
}
}
private void showVersion(boolean full) {
log.println(version(full ? "full" : "release"));
}
private static final String versionRBName = "com.sun.tools.javap.resources.version";
private static ResourceBundle versionRB;
private String version(String key) {
// key=version: mm.nn.oo[-milestone]
// key=full: mm.mm.oo[-milestone]-build
if (versionRB == null) {
try {
versionRB = ResourceBundle.getBundle(versionRBName);
} catch (MissingResourceException e) {
return getMessage("version.resource.missing", System.getProperty("java.version"));
}
}
try {
return versionRB.getString(key);
}
catch (MissingResourceException e) {
return getMessage("version.unknown", System.getProperty("java.version"));
}
}
private Diagnostic<JavaFileObject> createDiagnostic(final String key, final Object... args) {
return new Diagnostic<JavaFileObject>() {
public Kind getKind() {
return Diagnostic.Kind.ERROR;
}
public JavaFileObject getSource() {
return null;
}
public long getPosition() {
return Diagnostic.NOPOS;
}
public long getStartPosition() {
return Diagnostic.NOPOS;
}
public long getEndPosition() {
return Diagnostic.NOPOS;
}
public long getLineNumber() {
return Diagnostic.NOPOS;
}
public long getColumnNumber() {
return Diagnostic.NOPOS;
}
public String getCode() {
return key;
}
public String getMessage(Locale locale) {
return JavapTask.this.getMessage(locale, key, args);
}
};
}
private String getMessage(String key, Object... args) {
return getMessage(task_locale, key, args);
}
private String getMessage(Locale locale, String key, Object... args) {
if (bundles == null) {
// could make this a HashMap<Locale,SoftReference<ResourceBundle>>
// and for efficiency, keep a hard reference to the bundle for the task
// locale
bundles = new HashMap<Locale, ResourceBundle>();
}
if (locale == null)
locale = Locale.getDefault();
ResourceBundle b = bundles.get(locale);
if (b == null) {
try {
b = ResourceBundle.getBundle("com.sun.tools.javap.resources.javap", locale);
bundles.put(locale, b);
} catch (MissingResourceException e) {
throw new InternalError("Cannot find javap resource bundle for locale " + locale);
}
}
try {
return MessageFormat.format(b.getString(key), args);
} catch (MissingResourceException e) {
throw new InternalError(e, key);
}
}
Context context;
JavaFileManager fileManager;
PrintWriter log;
DiagnosticListener<? super JavaFileObject> diagnosticListener;
List<String> classes;
Options options;
//ResourceBundle bundle;
Locale task_locale;
Map<Locale, ResourceBundle> bundles;
private static final String progname = "javap";
}