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