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 
  26 package com.sun.tools.javap;
  27 
  28 import java.io.EOFException;
  29 import java.io.FileNotFoundException;
  30 import java.io.FilterInputStream;
  31 import java.io.InputStream;
  32 import java.io.IOException;
  33 import java.io.OutputStream;
  34 import java.io.PrintWriter;
  35 import java.io.Reader;
  36 import java.io.StringWriter;
  37 import java.io.Writer;
  38 import java.net.URI;
  39 import java.net.URISyntaxException;
  40 import java.net.URL;
  41 import java.net.URLConnection;
  42 import java.nio.file.NoSuchFileException;
  43 import java.security.DigestInputStream;
  44 import java.security.MessageDigest;
  45 import java.security.NoSuchAlgorithmException;
  46 import java.text.MessageFormat;
  47 import java.util.ArrayList;
  48 import java.util.Arrays;
  49 import java.util.EnumSet;
  50 import java.util.HashMap;
  51 import java.util.Iterator;
  52 import java.util.List;
  53 import java.util.Locale;
  54 import java.util.Map;
  55 import java.util.MissingResourceException;
  56 import java.util.Objects;
  57 import java.util.ResourceBundle;
  58 import java.util.Set;
  59 
  60 import javax.lang.model.element.Modifier;
  61 import javax.lang.model.element.NestingKind;
  62 import javax.tools.Diagnostic;
  63 import javax.tools.DiagnosticListener;
  64 import javax.tools.JavaFileManager;
  65 import javax.tools.JavaFileManager.Location;
  66 import javax.tools.JavaFileObject;
  67 import javax.tools.StandardJavaFileManager;
  68 import javax.tools.StandardLocation;
  69 
  70 import com.sun.tools.classfile.*;
  71 import com.sun.tools.javac.util.DefinedBy;
  72 import 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  */
  83 public 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 }