1 /*
   2  * Copyright (c) 2007, 2019, 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.net.URI;
  29 import java.text.DateFormat;
  30 import java.util.Collection;
  31 import java.util.Date;
  32 import java.util.List;
  33 import java.util.Set;
  34 
  35 import com.sun.tools.classfile.AccessFlags;
  36 import com.sun.tools.classfile.Attribute;
  37 import com.sun.tools.classfile.Attributes;
  38 import com.sun.tools.classfile.ClassFile;
  39 import com.sun.tools.classfile.Code_attribute;
  40 import com.sun.tools.classfile.ConstantPool;
  41 import com.sun.tools.classfile.ConstantPoolException;
  42 import com.sun.tools.classfile.ConstantValue_attribute;
  43 import com.sun.tools.classfile.Descriptor;
  44 import com.sun.tools.classfile.Descriptor.InvalidDescriptor;
  45 import com.sun.tools.classfile.Exceptions_attribute;
  46 import com.sun.tools.classfile.Field;
  47 import com.sun.tools.classfile.Method;
  48 import com.sun.tools.classfile.Module_attribute;
  49 import com.sun.tools.classfile.Signature;
  50 import com.sun.tools.classfile.Signature_attribute;
  51 import com.sun.tools.classfile.SourceFile_attribute;
  52 import com.sun.tools.classfile.Type;
  53 import com.sun.tools.classfile.Type.ArrayType;
  54 import com.sun.tools.classfile.Type.ClassSigType;
  55 import com.sun.tools.classfile.Type.ClassType;
  56 import com.sun.tools.classfile.Type.MethodType;
  57 import com.sun.tools.classfile.Type.SimpleType;
  58 import com.sun.tools.classfile.Type.TypeParamType;
  59 import com.sun.tools.classfile.Type.WildcardType;
  60 
  61 import static com.sun.tools.classfile.AccessFlags.*;
  62 import static com.sun.tools.classfile.ConstantPool.CONSTANT_Module;
  63 import static com.sun.tools.classfile.ConstantPool.CONSTANT_Package;
  64 
  65 /*
  66  *  The main javap class to write the contents of a class file as text.
  67  *
  68  *  <p><b>This is NOT part of any supported API.
  69  *  If you write code that depends on this, you do so at your own risk.
  70  *  This code and its internal interfaces are subject to change or
  71  *  deletion without notice.</b>
  72  */
  73 public class ClassWriter extends BasicWriter {
  74     static ClassWriter instance(Context context) {
  75         ClassWriter instance = context.get(ClassWriter.class);
  76         if (instance == null)
  77             instance = new ClassWriter(context);
  78         return instance;
  79     }
  80 
  81     protected ClassWriter(Context context) {
  82         super(context);
  83         context.put(ClassWriter.class, this);
  84         options = Options.instance(context);
  85         attrWriter = AttributeWriter.instance(context);
  86         codeWriter = CodeWriter.instance(context);
  87         constantWriter = ConstantWriter.instance(context);
  88     }
  89 
  90     void setDigest(String name, byte[] digest) {
  91         this.digestName = name;
  92         this.digest = digest;
  93     }
  94 
  95     void setFile(URI uri) {
  96         this.uri = uri;
  97     }
  98 
  99     void setFileSize(int size) {
 100         this.size = size;
 101     }
 102 
 103     void setLastModified(long lastModified) {
 104         this.lastModified = lastModified;
 105     }
 106 
 107     protected ClassFile getClassFile() {
 108         return classFile;
 109     }
 110 
 111     protected void setClassFile(ClassFile cf) {
 112         classFile = cf;
 113         constant_pool = classFile.constant_pool;
 114     }
 115 
 116     protected Method getMethod() {
 117         return method;
 118     }
 119 
 120     protected void setMethod(Method m) {
 121         method = m;
 122     }
 123 
 124     public void write(ClassFile cf) {
 125         setClassFile(cf);
 126 
 127         if (options.sysInfo || options.verbose) {
 128             if (uri != null) {
 129                 if (uri.getScheme().equals("file"))
 130                     println("Classfile " + uri.getPath());
 131                 else
 132                     println("Classfile " + uri);
 133             }
 134             indent(+1);
 135             if (lastModified != -1) {
 136                 Date lm = new Date(lastModified);
 137                 DateFormat df = DateFormat.getDateInstance();
 138                 if (size > 0) {
 139                     println("Last modified " + df.format(lm) + "; size " + size + " bytes");
 140                 } else {
 141                     println("Last modified " + df.format(lm));
 142                 }
 143             } else if (size > 0) {
 144                 println("Size " + size + " bytes");
 145             }
 146             if (digestName != null && digest != null) {
 147                 StringBuilder sb = new StringBuilder();
 148                 for (byte b: digest)
 149                     sb.append(String.format("%02x", b));
 150                 println(digestName + " checksum " + sb);
 151             }
 152         }
 153 
 154         Attribute sfa = cf.getAttribute(Attribute.SourceFile);
 155         if (sfa instanceof SourceFile_attribute) {
 156             println("Compiled from \"" + getSourceFile((SourceFile_attribute) sfa) + "\"");
 157         }
 158 
 159         if (options.sysInfo || options.verbose) {
 160             indent(-1);
 161         }
 162 
 163         AccessFlags flags = cf.access_flags;
 164         writeModifiers(flags.getClassModifiers());
 165 
 166         if (classFile.access_flags.is(AccessFlags.ACC_MODULE)) {
 167             Attribute attr = classFile.attributes.get(Attribute.Module);
 168             if (attr instanceof Module_attribute) {
 169                 Module_attribute modAttr = (Module_attribute) attr;
 170                 String name;
 171                 try {
 172                     // FIXME: compatibility code
 173                     if (constant_pool.get(modAttr.module_name).getTag() == CONSTANT_Module) {
 174                         name = getJavaName(constant_pool.getModuleInfo(modAttr.module_name).getName());
 175                     } else {
 176                         name = getJavaName(constant_pool.getUTF8Value(modAttr.module_name));
 177                     }
 178                 } catch (ConstantPoolException e) {
 179                     name = report(e);
 180                 }
 181                 if ((modAttr.module_flags & Module_attribute.ACC_OPEN) != 0) {
 182                     print("open ");
 183                 }
 184                 print("module ");
 185                 print(name);
 186                 if (modAttr.module_version_index != 0) {
 187                     print("@");
 188                     print(getUTF8Value(modAttr.module_version_index));
 189                 }
 190             } else {
 191                 // fallback for malformed class files
 192                 print("class ");
 193                 print(getJavaName(classFile));
 194             }
 195         } else {
 196             if (classFile.isClass())
 197                 print("class ");
 198             else if (classFile.isInterface())
 199                 print("interface ");
 200 
 201             print(getJavaName(classFile));
 202         }
 203 
 204         Signature_attribute sigAttr = getSignature(cf.attributes);
 205         if (sigAttr == null) {
 206             // use info from class file header
 207             if (classFile.isClass() && classFile.super_class != 0 ) {
 208                 String sn = getJavaSuperclassName(cf);
 209                 if (!sn.equals("java.lang.Object")) {
 210                     print(" extends ");
 211                     print(sn);
 212                 }
 213             }
 214             for (int i = 0; i < classFile.interfaces.length; i++) {
 215                 print(i == 0 ? (classFile.isClass() ? " implements " : " extends ") : ",");
 216                 print(getJavaInterfaceName(classFile, i));
 217             }
 218         } else {
 219             try {
 220                 Type t = sigAttr.getParsedSignature().getType(constant_pool);
 221                 JavaTypePrinter p = new JavaTypePrinter(classFile.isInterface());
 222                 // The signature parser cannot disambiguate between a
 223                 // FieldType and a ClassSignatureType that only contains a superclass type.
 224                 if (t instanceof Type.ClassSigType) {
 225                     print(p.print(t));
 226                 } else if (options.verbose || !t.isObject()) {
 227                     print(" extends ");
 228                     print(p.print(t));
 229                 }
 230             } catch (ConstantPoolException e) {
 231                 print(report(e));
 232             }
 233         }
 234 
 235         if (options.verbose) {
 236             println();
 237             indent(+1);
 238             println("minor version: " + cf.minor_version);
 239             println("major version: " + cf.major_version);
 240             writeList(String.format("flags: (0x%04x) ", flags.flags), flags.getClassFlags(), "\n");
 241             print("this_class: #" + cf.this_class);
 242             if (cf.this_class != 0) {
 243                 tab();
 244                 print("// " + constantWriter.stringValue(cf.this_class));
 245             }
 246             println();
 247             print("super_class: #" + cf.super_class);
 248             if (cf.super_class != 0) {
 249                 tab();
 250                 print("// " + constantWriter.stringValue(cf.super_class));
 251             }
 252             println();
 253             print("interfaces: " + cf.interfaces.length);
 254             print(", fields: " + cf.fields.length);
 255             print(", methods: " + cf.methods.length);
 256             println(", attributes: " + cf.attributes.attrs.length);
 257             indent(-1);
 258             constantWriter.writeConstantPool();
 259         } else {
 260             print(" ");
 261         }
 262 
 263         println("{");
 264         indent(+1);
 265         if (flags.is(AccessFlags.ACC_MODULE) && !options.verbose) {
 266             writeDirectives();
 267         }
 268         writeFields();
 269         writeMethods();
 270         indent(-1);
 271         println("}");
 272 
 273         if (options.verbose) {
 274             attrWriter.write(cf, cf.attributes, constant_pool);
 275         }
 276     }
 277     // where
 278         class JavaTypePrinter implements Type.Visitor<StringBuilder,StringBuilder> {
 279             boolean isInterface;
 280 
 281             JavaTypePrinter(boolean isInterface) {
 282                 this.isInterface = isInterface;
 283             }
 284 
 285             String print(Type t) {
 286                 return t.accept(this, new StringBuilder()).toString();
 287             }
 288 
 289             String printTypeArgs(List<? extends TypeParamType> typeParamTypes) {
 290                 StringBuilder builder = new StringBuilder();
 291                 appendIfNotEmpty(builder, "<", typeParamTypes, "> ");
 292                 return builder.toString();
 293             }
 294 
 295             @Override
 296             public StringBuilder visitSimpleType(SimpleType type, StringBuilder sb) {
 297                 sb.append(getJavaName(type.name));
 298                 return sb;
 299             }
 300 
 301             @Override
 302             public StringBuilder visitArrayType(ArrayType type, StringBuilder sb) {
 303                 append(sb, type.elemType);
 304                 sb.append("[]");
 305                 return sb;
 306             }
 307 
 308             @Override
 309             public StringBuilder visitMethodType(MethodType type, StringBuilder sb) {
 310                 appendIfNotEmpty(sb, "<", type.typeParamTypes, "> ");
 311                 append(sb, type.returnType);
 312                 append(sb, " (", type.paramTypes, ")");
 313                 appendIfNotEmpty(sb, " throws ", type.throwsTypes, "");
 314                 return sb;
 315             }
 316 
 317             @Override
 318             public StringBuilder visitClassSigType(ClassSigType type, StringBuilder sb) {
 319                 appendIfNotEmpty(sb, "<", type.typeParamTypes, ">");
 320                 if (isInterface) {
 321                     appendIfNotEmpty(sb, " extends ", type.superinterfaceTypes, "");
 322                 } else {
 323                     if (type.superclassType != null
 324                             && (options.verbose || !type.superclassType.isObject())) {
 325                         sb.append(" extends ");
 326                         append(sb, type.superclassType);
 327                     }
 328                     appendIfNotEmpty(sb, " implements ", type.superinterfaceTypes, "");
 329                 }
 330                 return sb;
 331             }
 332 
 333             @Override
 334             public StringBuilder visitClassType(ClassType type, StringBuilder sb) {
 335                 if (type.outerType != null) {
 336                     append(sb, type.outerType);
 337                     sb.append(".");
 338                 }
 339                 sb.append(getJavaName(type.name));
 340                 appendIfNotEmpty(sb, "<", type.typeArgs, ">");
 341                 return sb;
 342             }
 343 
 344             @Override
 345             public StringBuilder visitTypeParamType(TypeParamType type, StringBuilder sb) {
 346                 sb.append(type.name);
 347                 String sep = " extends ";
 348                 if (type.classBound != null
 349                         && (options.verbose || !type.classBound.isObject())) {
 350                     sb.append(sep);
 351                     append(sb, type.classBound);
 352                     sep = " & ";
 353                 }
 354                 if (type.interfaceBounds != null) {
 355                     for (Type bound: type.interfaceBounds) {
 356                         sb.append(sep);
 357                         append(sb, bound);
 358                         sep = " & ";
 359                     }
 360                 }
 361                 return sb;
 362             }
 363 
 364             @Override
 365             public StringBuilder visitWildcardType(WildcardType type, StringBuilder sb) {
 366                 switch (type.kind) {
 367                     case UNBOUNDED:
 368                         sb.append("?");
 369                         break;
 370                     case EXTENDS:
 371                         sb.append("? extends ");
 372                         append(sb, type.boundType);
 373                         break;
 374                     case SUPER:
 375                         sb.append("? super ");
 376                         append(sb, type.boundType);
 377                         break;
 378                     default:
 379                         throw new AssertionError();
 380                 }
 381                 return sb;
 382             }
 383 
 384             private void append(StringBuilder sb, Type t) {
 385                 t.accept(this, sb);
 386             }
 387 
 388             private void append(StringBuilder sb, String prefix, List<? extends Type> list, String suffix) {
 389                 sb.append(prefix);
 390                 String sep = "";
 391                 for (Type t: list) {
 392                     sb.append(sep);
 393                     append(sb, t);
 394                     sep = ", ";
 395                 }
 396                 sb.append(suffix);
 397             }
 398 
 399             private void appendIfNotEmpty(StringBuilder sb, String prefix, List<? extends Type> list, String suffix) {
 400                 if (!isEmpty(list))
 401                     append(sb, prefix, list, suffix);
 402             }
 403 
 404             private boolean isEmpty(List<? extends Type> list) {
 405                 return (list == null || list.isEmpty());
 406             }
 407         }
 408 
 409     protected void writeFields() {
 410         for (Field f: classFile.fields) {
 411             writeField(f);
 412         }
 413     }
 414 
 415     protected void writeField(Field f) {
 416         if (!options.checkAccess(f.access_flags))
 417             return;
 418 
 419         AccessFlags flags = f.access_flags;
 420         writeModifiers(flags.getFieldModifiers());
 421         Signature_attribute sigAttr = getSignature(f.attributes);
 422         if (sigAttr == null)
 423             print(getJavaFieldType(f.descriptor));
 424         else {
 425             try {
 426                 Type t = sigAttr.getParsedSignature().getType(constant_pool);
 427                 print(getJavaName(t.toString()));
 428             } catch (ConstantPoolException e) {
 429                 // report error?
 430                 // fall back on non-generic descriptor
 431                 print(getJavaFieldType(f.descriptor));
 432             }
 433         }
 434         print(" ");
 435         print(getFieldName(f));
 436         if (options.showConstants) {
 437             Attribute a = f.attributes.get(Attribute.ConstantValue);
 438             if (a instanceof ConstantValue_attribute) {
 439                 print(" = ");
 440                 ConstantValue_attribute cv = (ConstantValue_attribute) a;
 441                 print(getConstantValue(f.descriptor, cv.constantvalue_index));
 442             }
 443         }
 444         print(";");
 445         println();
 446 
 447         indent(+1);
 448 
 449         boolean showBlank = false;
 450 
 451         if (options.showDescriptors)
 452             println("descriptor: " + getValue(f.descriptor));
 453 
 454         if (options.verbose)
 455             writeList(String.format("flags: (0x%04x) ", flags.flags), flags.getFieldFlags(), "\n");
 456 
 457         if (options.showAllAttrs) {
 458             for (Attribute attr: f.attributes)
 459                 attrWriter.write(f, attr, constant_pool);
 460             showBlank = true;
 461         }
 462 
 463         indent(-1);
 464 
 465         if (showBlank || options.showDisassembled || options.showLineAndLocalVariableTables)
 466             println();
 467     }
 468 
 469     protected void writeMethods() {
 470         for (Method m: classFile.methods)
 471             writeMethod(m);
 472         setPendingNewline(false);
 473     }
 474 
 475     private static final int DEFAULT_ALLOWED_MAJOR_VERSION = 52;
 476     private static final int DEFAULT_ALLOWED_MINOR_VERSION = 0;
 477 
 478     protected void writeMethod(Method m) {
 479         if (!options.checkAccess(m.access_flags))
 480             return;
 481 
 482         method = m;
 483 
 484         AccessFlags flags = m.access_flags;
 485 
 486         Descriptor d;
 487         Type.MethodType methodType;
 488         List<? extends Type> methodExceptions;
 489 
 490         Signature_attribute sigAttr = getSignature(m.attributes);
 491         if (sigAttr == null) {
 492             d = m.descriptor;
 493             methodType = null;
 494             methodExceptions = null;
 495         } else {
 496             Signature methodSig = sigAttr.getParsedSignature();
 497             d = methodSig;
 498             try {
 499                 methodType = (Type.MethodType) methodSig.getType(constant_pool);
 500                 methodExceptions = methodType.throwsTypes;
 501                 if (methodExceptions != null && methodExceptions.isEmpty())
 502                     methodExceptions = null;
 503             } catch (ConstantPoolException e) {
 504                 // report error?
 505                 // fall back on standard descriptor
 506                 methodType = null;
 507                 methodExceptions = null;
 508             }
 509         }
 510 
 511         Set<String> modifiers = flags.getMethodModifiers();
 512 
 513         String name = getName(m);
 514         if (classFile.isInterface() &&
 515                 (!flags.is(AccessFlags.ACC_ABSTRACT)) && !name.equals("<clinit>")) {
 516             if (classFile.major_version > DEFAULT_ALLOWED_MAJOR_VERSION ||
 517                     (classFile.major_version == DEFAULT_ALLOWED_MAJOR_VERSION && classFile.minor_version >= DEFAULT_ALLOWED_MINOR_VERSION)) {
 518                 if (!flags.is(AccessFlags.ACC_STATIC | AccessFlags.ACC_PRIVATE)) {
 519                     modifiers.add("default");
 520                 }
 521             }
 522         }
 523 
 524         writeModifiers(modifiers);
 525         if (methodType != null) {
 526             print(new JavaTypePrinter(false).printTypeArgs(methodType.typeParamTypes));
 527         }
 528         switch (name) {
 529             case "<init>":
 530                 print(getJavaName(classFile));
 531                 print(getJavaParameterTypes(d, flags));
 532                 break;
 533             case "<clinit>":
 534                 print("{}");
 535                 break;
 536             default:
 537                 print(getJavaReturnType(d));
 538                 print(" ");
 539                 print(name);
 540                 print(getJavaParameterTypes(d, flags));
 541                 break;
 542         }
 543 
 544         Attribute e_attr = m.attributes.get(Attribute.Exceptions);
 545         if (e_attr != null) { // if there are generic exceptions, there must be erased exceptions
 546             if (e_attr instanceof Exceptions_attribute) {
 547                 Exceptions_attribute exceptions = (Exceptions_attribute) e_attr;
 548                 print(" throws ");
 549                 if (methodExceptions != null) { // use generic list if available
 550                     writeList("", methodExceptions, "");
 551                 } else {
 552                     for (int i = 0; i < exceptions.number_of_exceptions; i++) {
 553                         if (i > 0)
 554                             print(", ");
 555                         print(getJavaException(exceptions, i));
 556                     }
 557                 }
 558             } else {
 559                 report("Unexpected or invalid value for Exceptions attribute");
 560             }
 561         }
 562 
 563         println(";");
 564 
 565         indent(+1);
 566 
 567         if (options.showDescriptors) {
 568             println("descriptor: " + getValue(m.descriptor));
 569         }
 570 
 571         if (options.verbose) {
 572             writeList(String.format("flags: (0x%04x) ", flags.flags), flags.getMethodFlags(), "\n");
 573         }
 574 
 575         Code_attribute code = null;
 576         Attribute c_attr = m.attributes.get(Attribute.Code);
 577         if (c_attr != null) {
 578             if (c_attr instanceof Code_attribute)
 579                 code = (Code_attribute) c_attr;
 580             else
 581                 report("Unexpected or invalid value for Code attribute");
 582         }
 583 
 584         if (options.showAllAttrs) {
 585             Attribute[] attrs = m.attributes.attrs;
 586             for (Attribute attr: attrs)
 587                 attrWriter.write(m, attr, constant_pool);
 588         } else if (code != null) {
 589             if (options.showDisassembled) {
 590                 println("Code:");
 591                 codeWriter.writeInstrs(code);
 592                 codeWriter.writeExceptionTable(code);
 593             }
 594 
 595             if (options.showLineAndLocalVariableTables) {
 596                 attrWriter.write(code, code.attributes.get(Attribute.LineNumberTable), constant_pool);
 597                 attrWriter.write(code, code.attributes.get(Attribute.LocalVariableTable), constant_pool);
 598             }
 599         }
 600 
 601         indent(-1);
 602 
 603         // set pendingNewline to write a newline before the next method (if any)
 604         // if a separator is desired
 605         setPendingNewline(
 606                 options.showDisassembled ||
 607                 options.showAllAttrs ||
 608                 options.showDescriptors ||
 609                 options.showLineAndLocalVariableTables ||
 610                 options.verbose);
 611     }
 612 
 613     void writeModifiers(Collection<String> items) {
 614         for (Object item: items) {
 615             print(item);
 616             print(" ");
 617         }
 618     }
 619 
 620     void writeDirectives() {
 621         Attribute attr = classFile.attributes.get(Attribute.Module);
 622         if (!(attr instanceof Module_attribute))
 623             return;
 624 
 625         Module_attribute m = (Module_attribute) attr;
 626         for (Module_attribute.RequiresEntry entry: m.requires) {
 627             print("requires");
 628             if ((entry.requires_flags & Module_attribute.ACC_STATIC_PHASE) != 0)
 629                 print(" static");
 630             if ((entry.requires_flags & Module_attribute.ACC_TRANSITIVE) != 0)
 631                 print(" transitive");
 632             print(" ");
 633             String mname;
 634             try {
 635                 mname = getModuleName(entry.requires_index);
 636             } catch (ConstantPoolException e) {
 637                 mname = report(e);
 638             }
 639             print(mname);
 640             println(";");
 641         }
 642 
 643         for (Module_attribute.ExportsEntry entry: m.exports) {
 644             print("exports");
 645             print(" ");
 646             String pname;
 647             try {
 648                 pname = getPackageName(entry.exports_index).replace('/', '.');
 649             } catch (ConstantPoolException e) {
 650                 pname = report(e);
 651             }
 652             print(pname);
 653             boolean first = true;
 654             for (int i: entry.exports_to_index) {
 655                 String mname;
 656                 try {
 657                     mname = getModuleName(i);
 658                 } catch (ConstantPoolException e) {
 659                     mname = report(e);
 660                 }
 661                 if (first) {
 662                     println(" to");
 663                     indent(+1);
 664                     first = false;
 665                 } else {
 666                     println(",");
 667                 }
 668                 print(mname);
 669             }
 670             println(";");
 671             if (!first)
 672                 indent(-1);
 673         }
 674 
 675         for (Module_attribute.OpensEntry entry: m.opens) {
 676             print("opens");
 677             print(" ");
 678             String pname;
 679             try {
 680                 pname = getPackageName(entry.opens_index).replace('/', '.');
 681             } catch (ConstantPoolException e) {
 682                 pname = report(e);
 683             }
 684             print(pname);
 685             boolean first = true;
 686             for (int i: entry.opens_to_index) {
 687                 String mname;
 688                 try {
 689                     mname = getModuleName(i);
 690                 } catch (ConstantPoolException e) {
 691                     mname = report(e);
 692                 }
 693                 if (first) {
 694                     println(" to");
 695                     indent(+1);
 696                     first = false;
 697                 } else {
 698                     println(",");
 699                 }
 700                 print(mname);
 701             }
 702             println(";");
 703             if (!first)
 704                 indent(-1);
 705         }
 706 
 707         for (int entry: m.uses_index) {
 708             print("uses ");
 709             print(getClassName(entry).replace('/', '.'));
 710             println(";");
 711         }
 712 
 713         for (Module_attribute.ProvidesEntry entry: m.provides) {
 714             print("provides  ");
 715             print(getClassName(entry.provides_index).replace('/', '.'));
 716             boolean first = true;
 717             for (int i: entry.with_index) {
 718                 if (first) {
 719                     println(" with");
 720                     indent(+1);
 721                     first = false;
 722                 } else {
 723                     println(",");
 724                 }
 725                 print(getClassName(i).replace('/', '.'));
 726             }
 727             println(";");
 728             if (!first)
 729                 indent(-1);
 730         }
 731     }
 732 
 733     String getModuleName(int index) throws ConstantPoolException {
 734         if (constant_pool.get(index).getTag() == CONSTANT_Module) {
 735             return constant_pool.getModuleInfo(index).getName();
 736         } else {
 737             return constant_pool.getUTF8Value(index);
 738         }
 739     }
 740 
 741     String getPackageName(int index) throws ConstantPoolException {
 742         if (constant_pool.get(index).getTag() == CONSTANT_Package) {
 743             return constant_pool.getPackageInfo(index).getName();
 744         } else {
 745             return constant_pool.getUTF8Value(index);
 746         }
 747     }
 748 
 749     String getUTF8Value(int index) {
 750         try {
 751             return classFile.constant_pool.getUTF8Value(index);
 752         } catch (ConstantPoolException e) {
 753             return report(e);
 754         }
 755     }
 756 
 757     String getClassName(int index) {
 758         try {
 759             return classFile.constant_pool.getClassInfo(index).getName();
 760         } catch (ConstantPoolException e) {
 761             return report(e);
 762         }
 763     }
 764 
 765     void writeList(String prefix, Collection<?> items, String suffix) {
 766         print(prefix);
 767         String sep = "";
 768         for (Object item: items) {
 769             print(sep);
 770             print(item);
 771             sep = ", ";
 772         }
 773         print(suffix);
 774     }
 775 
 776     void writeListIfNotEmpty(String prefix, List<?> items, String suffix) {
 777         if (items != null && items.size() > 0)
 778             writeList(prefix, items, suffix);
 779     }
 780 
 781     Signature_attribute getSignature(Attributes attributes) {
 782         return (Signature_attribute) attributes.get(Attribute.Signature);
 783     }
 784 
 785     String adjustVarargs(AccessFlags flags, String params) {
 786         if (flags.is(ACC_VARARGS)) {
 787             int i = params.lastIndexOf("[]");
 788             if (i > 0)
 789                 return params.substring(0, i) + "..." + params.substring(i+2);
 790         }
 791 
 792         return params;
 793     }
 794 
 795     String getJavaName(ClassFile cf) {
 796         try {
 797             return getJavaName(cf.getName());
 798         } catch (ConstantPoolException e) {
 799             return report(e);
 800         }
 801     }
 802 
 803     String getJavaSuperclassName(ClassFile cf) {
 804         try {
 805             return getJavaName(cf.getSuperclassName());
 806         } catch (ConstantPoolException e) {
 807             return report(e);
 808         }
 809     }
 810 
 811     String getJavaInterfaceName(ClassFile cf, int index) {
 812         try {
 813             return getJavaName(cf.getInterfaceName(index));
 814         } catch (ConstantPoolException e) {
 815             return report(e);
 816         }
 817     }
 818 
 819     String getJavaFieldType(Descriptor d) {
 820         try {
 821             return getJavaName(d.getFieldType(constant_pool));
 822         } catch (ConstantPoolException e) {
 823             return report(e);
 824         } catch (InvalidDescriptor e) {
 825             return report(e);
 826         }
 827     }
 828 
 829     String getJavaReturnType(Descriptor d) {
 830         try {
 831             return getJavaName(d.getReturnType(constant_pool));
 832         } catch (ConstantPoolException e) {
 833             return report(e);
 834         } catch (InvalidDescriptor e) {
 835             return report(e);
 836         }
 837     }
 838 
 839     String getJavaParameterTypes(Descriptor d, AccessFlags flags) {
 840         try {
 841             return getJavaName(adjustVarargs(flags, d.getParameterTypes(constant_pool)));
 842         } catch (ConstantPoolException e) {
 843             return report(e);
 844         } catch (InvalidDescriptor e) {
 845             return report(e);
 846         }
 847     }
 848 
 849     String getJavaException(Exceptions_attribute attr, int index) {
 850         try {
 851             return getJavaName(attr.getException(index, constant_pool));
 852         } catch (ConstantPoolException e) {
 853             return report(e);
 854         }
 855     }
 856 
 857     String getValue(Descriptor d) {
 858         try {
 859             return d.getValue(constant_pool);
 860         } catch (ConstantPoolException e) {
 861             return report(e);
 862         }
 863     }
 864 
 865     String getFieldName(Field f) {
 866         try {
 867             return f.getName(constant_pool);
 868         } catch (ConstantPoolException e) {
 869             return report(e);
 870         }
 871     }
 872 
 873     String getName(Method m) {
 874         try {
 875             return m.getName(constant_pool);
 876         } catch (ConstantPoolException e) {
 877             return report(e);
 878         }
 879     }
 880 
 881     static String getJavaName(String name) {
 882         return name.replace('/', '.');
 883     }
 884 
 885     String getSourceFile(SourceFile_attribute attr) {
 886         try {
 887             return attr.getSourceFile(constant_pool);
 888         } catch (ConstantPoolException e) {
 889             return report(e);
 890         }
 891     }
 892 
 893     /**
 894      * Get the value of an entry in the constant pool as a Java constant.
 895      * Characters and booleans are represented by CONSTANT_Intgere entries.
 896      * Character and string values are processed to escape characters outside
 897      * the basic printable ASCII set.
 898      * @param d the descriptor, giving the expected type of the constant
 899      * @param index the index of the value in the constant pool
 900      * @return a printable string containing the value of the constant.
 901      */
 902     String getConstantValue(Descriptor d, int index) {
 903         try {
 904             ConstantPool.CPInfo cpInfo = constant_pool.get(index);
 905 
 906             switch (cpInfo.getTag()) {
 907                 case ConstantPool.CONSTANT_Integer: {
 908                     ConstantPool.CONSTANT_Integer_info info =
 909                             (ConstantPool.CONSTANT_Integer_info) cpInfo;
 910                     String t = d.getValue(constant_pool);
 911                     switch (t) {
 912                         case "C":
 913                             // character
 914                             return getConstantCharValue((char) info.value);
 915                         case "Z":
 916                             // boolean
 917                             return String.valueOf(info.value == 1);
 918                         default:
 919                             // other: assume integer
 920                             return String.valueOf(info.value);
 921                     }
 922                 }
 923 
 924                 case ConstantPool.CONSTANT_String: {
 925                     ConstantPool.CONSTANT_String_info info =
 926                             (ConstantPool.CONSTANT_String_info) cpInfo;
 927                     return getConstantStringValue(info.getString());
 928                 }
 929 
 930                 default:
 931                     return constantWriter.stringValue(cpInfo);
 932             }
 933         } catch (ConstantPoolException e) {
 934             return "#" + index;
 935         }
 936     }
 937 
 938     private String getConstantCharValue(char c) {
 939         StringBuilder sb = new StringBuilder();
 940         sb.append('\'');
 941         sb.append(esc(c, '\''));
 942         sb.append('\'');
 943         return sb.toString();
 944     }
 945 
 946     private String getConstantStringValue(String s) {
 947         StringBuilder sb = new StringBuilder();
 948         sb.append("\"");
 949         for (int i = 0; i < s.length(); i++) {
 950             sb.append(esc(s.charAt(i), '"'));
 951         }
 952         sb.append("\"");
 953         return sb.toString();
 954     }
 955 
 956     private String esc(char c, char quote) {
 957         if (32 <= c && c <= 126 && c != quote && c != '\\')
 958             return String.valueOf(c);
 959         else switch (c) {
 960             case '\b': return "\\b";
 961             case '\n': return "\\n";
 962             case '\t': return "\\t";
 963             case '\f': return "\\f";
 964             case '\r': return "\\r";
 965             case '\\': return "\\\\";
 966             case '\'': return "\\'";
 967             case '\"': return "\\\"";
 968             default:   return String.format("\\u%04x", (int) c);
 969         }
 970     }
 971 
 972     private final Options options;
 973     private final AttributeWriter attrWriter;
 974     private final CodeWriter codeWriter;
 975     private final ConstantWriter constantWriter;
 976     private ClassFile classFile;
 977     private URI uri;
 978     private long lastModified;
 979     private String digestName;
 980     private byte[] digest;
 981     private int size;
 982     private ConstantPool constant_pool;
 983     private Method method;
 984 }