JavapTask.java revision 2942:08092deced3f
1/*
2 * Copyright (c) 2007, 2014, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javap;
27
28import java.io.EOFException;
29import java.io.FileNotFoundException;
30import java.io.FilterInputStream;
31import java.io.InputStream;
32import java.io.IOException;
33import java.io.OutputStream;
34import java.io.PrintWriter;
35import java.io.Reader;
36import java.io.StringWriter;
37import java.io.Writer;
38import java.net.URI;
39import java.net.URISyntaxException;
40import java.net.URL;
41import java.net.URLConnection;
42import java.nio.file.NoSuchFileException;
43import java.security.DigestInputStream;
44import java.security.MessageDigest;
45import java.security.NoSuchAlgorithmException;
46import java.text.MessageFormat;
47import java.util.ArrayList;
48import java.util.Arrays;
49import java.util.EnumSet;
50import java.util.HashMap;
51import java.util.Iterator;
52import java.util.List;
53import java.util.Locale;
54import java.util.Map;
55import java.util.MissingResourceException;
56import java.util.Objects;
57import java.util.ResourceBundle;
58
59import javax.lang.model.element.Modifier;
60import javax.lang.model.element.NestingKind;
61import javax.tools.Diagnostic;
62import javax.tools.DiagnosticListener;
63import javax.tools.JavaFileManager;
64import javax.tools.JavaFileObject;
65import javax.tools.StandardJavaFileManager;
66import javax.tools.StandardLocation;
67
68import com.sun.tools.classfile.*;
69import com.sun.tools.javac.util.DefinedBy;
70import com.sun.tools.javac.util.DefinedBy.Api;
71
72/**
73 *  "Main" class for javap, normally accessed from the command line
74 *  via Main, or from JSR199 via DisassemblerTool.
75 *
76 *  <p><b>This is NOT part of any supported API.
77 *  If you write code that depends on this, you do so at your own risk.
78 *  This code and its internal interfaces are subject to change or
79 *  deletion without notice.</b>
80 */
81public class JavapTask implements DisassemblerTool.DisassemblerTask, Messages {
82    public class BadArgs extends Exception {
83        static final long serialVersionUID = 8765093759964640721L;
84        BadArgs(String key, Object... args) {
85            super(JavapTask.this.getMessage(key, args));
86            this.key = key;
87            this.args = args;
88        }
89
90        BadArgs showUsage(boolean b) {
91            showUsage = b;
92            return this;
93        }
94
95        final String key;
96        final Object[] args;
97        boolean showUsage;
98    }
99
100    static abstract class Option {
101        Option(boolean hasArg, String... aliases) {
102            this.hasArg = hasArg;
103            this.aliases = aliases;
104        }
105
106        boolean matches(String opt) {
107            for (String a: aliases) {
108                if (a.equals(opt))
109                    return true;
110            }
111            return false;
112        }
113
114        boolean ignoreRest() {
115            return false;
116        }
117
118        abstract void process(JavapTask task, String opt, String arg) throws BadArgs;
119
120        final boolean hasArg;
121        final String[] aliases;
122    }
123
124    static final Option[] recognizedOptions = {
125
126        new Option(false, "-help", "--help", "-?") {
127            void process(JavapTask task, String opt, String arg) {
128                task.options.help = true;
129            }
130        },
131
132        new Option(false, "-version") {
133            void process(JavapTask task, String opt, String arg) {
134                task.options.version = true;
135            }
136        },
137
138        new Option(false, "-fullversion") {
139            void process(JavapTask task, String opt, String arg) {
140                task.options.fullVersion = true;
141            }
142        },
143
144        new Option(false, "-v", "-verbose", "-all") {
145            void process(JavapTask task, String opt, String arg) {
146                task.options.verbose = true;
147                task.options.showDescriptors = true;
148                task.options.showFlags = true;
149                task.options.showAllAttrs = true;
150            }
151        },
152
153        new Option(false, "-l") {
154            void process(JavapTask task, String opt, String arg) {
155                task.options.showLineAndLocalVariableTables = true;
156            }
157        },
158
159        new Option(false, "-public") {
160            void process(JavapTask task, String opt, String arg) {
161                task.options.accessOptions.add(opt);
162                task.options.showAccess = AccessFlags.ACC_PUBLIC;
163            }
164        },
165
166        new Option(false, "-protected") {
167            void process(JavapTask task, String opt, String arg) {
168                task.options.accessOptions.add(opt);
169                task.options.showAccess = AccessFlags.ACC_PROTECTED;
170            }
171        },
172
173        new Option(false, "-package") {
174            void process(JavapTask task, String opt, String arg) {
175                task.options.accessOptions.add(opt);
176                task.options.showAccess = 0;
177            }
178        },
179
180        new Option(false, "-p", "-private") {
181            void process(JavapTask task, String opt, String arg) {
182                if (!task.options.accessOptions.contains("-p") &&
183                        !task.options.accessOptions.contains("-private")) {
184                    task.options.accessOptions.add(opt);
185                }
186                task.options.showAccess = AccessFlags.ACC_PRIVATE;
187            }
188        },
189
190        new Option(false, "-c") {
191            void process(JavapTask task, String opt, String arg) {
192                task.options.showDisassembled = true;
193            }
194        },
195
196        new Option(false, "-s") {
197            void process(JavapTask task, String opt, String arg) {
198                task.options.showDescriptors = true;
199            }
200        },
201
202        new Option(false, "-sysinfo") {
203            void process(JavapTask task, String opt, String arg) {
204                task.options.sysInfo = true;
205            }
206        },
207
208        new Option(false, "-XDdetails") {
209            void process(JavapTask task, String opt, String arg) {
210                task.options.details = EnumSet.allOf(InstructionDetailWriter.Kind.class);
211            }
212
213        },
214
215        new Option(false, "-XDdetails:") {
216            @Override
217            boolean matches(String opt) {
218                int sep = opt.indexOf(":");
219                return sep != -1 && super.matches(opt.substring(0, sep + 1));
220            }
221
222            void process(JavapTask task, String opt, String arg) throws BadArgs {
223                int sep = opt.indexOf(":");
224                for (String v: opt.substring(sep + 1).split("[,: ]+")) {
225                    if (!handleArg(task, v))
226                        throw task.new BadArgs("err.invalid.arg.for.option", v);
227                }
228            }
229
230            boolean handleArg(JavapTask task, String arg) {
231                if (arg.length() == 0)
232                    return true;
233
234                if (arg.equals("all")) {
235                    task.options.details = EnumSet.allOf(InstructionDetailWriter.Kind.class);
236                    return true;
237                }
238
239                boolean on = true;
240                if (arg.startsWith("-")) {
241                    on = false;
242                    arg = arg.substring(1);
243                }
244
245                for (InstructionDetailWriter.Kind k: InstructionDetailWriter.Kind.values()) {
246                    if (arg.equalsIgnoreCase(k.option)) {
247                        if (on)
248                            task.options.details.add(k);
249                        else
250                            task.options.details.remove(k);
251                        return true;
252                    }
253                }
254                return false;
255            }
256        },
257
258        new Option(false, "-constants") {
259            void process(JavapTask task, String opt, String arg) {
260                task.options.showConstants = true;
261            }
262        },
263
264        new Option(false, "-XDinner") {
265            void process(JavapTask task, String opt, String arg) {
266                task.options.showInnerClasses = true;
267            }
268        },
269
270        new Option(false, "-XDindent:") {
271            @Override
272            boolean matches(String opt) {
273                int sep = opt.indexOf(":");
274                return sep != -1 && super.matches(opt.substring(0, sep + 1));
275            }
276
277            void process(JavapTask task, String opt, String arg) throws BadArgs {
278                int sep = opt.indexOf(":");
279                try {
280                    int i = Integer.valueOf(opt.substring(sep + 1));
281                    if (i > 0) // silently ignore invalid values
282                        task.options.indentWidth = i;
283                } catch (NumberFormatException e) {
284                }
285            }
286        },
287
288        new Option(false, "-XDtab:") {
289            @Override
290            boolean matches(String opt) {
291                int sep = opt.indexOf(":");
292                return sep != -1 && super.matches(opt.substring(0, sep + 1));
293            }
294
295            void process(JavapTask task, String opt, String arg) throws BadArgs {
296                int sep = opt.indexOf(":");
297                try {
298                    int i = Integer.valueOf(opt.substring(sep + 1));
299                    if (i > 0) // silently ignore invalid values
300                        task.options.tabColumn = i;
301                } catch (NumberFormatException e) {
302                }
303            }
304        }
305
306    };
307
308    public JavapTask() {
309        context = new Context();
310        context.put(Messages.class, this);
311        options = Options.instance(context);
312        attributeFactory = new Attribute.Factory();
313    }
314
315    public JavapTask(Writer out,
316            JavaFileManager fileManager,
317            DiagnosticListener<? super JavaFileObject> diagnosticListener) {
318        this();
319        this.log = getPrintWriterForWriter(out);
320        this.fileManager = fileManager;
321        this.diagnosticListener = diagnosticListener;
322    }
323
324    public JavapTask(Writer out,
325            JavaFileManager fileManager,
326            DiagnosticListener<? super JavaFileObject> diagnosticListener,
327            Iterable<String> options,
328            Iterable<String> classes) {
329        this(out, fileManager, diagnosticListener);
330
331        this.classes = new ArrayList<>();
332        for (String classname: classes) {
333            Objects.requireNonNull(classname);
334            this.classes.add(classname);
335        }
336
337        try {
338            if (options != null)
339                handleOptions(options, false);
340        } catch (BadArgs e) {
341            throw new IllegalArgumentException(e.getMessage());
342        }
343    }
344
345    public void setLocale(Locale locale) {
346        if (locale == null)
347            locale = Locale.getDefault();
348        task_locale = locale;
349    }
350
351    public void setLog(Writer log) {
352        this.log = getPrintWriterForWriter(log);
353    }
354
355    public void setLog(OutputStream s) {
356        setLog(getPrintWriterForStream(s));
357    }
358
359    private static PrintWriter getPrintWriterForStream(OutputStream s) {
360        return new PrintWriter(s == null ? System.err : s, true);
361    }
362
363    private static PrintWriter getPrintWriterForWriter(Writer w) {
364        if (w == null)
365            return getPrintWriterForStream(null);
366        else if (w instanceof PrintWriter)
367            return (PrintWriter) w;
368        else
369            return new PrintWriter(w, true);
370    }
371
372    public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
373        diagnosticListener = dl;
374    }
375
376    public void setDiagnosticListener(OutputStream s) {
377        setDiagnosticListener(getDiagnosticListenerForStream(s));
378    }
379
380    private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
381        return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
382    }
383
384    private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) {
385        final PrintWriter pw = getPrintWriterForWriter(w);
386        return new DiagnosticListener<JavaFileObject> () {
387            @DefinedBy(Api.COMPILER)
388            public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
389                switch (diagnostic.getKind()) {
390                    case ERROR:
391                        pw.print(getMessage("err.prefix"));
392                        break;
393                    case WARNING:
394                        pw.print(getMessage("warn.prefix"));
395                        break;
396                    case NOTE:
397                        pw.print(getMessage("note.prefix"));
398                        break;
399                }
400                pw.print(" ");
401                pw.println(diagnostic.getMessage(null));
402            }
403        };
404    }
405
406    /** Result codes.
407     */
408    static final int
409        EXIT_OK = 0,        // Compilation completed with no errors.
410        EXIT_ERROR = 1,     // Completed but reported errors.
411        EXIT_CMDERR = 2,    // Bad command-line arguments
412        EXIT_SYSERR = 3,    // System error or resource exhaustion.
413        EXIT_ABNORMAL = 4;  // Compiler terminated abnormally
414
415    int run(String[] args) {
416        try {
417            try {
418                handleOptions(args);
419
420                // the following gives consistent behavior with javac
421                if (classes == null || classes.size() == 0) {
422                    if (options.help || options.version || options.fullVersion)
423                        return EXIT_OK;
424                    else
425                        return EXIT_CMDERR;
426                }
427
428                return run();
429            } finally {
430                if (defaultFileManager != null) {
431                    try {
432                        defaultFileManager.close();
433                        defaultFileManager = null;
434                    } catch (IOException e) {
435                        throw new InternalError(e);
436                    }
437                }
438            }
439        } catch (BadArgs e) {
440            reportError(e.key, e.args);
441            if (e.showUsage) {
442                printLines(getMessage("main.usage.summary", progname));
443            }
444            return EXIT_CMDERR;
445        } catch (InternalError e) {
446            Object[] e_args;
447            if (e.getCause() == null)
448                e_args = e.args;
449            else {
450                e_args = new Object[e.args.length + 1];
451                e_args[0] = e.getCause();
452                System.arraycopy(e.args, 0, e_args, 1, e.args.length);
453            }
454            reportError("err.internal.error", e_args);
455            return EXIT_ABNORMAL;
456        } finally {
457            log.flush();
458        }
459    }
460
461    public void handleOptions(String[] args) throws BadArgs {
462        handleOptions(Arrays.asList(args), true);
463    }
464
465    private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs {
466        if (log == null) {
467            log = getPrintWriterForStream(System.out);
468            if (diagnosticListener == null)
469              diagnosticListener = getDiagnosticListenerForStream(System.err);
470        } else {
471            if (diagnosticListener == null)
472              diagnosticListener = getDiagnosticListenerForWriter(log);
473        }
474
475
476        if (fileManager == null)
477            fileManager = getDefaultFileManager(diagnosticListener, log);
478
479        Iterator<String> iter = args.iterator();
480        boolean noArgs = !iter.hasNext();
481
482        while (iter.hasNext()) {
483            String arg = iter.next();
484            if (arg.startsWith("-"))
485                handleOption(arg, iter);
486            else if (allowClasses) {
487                if (classes == null)
488                    classes = new ArrayList<>();
489                classes.add(arg);
490                while (iter.hasNext())
491                    classes.add(iter.next());
492            } else
493                throw new BadArgs("err.unknown.option", arg).showUsage(true);
494        }
495
496        if (options.accessOptions.size() > 1) {
497            StringBuilder sb = new StringBuilder();
498            for (String opt: options.accessOptions) {
499                if (sb.length() > 0)
500                    sb.append(" ");
501                sb.append(opt);
502            }
503            throw new BadArgs("err.incompatible.options", sb);
504        }
505
506        if ((classes == null || classes.size() == 0) &&
507                !(noArgs || options.help || options.version || options.fullVersion)) {
508            throw new BadArgs("err.no.classes.specified");
509        }
510
511        if (noArgs || options.help)
512            showHelp();
513
514        if (options.version || options.fullVersion)
515            showVersion(options.fullVersion);
516    }
517
518    private void handleOption(String name, Iterator<String> rest) throws BadArgs {
519        for (Option o: recognizedOptions) {
520            if (o.matches(name)) {
521                if (o.hasArg) {
522                    if (rest.hasNext())
523                        o.process(this, name, rest.next());
524                    else
525                        throw new BadArgs("err.missing.arg", name).showUsage(true);
526                } else
527                    o.process(this, name, null);
528
529                if (o.ignoreRest()) {
530                    while (rest.hasNext())
531                        rest.next();
532                }
533                return;
534            }
535        }
536
537        try {
538            if (fileManager.handleOption(name, rest))
539                return;
540        } catch (IllegalArgumentException e) {
541            throw new BadArgs("err.invalid.use.of.option", name).showUsage(true);
542        }
543
544        throw new BadArgs("err.unknown.option", name).showUsage(true);
545    }
546
547    public Boolean call() {
548        return run() == 0;
549    }
550
551    public int run() {
552        if (classes == null || classes.isEmpty()) {
553            return EXIT_ERROR;
554        }
555
556        context.put(PrintWriter.class, log);
557        ClassWriter classWriter = ClassWriter.instance(context);
558        SourceWriter sourceWriter = SourceWriter.instance(context);
559        sourceWriter.setFileManager(fileManager);
560
561        int result = EXIT_OK;
562
563        for (String className: classes) {
564            try {
565                result = writeClass(classWriter, className);
566            } catch (ConstantPoolException e) {
567                reportError("err.bad.constant.pool", className, e.getLocalizedMessage());
568                result = EXIT_ERROR;
569            } catch (EOFException e) {
570                reportError("err.end.of.file", className);
571                result = EXIT_ERROR;
572            } catch (FileNotFoundException | NoSuchFileException e) {
573                reportError("err.file.not.found", e.getLocalizedMessage());
574                result = EXIT_ERROR;
575            } catch (IOException e) {
576                //e.printStackTrace();
577                Object msg = e.getLocalizedMessage();
578                if (msg == null) {
579                    msg = e;
580                }
581                reportError("err.ioerror", className, msg);
582                result = EXIT_ERROR;
583            } catch (OutOfMemoryError e) {
584                reportError("err.nomem");
585                result = EXIT_ERROR;
586            } catch (Throwable t) {
587                StringWriter sw = new StringWriter();
588                PrintWriter pw = new PrintWriter(sw);
589                t.printStackTrace(pw);
590                pw.close();
591                reportError("err.crash", t.toString(), sw.toString());
592                result = EXIT_ABNORMAL;
593            }
594        }
595
596        return result;
597    }
598
599    protected int writeClass(ClassWriter classWriter, String className)
600            throws IOException, ConstantPoolException {
601        JavaFileObject fo = open(className);
602        if (fo == null) {
603            reportError("err.class.not.found", className);
604            return EXIT_ERROR;
605        }
606
607        ClassFileInfo cfInfo = read(fo);
608        if (!className.endsWith(".class")) {
609            String cfName = cfInfo.cf.getName();
610            if (!cfName.replaceAll("[/$]", ".").equals(className.replaceAll("[/$]", "."))) {
611                reportWarning("warn.unexpected.class", className, cfName.replace('/', '.'));
612            }
613        }
614        write(cfInfo);
615
616        if (options.showInnerClasses) {
617            ClassFile cf = cfInfo.cf;
618            Attribute a = cf.getAttribute(Attribute.InnerClasses);
619            if (a instanceof InnerClasses_attribute) {
620                InnerClasses_attribute inners = (InnerClasses_attribute) a;
621                try {
622                    int result = EXIT_OK;
623                    for (int i = 0; i < inners.classes.length; i++) {
624                        int outerIndex = inners.classes[i].outer_class_info_index;
625                        ConstantPool.CONSTANT_Class_info outerClassInfo = cf.constant_pool.getClassInfo(outerIndex);
626                        String outerClassName = outerClassInfo.getName();
627                        if (outerClassName.equals(cf.getName())) {
628                            int innerIndex = inners.classes[i].inner_class_info_index;
629                            ConstantPool.CONSTANT_Class_info innerClassInfo = cf.constant_pool.getClassInfo(innerIndex);
630                            String innerClassName = innerClassInfo.getName();
631                            classWriter.println("// inner class " + innerClassName.replaceAll("[/$]", "."));
632                            classWriter.println();
633                            result = writeClass(classWriter, innerClassName);
634                            if (result != EXIT_OK) return result;
635                        }
636                    }
637                    return result;
638                } catch (ConstantPoolException e) {
639                    reportError("err.bad.innerclasses.attribute", className);
640                    return EXIT_ERROR;
641                }
642            } else if (a != null) {
643                reportError("err.bad.innerclasses.attribute", className);
644                return EXIT_ERROR;
645            }
646        }
647
648        return EXIT_OK;
649    }
650
651    protected JavaFileObject open(String className) throws IOException {
652        // for compatibility, first see if it is a class name
653        JavaFileObject fo = getClassFileObject(className);
654        if (fo != null)
655            return fo;
656
657        // see if it is an inner class, by replacing dots to $, starting from the right
658        String cn = className;
659        int lastDot;
660        while ((lastDot = cn.lastIndexOf(".")) != -1) {
661            cn = cn.substring(0, lastDot) + "$" + cn.substring(lastDot + 1);
662            fo = getClassFileObject(cn);
663            if (fo != null)
664                return fo;
665        }
666
667        if (!className.endsWith(".class"))
668            return null;
669
670        if (fileManager instanceof StandardJavaFileManager) {
671            StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
672            try {
673                fo = sfm.getJavaFileObjects(className).iterator().next();
674                if (fo != null && fo.getLastModified() != 0) {
675                    return fo;
676                }
677            } catch (IllegalArgumentException ignore) {
678            }
679        }
680
681        // see if it is a URL, and if so, wrap it in just enough of a JavaFileObject
682        // to suit javap's needs
683        if (className.matches("^[A-Za-z]+:.*")) {
684            try {
685                final URI uri = new URI(className);
686                final URL url = uri.toURL();
687                final URLConnection conn = url.openConnection();
688                conn.setUseCaches(false);
689                return new JavaFileObject() {
690                    @DefinedBy(Api.COMPILER)
691                    public Kind getKind() {
692                        return JavaFileObject.Kind.CLASS;
693                    }
694
695                    @DefinedBy(Api.COMPILER)
696                    public boolean isNameCompatible(String simpleName, Kind kind) {
697                        throw new UnsupportedOperationException();
698                    }
699
700                    @DefinedBy(Api.COMPILER)
701                    public NestingKind getNestingKind() {
702                        throw new UnsupportedOperationException();
703                    }
704
705                    @DefinedBy(Api.COMPILER)
706                    public Modifier getAccessLevel() {
707                        throw new UnsupportedOperationException();
708                    }
709
710                    @DefinedBy(Api.COMPILER)
711                    public URI toUri() {
712                        return uri;
713                    }
714
715                    @DefinedBy(Api.COMPILER)
716                    public String getName() {
717                        return uri.toString();
718                    }
719
720                    @DefinedBy(Api.COMPILER)
721                    public InputStream openInputStream() throws IOException {
722                        return conn.getInputStream();
723                    }
724
725                    @DefinedBy(Api.COMPILER)
726                    public OutputStream openOutputStream() throws IOException {
727                        throw new UnsupportedOperationException();
728                    }
729
730                    @DefinedBy(Api.COMPILER)
731                    public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
732                        throw new UnsupportedOperationException();
733                    }
734
735                    @DefinedBy(Api.COMPILER)
736                    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
737                        throw new UnsupportedOperationException();
738                    }
739
740                    @DefinedBy(Api.COMPILER)
741                    public Writer openWriter() throws IOException {
742                        throw new UnsupportedOperationException();
743                    }
744
745                    @DefinedBy(Api.COMPILER)
746                    public long getLastModified() {
747                        return conn.getLastModified();
748                    }
749
750                    @DefinedBy(Api.COMPILER)
751                    public boolean delete() {
752                        throw new UnsupportedOperationException();
753                    }
754
755                };
756            } catch (URISyntaxException | IOException ignore) {
757            }
758        }
759
760        return null;
761    }
762
763    public static class ClassFileInfo {
764        ClassFileInfo(JavaFileObject fo, ClassFile cf, byte[] digest, int size) {
765            this.fo = fo;
766            this.cf = cf;
767            this.digest = digest;
768            this.size = size;
769        }
770        public final JavaFileObject fo;
771        public final ClassFile cf;
772        public final byte[] digest;
773        public final int size;
774    }
775
776    public ClassFileInfo read(JavaFileObject fo) throws IOException, ConstantPoolException {
777        InputStream in = fo.openInputStream();
778        try {
779            SizeInputStream sizeIn = null;
780            MessageDigest md  = null;
781            if (options.sysInfo || options.verbose) {
782                try {
783                    md = MessageDigest.getInstance("MD5");
784                } catch (NoSuchAlgorithmException ignore) {
785                }
786                in = new DigestInputStream(in, md);
787                in = sizeIn = new SizeInputStream(in);
788            }
789
790            ClassFile cf = ClassFile.read(in, attributeFactory);
791            byte[] digest = (md == null) ? null : md.digest();
792            int size = (sizeIn == null) ? -1 : sizeIn.size();
793            return new ClassFileInfo(fo, cf, digest, size);
794        } finally {
795            in.close();
796        }
797    }
798
799    public void write(ClassFileInfo info) {
800        ClassWriter classWriter = ClassWriter.instance(context);
801        if (options.sysInfo || options.verbose) {
802            classWriter.setFile(info.fo.toUri());
803            classWriter.setLastModified(info.fo.getLastModified());
804            classWriter.setDigest("MD5", info.digest);
805            classWriter.setFileSize(info.size);
806        }
807
808        classWriter.write(info.cf);
809    }
810
811    protected void setClassFile(ClassFile classFile) {
812        ClassWriter classWriter = ClassWriter.instance(context);
813        classWriter.setClassFile(classFile);
814    }
815
816    protected void setMethod(Method enclosingMethod) {
817        ClassWriter classWriter = ClassWriter.instance(context);
818        classWriter.setMethod(enclosingMethod);
819    }
820
821    protected void write(Attribute value) {
822        AttributeWriter attrWriter = AttributeWriter.instance(context);
823        ClassWriter classWriter = ClassWriter.instance(context);
824        ClassFile cf = classWriter.getClassFile();
825        attrWriter.write(cf, value, cf.constant_pool);
826    }
827
828    protected void write(Attributes attrs) {
829        AttributeWriter attrWriter = AttributeWriter.instance(context);
830        ClassWriter classWriter = ClassWriter.instance(context);
831        ClassFile cf = classWriter.getClassFile();
832        attrWriter.write(cf, attrs, cf.constant_pool);
833    }
834
835    protected void write(ConstantPool constant_pool) {
836        ConstantWriter constantWriter = ConstantWriter.instance(context);
837        constantWriter.writeConstantPool(constant_pool);
838    }
839
840    protected void write(ConstantPool constant_pool, int value) {
841        ConstantWriter constantWriter = ConstantWriter.instance(context);
842        constantWriter.write(value);
843    }
844
845    protected void write(ConstantPool.CPInfo value) {
846        ConstantWriter constantWriter = ConstantWriter.instance(context);
847        constantWriter.println(value);
848    }
849
850    protected void write(Field value) {
851        ClassWriter classWriter = ClassWriter.instance(context);
852        classWriter.writeField(value);
853    }
854
855    protected void write(Method value) {
856        ClassWriter classWriter = ClassWriter.instance(context);
857        classWriter.writeMethod(value);
858    }
859
860    private JavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
861        if (defaultFileManager == null)
862            defaultFileManager = JavapFileManager.create(dl, log);
863        return defaultFileManager;
864    }
865
866    private JavaFileObject getClassFileObject(String className) throws IOException {
867        try {
868            JavaFileObject fo;
869            fo = fileManager.getJavaFileForInput(StandardLocation.PLATFORM_CLASS_PATH, className, JavaFileObject.Kind.CLASS);
870            if (fo == null)
871                fo = fileManager.getJavaFileForInput(StandardLocation.CLASS_PATH, className, JavaFileObject.Kind.CLASS);
872            return fo;
873        } catch (IllegalArgumentException e) {
874            return null;
875        }
876    }
877
878    private void showHelp() {
879        printLines(getMessage("main.usage", progname));
880        for (Option o: recognizedOptions) {
881            String name = o.aliases[0].substring(1); // there must always be at least one name
882            if (name.startsWith("X") || name.equals("fullversion") || name.equals("h") || name.equals("verify"))
883                continue;
884            printLines(getMessage("main.opt." + name));
885        }
886        String[] fmOptions = { "-classpath", "-cp", "-bootclasspath" };
887        for (String o: fmOptions) {
888            if (fileManager.isSupportedOption(o) == -1)
889                continue;
890            String name = o.substring(1);
891            printLines(getMessage("main.opt." + name));
892        }
893
894    }
895
896    private void showVersion(boolean full) {
897        printLines(version(full ? "full" : "release"));
898    }
899
900    private void printLines(String msg) {
901        log.println(msg.replace("\n", nl));
902    }
903
904    private static final String nl = System.getProperty("line.separator");
905
906    private static final String versionRBName = "com.sun.tools.javap.resources.version";
907    private static ResourceBundle versionRB;
908
909    private String version(String key) {
910        // key=version:  mm.nn.oo[-milestone]
911        // key=full:     mm.mm.oo[-milestone]-build
912        if (versionRB == null) {
913            try {
914                versionRB = ResourceBundle.getBundle(versionRBName);
915            } catch (MissingResourceException e) {
916                return getMessage("version.resource.missing", System.getProperty("java.version"));
917            }
918        }
919        try {
920            return versionRB.getString(key);
921        }
922        catch (MissingResourceException e) {
923            return getMessage("version.unknown", System.getProperty("java.version"));
924        }
925    }
926
927    private void reportError(String key, Object... args) {
928        diagnosticListener.report(createDiagnostic(Diagnostic.Kind.ERROR, key, args));
929    }
930
931    private void reportNote(String key, Object... args) {
932        diagnosticListener.report(createDiagnostic(Diagnostic.Kind.NOTE, key, args));
933    }
934
935    private void reportWarning(String key, Object... args) {
936        diagnosticListener.report(createDiagnostic(Diagnostic.Kind.WARNING, key, args));
937    }
938
939    private Diagnostic<JavaFileObject> createDiagnostic(
940            final Diagnostic.Kind kind, final String key, final Object... args) {
941        return new Diagnostic<JavaFileObject>() {
942            @DefinedBy(Api.COMPILER)
943            public Kind getKind() {
944                return kind;
945            }
946
947            @DefinedBy(Api.COMPILER)
948            public JavaFileObject getSource() {
949                return null;
950            }
951
952            @DefinedBy(Api.COMPILER)
953            public long getPosition() {
954                return Diagnostic.NOPOS;
955            }
956
957            @DefinedBy(Api.COMPILER)
958            public long getStartPosition() {
959                return Diagnostic.NOPOS;
960            }
961
962            @DefinedBy(Api.COMPILER)
963            public long getEndPosition() {
964                return Diagnostic.NOPOS;
965            }
966
967            @DefinedBy(Api.COMPILER)
968            public long getLineNumber() {
969                return Diagnostic.NOPOS;
970            }
971
972            @DefinedBy(Api.COMPILER)
973            public long getColumnNumber() {
974                return Diagnostic.NOPOS;
975            }
976
977            @DefinedBy(Api.COMPILER)
978            public String getCode() {
979                return key;
980            }
981
982            @DefinedBy(Api.COMPILER)
983            public String getMessage(Locale locale) {
984                return JavapTask.this.getMessage(locale, key, args);
985            }
986
987            @Override
988            public String toString() {
989                return getClass().getName() + "[key=" + key + ",args=" + Arrays.asList(args) + "]";
990            }
991
992        };
993
994    }
995
996    public String getMessage(String key, Object... args) {
997        return getMessage(task_locale, key, args);
998    }
999
1000    public String getMessage(Locale locale, String key, Object... args) {
1001        if (bundles == null) {
1002            // could make this a HashMap<Locale,SoftReference<ResourceBundle>>
1003            // and for efficiency, keep a hard reference to the bundle for the task
1004            // locale
1005            bundles = new HashMap<>();
1006        }
1007
1008        if (locale == null)
1009            locale = Locale.getDefault();
1010
1011        ResourceBundle b = bundles.get(locale);
1012        if (b == null) {
1013            try {
1014                b = ResourceBundle.getBundle("com.sun.tools.javap.resources.javap", locale);
1015                bundles.put(locale, b);
1016            } catch (MissingResourceException e) {
1017                throw new InternalError("Cannot find javap resource bundle for locale " + locale);
1018            }
1019        }
1020
1021        try {
1022            return MessageFormat.format(b.getString(key), args);
1023        } catch (MissingResourceException e) {
1024            throw new InternalError(e, key);
1025        }
1026    }
1027
1028    protected Context context;
1029    JavaFileManager fileManager;
1030    JavaFileManager defaultFileManager;
1031    PrintWriter log;
1032    DiagnosticListener<? super JavaFileObject> diagnosticListener;
1033    List<String> classes;
1034    Options options;
1035    //ResourceBundle bundle;
1036    Locale task_locale;
1037    Map<Locale, ResourceBundle> bundles;
1038    protected Attribute.Factory attributeFactory;
1039
1040    private static final String progname = "javap";
1041
1042    private static class SizeInputStream extends FilterInputStream {
1043        SizeInputStream(InputStream in) {
1044            super(in);
1045        }
1046
1047        int size() {
1048            return size;
1049        }
1050
1051        @Override
1052        public int read(byte[] buf, int offset, int length) throws IOException {
1053            int n = super.read(buf, offset, length);
1054            if (n > 0)
1055                size += n;
1056            return n;
1057        }
1058
1059        @Override
1060        public int read() throws IOException {
1061            int b = super.read();
1062            size += 1;
1063            return b;
1064        }
1065
1066        private int size;
1067    }
1068}
1069