JavapTask.java revision 3770:d813bfb238a9
1230557Sjimharris/*
2230557Sjimharris * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3230557Sjimharris * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4230557Sjimharris *
5230557Sjimharris * This code is free software; you can redistribute it and/or modify it
6230557Sjimharris * under the terms of the GNU General Public License version 2 only, as
7230557Sjimharris * published by the Free Software Foundation.  Oracle designates this
8230557Sjimharris * particular file as subject to the "Classpath" exception as provided
9230557Sjimharris * by Oracle in the LICENSE file that accompanied this code.
10230557Sjimharris *
11230557Sjimharris * This code is distributed in the hope that it will be useful, but WITHOUT
12230557Sjimharris * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13230557Sjimharris * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14230557Sjimharris * version 2 for more details (a copy is included in the LICENSE file that
15230557Sjimharris * accompanied this code).
16230557Sjimharris *
17230557Sjimharris * You should have received a copy of the GNU General Public License version
18230557Sjimharris * 2 along with this work; if not, write to the Free Software Foundation,
19230557Sjimharris * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20230557Sjimharris *
21230557Sjimharris * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22230557Sjimharris * or visit www.oracle.com if you need additional information or have any
23230557Sjimharris * questions.
24230557Sjimharris */
25230557Sjimharris
26230557Sjimharrispackage com.sun.tools.javap;
27230557Sjimharris
28230557Sjimharrisimport java.io.EOFException;
29230557Sjimharrisimport java.io.FileNotFoundException;
30230557Sjimharrisimport java.io.FilterInputStream;
31230557Sjimharrisimport java.io.InputStream;
32230557Sjimharrisimport java.io.IOException;
33230557Sjimharrisimport java.io.OutputStream;
34230557Sjimharrisimport java.io.PrintWriter;
35230557Sjimharrisimport java.io.Reader;
36230557Sjimharrisimport java.io.StringWriter;
37230557Sjimharrisimport java.io.Writer;
38230557Sjimharrisimport java.net.URI;
39230557Sjimharrisimport java.net.URISyntaxException;
40230557Sjimharrisimport java.net.URL;
41230557Sjimharrisimport java.net.URLConnection;
42230557Sjimharrisimport java.nio.file.NoSuchFileException;
43230557Sjimharrisimport java.security.DigestInputStream;
44230557Sjimharrisimport java.security.MessageDigest;
45230557Sjimharrisimport java.security.NoSuchAlgorithmException;
46230557Sjimharrisimport java.text.MessageFormat;
47230557Sjimharrisimport java.util.ArrayList;
48230557Sjimharrisimport java.util.Arrays;
49230557Sjimharrisimport java.util.EnumSet;
50230557Sjimharrisimport java.util.HashMap;
51230557Sjimharrisimport java.util.Iterator;
52230557Sjimharrisimport java.util.List;
53230557Sjimharrisimport java.util.Locale;
54230557Sjimharrisimport java.util.Map;
55230557Sjimharrisimport java.util.MissingResourceException;
56230557Sjimharrisimport java.util.Objects;
57230557Sjimharrisimport java.util.ResourceBundle;
58230557Sjimharrisimport java.util.Set;
59230557Sjimharris
60230557Sjimharrisimport javax.lang.model.element.Modifier;
61230557Sjimharrisimport javax.lang.model.element.NestingKind;
62230557Sjimharrisimport javax.tools.Diagnostic;
63230557Sjimharrisimport javax.tools.DiagnosticListener;
64230557Sjimharrisimport javax.tools.JavaFileManager;
65230557Sjimharrisimport javax.tools.JavaFileManager.Location;
66230557Sjimharrisimport javax.tools.JavaFileObject;
67230557Sjimharrisimport javax.tools.StandardJavaFileManager;
68230557Sjimharrisimport javax.tools.StandardLocation;
69230557Sjimharris
70230557Sjimharrisimport com.sun.tools.classfile.*;
71230557Sjimharris
72230557Sjimharris/**
73230557Sjimharris *  "Main" class for javap, normally accessed from the command line
74230557Sjimharris *  via Main, or from JSR199 via DisassemblerTool.
75230557Sjimharris *
76230557Sjimharris *  <p><b>This is NOT part of any supported API.
77230557Sjimharris *  If you write code that depends on this, you do so at your own risk.
78230557Sjimharris *  This code and its internal interfaces are subject to change or
79230557Sjimharris *  deletion without notice.</b>
80230557Sjimharris */
81230557Sjimharrispublic class JavapTask implements DisassemblerTool.DisassemblerTask, Messages {
82230557Sjimharris    public class BadArgs extends Exception {
83230557Sjimharris        static final long serialVersionUID = 8765093759964640721L;
84230557Sjimharris        BadArgs(String key, Object... args) {
85230557Sjimharris            super(JavapTask.this.getMessage(key, args));
86230557Sjimharris            this.key = key;
87230557Sjimharris            this.args = args;
88230557Sjimharris        }
89230557Sjimharris
90230557Sjimharris        BadArgs showUsage(boolean b) {
91230557Sjimharris            showUsage = b;
92230557Sjimharris            return this;
93230557Sjimharris        }
94230557Sjimharris
95230557Sjimharris        final String key;
96230557Sjimharris        final Object[] args;
97230557Sjimharris        boolean showUsage;
98230557Sjimharris    }
99230557Sjimharris
100230557Sjimharris    static abstract class Option {
101230557Sjimharris        Option(boolean hasArg, String... aliases) {
102230557Sjimharris            this.hasArg = hasArg;
103230557Sjimharris            this.aliases = aliases;
104230557Sjimharris        }
105230557Sjimharris
106230557Sjimharris        boolean matches(String opt) {
107230557Sjimharris            for (String a: aliases) {
108230557Sjimharris                if (a.equals(opt))
109230557Sjimharris                    return true;
110230557Sjimharris            }
111230557Sjimharris            return false;
112230557Sjimharris        }
113230557Sjimharris
114230557Sjimharris        boolean ignoreRest() {
115230557Sjimharris            return false;
116230557Sjimharris        }
117230557Sjimharris
118230557Sjimharris        abstract void process(JavapTask task, String opt, String arg) throws BadArgs;
119230557Sjimharris
120230557Sjimharris        final boolean hasArg;
121230557Sjimharris        final String[] aliases;
122230557Sjimharris    }
123230557Sjimharris
124230557Sjimharris    static final Option[] recognizedOptions = {
125230557Sjimharris
126230557Sjimharris        new Option(false, "-help", "--help", "-?") {
127230557Sjimharris            @Override
128230557Sjimharris            void process(JavapTask task, String opt, String arg) {
129230557Sjimharris                task.options.help = true;
130230557Sjimharris            }
131230557Sjimharris        },
132230557Sjimharris
133230557Sjimharris        new Option(false, "-version") {
134230557Sjimharris            @Override
135230557Sjimharris            void process(JavapTask task, String opt, String arg) {
136230557Sjimharris                task.options.version = true;
137230557Sjimharris            }
138230557Sjimharris        },
139230557Sjimharris
140230557Sjimharris        new Option(false, "-fullversion") {
141230557Sjimharris            @Override
142230557Sjimharris            void process(JavapTask task, String opt, String arg) {
143230557Sjimharris                task.options.fullVersion = true;
144230557Sjimharris            }
145230557Sjimharris        },
146230557Sjimharris
147230557Sjimharris        new Option(false, "-v", "-verbose", "-all") {
148230557Sjimharris            @Override
149230557Sjimharris            void process(JavapTask task, String opt, String arg) {
150230557Sjimharris                task.options.verbose = true;
151230557Sjimharris                task.options.showDescriptors = true;
152230557Sjimharris                task.options.showFlags = true;
153230557Sjimharris                task.options.showAllAttrs = true;
154230557Sjimharris            }
155230557Sjimharris        },
156230557Sjimharris
157230557Sjimharris        new Option(false, "-l") {
158230557Sjimharris            @Override
159230557Sjimharris            void process(JavapTask task, String opt, String arg) {
160230557Sjimharris                task.options.showLineAndLocalVariableTables = true;
161230557Sjimharris            }
162230557Sjimharris        },
163230557Sjimharris
164230557Sjimharris        new Option(false, "-public") {
165230557Sjimharris            @Override
166230557Sjimharris            void process(JavapTask task, String opt, String arg) {
167230557Sjimharris                task.options.accessOptions.add(opt);
168230557Sjimharris                task.options.showAccess = AccessFlags.ACC_PUBLIC;
169230557Sjimharris            }
170230557Sjimharris        },
171230557Sjimharris
172230557Sjimharris        new Option(false, "-protected") {
173230557Sjimharris            @Override
174230557Sjimharris            void process(JavapTask task, String opt, String arg) {
175230557Sjimharris                task.options.accessOptions.add(opt);
176230557Sjimharris                task.options.showAccess = AccessFlags.ACC_PROTECTED;
177230557Sjimharris            }
178230557Sjimharris        },
179230557Sjimharris
180230557Sjimharris        new Option(false, "-package") {
181230557Sjimharris            @Override
182230557Sjimharris            void process(JavapTask task, String opt, String arg) {
183230557Sjimharris                task.options.accessOptions.add(opt);
184230557Sjimharris                task.options.showAccess = 0;
185230557Sjimharris            }
186230557Sjimharris        },
187230557Sjimharris
188230557Sjimharris        new Option(false, "-p", "-private") {
189230557Sjimharris            @Override
190230557Sjimharris            void process(JavapTask task, String opt, String arg) {
191230557Sjimharris                if (!task.options.accessOptions.contains("-p") &&
192230557Sjimharris                        !task.options.accessOptions.contains("-private")) {
193230557Sjimharris                    task.options.accessOptions.add(opt);
194230557Sjimharris                }
195230557Sjimharris                task.options.showAccess = AccessFlags.ACC_PRIVATE;
196230557Sjimharris            }
197230557Sjimharris        },
198230557Sjimharris
199230557Sjimharris        new Option(false, "-c") {
200230557Sjimharris            @Override
201230557Sjimharris            void process(JavapTask task, String opt, String arg) {
202230557Sjimharris                task.options.showDisassembled = true;
203230557Sjimharris            }
204230557Sjimharris        },
205230557Sjimharris
206230557Sjimharris        new Option(false, "-s") {
207230557Sjimharris            @Override
208230557Sjimharris            void process(JavapTask task, String opt, String arg) {
209230557Sjimharris                task.options.showDescriptors = true;
210230557Sjimharris            }
211230557Sjimharris        },
212230557Sjimharris
213230557Sjimharris        new Option(false, "-sysinfo") {
214230557Sjimharris            @Override
215230557Sjimharris            void process(JavapTask task, String opt, String arg) {
216230557Sjimharris                task.options.sysInfo = true;
217230557Sjimharris            }
218230557Sjimharris        },
219230557Sjimharris
220230557Sjimharris        new Option(false, "-XDdetails") {
221230557Sjimharris            @Override
222230557Sjimharris            void process(JavapTask task, String opt, String arg) {
223230557Sjimharris                task.options.details = EnumSet.allOf(InstructionDetailWriter.Kind.class);
224230557Sjimharris            }
225230557Sjimharris
226230557Sjimharris        },
227230557Sjimharris
228230557Sjimharris        new Option(false, "-XDdetails:") {
229230557Sjimharris            @Override
230230557Sjimharris            boolean matches(String opt) {
231230557Sjimharris                int sep = opt.indexOf(":");
232230557Sjimharris                return sep != -1 && super.matches(opt.substring(0, sep + 1));
233230557Sjimharris            }
234230557Sjimharris
235230557Sjimharris            @Override
236230557Sjimharris            void process(JavapTask task, String opt, String arg) throws BadArgs {
237230557Sjimharris                int sep = opt.indexOf(":");
238230557Sjimharris                for (String v: opt.substring(sep + 1).split("[,: ]+")) {
239230557Sjimharris                    if (!handleArg(task, v))
240230557Sjimharris                        throw task.new BadArgs("err.invalid.arg.for.option", v);
241230557Sjimharris                }
242230557Sjimharris            }
243230557Sjimharris
244230557Sjimharris            boolean handleArg(JavapTask task, String arg) {
245230557Sjimharris                if (arg.length() == 0)
246230557Sjimharris                    return true;
247230557Sjimharris
248230557Sjimharris                if (arg.equals("all")) {
249230557Sjimharris                    task.options.details = EnumSet.allOf(InstructionDetailWriter.Kind.class);
250230557Sjimharris                    return true;
251230557Sjimharris                }
252230557Sjimharris
253230557Sjimharris                boolean on = true;
254230557Sjimharris                if (arg.startsWith("-")) {
255230557Sjimharris                    on = false;
256230557Sjimharris                    arg = arg.substring(1);
257230557Sjimharris                }
258230557Sjimharris
259230557Sjimharris                for (InstructionDetailWriter.Kind k: InstructionDetailWriter.Kind.values()) {
260230557Sjimharris                    if (arg.equalsIgnoreCase(k.option)) {
261230557Sjimharris                        if (on)
262230557Sjimharris                            task.options.details.add(k);
263230557Sjimharris                        else
264230557Sjimharris                            task.options.details.remove(k);
265230557Sjimharris                        return true;
266230557Sjimharris                    }
267230557Sjimharris                }
268230557Sjimharris                return false;
269230557Sjimharris            }
270230557Sjimharris        },
271230557Sjimharris
272230557Sjimharris        new Option(false, "-constants") {
273230557Sjimharris            @Override
274230557Sjimharris            void process(JavapTask task, String opt, String arg) {
275230557Sjimharris                task.options.showConstants = true;
276            }
277        },
278
279        new Option(false, "-XDinner") {
280            @Override
281            void process(JavapTask task, String opt, String arg) {
282                task.options.showInnerClasses = true;
283            }
284        },
285
286        new Option(false, "-XDindent:") {
287            @Override
288            boolean matches(String opt) {
289                int sep = opt.indexOf(":");
290                return sep != -1 && super.matches(opt.substring(0, sep + 1));
291            }
292
293            @Override
294            void process(JavapTask task, String opt, String arg) throws BadArgs {
295                int sep = opt.indexOf(":");
296                try {
297                    int i = Integer.valueOf(opt.substring(sep + 1));
298                    if (i > 0) // silently ignore invalid values
299                        task.options.indentWidth = i;
300                } catch (NumberFormatException e) {
301                }
302            }
303        },
304
305        new Option(false, "-XDtab:") {
306            @Override
307            boolean matches(String opt) {
308                int sep = opt.indexOf(":");
309                return sep != -1 && super.matches(opt.substring(0, sep + 1));
310            }
311
312            @Override
313            void process(JavapTask task, String opt, String arg) throws BadArgs {
314                int sep = opt.indexOf(":");
315                try {
316                    int i = Integer.valueOf(opt.substring(sep + 1));
317                    if (i > 0) // silently ignore invalid values
318                        task.options.tabColumn = i;
319                } catch (NumberFormatException e) {
320                }
321            }
322        },
323
324        new Option(true, "--module", "-m") {
325            @Override
326            void process(JavapTask task, String opt, String arg) throws BadArgs {
327                task.options.moduleName = arg;
328            }
329        }
330
331    };
332
333    public JavapTask() {
334        context = new Context();
335        context.put(Messages.class, this);
336        options = Options.instance(context);
337        attributeFactory = new Attribute.Factory();
338    }
339
340    public JavapTask(Writer out,
341            JavaFileManager fileManager,
342            DiagnosticListener<? super JavaFileObject> diagnosticListener) {
343        this();
344        this.log = getPrintWriterForWriter(out);
345        this.fileManager = fileManager;
346        this.diagnosticListener = diagnosticListener;
347    }
348
349    public JavapTask(Writer out,
350            JavaFileManager fileManager,
351            DiagnosticListener<? super JavaFileObject> diagnosticListener,
352            Iterable<String> options,
353            Iterable<String> classes) {
354        this(out, fileManager, diagnosticListener);
355
356        this.classes = new ArrayList<>();
357        for (String classname: classes) {
358            Objects.requireNonNull(classname);
359            this.classes.add(classname);
360        }
361
362        try {
363            if (options != null)
364                handleOptions(options, false);
365        } catch (BadArgs e) {
366            throw new IllegalArgumentException(e.getMessage());
367        }
368    }
369
370    public void setLocale(Locale locale) {
371        if (locale == null)
372            locale = Locale.getDefault();
373        task_locale = locale;
374    }
375
376    public void setLog(Writer log) {
377        this.log = getPrintWriterForWriter(log);
378    }
379
380    public void setLog(OutputStream s) {
381        setLog(getPrintWriterForStream(s));
382    }
383
384    private static PrintWriter getPrintWriterForStream(OutputStream s) {
385        return new PrintWriter(s == null ? System.err : s, true);
386    }
387
388    private static PrintWriter getPrintWriterForWriter(Writer w) {
389        if (w == null)
390            return getPrintWriterForStream(null);
391        else if (w instanceof PrintWriter)
392            return (PrintWriter) w;
393        else
394            return new PrintWriter(w, true);
395    }
396
397    public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
398        diagnosticListener = dl;
399    }
400
401    public void setDiagnosticListener(OutputStream s) {
402        setDiagnosticListener(getDiagnosticListenerForStream(s));
403    }
404
405    private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
406        return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
407    }
408
409    private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) {
410        final PrintWriter pw = getPrintWriterForWriter(w);
411        return new DiagnosticListener<JavaFileObject> () {
412            public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
413                switch (diagnostic.getKind()) {
414                    case ERROR:
415                        pw.print(getMessage("err.prefix"));
416                        break;
417                    case WARNING:
418                        pw.print(getMessage("warn.prefix"));
419                        break;
420                    case NOTE:
421                        pw.print(getMessage("note.prefix"));
422                        break;
423                }
424                pw.print(" ");
425                pw.println(diagnostic.getMessage(null));
426            }
427        };
428    }
429
430    /** Result codes.
431     */
432    static final int
433        EXIT_OK = 0,        // Compilation completed with no errors.
434        EXIT_ERROR = 1,     // Completed but reported errors.
435        EXIT_CMDERR = 2,    // Bad command-line arguments
436        EXIT_SYSERR = 3,    // System error or resource exhaustion.
437        EXIT_ABNORMAL = 4;  // Compiler terminated abnormally
438
439    int run(String[] args) {
440        try {
441            try {
442                handleOptions(args);
443
444                // the following gives consistent behavior with javac
445                if (classes == null || classes.size() == 0) {
446                    if (options.help || options.version || options.fullVersion)
447                        return EXIT_OK;
448                    else
449                        return EXIT_CMDERR;
450                }
451
452                return run();
453            } finally {
454                if (defaultFileManager != null) {
455                    try {
456                        defaultFileManager.close();
457                        defaultFileManager = null;
458                    } catch (IOException e) {
459                        throw new InternalError(e);
460                    }
461                }
462            }
463        } catch (BadArgs e) {
464            reportError(e.key, e.args);
465            if (e.showUsage) {
466                printLines(getMessage("main.usage.summary", progname));
467            }
468            return EXIT_CMDERR;
469        } catch (InternalError e) {
470            Object[] e_args;
471            if (e.getCause() == null)
472                e_args = e.args;
473            else {
474                e_args = new Object[e.args.length + 1];
475                e_args[0] = e.getCause();
476                System.arraycopy(e.args, 0, e_args, 1, e.args.length);
477            }
478            reportError("err.internal.error", e_args);
479            return EXIT_ABNORMAL;
480        } finally {
481            log.flush();
482        }
483    }
484
485    public void handleOptions(String[] args) throws BadArgs {
486        handleOptions(Arrays.asList(args), true);
487    }
488
489    private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs {
490        if (log == null) {
491            log = getPrintWriterForStream(System.out);
492            if (diagnosticListener == null)
493              diagnosticListener = getDiagnosticListenerForStream(System.err);
494        } else {
495            if (diagnosticListener == null)
496              diagnosticListener = getDiagnosticListenerForWriter(log);
497        }
498
499
500        if (fileManager == null)
501            fileManager = getDefaultFileManager(diagnosticListener, log);
502
503        Iterator<String> iter = args.iterator();
504        boolean noArgs = !iter.hasNext();
505
506        while (iter.hasNext()) {
507            String arg = iter.next();
508            if (arg.startsWith("-"))
509                handleOption(arg, iter);
510            else if (allowClasses) {
511                if (classes == null)
512                    classes = new ArrayList<>();
513                classes.add(arg);
514                while (iter.hasNext())
515                    classes.add(iter.next());
516            } else
517                throw new BadArgs("err.unknown.option", arg).showUsage(true);
518        }
519
520        if (options.accessOptions.size() > 1) {
521            StringBuilder sb = new StringBuilder();
522            for (String opt: options.accessOptions) {
523                if (sb.length() > 0)
524                    sb.append(" ");
525                sb.append(opt);
526            }
527            throw new BadArgs("err.incompatible.options", sb);
528        }
529
530        if ((classes == null || classes.size() == 0) &&
531                !(noArgs || options.help || options.version || options.fullVersion)) {
532            throw new BadArgs("err.no.classes.specified");
533        }
534
535        if (noArgs || options.help)
536            showHelp();
537
538        if (options.version || options.fullVersion)
539            showVersion(options.fullVersion);
540    }
541
542    private void handleOption(String name, Iterator<String> rest) throws BadArgs {
543        for (Option o: recognizedOptions) {
544            if (o.matches(name)) {
545                if (o.hasArg) {
546                    if (rest.hasNext())
547                        o.process(this, name, rest.next());
548                    else
549                        throw new BadArgs("err.missing.arg", name).showUsage(true);
550                } else
551                    o.process(this, name, null);
552
553                if (o.ignoreRest()) {
554                    while (rest.hasNext())
555                        rest.next();
556                }
557                return;
558            }
559        }
560
561        try {
562            if (fileManager.handleOption(name, rest))
563                return;
564        } catch (IllegalArgumentException e) {
565            throw new BadArgs("err.invalid.use.of.option", name).showUsage(true);
566        }
567
568        throw new BadArgs("err.unknown.option", name).showUsage(true);
569    }
570
571    public Boolean call() {
572        return run() == 0;
573    }
574
575    public int run() {
576        if (classes == null || classes.isEmpty()) {
577            return EXIT_ERROR;
578        }
579
580        context.put(PrintWriter.class, log);
581        ClassWriter classWriter = ClassWriter.instance(context);
582        SourceWriter sourceWriter = SourceWriter.instance(context);
583        sourceWriter.setFileManager(fileManager);
584
585        if (options.moduleName != null) {
586            try {
587                moduleLocation = findModule(options.moduleName);
588                if (moduleLocation == null) {
589                    reportError("err.cant.find.module", options.moduleName);
590                    return EXIT_ERROR;
591                }
592            } catch (IOException e) {
593                reportError("err.cant.find.module.ex", options.moduleName, e);
594                return EXIT_ERROR;
595            }
596        }
597
598        int result = EXIT_OK;
599
600        for (String className: classes) {
601            try {
602                result = writeClass(classWriter, className);
603            } catch (ConstantPoolException e) {
604                reportError("err.bad.constant.pool", className, e.getLocalizedMessage());
605                result = EXIT_ERROR;
606            } catch (EOFException e) {
607                reportError("err.end.of.file", className);
608                result = EXIT_ERROR;
609            } catch (FileNotFoundException | NoSuchFileException e) {
610                reportError("err.file.not.found", e.getLocalizedMessage());
611                result = EXIT_ERROR;
612            } catch (IOException e) {
613                //e.printStackTrace();
614                Object msg = e.getLocalizedMessage();
615                if (msg == null) {
616                    msg = e;
617                }
618                reportError("err.ioerror", className, msg);
619                result = EXIT_ERROR;
620            } catch (OutOfMemoryError e) {
621                reportError("err.nomem");
622                result = EXIT_ERROR;
623            } catch (Throwable t) {
624                StringWriter sw = new StringWriter();
625                PrintWriter pw = new PrintWriter(sw);
626                t.printStackTrace(pw);
627                pw.close();
628                reportError("err.crash", t.toString(), sw.toString());
629                result = EXIT_ABNORMAL;
630            }
631        }
632
633        return result;
634    }
635
636    protected int writeClass(ClassWriter classWriter, String className)
637            throws IOException, ConstantPoolException {
638        JavaFileObject fo = open(className);
639        if (fo == null) {
640            reportError("err.class.not.found", className);
641            return EXIT_ERROR;
642        }
643
644        ClassFileInfo cfInfo = read(fo);
645        if (!className.endsWith(".class")) {
646            String cfName = cfInfo.cf.getName();
647            if (!cfName.replaceAll("[/$]", ".").equals(className.replaceAll("[/$]", "."))) {
648                reportWarning("warn.unexpected.class", className, cfName.replace('/', '.'));
649            }
650        }
651        write(cfInfo);
652
653        if (options.showInnerClasses) {
654            ClassFile cf = cfInfo.cf;
655            Attribute a = cf.getAttribute(Attribute.InnerClasses);
656            if (a instanceof InnerClasses_attribute) {
657                InnerClasses_attribute inners = (InnerClasses_attribute) a;
658                try {
659                    int result = EXIT_OK;
660                    for (int i = 0; i < inners.classes.length; i++) {
661                        int outerIndex = inners.classes[i].outer_class_info_index;
662                        ConstantPool.CONSTANT_Class_info outerClassInfo = cf.constant_pool.getClassInfo(outerIndex);
663                        String outerClassName = outerClassInfo.getName();
664                        if (outerClassName.equals(cf.getName())) {
665                            int innerIndex = inners.classes[i].inner_class_info_index;
666                            ConstantPool.CONSTANT_Class_info innerClassInfo = cf.constant_pool.getClassInfo(innerIndex);
667                            String innerClassName = innerClassInfo.getName();
668                            classWriter.println("// inner class " + innerClassName.replaceAll("[/$]", "."));
669                            classWriter.println();
670                            result = writeClass(classWriter, innerClassName);
671                            if (result != EXIT_OK) return result;
672                        }
673                    }
674                    return result;
675                } catch (ConstantPoolException e) {
676                    reportError("err.bad.innerclasses.attribute", className);
677                    return EXIT_ERROR;
678                }
679            } else if (a != null) {
680                reportError("err.bad.innerclasses.attribute", className);
681                return EXIT_ERROR;
682            }
683        }
684
685        return EXIT_OK;
686    }
687
688    protected JavaFileObject open(String className) throws IOException {
689        // for compatibility, first see if it is a class name
690        JavaFileObject fo = getClassFileObject(className);
691        if (fo != null)
692            return fo;
693
694        // see if it is an inner class, by replacing dots to $, starting from the right
695        String cn = className;
696        int lastDot;
697        while ((lastDot = cn.lastIndexOf(".")) != -1) {
698            cn = cn.substring(0, lastDot) + "$" + cn.substring(lastDot + 1);
699            fo = getClassFileObject(cn);
700            if (fo != null)
701                return fo;
702        }
703
704        if (!className.endsWith(".class"))
705            return null;
706
707        if (fileManager instanceof StandardJavaFileManager) {
708            StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
709            try {
710                fo = sfm.getJavaFileObjects(className).iterator().next();
711                if (fo != null && fo.getLastModified() != 0) {
712                    return fo;
713                }
714            } catch (IllegalArgumentException ignore) {
715            }
716        }
717
718        // see if it is a URL, and if so, wrap it in just enough of a JavaFileObject
719        // to suit javap's needs
720        if (className.matches("^[A-Za-z]+:.*")) {
721            try {
722                final URI uri = new URI(className);
723                final URL url = uri.toURL();
724                final URLConnection conn = url.openConnection();
725                conn.setUseCaches(false);
726                return new JavaFileObject() {
727                    public Kind getKind() {
728                        return JavaFileObject.Kind.CLASS;
729                    }
730
731                    public boolean isNameCompatible(String simpleName, Kind kind) {
732                        throw new UnsupportedOperationException();
733                    }
734
735                    public NestingKind getNestingKind() {
736                        throw new UnsupportedOperationException();
737                    }
738
739                    public Modifier getAccessLevel() {
740                        throw new UnsupportedOperationException();
741                    }
742
743                    public URI toUri() {
744                        return uri;
745                    }
746
747                    public String getName() {
748                        return uri.toString();
749                    }
750
751                    public InputStream openInputStream() throws IOException {
752                        return conn.getInputStream();
753                    }
754
755                    public OutputStream openOutputStream() throws IOException {
756                        throw new UnsupportedOperationException();
757                    }
758
759                    public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
760                        throw new UnsupportedOperationException();
761                    }
762
763                    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
764                        throw new UnsupportedOperationException();
765                    }
766
767                    public Writer openWriter() throws IOException {
768                        throw new UnsupportedOperationException();
769                    }
770
771                    public long getLastModified() {
772                        return conn.getLastModified();
773                    }
774
775                    public boolean delete() {
776                        throw new UnsupportedOperationException();
777                    }
778
779                };
780            } catch (URISyntaxException | IOException ignore) {
781            }
782        }
783
784        return null;
785    }
786
787    public static class ClassFileInfo {
788        ClassFileInfo(JavaFileObject fo, ClassFile cf, byte[] digest, int size) {
789            this.fo = fo;
790            this.cf = cf;
791            this.digest = digest;
792            this.size = size;
793        }
794        public final JavaFileObject fo;
795        public final ClassFile cf;
796        public final byte[] digest;
797        public final int size;
798    }
799
800    public ClassFileInfo read(JavaFileObject fo) throws IOException, ConstantPoolException {
801        InputStream in = fo.openInputStream();
802        try {
803            SizeInputStream sizeIn = null;
804            MessageDigest md  = null;
805            if (options.sysInfo || options.verbose) {
806                try {
807                    md = MessageDigest.getInstance("MD5");
808                } catch (NoSuchAlgorithmException ignore) {
809                }
810                in = new DigestInputStream(in, md);
811                in = sizeIn = new SizeInputStream(in);
812            }
813
814            ClassFile cf = ClassFile.read(in, attributeFactory);
815            byte[] digest = (md == null) ? null : md.digest();
816            int size = (sizeIn == null) ? -1 : sizeIn.size();
817            return new ClassFileInfo(fo, cf, digest, size);
818        } finally {
819            in.close();
820        }
821    }
822
823    public void write(ClassFileInfo info) {
824        ClassWriter classWriter = ClassWriter.instance(context);
825        if (options.sysInfo || options.verbose) {
826            classWriter.setFile(info.fo.toUri());
827            classWriter.setLastModified(info.fo.getLastModified());
828            classWriter.setDigest("MD5", info.digest);
829            classWriter.setFileSize(info.size);
830        }
831
832        classWriter.write(info.cf);
833    }
834
835    protected void setClassFile(ClassFile classFile) {
836        ClassWriter classWriter = ClassWriter.instance(context);
837        classWriter.setClassFile(classFile);
838    }
839
840    protected void setMethod(Method enclosingMethod) {
841        ClassWriter classWriter = ClassWriter.instance(context);
842        classWriter.setMethod(enclosingMethod);
843    }
844
845    protected void write(Attribute value) {
846        AttributeWriter attrWriter = AttributeWriter.instance(context);
847        ClassWriter classWriter = ClassWriter.instance(context);
848        ClassFile cf = classWriter.getClassFile();
849        attrWriter.write(cf, value, cf.constant_pool);
850    }
851
852    protected void write(Attributes attrs) {
853        AttributeWriter attrWriter = AttributeWriter.instance(context);
854        ClassWriter classWriter = ClassWriter.instance(context);
855        ClassFile cf = classWriter.getClassFile();
856        attrWriter.write(cf, attrs, cf.constant_pool);
857    }
858
859    protected void write(ConstantPool constant_pool) {
860        ConstantWriter constantWriter = ConstantWriter.instance(context);
861        constantWriter.writeConstantPool(constant_pool);
862    }
863
864    protected void write(ConstantPool constant_pool, int value) {
865        ConstantWriter constantWriter = ConstantWriter.instance(context);
866        constantWriter.write(value);
867    }
868
869    protected void write(ConstantPool.CPInfo value) {
870        ConstantWriter constantWriter = ConstantWriter.instance(context);
871        constantWriter.println(value);
872    }
873
874    protected void write(Field value) {
875        ClassWriter classWriter = ClassWriter.instance(context);
876        classWriter.writeField(value);
877    }
878
879    protected void write(Method value) {
880        ClassWriter classWriter = ClassWriter.instance(context);
881        classWriter.writeMethod(value);
882    }
883
884    private JavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
885        if (defaultFileManager == null)
886            defaultFileManager = JavapFileManager.create(dl, log);
887        return defaultFileManager;
888    }
889
890    private JavaFileObject getClassFileObject(String className) throws IOException {
891        try {
892            JavaFileObject fo;
893            if (moduleLocation != null) {
894                fo = fileManager.getJavaFileForInput(moduleLocation, className, JavaFileObject.Kind.CLASS);
895            } else {
896                fo = fileManager.getJavaFileForInput(StandardLocation.PLATFORM_CLASS_PATH, className, JavaFileObject.Kind.CLASS);
897                if (fo == null)
898                    fo = fileManager.getJavaFileForInput(StandardLocation.CLASS_PATH, className, JavaFileObject.Kind.CLASS);
899            }
900            return fo;
901        } catch (IllegalArgumentException e) {
902            return null;
903        }
904    }
905
906    private Location findModule(String moduleName) throws IOException {
907        Location[] locns = {
908            StandardLocation.UPGRADE_MODULE_PATH,
909            StandardLocation.SYSTEM_MODULES,
910            StandardLocation.MODULE_PATH
911        };
912        for (Location segment: locns) {
913            for (Set<Location> set: fileManager.listLocationsForModules(segment)) {
914                Location result = null;
915                for (Location l: set) {
916                    String name = fileManager.inferModuleName(l);
917                    if (name.equals(moduleName)) {
918                        if (result == null)
919                            result = l;
920                        else
921                            throw new IOException("multiple definitions found for " + moduleName);
922                    }
923                }
924                if (result != null)
925                    return result;
926            }
927        }
928        return null;
929    }
930
931    private void showHelp() {
932        printLines(getMessage("main.usage", progname));
933        for (Option o: recognizedOptions) {
934            String name = o.aliases[0].replaceAll("^-+", "").replaceAll("-+", "_"); // there must always be at least one name
935            if (name.startsWith("X") || name.equals("fullversion") || name.equals("h") || name.equals("verify"))
936                continue;
937            printLines(getMessage("main.opt." + name));
938        }
939
940        String[] fmOptions = {
941            "--module-path", "--system",
942            "--class-path", "-classpath", "-cp",
943            "-bootclasspath"
944        };
945
946        for (String o: fmOptions) {
947            if (fileManager.isSupportedOption(o) == -1)
948                continue;
949            String name = o.replaceAll("^-+", "").replaceAll("-+", "_");
950            printLines(getMessage("main.opt." + name));
951        }
952
953        printLines(getMessage("main.usage.foot"));
954    }
955
956    private void showVersion(boolean full) {
957        printLines(version(full ? "full" : "release"));
958    }
959
960    private void printLines(String msg) {
961        log.println(msg.replace("\n", nl));
962    }
963
964    private static final String nl = System.getProperty("line.separator");
965
966    private static final String versionRBName = "com.sun.tools.javap.resources.version";
967    private static ResourceBundle versionRB;
968
969    private String version(String key) {
970        // key=version:  mm.nn.oo[-milestone]
971        // key=full:     mm.mm.oo[-milestone]-build
972        if (versionRB == null) {
973            try {
974                versionRB = ResourceBundle.getBundle(versionRBName);
975            } catch (MissingResourceException e) {
976                return getMessage("version.resource.missing", System.getProperty("java.version"));
977            }
978        }
979        try {
980            return versionRB.getString(key);
981        }
982        catch (MissingResourceException e) {
983            return getMessage("version.unknown", System.getProperty("java.version"));
984        }
985    }
986
987    private void reportError(String key, Object... args) {
988        diagnosticListener.report(createDiagnostic(Diagnostic.Kind.ERROR, key, args));
989    }
990
991    private void reportNote(String key, Object... args) {
992        diagnosticListener.report(createDiagnostic(Diagnostic.Kind.NOTE, key, args));
993    }
994
995    private void reportWarning(String key, Object... args) {
996        diagnosticListener.report(createDiagnostic(Diagnostic.Kind.WARNING, key, args));
997    }
998
999    private Diagnostic<JavaFileObject> createDiagnostic(
1000            final Diagnostic.Kind kind, final String key, final Object... args) {
1001        return new Diagnostic<JavaFileObject>() {
1002            public Kind getKind() {
1003                return kind;
1004            }
1005
1006            public JavaFileObject getSource() {
1007                return null;
1008            }
1009
1010            public long getPosition() {
1011                return Diagnostic.NOPOS;
1012            }
1013
1014            public long getStartPosition() {
1015                return Diagnostic.NOPOS;
1016            }
1017
1018            public long getEndPosition() {
1019                return Diagnostic.NOPOS;
1020            }
1021
1022            public long getLineNumber() {
1023                return Diagnostic.NOPOS;
1024            }
1025
1026            public long getColumnNumber() {
1027                return Diagnostic.NOPOS;
1028            }
1029
1030            public String getCode() {
1031                return key;
1032            }
1033
1034            public String getMessage(Locale locale) {
1035                return JavapTask.this.getMessage(locale, key, args);
1036            }
1037
1038            @Override
1039            public String toString() {
1040                return getClass().getName() + "[key=" + key + ",args=" + Arrays.asList(args) + "]";
1041            }
1042
1043        };
1044
1045    }
1046
1047    public String getMessage(String key, Object... args) {
1048        return getMessage(task_locale, key, args);
1049    }
1050
1051    public String getMessage(Locale locale, String key, Object... args) {
1052        if (bundles == null) {
1053            // could make this a HashMap<Locale,SoftReference<ResourceBundle>>
1054            // and for efficiency, keep a hard reference to the bundle for the task
1055            // locale
1056            bundles = new HashMap<>();
1057        }
1058
1059        if (locale == null)
1060            locale = Locale.getDefault();
1061
1062        ResourceBundle b = bundles.get(locale);
1063        if (b == null) {
1064            try {
1065                b = ResourceBundle.getBundle("com.sun.tools.javap.resources.javap", locale);
1066                bundles.put(locale, b);
1067            } catch (MissingResourceException e) {
1068                throw new InternalError("Cannot find javap resource bundle for locale " + locale);
1069            }
1070        }
1071
1072        try {
1073            return MessageFormat.format(b.getString(key), args);
1074        } catch (MissingResourceException e) {
1075            throw new InternalError(e, key);
1076        }
1077    }
1078
1079    protected Context context;
1080    JavaFileManager fileManager;
1081    JavaFileManager defaultFileManager;
1082    PrintWriter log;
1083    DiagnosticListener<? super JavaFileObject> diagnosticListener;
1084    List<String> classes;
1085    Location moduleLocation;
1086    Options options;
1087    //ResourceBundle bundle;
1088    Locale task_locale;
1089    Map<Locale, ResourceBundle> bundles;
1090    protected Attribute.Factory attributeFactory;
1091
1092    private static final String progname = "javap";
1093
1094    private static class SizeInputStream extends FilterInputStream {
1095        SizeInputStream(InputStream in) {
1096            super(in);
1097        }
1098
1099        int size() {
1100            return size;
1101        }
1102
1103        @Override
1104        public int read(byte[] buf, int offset, int length) throws IOException {
1105            int n = super.read(buf, offset, length);
1106            if (n > 0)
1107                size += n;
1108            return n;
1109        }
1110
1111        @Override
1112        public int read() throws IOException {
1113            int b = super.read();
1114            size += 1;
1115            return b;
1116        }
1117
1118        private int size;
1119    }
1120}
1121