Tue, 16 Sep 2008 18:35:18 -0700
6574134: Allow for alternative implementation of Name Table with garbage collection of name bytes
Reviewed-by: darcy, mcimadamore
1 /*
2 * Copyright 2007-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
26 package com.sun.tools.javap;
28 import java.io.EOFException;
29 import java.io.FileNotFoundException;
30 import java.io.FilterInputStream;
31 import java.io.InputStream;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.io.PrintWriter;
35 import java.io.StringWriter;
36 import java.io.Writer;
37 import java.security.DigestInputStream;
38 import java.security.MessageDigest;
39 import java.text.MessageFormat;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.MissingResourceException;
48 import java.util.ResourceBundle;
50 import javax.tools.Diagnostic;
51 import javax.tools.DiagnosticListener;
52 import javax.tools.JavaFileManager;
53 import javax.tools.JavaFileObject;
54 import javax.tools.StandardJavaFileManager;
55 import javax.tools.StandardLocation;
57 import com.sun.tools.classfile.*;
59 /**
60 * "Main" class for javap, normally accessed from the command line
61 * via Main, or from JSR199 via DisassemblerTool.
62 *
63 * <p><b>This is NOT part of any API supported by Sun Microsystems. If
64 * you write code that depends on this, you do so at your own risk.
65 * This code and its internal interfaces are subject to change or
66 * deletion without notice.</b>
67 */
68 public class JavapTask implements DisassemblerTool.DisassemblerTask {
69 public class BadArgs extends Exception {
70 static final long serialVersionUID = 8765093759964640721L;
71 BadArgs(String key, Object... args) {
72 super(JavapTask.this.getMessage(key, args));
73 this.key = key;
74 this.args = args;
75 }
77 BadArgs showUsage(boolean b) {
78 showUsage = b;
79 return this;
80 }
82 final String key;
83 final Object[] args;
84 boolean showUsage;
85 }
87 static abstract class Option {
88 Option(boolean hasArg, String... aliases) {
89 this.hasArg = hasArg;
90 this.aliases = aliases;
91 }
93 boolean matches(String opt) {
94 for (String a: aliases) {
95 if (a.equals(opt))
96 return true;
97 }
98 return false;
99 }
101 boolean ignoreRest() {
102 return false;
103 }
105 abstract void process(JavapTask task, String opt, String arg) throws BadArgs;
107 final boolean hasArg;
108 final String[] aliases;
109 }
111 static Option[] recognizedOptions = {
113 new Option(false, "-help", "--help", "-?") {
114 void process(JavapTask task, String opt, String arg) {
115 task.options.help = true;
116 }
117 },
119 new Option(false, "-version") {
120 void process(JavapTask task, String opt, String arg) {
121 task.options.version = true;
122 }
123 },
125 new Option(false, "-fullversion") {
126 void process(JavapTask task, String opt, String arg) {
127 task.options.fullVersion = true;
128 }
129 },
131 new Option(false, "-v", "-verbose", "-all") {
132 void process(JavapTask task, String opt, String arg) {
133 task.options.verbose = true;
134 task.options.showFlags = true;
135 task.options.showAllAttrs = true;
136 }
137 },
139 new Option(false, "-l") {
140 void process(JavapTask task, String opt, String arg) {
141 task.options.showLineAndLocalVariableTables = true;
142 }
143 },
145 new Option(false, "-public") {
146 void process(JavapTask task, String opt, String arg) {
147 task.options.accessOptions.add(opt);
148 task.options.showAccess = AccessFlags.ACC_PUBLIC;
149 }
150 },
152 new Option(false, "-protected") {
153 void process(JavapTask task, String opt, String arg) {
154 task.options.accessOptions.add(opt);
155 task.options.showAccess = AccessFlags.ACC_PROTECTED;
156 }
157 },
159 new Option(false, "-package") {
160 void process(JavapTask task, String opt, String arg) {
161 task.options.accessOptions.add(opt);
162 task.options.showAccess = 0;
163 }
164 },
166 new Option(false, "-p", "-private") {
167 void process(JavapTask task, String opt, String arg) {
168 if (!task.options.accessOptions.contains("-p") &&
169 !task.options.accessOptions.contains("-private")) {
170 task.options.accessOptions.add(opt);
171 }
172 task.options.showAccess = AccessFlags.ACC_PRIVATE;
173 }
174 },
176 new Option(false, "-c") {
177 void process(JavapTask task, String opt, String arg) {
178 task.options.showDisassembled = true;
179 }
180 },
182 new Option(false, "-s") {
183 void process(JavapTask task, String opt, String arg) {
184 task.options.showInternalSignatures = true;
185 }
186 },
188 // new Option(false, "-all") {
189 // void process(JavapTask task, String opt, String arg) {
190 // task.options.showAllAttrs = true;
191 // }
192 // },
194 new Option(false, "-h") {
195 void process(JavapTask task, String opt, String arg) throws BadArgs {
196 throw task.new BadArgs("err.h.not.supported");
197 }
198 },
200 new Option(false, "-verify", "-verify-verbose") {
201 void process(JavapTask task, String opt, String arg) throws BadArgs {
202 throw task.new BadArgs("err.verify.not.supported");
203 }
204 },
206 new Option(false, "-sysinfo") {
207 void process(JavapTask task, String opt, String arg) {
208 task.options.sysInfo = true;
209 }
210 },
212 new Option(false, "-Xold") {
213 void process(JavapTask task, String opt, String arg) throws BadArgs {
214 // -Xold is only supported as first arg when invoked from
215 // command line; this is handled in Main,main
216 throw task.new BadArgs("err.Xold.not.supported.here");
217 }
218 },
220 new Option(false, "-Xnew") {
221 void process(JavapTask task, String opt, String arg) throws BadArgs {
222 // ignore: this _is_ the new version
223 }
224 },
226 new Option(false, "-XDcompat") {
227 void process(JavapTask task, String opt, String arg) {
228 task.options.compat = true;
229 }
230 },
232 new Option(false, "-XDjsr277") {
233 void process(JavapTask task, String opt, String arg) {
234 task.options.jsr277 = true;
235 }
236 },
238 new Option(false, "-XDignore.symbol.file") {
239 void process(JavapTask task, String opt, String arg) {
240 task.options.ignoreSymbolFile = true;
241 }
242 },
244 new Option(false, "-constants") {
245 void process(JavapTask task, String opt, String arg) {
246 task.options.showConstants = true;
247 }
248 }
250 };
252 JavapTask() {
253 context = new Context();
254 options = Options.instance(context);
255 }
257 JavapTask(Writer out,
258 JavaFileManager fileManager,
259 DiagnosticListener<? super JavaFileObject> diagnosticListener,
260 Iterable<String> options,
261 Iterable<String> classes) {
262 this();
263 this.log = getPrintWriterForWriter(out);
264 this.fileManager = fileManager;
265 this.diagnosticListener = diagnosticListener;
267 try {
268 handleOptions(options, false);
269 } catch (BadArgs e) {
270 throw new IllegalArgumentException(e.getMessage());
271 }
273 this.classes = new ArrayList<String>();
274 for (String classname: classes) {
275 classname.getClass(); // null-check
276 this.classes.add(classname);
277 }
278 }
280 public void setLocale(Locale locale) {
281 if (locale == null)
282 locale = Locale.getDefault();
283 task_locale = locale;
284 }
286 public void setLog(PrintWriter log) {
287 this.log = log;
288 }
290 public void setLog(OutputStream s) {
291 setLog(getPrintWriterForStream(s));
292 }
294 private static PrintWriter getPrintWriterForStream(OutputStream s) {
295 return new PrintWriter(s, true);
296 }
298 private static PrintWriter getPrintWriterForWriter(Writer w) {
299 if (w == null)
300 return getPrintWriterForStream(null);
301 else if (w instanceof PrintWriter)
302 return (PrintWriter) w;
303 else
304 return new PrintWriter(w, true);
305 }
307 public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
308 diagnosticListener = dl;
309 }
311 public void setDiagnosticListener(OutputStream s) {
312 setDiagnosticListener(getDiagnosticListenerForStream(s));
313 }
315 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
316 return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
317 }
319 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) {
320 final PrintWriter pw = getPrintWriterForWriter(w);
321 return new DiagnosticListener<JavaFileObject> () {
322 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
323 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
324 pw.print(getMessage("err.prefix"));
325 pw.print(" ");
326 }
327 pw.println(diagnostic.getMessage(null));
328 }
329 };
330 }
332 /** Result codes.
333 */
334 static final int
335 EXIT_OK = 0, // Compilation completed with no errors.
336 EXIT_ERROR = 1, // Completed but reported errors.
337 EXIT_CMDERR = 2, // Bad command-line arguments
338 EXIT_SYSERR = 3, // System error or resource exhaustion.
339 EXIT_ABNORMAL = 4; // Compiler terminated abnormally
341 int run(String[] args) {
342 try {
343 handleOptions(args);
345 // the following gives consistent behavior with javac
346 if (classes == null || classes.size() == 0) {
347 if (options.help || options.version || options.fullVersion)
348 return EXIT_OK;
349 else
350 return EXIT_CMDERR;
351 }
353 boolean ok = run();
354 return ok ? EXIT_OK : EXIT_ERROR;
355 } catch (BadArgs e) {
356 diagnosticListener.report(createDiagnostic(e.key, e.args));
357 if (e.showUsage) {
358 log.println(getMessage("main.usage.summary", progname));
359 }
360 return EXIT_CMDERR;
361 } catch (InternalError e) {
362 Object[] e_args;
363 if (e.getCause() == null)
364 e_args = e.args;
365 else {
366 e_args = new Object[e.args.length + 1];
367 e_args[0] = e.getCause();
368 System.arraycopy(e.args, 0, e_args, 1, e.args.length);
369 }
370 diagnosticListener.report(createDiagnostic("err.internal.error", e_args));
371 return EXIT_ABNORMAL;
372 } finally {
373 log.flush();
374 }
375 }
377 public void handleOptions(String[] args) throws BadArgs {
378 handleOptions(Arrays.asList(args), true);
379 }
381 private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs {
382 if (log == null) {
383 log = getPrintWriterForStream(System.out);
384 if (diagnosticListener == null)
385 diagnosticListener = getDiagnosticListenerForStream(System.err);
386 } else {
387 if (diagnosticListener == null)
388 diagnosticListener = getDiagnosticListenerForWriter(log);
389 }
392 if (fileManager == null)
393 fileManager = getDefaultFileManager(diagnosticListener, log);
395 Iterator<String> iter = args.iterator();
396 boolean noArgs = !iter.hasNext();
398 while (iter.hasNext()) {
399 String arg = iter.next();
400 if (arg.startsWith("-"))
401 handleOption(arg, iter);
402 else if (allowClasses) {
403 if (classes == null)
404 classes = new ArrayList<String>();
405 classes.add(arg);
406 while (iter.hasNext())
407 classes.add(iter.next());
408 } else
409 throw new BadArgs("err.unknown.option", arg).showUsage(true);
410 }
412 if (!options.compat && options.accessOptions.size() > 1) {
413 StringBuilder sb = new StringBuilder();
414 for (String opt: options.accessOptions) {
415 if (sb.length() > 0)
416 sb.append(" ");
417 sb.append(opt);
418 }
419 throw new BadArgs("err.incompatible.options", sb);
420 }
422 if (options.ignoreSymbolFile && fileManager instanceof JavapFileManager)
423 ((JavapFileManager) fileManager).setIgnoreSymbolFile(true);
425 if ((classes == null || classes.size() == 0) &&
426 !(noArgs || options.help || options.version || options.fullVersion)) {
427 throw new BadArgs("err.no.classes.specified");
428 }
430 if (noArgs || options.help)
431 showHelp();
433 if (options.version || options.fullVersion)
434 showVersion(options.fullVersion);
435 }
437 private void handleOption(String name, Iterator<String> rest) throws BadArgs {
438 for (Option o: recognizedOptions) {
439 if (o.matches(name)) {
440 if (o.hasArg) {
441 if (rest.hasNext())
442 o.process(this, name, rest.next());
443 else
444 throw new BadArgs("err.missing.arg", name).showUsage(true);
445 } else
446 o.process(this, name, null);
448 if (o.ignoreRest()) {
449 while (rest.hasNext())
450 rest.next();
451 }
452 return;
453 }
454 }
456 if (fileManager.handleOption(name, rest))
457 return;
459 throw new BadArgs("err.unknown.option", name).showUsage(true);
460 }
462 public Boolean call() {
463 return run();
464 }
466 public boolean run() {
467 if (classes == null || classes.size() == 0)
468 return false;
470 context.put(PrintWriter.class, log);
471 ClassWriter classWriter = ClassWriter.instance(context);
473 boolean ok = true;
475 for (String className: classes) {
476 JavaFileObject fo;
477 try {
478 if (className.endsWith(".class")) {
479 if (fileManager instanceof StandardJavaFileManager) {
480 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
481 fo = sfm.getJavaFileObjects(className).iterator().next();
482 } else {
483 diagnosticListener.report(createDiagnostic("err.not.standard.file.manager", className));
484 ok = false;
485 continue;
486 }
487 } else {
488 fo = getClassFileObject(className);
489 if (fo == null) {
490 // see if it is an inner class, by replacing dots to $, starting from the right
491 String cn = className;
492 int lastDot;
493 while (fo == null && (lastDot = cn.lastIndexOf(".")) != -1) {
494 cn = cn.substring(0, lastDot) + "$" + cn.substring(lastDot + 1);
495 fo = getClassFileObject(cn);
496 }
497 }
498 if (fo == null) {
499 diagnosticListener.report(createDiagnostic("err.class.not.found", className));
500 ok = false;
501 continue;
502 }
503 }
504 Attribute.Factory attributeFactory = new Attribute.Factory();
505 attributeFactory.setCompat(options.compat);
506 attributeFactory.setJSR277(options.jsr277);
508 InputStream in = fo.openInputStream();
509 SizeInputStream sizeIn = null;
510 MessageDigest md = null;
511 if (options.sysInfo || options.verbose) {
512 md = MessageDigest.getInstance("MD5");
513 in = new DigestInputStream(in, md);
514 in = sizeIn = new SizeInputStream(in);
515 }
517 ClassFile cf = ClassFile.read(in, attributeFactory);
519 if (options.sysInfo || options.verbose) {
520 classWriter.setFile(fo.toUri());
521 classWriter.setLastModified(fo.getLastModified());
522 classWriter.setDigest("MD5", md.digest());
523 classWriter.setFileSize(sizeIn.size());
524 }
526 classWriter.write(cf);
528 } catch (ConstantPoolException e) {
529 diagnosticListener.report(createDiagnostic("err.bad.constant.pool", className, e.getLocalizedMessage()));
530 ok = false;
531 } catch (EOFException e) {
532 diagnosticListener.report(createDiagnostic("err.end.of.file", className));
533 ok = false;
534 } catch (FileNotFoundException e) {
535 diagnosticListener.report(createDiagnostic("err.file.not.found", e.getLocalizedMessage()));
536 ok = false;
537 } catch (IOException e) {
538 //e.printStackTrace();
539 Object msg = e.getLocalizedMessage();
540 if (msg == null)
541 msg = e;
542 diagnosticListener.report(createDiagnostic("err.ioerror", className, msg));
543 ok = false;
544 } catch (Throwable t) {
545 StringWriter sw = new StringWriter();
546 PrintWriter pw = new PrintWriter(sw);
547 t.printStackTrace(pw);
548 pw.close();
549 diagnosticListener.report(createDiagnostic("err.crash", t.toString(), sw.toString()));
550 ok = false;
551 }
552 }
554 return ok;
555 }
557 private JavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
558 return JavapFileManager.create(dl, log, options);
559 }
561 private JavaFileObject getClassFileObject(String className) throws IOException {
562 JavaFileObject fo;
563 fo = fileManager.getJavaFileForInput(StandardLocation.PLATFORM_CLASS_PATH, className, JavaFileObject.Kind.CLASS);
564 if (fo == null)
565 fo = fileManager.getJavaFileForInput(StandardLocation.CLASS_PATH, className, JavaFileObject.Kind.CLASS);
566 return fo;
567 }
569 private void showHelp() {
570 log.println(getMessage("main.usage", progname));
571 for (Option o: recognizedOptions) {
572 String name = o.aliases[0].substring(1); // there must always be at least one name
573 if (name.startsWith("X") || name.equals("fullversion") || name.equals("h") || name.equals("verify"))
574 continue;
575 log.println(getMessage("main.opt." + name));
576 }
577 String[] fmOptions = { "-classpath", "-bootclasspath" };
578 for (String o: fmOptions) {
579 if (fileManager.isSupportedOption(o) == -1)
580 continue;
581 String name = o.substring(1);
582 log.println(getMessage("main.opt." + name));
583 }
585 }
587 private void showVersion(boolean full) {
588 log.println(version(full ? "full" : "release"));
589 }
591 private static final String versionRBName = "com.sun.tools.javap.resources.version";
592 private static ResourceBundle versionRB;
594 private String version(String key) {
595 // key=version: mm.nn.oo[-milestone]
596 // key=full: mm.mm.oo[-milestone]-build
597 if (versionRB == null) {
598 try {
599 versionRB = ResourceBundle.getBundle(versionRBName);
600 } catch (MissingResourceException e) {
601 return getMessage("version.resource.missing", System.getProperty("java.version"));
602 }
603 }
604 try {
605 return versionRB.getString(key);
606 }
607 catch (MissingResourceException e) {
608 return getMessage("version.unknown", System.getProperty("java.version"));
609 }
610 }
612 private Diagnostic<JavaFileObject> createDiagnostic(final String key, final Object... args) {
613 return new Diagnostic<JavaFileObject>() {
614 public Kind getKind() {
615 return Diagnostic.Kind.ERROR;
616 }
618 public JavaFileObject getSource() {
619 return null;
620 }
622 public long getPosition() {
623 return Diagnostic.NOPOS;
624 }
626 public long getStartPosition() {
627 return Diagnostic.NOPOS;
628 }
630 public long getEndPosition() {
631 return Diagnostic.NOPOS;
632 }
634 public long getLineNumber() {
635 return Diagnostic.NOPOS;
636 }
638 public long getColumnNumber() {
639 return Diagnostic.NOPOS;
640 }
642 public String getCode() {
643 return key;
644 }
646 public String getMessage(Locale locale) {
647 return JavapTask.this.getMessage(locale, key, args);
648 }
650 };
652 }
654 private String getMessage(String key, Object... args) {
655 return getMessage(task_locale, key, args);
656 }
658 private String getMessage(Locale locale, String key, Object... args) {
659 if (bundles == null) {
660 // could make this a HashMap<Locale,SoftReference<ResourceBundle>>
661 // and for efficiency, keep a hard reference to the bundle for the task
662 // locale
663 bundles = new HashMap<Locale, ResourceBundle>();
664 }
666 if (locale == null)
667 locale = Locale.getDefault();
669 ResourceBundle b = bundles.get(locale);
670 if (b == null) {
671 try {
672 b = ResourceBundle.getBundle("com.sun.tools.javap.resources.javap", locale);
673 bundles.put(locale, b);
674 } catch (MissingResourceException e) {
675 throw new InternalError("Cannot find javap resource bundle for locale " + locale);
676 }
677 }
679 try {
680 return MessageFormat.format(b.getString(key), args);
681 } catch (MissingResourceException e) {
682 throw new InternalError(e, key);
683 }
684 }
686 Context context;
687 JavaFileManager fileManager;
688 PrintWriter log;
689 DiagnosticListener<? super JavaFileObject> diagnosticListener;
690 List<String> classes;
691 Options options;
692 //ResourceBundle bundle;
693 Locale task_locale;
694 Map<Locale, ResourceBundle> bundles;
696 private static final String progname = "javap";
698 private static class SizeInputStream extends FilterInputStream {
699 SizeInputStream(InputStream in) {
700 super(in);
701 }
703 int size() {
704 return size;
705 }
707 @Override
708 public int read(byte[] buf, int offset, int length) throws IOException {
709 int n = super.read(buf, offset, length);
710 if (n > 0)
711 size += n;
712 return n;
713 }
715 @Override
716 public int read() throws IOException {
717 int b = super.read();
718 size += 1;
719 return b;
720 }
722 private int size;
723 }
724 }