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