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