001    /*
002     * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.javap;
027    
028    import java.io.EOFException;
029    import java.io.FileNotFoundException;
030    import java.io.FilterInputStream;
031    import java.io.InputStream;
032    import java.io.IOException;
033    import java.io.OutputStream;
034    import java.io.PrintWriter;
035    import java.io.StringWriter;
036    import java.io.Writer;
037    import java.security.DigestInputStream;
038    import java.security.MessageDigest;
039    import java.text.MessageFormat;
040    import java.util.ArrayList;
041    import java.util.Arrays;
042    import java.util.HashMap;
043    import java.util.Iterator;
044    import java.util.List;
045    import java.util.Locale;
046    import java.util.Map;
047    import java.util.MissingResourceException;
048    import java.util.ResourceBundle;
049    
050    import javax.tools.Diagnostic;
051    import javax.tools.DiagnosticListener;
052    import javax.tools.JavaFileManager;
053    import javax.tools.JavaFileObject;
054    import javax.tools.StandardJavaFileManager;
055    import javax.tools.StandardLocation;
056    
057    import com.sun.tools.classfile.*;
058    
059    /**
060     *  "Main" class for javap, normally accessed from the command line
061     *  via Main, or from JSR199 via DisassemblerTool.
062     *
063     *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
064     *  you write code that depends on this, you do so at your own risk.
065     *  This code and its internal interfaces are subject to change or
066     *  deletion without notice.</b>
067     */
068    public class JavapTask implements DisassemblerTool.DisassemblerTask {
069        public class BadArgs extends Exception {
070            static final long serialVersionUID = 8765093759964640721L;
071            BadArgs(String key, Object... args) {
072                super(JavapTask.this.getMessage(key, args));
073                this.key = key;
074                this.args = args;
075            }
076    
077            BadArgs showUsage(boolean b) {
078                showUsage = b;
079                return this;
080            }
081    
082            final String key;
083            final Object[] args;
084            boolean showUsage;
085        }
086    
087        static abstract class Option {
088            Option(boolean hasArg, String... aliases) {
089                this.hasArg = hasArg;
090                this.aliases = aliases;
091            }
092    
093            boolean matches(String opt) {
094                for (String a: aliases) {
095                    if (a.equals(opt))
096                        return true;
097                }
098                return false;
099            }
100    
101            boolean ignoreRest() {
102                return false;
103            }
104    
105            abstract void process(JavapTask task, String opt, String arg) throws BadArgs;
106    
107            final boolean hasArg;
108            final String[] aliases;
109        }
110    
111        static Option[] recognizedOptions = {
112    
113            new Option(false, "-help", "--help", "-?") {
114                void process(JavapTask task, String opt, String arg) {
115                    task.options.help = true;
116                }
117            },
118    
119            new Option(false, "-version") {
120                void process(JavapTask task, String opt, String arg) {
121                    task.options.version = true;
122                }
123            },
124    
125            new Option(false, "-fullversion") {
126                void process(JavapTask task, String opt, String arg) {
127                    task.options.fullVersion = true;
128                }
129            },
130    
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            },
138    
139            new Option(false, "-l") {
140                void process(JavapTask task, String opt, String arg) {
141                    task.options.showLineAndLocalVariableTables = true;
142                }
143            },
144    
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            },
151    
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            },
158    
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            },
165    
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            },
175    
176            new Option(false, "-c") {
177                void process(JavapTask task, String opt, String arg) {
178                    task.options.showDisassembled = true;
179                }
180            },
181    
182            new Option(false, "-s") {
183                void process(JavapTask task, String opt, String arg) {
184                    task.options.showInternalSignatures = true;
185                }
186            },
187    
188    //        new Option(false, "-all") {
189    //            void process(JavapTask task, String opt, String arg) {
190    //                task.options.showAllAttrs = true;
191    //            }
192    //        },
193    
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            },
199    
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            },
205    
206            new Option(false, "-sysinfo") {
207                void process(JavapTask task, String opt, String arg) {
208                    task.options.sysInfo = true;
209                }
210            },
211    
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            },
219    
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            },
225    
226            new Option(false, "-XDcompat") {
227                void process(JavapTask task, String opt, String arg) {
228                    task.options.compat = true;
229                }
230            },
231    
232            new Option(false, "-XDjsr277") {
233                void process(JavapTask task, String opt, String arg) {
234                    task.options.jsr277 = true;
235                }
236            },
237    
238            new Option(false, "-XDignore.symbol.file") {
239                void process(JavapTask task, String opt, String arg) {
240                    task.options.ignoreSymbolFile = true;
241                }
242            },
243    
244            new Option(false, "-constants") {
245                void process(JavapTask task, String opt, String arg) {
246                    task.options.showConstants = true;
247                }
248            }
249    
250        };
251    
252        JavapTask() {
253            context = new Context();
254            options = Options.instance(context);
255        }
256    
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;
266    
267            try {
268                handleOptions(options, false);
269            } catch (BadArgs e) {
270                throw new IllegalArgumentException(e.getMessage());
271            }
272    
273            this.classes = new ArrayList<String>();
274            for (String classname: classes) {
275                classname.getClass(); // null-check
276                this.classes.add(classname);
277            }
278        }
279    
280        public void setLocale(Locale locale) {
281            if (locale == null)
282                locale = Locale.getDefault();
283            task_locale = locale;
284        }
285    
286        public void setLog(PrintWriter log) {
287            this.log = log;
288        }
289    
290        public void setLog(OutputStream s) {
291            setLog(getPrintWriterForStream(s));
292        }
293    
294        private static PrintWriter getPrintWriterForStream(OutputStream s) {
295            return new PrintWriter(s, true);
296        }
297    
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        }
306    
307        public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
308            diagnosticListener = dl;
309        }
310    
311        public void setDiagnosticListener(OutputStream s) {
312            setDiagnosticListener(getDiagnosticListenerForStream(s));
313        }
314    
315        private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
316            return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
317        }
318    
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        }
331    
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
340    
341        int run(String[] args) {
342            try {
343                handleOptions(args);
344    
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                }
352    
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        }
376    
377        public void handleOptions(String[] args) throws BadArgs {
378            handleOptions(Arrays.asList(args), true);
379        }
380    
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            }
390    
391    
392            if (fileManager == null)
393                fileManager = getDefaultFileManager(diagnosticListener, log);
394    
395            Iterator<String> iter = args.iterator();
396            boolean noArgs = !iter.hasNext();
397    
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            }
411    
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            }
421    
422            if (options.ignoreSymbolFile && fileManager instanceof JavapFileManager)
423                ((JavapFileManager) fileManager).setIgnoreSymbolFile(true);
424    
425            if ((classes == null || classes.size() == 0) &&
426                    !(noArgs || options.help || options.version || options.fullVersion)) {
427                throw new BadArgs("err.no.classes.specified");
428            }
429    
430            if (noArgs || options.help)
431                showHelp();
432    
433            if (options.version || options.fullVersion)
434                showVersion(options.fullVersion);
435        }
436    
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);
447    
448                    if (o.ignoreRest()) {
449                        while (rest.hasNext())
450                            rest.next();
451                    }
452                    return;
453                }
454            }
455    
456            if (fileManager.handleOption(name, rest))
457                return;
458    
459            throw new BadArgs("err.unknown.option", name).showUsage(true);
460        }
461    
462        public Boolean call() {
463            return run();
464        }
465    
466        public boolean run() {
467            if (classes == null || classes.size() == 0)
468                return false;
469    
470            context.put(PrintWriter.class, log);
471            ClassWriter classWriter = ClassWriter.instance(context);
472    
473            boolean ok = true;
474    
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);
507    
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                    }
516    
517                    ClassFile cf = ClassFile.read(in, attributeFactory);
518    
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                    }
525    
526                    classWriter.write(cf);
527    
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            }
553    
554            return ok;
555        }
556    
557        private JavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
558            return JavapFileManager.create(dl, log, options);
559        }
560    
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        }
568    
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            }
584    
585        }
586    
587        private void showVersion(boolean full) {
588            log.println(version(full ? "full" : "release"));
589        }
590    
591        private static final String versionRBName = "com.sun.tools.javap.resources.version";
592        private static ResourceBundle versionRB;
593    
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        }
611    
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                }
617    
618                public JavaFileObject getSource() {
619                    return null;
620                }
621    
622                public long getPosition() {
623                    return Diagnostic.NOPOS;
624                }
625    
626                public long getStartPosition() {
627                    return Diagnostic.NOPOS;
628                }
629    
630                public long getEndPosition() {
631                    return Diagnostic.NOPOS;
632                }
633    
634                public long getLineNumber() {
635                    return Diagnostic.NOPOS;
636                }
637    
638                public long getColumnNumber() {
639                    return Diagnostic.NOPOS;
640                }
641    
642                public String getCode() {
643                    return key;
644                }
645    
646                public String getMessage(Locale locale) {
647                    return JavapTask.this.getMessage(locale, key, args);
648                }
649    
650            };
651    
652        }
653    
654        private String getMessage(String key, Object... args) {
655            return getMessage(task_locale, key, args);
656        }
657    
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            }
665    
666            if (locale == null)
667                locale = Locale.getDefault();
668    
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            }
678    
679            try {
680                return MessageFormat.format(b.getString(key), args);
681            } catch (MissingResourceException e) {
682                throw new InternalError(e, key);
683            }
684        }
685    
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;
695    
696        private static final String progname = "javap";
697    
698        private static class SizeInputStream extends FilterInputStream {
699            SizeInputStream(InputStream in) {
700                super(in);
701            }
702    
703            int size() {
704                return size;
705            }
706    
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            }
714    
715            @Override
716            public int read() throws IOException {
717                int b = super.read();
718                size += 1;
719                return b;
720            }
721    
722            private int size;
723        }
724    }