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