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