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