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