1 /*
  2  * Copyright (c) 2007, 2024, 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.lang.reflect.AccessFlag;
 29 import java.net.URI;
 30 import java.text.DateFormat;
 31 import java.util.Collection;
 32 import java.util.Date;
 33 import java.util.EnumSet;
 34 import java.util.List;
 35 import java.util.Set;
 36 
 37 import java.lang.constant.ClassDesc;
 38 import java.lang.reflect.Modifier;
 39 import java.util.ArrayList;
 40 import java.util.LinkedHashSet;
 41 import java.lang.classfile.AccessFlags;
 42 import java.lang.classfile.Attributes;
 43 import java.lang.classfile.ClassModel;
 44 import java.lang.classfile.ClassSignature;
 45 import java.lang.classfile.ClassFile;
 46 import static java.lang.classfile.ClassFile.*;
 47 import java.lang.classfile.ClassHierarchyResolver;
 48 import java.lang.classfile.constantpool.*;
 49 import java.lang.classfile.FieldModel;
 50 import java.lang.classfile.MethodModel;
 51 import java.lang.classfile.MethodSignature;
 52 import java.lang.classfile.Signature;
 53 import java.lang.classfile.attribute.CodeAttribute;
 54 import java.lang.classfile.attribute.SignatureAttribute;
 55 
 56 /*
 57  *  The main javap class to write the contents of a class file as text.
 58  *
 59  *  <p><b>This is NOT part of any supported API.
 60  *  If you write code that depends on this, you do so at your own risk.
 61  *  This code and its internal interfaces are subject to change or
 62  *  deletion without notice.</b>
 63  */
 64 public class ClassWriter extends BasicWriter {
 65     static ClassWriter instance(Context context) {
 66         ClassWriter instance = context.get(ClassWriter.class);
 67         if (instance == null)
 68             instance = new ClassWriter(context);
 69         return instance;
 70     }
 71 
 72     protected ClassWriter(Context context) {
 73         super(context);
 74         context.put(ClassWriter.class, this);
 75         options = Options.instance(context);
 76         attrWriter = AttributeWriter.instance(context);
 77         codeWriter = CodeWriter.instance(context);
 78         constantWriter = ConstantWriter.instance(context);
 79         sigPrinter = new SignaturePrinter(options.verbose);
 80     }
 81 
 82     void setDigest(String name, byte[] digest) {
 83         this.digestName = name;
 84         this.digest = digest;
 85     }
 86 
 87     void setFile(URI uri) {
 88         this.uri = uri;
 89     }
 90 
 91     void setFileSize(int size) {
 92         this.size = size;
 93     }
 94 
 95     void setLastModified(long lastModified) {
 96         this.lastModified = lastModified;
 97     }
 98 
 99     protected ClassModel getClassModel() {
100         return classModel;
101     }
102 
103     protected void setClassFile(ClassModel cm) {
104         classModel = cm;
105     }
106 
107     protected MethodModel getMethod() {
108         return method;
109     }
110 
111     protected void setMethod(MethodModel m) {
112         method = m;
113     }
114 
115     public boolean write(ClassModel cm) {
116         errorReported = false;
117         setClassFile(cm);
118 
119         if (options.sysInfo || options.verbose) {
120             if (uri != null) {
121                 if (uri.getScheme().equals("file"))
122                     println("Classfile " + uri.getPath());
123                 else
124                     println("Classfile " + uri);
125             }
126             indent(+1);
127             if (lastModified != -1) {
128                 Date lm = new Date(lastModified);
129                 DateFormat df = DateFormat.getDateInstance();
130                 if (size > 0) {
131                     println("Last modified " + df.format(lm) + "; size " + size
132                             + " bytes");
133                 } else {
134                     println("Last modified " + df.format(lm));
135                 }
136             } else if (size > 0) {
137                 println("Size " + size + " bytes");
138             }
139             if (digestName != null && digest != null) {
140                 StringBuilder sb = new StringBuilder();
141                 for (byte b: digest)
142                     sb.append(String.format("%02x", b));
143                 println(digestName + " checksum " + sb);
144             }
145         }
146 
147         cm.findAttribute(Attributes.sourceFile()).ifPresent(sfa ->
148             println("Compiled from \"" + sfa.sourceFile().stringValue() + "\""));
149 
150         if (options.sysInfo || options.verbose) {
151             indent(-1);
152         }
153 
154         writeModifiers(getClassModifiers(cm.flags()));
155 
156         if ((classModel.flags().flagsMask() & ACC_MODULE) != 0) {
157             var attr = classModel.findAttribute(Attributes.module());
158             if (attr.isPresent()) {
159                 var modAttr = attr.get();
160                 if ((modAttr.moduleFlagsMask() & ACC_OPEN) != 0) {
161                     print("open ");
162                 }
163                 print("module ");
164                 print(() -> modAttr.moduleName().name().stringValue());
165                 if (modAttr.moduleVersion().isPresent()) {
166                     print("@");
167                     print(() -> modAttr.moduleVersion().get().stringValue());
168                 }
169             } else {
170                 // fallback for malformed class files
171                 print("class ");
172                 print(() -> getJavaName(classModel.thisClass().asInternalName()));
173             }
174         } else {
175             if ((classModel.flags().flagsMask() & ACC_INTERFACE) == 0)
176                 print("class ");
177             else
178                 print("interface ");
179 
180             print(() -> getJavaName(classModel.thisClass().asInternalName()));
181         }
182 
183         try {
184             var sigAttr = classModel.findAttribute(Attributes.signature()).orElse(null);
185             if (sigAttr == null) {
186                 // use info from class file header
187                 if ((classModel.flags().flagsMask() & ACC_INTERFACE) == 0
188                         && classModel.superclass().isPresent()) {
189                     String sn = getJavaName(classModel.superclass().get().asInternalName());
190                     if (!sn.equals("java.lang.Object")) {
191                         print(" extends ");
192                         print(sn);
193                     }
194                 }
195                 var interfaces = classModel.interfaces();
196                 for (int i = 0; i < interfaces.size(); i++) {
197                     print(i == 0 ? ((classModel.flags().flagsMask() & ACC_INTERFACE) == 0
198                             ? " implements " : " extends ") : ",");
199                     print(getJavaName(interfaces.get(i).asInternalName()));
200                 }
201             } else {
202                 var t = sigAttr.asClassSignature();
203                 print(sigPrinter.print(t, (classModel.flags().flagsMask() & ACC_INTERFACE) != 0));
204             }
205         } catch (IllegalArgumentException e) {
206             report(e);
207         }
208 
209         if (options.verbose) {
210             println();
211             indent(+1);
212             println("minor version: " + classModel.minorVersion());
213             println("major version: " + classModel.majorVersion());
214             writeList(String.format("flags: (0x%04x) ", cm.flags().flagsMask()),
215                     getClassFlags(cm.flags()), "\n");
216             print("this_class: #");print(() -> classModel.thisClass().index());
217             tab();
218             print(() -> "// " + classModel.thisClass().asInternalName());
219             println();
220             print("super_class: #");print(() -> classModel.superclass()
221                     .map(ClassEntry::index).orElse(0));
222             try {
223                 if (classModel.superclass().isPresent()) {
224                     tab();
225                     print(() -> "// " + classModel.superclass().get().asInternalName());
226                 }
227             } catch (IllegalArgumentException e) {
228                 report(e);
229             }
230             println();
231             print("interfaces: ");print(() -> classModel.interfaces().size());
232             print(", fields: " + classModel.fields().size());
233             print(", methods: " + classModel.methods().size());
234             println(", attributes: " + classModel.attributes().size());
235             indent(-1);
236             constantWriter.writeConstantPool();
237         } else {
238             print(" ");
239         }
240 
241         println("{");
242         indent(+1);
243         if ((cm.flags().flagsMask() & ACC_MODULE) != 0 && !options.verbose) {
244             writeDirectives();
245         }
246         writeFields();
247         writeMethods();
248         indent(-1);
249         println("}");
250 
251         if (options.verbose) {
252             attrWriter.write(classModel.attributes());
253         }
254 
255         if (options.verify) {
256             var vErrors = VERIFIER.verify(classModel);
257             if (!vErrors.isEmpty()) {
258                 println();
259                 for (var ve : vErrors) {
260                     println(ve.getMessage());
261                 }
262                 errorReported = true;
263             }
264         }
265         return !errorReported;
266     }
267     // where
268 
269     private static final ClassFile VERIFIER = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(
270             ClassHierarchyResolver.defaultResolver().orElse(new ClassHierarchyResolver() {
271                 @Override
272                 public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDesc) {
273                     // mark all unresolved classes as interfaces to exclude them from assignability verification
274                     return ClassHierarchyInfo.ofInterface();
275                 }
276             })));
277 
278     final SignaturePrinter sigPrinter;
279 
280     public static record SignaturePrinter(boolean verbose) {
281 
282         public String print(ClassSignature cs, boolean isInterface) {
283             var sb = new StringBuilder();
284             print(sb, cs.typeParameters());
285             if (isInterface) {
286                 String sep = " extends ";
287                 for (var is : cs.superinterfaceSignatures()) {
288                     sb.append(sep);
289                     print(sb, is);
290                     sep = ", ";
291                 }
292             } else {
293                 if (cs.superclassSignature() != null
294                         && (verbose || !isObject(cs.superclassSignature()))) {
295                     sb.append(" extends ");
296                     print(sb, cs.superclassSignature());
297                 }
298                 String sep = " implements ";
299                 for (var is : cs.superinterfaceSignatures()) {
300                     sb.append(sep);
301                     print(sb, is);
302                     sep = ", ";
303                 }
304             }
305             return sb.toString();
306         }
307 
308         public String print(Signature sig) {
309             var sb = new StringBuilder();
310             print(sb, sig);
311             return sb.toString();
312         }
313 
314         public String printTypeParams(List<Signature.TypeParam> tps) {
315             var sb = new StringBuilder();
316             print(sb, tps);
317             return sb.toString();
318         }
319 
320         public String printList(String prefix, List<? extends Signature> args,
321                 String postfix) {
322             var sb = new StringBuilder();
323             sb.append(prefix);
324             String sep = "";
325             for (var arg : args) {
326                 sb.append(sep);
327                 print(sb, arg);
328                 sep = ", ";
329             }
330             return sb.append(postfix).toString();
331         }
332 
333         private boolean isObject(Signature sig) {
334             return (sig instanceof Signature.ClassTypeSig cts)
335                     && cts.outerType().isEmpty()
336                     && cts.className().equals("java/lang/Object")
337                     && (cts.typeArgs().isEmpty());
338         }
339 
340         private void print(StringBuilder sb, List<Signature.TypeParam> tps) {
341             if (!tps.isEmpty()) {
342                 sb.append('<');
343                 String sep = "";
344                 for (var tp : tps) {
345                     sb.append(sep).append(tp.identifier());
346                     sep = " extends ";
347                     if (tp.classBound().isPresent()
348                             && (verbose || !isObject(tp.classBound().get()))) {
349                         sb.append(sep);
350                         print(sb, tp.classBound().get());
351                         sep = " & ";
352                     }
353                     for (var bound: tp.interfaceBounds()) {
354                         sb.append(sep);
355                         print(sb, bound);
356                         sep = " & ";
357                     }
358                     sep = ", ";
359                 }
360                 sb.append('>');
361             }
362         }
363 
364         private void print(StringBuilder sb, Signature sig) {
365             if (sig instanceof Signature.BaseTypeSig bts) {
366                     sb.append(ClassDesc.ofDescriptor("" + bts.baseType()).displayName());
367             } else if (sig instanceof Signature.ClassTypeSig cts) {
368                 if (cts.outerType().isPresent()) {
369                     print(sb, cts.outerType().get());
370                     sb.append(".");
371                 }
372                 sb.append(getJavaName(cts.className()));
373                 if (!cts.typeArgs().isEmpty()) {
374                     String sep = "";
375                     sb.append('<');
376                     for (var ta : cts.typeArgs()) {
377                         sb.append(sep);
378                         print(sb, ta);
379                         sep = ", ";
380                     }
381                     sb.append('>');
382                 }
383             } else if (sig instanceof Signature.TypeVarSig tvs) {
384                 sb.append(tvs.identifier());
385             } else if (sig instanceof Signature.ArrayTypeSig ats) {
386                 print(sb, ats.componentSignature());
387                 sb.append("[]");
388             }
389         }
390 
391         private void print(StringBuilder sb, Signature.TypeArg ta) {
392             switch (ta) {
393                 case Signature.TypeArg.Unbounded _ -> sb.append('?');
394                 case Signature.TypeArg.Bounded bta -> {
395                     switch (bta.wildcardIndicator()) {
396                         case NONE -> print(sb, bta.boundType());
397                         case EXTENDS -> {
398                             sb.append("? extends ");
399                             print(sb, bta.boundType());
400                         }
401                         case SUPER -> {
402                             sb.append("? super ");
403                             print(sb, bta.boundType());
404                         }
405                     }
406                 }
407             }
408         }
409     }
410 
411     protected void writeFields() {
412         for (var f: classModel.fields()) {
413             writeField(f);
414         }
415     }
416 
417     protected void writeField(FieldModel f) {
418         if (!options.checkAccess(f.flags().flagsMask()))
419             return;
420 
421         var flags = f.flags();
422         writeModifiers(flagsReportUnknown(flags).stream().filter(fl -> fl.sourceModifier())
423                 .map(fl -> Modifier.toString(fl.mask())).toList());
424         print(() -> sigPrinter.print(
425                 f.findAttribute(Attributes.signature())
426                         .map(SignatureAttribute::asTypeSignature)
427                         .orElseGet(() -> Signature.of(f.fieldTypeSymbol()))));
428         print(" ");
429         print(() -> f.fieldName().stringValue());
430         if (options.showConstants) {
431             var a = f.findAttribute(Attributes.constantValue());
432             if (a.isPresent()) {
433                 print(" = ");
434                 var cv = a.get();
435                 print(() -> getConstantValue(f.fieldTypeSymbol(), cv.constant()));
436             }
437         }
438         print(";");
439         println();
440 
441         indent(+1);
442 
443         boolean showBlank = false;
444 
445         if (options.showDescriptors) {
446             print("descriptor: ");println(() -> f.fieldType().stringValue());
447         }
448 
449         if (options.verbose)
450             writeList(String.format("flags: (0x%04x) ", flags.flagsMask()),
451                     flagsReportUnknown(flags).stream().map(fl -> "ACC_" + fl.name()).toList(),
452                     "\n");
453 
454         if (options.showAllAttrs) {
455             attrWriter.write(f.attributes());
456             showBlank = true;
457         }
458 
459         indent(-1);
460 
461         if (showBlank || options.showDisassembled || options.showLineAndLocalVariableTables)
462             println();
463     }
464 
465     protected void writeMethods() {
466         for (MethodModel m: classModel.methods())
467             writeMethod(m);
468         setPendingNewline(false);
469     }
470 
471     private static final int DEFAULT_ALLOWED_MAJOR_VERSION = 52;
472     private static final int DEFAULT_ALLOWED_MINOR_VERSION = 0;
473 
474     protected void writeMethod(MethodModel m) {
475         if (!options.checkAccess(m.flags().flagsMask()))
476             return;
477 
478         method = m;
479 
480         int flags = m.flags().flagsMask();
481 
482         var modifiers = new ArrayList<String>();
483         for (var f : flagsReportUnknown(m.flags()))
484             if (f.sourceModifier()) modifiers.add(Modifier.toString(f.mask()));
485 
486         String name = "???";
487         try {
488             name = m.methodName().stringValue();
489         } catch (IllegalArgumentException e) {
490             report(e);
491         }
492 
493         if ((classModel.flags().flagsMask() & ACC_INTERFACE) != 0 &&
494                 ((flags & ACC_ABSTRACT) == 0) && !name.equals("<clinit>")) {
495             if (classModel.majorVersion() > DEFAULT_ALLOWED_MAJOR_VERSION ||
496                     (classModel.majorVersion() == DEFAULT_ALLOWED_MAJOR_VERSION
497                     && classModel.minorVersion() >= DEFAULT_ALLOWED_MINOR_VERSION)) {
498                 if ((flags & (ACC_STATIC | ACC_PRIVATE)) == 0) {
499                     modifiers.add("default");
500                 }
501             }
502         }
503         writeModifiers(modifiers);
504 
505         try {
506             var sigAttr = m.findAttribute(Attributes.signature());
507             MethodSignature d;
508             if (sigAttr.isEmpty()) {
509                 d = MethodSignature.parseFrom(m.methodType().stringValue());
510             } else {
511                 d = sigAttr.get().asMethodSignature();
512             }
513 
514             if (!d.typeParameters().isEmpty()) {
515                 print(sigPrinter.printTypeParams(d.typeParameters()) + " ");
516             }
517             switch (name) {
518                 case "<init>":
519                     print(getJavaName(classModel.thisClass().asInternalName()));
520                     print(getJavaParameterTypes(d, flags));
521                     break;
522                 case "<clinit>":
523                     print("{}");
524                     break;
525                 default:
526                     print(getJavaName(sigPrinter.print(d.result())));
527                     print(" ");
528                     print(name);
529                     print(getJavaParameterTypes(d, flags));
530                     break;
531             }
532 
533             var e_attr = m.findAttribute(Attributes.exceptions());
534             // if there are generic exceptions, there must be erased exceptions
535             if (e_attr.isPresent()) {
536                 var exceptions = e_attr.get();
537                 print(" throws ");
538                 if (d != null && !d.throwableSignatures().isEmpty()) { // use generic list if available
539                     print(() -> sigPrinter.printList("", d.throwableSignatures(), ""));
540                 } else {
541                     var exNames = exceptions.exceptions();
542                     for (int i = 0; i < exNames.size(); i++) {
543                         if (i > 0)
544                             print(", ");
545                         int ii = i;
546                         print(() -> getJavaName(exNames.get(ii).asInternalName()));
547                     }
548                 }
549             }
550         } catch (IllegalArgumentException e) {
551             report(e);
552         }
553 
554         println(";");
555 
556         indent(+1);
557 
558         if (options.showDescriptors) {
559             print("descriptor: ");println(() -> m.methodType().stringValue());
560         }
561 
562         if (options.verbose) {
563             StringBuilder sb = new StringBuilder();
564             String sep = "";
565             sb.append(String.format("flags: (0x%04x) ", flags));
566             for (var f : flagsReportUnknown(m.flags())) {
567                 sb.append(sep).append("ACC_").append(f.name());
568                 sep = ", ";
569             }
570             println(sb.toString());
571         }
572 
573         var code = (CodeAttribute)m.code().orElse(null);
574 
575         if (options.showAllAttrs) {
576             attrWriter.write(m.attributes());
577         } else if (code != null) {
578             if (options.showDisassembled) {
579                 codeWriter.writeMinimal(code);
580             }
581 
582             if (options.showLineAndLocalVariableTables) {
583                 code.findAttribute(Attributes.lineNumberTable())
584                         .ifPresent(a -> attrWriter.write(a, code));
585                 code.findAttribute(Attributes.localVariableTable())
586                         .ifPresent(a -> attrWriter.write(a, code));
587             }
588         }
589 
590         indent(-1);
591 
592         // set pendingNewline to write a newline before the next method (if any)
593         // if a separator is desired
594         setPendingNewline(
595                 options.showDisassembled ||
596                 options.showAllAttrs ||
597                 options.showDescriptors ||
598                 options.showLineAndLocalVariableTables ||
599                 options.verbose);
600     }
601 
602     void writeModifiers(Collection<String> items) {
603         for (Object item: items) {
604             print(item);
605             print(" ");
606         }
607     }
608 
609     public static final int ACC_TRANSITIVE = 0x0020;
610     public static final int ACC_STATIC_PHASE = 0x0040;
611 
612     void writeDirectives() {
613         var attr = classModel.findAttribute(Attributes.module());
614         if (attr.isEmpty())
615             return;
616 
617         var m = attr.get();
618         for (var entry: m.requires()) {
619             print("requires");
620             if ((entry.requiresFlagsMask() & ACC_STATIC_PHASE) != 0)
621                 print(" static");
622             if ((entry.requiresFlagsMask() & ACC_TRANSITIVE) != 0)
623                 print(" transitive");
624             print(" ");
625             String mname;
626             print(entry.requires().name().stringValue());
627             println(";");
628         }
629 
630         for (var entry: m.exports()) {
631             print("exports");
632             print(" ");
633             print(entry.exportedPackage().name().stringValue().replace('/', '.'));
634             boolean first = true;
635             for (var mod: entry.exportsTo()) {
636                 if (first) {
637                     println(" to");
638                     indent(+1);
639                     first = false;
640                 } else {
641                     println(",");
642                 }
643                 print(mod.name().stringValue());
644             }
645             println(";");
646             if (!first)
647                 indent(-1);
648         }
649 
650         for (var entry: m.opens()) {
651             print("opens");
652             print(" ");
653             print(entry.openedPackage().name().stringValue().replace('/', '.'));
654             boolean first = true;
655             for (var mod: entry.opensTo()) {
656                 if (first) {
657                     println(" to");
658                     indent(+1);
659                     first = false;
660                 } else {
661                     println(",");
662                 }
663                 print(mod.name().stringValue());
664             }
665             println(";");
666             if (!first)
667                 indent(-1);
668         }
669 
670         for (var entry: m.uses()) {
671             print("uses ");
672             print(entry.asInternalName().replace('/', '.'));
673             println(";");
674         }
675 
676         for (var entry: m.provides()) {
677             print("provides  ");
678             print(entry.provides().asInternalName().replace('/', '.'));
679             boolean first = true;
680             for (var ce: entry.providesWith()) {
681                 if (first) {
682                     println(" with");
683                     indent(+1);
684                     first = false;
685                 } else {
686                     println(",");
687                 }
688                 print(ce.asInternalName().replace('/', '.'));
689             }
690             println(";");
691             if (!first)
692                 indent(-1);
693         }
694     }
695 
696     void writeList(String prefix, Collection<?> items, String suffix) {
697         print(prefix);
698         String sep = "";
699         for (Object item: items) {
700             print(sep);
701             print(item);
702             sep = ", ";
703         }
704         print(suffix);
705     }
706 
707     void writeListIfNotEmpty(String prefix, List<?> items, String suffix) {
708         if (items != null && items.size() > 0)
709             writeList(prefix, items, suffix);
710     }
711 
712     String adjustVarargs(int flags, String params) {
713         if ((flags & ACC_VARARGS) != 0) {
714             int i = params.lastIndexOf("[]");
715             if (i > 0)
716                 return params.substring(0, i) + "..." + params.substring(i+2);
717         }
718 
719         return params;
720     }
721 
722     String getJavaParameterTypes(MethodSignature d, int flags) {
723         return getJavaName(adjustVarargs(flags,
724                 sigPrinter.printList("(", d.arguments(), ")")));
725     }
726 
727     static String getJavaName(String name) {
728         return name.replace('/', '.');
729     }
730 
731     /**
732      * Get the value of an entry in the constant pool as a Java constant.
733      * Characters and booleans are represented by CONSTANT_Intgere entries.
734      * Character and string values are processed to escape characters outside
735      * the basic printable ASCII set.
736      * @param d the descriptor, giving the expected type of the constant
737      * @param index the index of the value in the constant pool
738      * @return a printable string containing the value of the constant.
739      */
740     String getConstantValue(ClassDesc d, ConstantValueEntry cpInfo) {
741         switch (cpInfo.tag()) {
742             case PoolEntry.TAG_INTEGER: {
743                 var val = (Integer)cpInfo.constantValue();
744                 switch (d.descriptorString()) {
745                     case "C":
746                         // character
747                         return getConstantCharValue((char)val.intValue());
748                     case "Z":
749                         // boolean
750                         return String.valueOf(val == 1);
751                     default:
752                         // other: assume integer
753                         return String.valueOf(val);
754                 }
755             }
756             case PoolEntry.TAG_STRING:
757                 return getConstantStringValue(cpInfo.constantValue().toString());
758             default:
759                 return constantWriter.stringValue(cpInfo);
760         }
761     }
762 
763     private String getConstantCharValue(char c) {
764         StringBuilder sb = new StringBuilder();
765         sb.append('\'');
766         sb.append(esc(c, '\''));
767         sb.append('\'');
768         return sb.toString();
769     }
770 
771     private String getConstantStringValue(String s) {
772         StringBuilder sb = new StringBuilder();
773         sb.append("\"");
774         for (int i = 0; i < s.length(); i++) {
775             sb.append(esc(s.charAt(i), '"'));
776         }
777         sb.append("\"");
778         return sb.toString();
779     }
780 
781     private String esc(char c, char quote) {
782         if (32 <= c && c <= 126 && c != quote && c != '\\')
783             return String.valueOf(c);
784         else switch (c) {
785             case '\b': return "\\b";
786             case '\n': return "\\n";
787             case '\t': return "\\t";
788             case '\f': return "\\f";
789             case '\r': return "\\r";
790             case '\\': return "\\\\";
791             case '\'': return "\\'";
792             case '\"': return "\\\"";
793             default:   return String.format("\\u%04x", (int) c);
794         }
795     }
796 
797     private Set<String> getClassModifiers(AccessFlags flags) {
798         var flagSet = flagsReportUnknown(flags);
799         Set<AccessFlag> set;
800         if (flagSet.contains(AccessFlag.INTERFACE)) {
801             set = EnumSet.copyOf(flagSet);
802             set.remove(AccessFlag.ABSTRACT);
803         } else {
804             set = flagSet;
805         }
806         return getModifiers(set);
807     }
808 
809     private static Set<String> getModifiers(Set<java.lang.reflect.AccessFlag> flags) {
810         Set<String> s = new LinkedHashSet<>();
811         for (var f : flags)
812             if (f.sourceModifier()) s.add(Modifier.toString(f.mask()));
813         return s;
814     }
815 
816     private Set<String> getClassFlags(AccessFlags flags) {
817         return getFlags(flags.flagsMask(), flagsReportUnknown(flags));
818     }
819 
820     private static Set<String> getFlags(int mask, Set<java.lang.reflect.AccessFlag> flags) {
821         Set<String> s = new LinkedHashSet<>();
822         for (var f: flags) {
823             s.add("ACC_" + f.name());
824             mask = mask & ~f.mask();
825         }
826         while (mask != 0) {
827             int bit = Integer.highestOneBit(mask);
828             s.add("0x" + Integer.toHexString(bit));
829             mask = mask & ~bit;
830         }
831         return s;
832     }
833 
834     private final Options options;
835     private final AttributeWriter attrWriter;
836     private final CodeWriter codeWriter;
837     private final ConstantWriter constantWriter;
838     private ClassModel classModel;
839     private URI uri;
840     private long lastModified;
841     private String digestName;
842     private byte[] digest;
843     private int size;
844     private MethodModel method;
845 }