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