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             // preview versions aren't explicitly supported, but latest is good enough for now
127             return ClassFileFormatVersion.latest();
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(flagsReportUnknown(flags, cffv()).stream().filter(fl -> fl.sourceModifier())
440                 .map(fl -> Modifier.toString(fl.mask())).toList());
441         print(() -> sigPrinter.print(
442                 f.findAttribute(Attributes.signature())
443                         .map(SignatureAttribute::asTypeSignature)
444                         .orElseGet(() -> Signature.of(f.fieldTypeSymbol()))));
445         print(" ");
446         print(() -> f.fieldName().stringValue());
447         if (options.showConstants) {
448             var a = f.findAttribute(Attributes.constantValue());
449             if (a.isPresent()) {
450                 print(" = ");
451                 var cv = a.get();
452                 print(() -> getConstantValue(f.fieldTypeSymbol(), cv.constant()));
453             }
454         }
455         print(";");
456         println();
457 
458         indent(+1);
459 
460         boolean showBlank = false;
461 
462         if (options.showDescriptors) {
463             print("descriptor: ");println(() -> f.fieldType().stringValue());
464         }
465 
466         if (options.verbose)
467             writeList(String.format("flags: (0x%04x) ", flags.flagsMask()),
468                     flagsReportUnknown(flags, cffv()).stream().map(fl -> "ACC_" + fl.name()).toList(),
469                     "\n");
470 
471         if (options.showAllAttrs) {
472             attrWriter.write(f.attributes(), cffv());
473             showBlank = true;
474         }
475 
476         indent(-1);
477 
478         if (showBlank || options.showDisassembled || options.showLineAndLocalVariableTables)
479             println();
480     }
481 
482     protected void writeMethods() {
483         for (MethodModel m: classModel.methods())
484             writeMethod(m);
485         setPendingNewline(false);
486     }
487 
488     private static final int DEFAULT_ALLOWED_MAJOR_VERSION = 52;
489     private static final int DEFAULT_ALLOWED_MINOR_VERSION = 0;
490 
491     protected void writeMethod(MethodModel m) {
492         if (!options.checkAccess(m.flags().flagsMask()))
493             return;
494 
495         method = m;
496 
497         int flags = m.flags().flagsMask();
498 
499         var modifiers = new ArrayList<String>();
500         for (var f : flagsReportUnknown(m.flags(), cffv()))
501             if (f.sourceModifier()) modifiers.add(Modifier.toString(f.mask()));
502 
503         String name = "???";
504         try {
505             name = m.methodName().stringValue();
506         } catch (IllegalArgumentException e) {
507             report(e);
508         }
509 
510         if ((classModel.flags().flagsMask() & ACC_INTERFACE) != 0 &&
511                 ((flags & ACC_ABSTRACT) == 0) && !name.equals("<clinit>")) {
512             if (classModel.majorVersion() > DEFAULT_ALLOWED_MAJOR_VERSION ||
513                     (classModel.majorVersion() == DEFAULT_ALLOWED_MAJOR_VERSION
514                     && classModel.minorVersion() >= DEFAULT_ALLOWED_MINOR_VERSION)) {
515                 if ((flags & (ACC_STATIC | ACC_PRIVATE)) == 0) {
516                     modifiers.add("default");
517                 }
518             }
519         }
520         writeModifiers(modifiers);
521 
522         try {
523             var sigAttr = m.findAttribute(Attributes.signature());
524             MethodSignature d;
525             if (sigAttr.isEmpty()) {
526                 d = MethodSignature.parseFrom(m.methodType().stringValue());
527             } else {
528                 d = sigAttr.get().asMethodSignature();
529             }
530 
531             if (!d.typeParameters().isEmpty()) {
532                 print(sigPrinter.printTypeParams(d.typeParameters()) + " ");
533             }
534             switch (name) {
535                 case "<init>":
536                     print(getJavaName(classModel.thisClass().asInternalName()));
537                     print(getJavaParameterTypes(d, flags));
538                     break;
539                 case "<clinit>":
540                     print("{}");
541                     break;
542                 default:
543                     print(getJavaName(sigPrinter.print(d.result())));
544                     print(" ");
545                     print(name);
546                     print(getJavaParameterTypes(d, flags));
547                     break;
548             }
549 
550             var e_attr = m.findAttribute(Attributes.exceptions());
551             // if there are generic exceptions, there must be erased exceptions
552             if (e_attr.isPresent()) {
553                 var exceptions = e_attr.get();
554                 print(" throws ");
555                 if (d != null && !d.throwableSignatures().isEmpty()) { // use generic list if available
556                     print(() -> sigPrinter.printList("", d.throwableSignatures(), ""));
557                 } else {
558                     var exNames = exceptions.exceptions();
559                     for (int i = 0; i < exNames.size(); i++) {
560                         if (i > 0)
561                             print(", ");
562                         int ii = i;
563                         print(() -> getJavaName(exNames.get(ii).asInternalName()));
564                     }
565                 }
566             }
567         } catch (IllegalArgumentException e) {
568             report(e);
569         }
570 
571         println(";");
572 
573         indent(+1);
574 
575         if (options.showDescriptors) {
576             print("descriptor: ");println(() -> m.methodType().stringValue());
577         }
578 
579         if (options.verbose) {
580             StringBuilder sb = new StringBuilder();
581             String sep = "";
582             sb.append(String.format("flags: (0x%04x) ", flags));
583             for (var f : flagsReportUnknown(m.flags(), cffv())) {
584                 sb.append(sep).append("ACC_").append(f.name());
585                 sep = ", ";
586             }
587             println(sb.toString());
588         }
589 
590         var code = (CodeAttribute)m.code().orElse(null);
591 
592         if (options.showAllAttrs) {
593             attrWriter.write(m.attributes(), cffv());
594         } else if (code != null && options.showDisassembled) {
595             codeWriter.writeMinimal(code);
596         }
597 
598         indent(-1);
599 
600         // set pendingNewline to write a newline before the next method (if any)
601         // if a separator is desired
602         setPendingNewline(
603                 options.showDisassembled ||
604                 options.showAllAttrs ||
605                 options.showDescriptors ||
606                 options.showLineAndLocalVariableTables ||
607                 options.verbose);
608     }
609 
610     void writeModifiers(Collection<String> items) {
611         for (Object item: items) {
612             print(item);
613             print(" ");
614         }
615     }
616 
617     public static final int ACC_TRANSITIVE = 0x0020;
618     public static final int ACC_STATIC_PHASE = 0x0040;
619 
620     void writeDirectives() {
621         var attr = classModel.findAttribute(Attributes.module());
622         if (attr.isEmpty())
623             return;
624 
625         var m = attr.get();
626         for (var entry: m.requires()) {
627             print("requires");
628             if ((entry.requiresFlagsMask() & ACC_STATIC_PHASE) != 0)
629                 print(" static");
630             if ((entry.requiresFlagsMask() & ACC_TRANSITIVE) != 0)
631                 print(" transitive");
632             print(" ");
633             String mname;
634             print(entry.requires().name().stringValue());
635             println(";");
636         }
637 
638         for (var entry: m.exports()) {
639             print("exports");
640             print(" ");
641             print(entry.exportedPackage().name().stringValue().replace('/', '.'));
642             boolean first = true;
643             for (var mod: entry.exportsTo()) {
644                 if (first) {
645                     println(" to");
646                     indent(+1);
647                     first = false;
648                 } else {
649                     println(",");
650                 }
651                 print(mod.name().stringValue());
652             }
653             println(";");
654             if (!first)
655                 indent(-1);
656         }
657 
658         for (var entry: m.opens()) {
659             print("opens");
660             print(" ");
661             print(entry.openedPackage().name().stringValue().replace('/', '.'));
662             boolean first = true;
663             for (var mod: entry.opensTo()) {
664                 if (first) {
665                     println(" to");
666                     indent(+1);
667                     first = false;
668                 } else {
669                     println(",");
670                 }
671                 print(mod.name().stringValue());
672             }
673             println(";");
674             if (!first)
675                 indent(-1);
676         }
677 
678         for (var entry: m.uses()) {
679             print("uses ");
680             print(entry.asInternalName().replace('/', '.'));
681             println(";");
682         }
683 
684         for (var entry: m.provides()) {
685             print("provides  ");
686             print(entry.provides().asInternalName().replace('/', '.'));
687             boolean first = true;
688             for (var ce: entry.providesWith()) {
689                 if (first) {
690                     println(" with");
691                     indent(+1);
692                     first = false;
693                 } else {
694                     println(",");
695                 }
696                 print(ce.asInternalName().replace('/', '.'));
697             }
698             println(";");
699             if (!first)
700                 indent(-1);
701         }
702     }
703 
704     void writeList(String prefix, Collection<?> items, String suffix) {
705         print(prefix);
706         String sep = "";
707         for (Object item: items) {
708             print(sep);
709             print(item);
710             sep = ", ";
711         }
712         print(suffix);
713     }
714 
715     void writeListIfNotEmpty(String prefix, List<?> items, String suffix) {
716         if (items != null && items.size() > 0)
717             writeList(prefix, items, suffix);
718     }
719 
720     String adjustVarargs(int flags, String params) {
721         if ((flags & ACC_VARARGS) != 0) {
722             int i = params.lastIndexOf("[]");
723             if (i > 0)
724                 return params.substring(0, i) + "..." + params.substring(i+2);
725         }
726 
727         return params;
728     }
729 
730     String getJavaParameterTypes(MethodSignature d, int flags) {
731         return getJavaName(adjustVarargs(flags,
732                 sigPrinter.printList("(", d.arguments(), ")")));
733     }
734 
735     static String getJavaName(String name) {
736         return name.replace('/', '.');
737     }
738 
739     /**
740      * Get the value of an entry in the constant pool as a Java constant.
741      * Characters and booleans are represented by CONSTANT_Intgere entries.
742      * Character and string values are processed to escape characters outside
743      * the basic printable ASCII set.
744      * @param d the descriptor, giving the expected type of the constant
745      * @param index the index of the value in the constant pool
746      * @return a printable string containing the value of the constant.
747      */
748     String getConstantValue(ClassDesc d, ConstantValueEntry cpInfo) {
749         switch (cpInfo.tag()) {
750             case PoolEntry.TAG_INTEGER: {
751                 var val = (Integer)cpInfo.constantValue();
752                 switch (d.descriptorString()) {
753                     case "C":
754                         // character
755                         return getConstantCharValue((char)val.intValue());
756                     case "Z":
757                         // boolean
758                         return String.valueOf(val == 1);
759                     default:
760                         // other: assume integer
761                         return String.valueOf(val);
762                 }
763             }
764             case PoolEntry.TAG_STRING:
765                 return getConstantStringValue(cpInfo.constantValue().toString());
766             default:
767                 return constantWriter.stringValue(cpInfo);
768         }
769     }
770 
771     private String getConstantCharValue(char c) {
772         StringBuilder sb = new StringBuilder();
773         sb.append('\'');
774         sb.append(esc(c, '\''));
775         sb.append('\'');
776         return sb.toString();
777     }
778 
779     private String getConstantStringValue(String s) {
780         StringBuilder sb = new StringBuilder();
781         sb.append("\"");
782         for (int i = 0; i < s.length(); i++) {
783             sb.append(esc(s.charAt(i), '"'));
784         }
785         sb.append("\"");
786         return sb.toString();
787     }
788 
789     private String esc(char c, char quote) {
790         if (32 <= c && c <= 126 && c != quote && c != '\\')
791             return String.valueOf(c);
792         else switch (c) {
793             case '\b': return "\\b";
794             case '\n': return "\\n";
795             case '\t': return "\\t";
796             case '\f': return "\\f";
797             case '\r': return "\\r";
798             case '\\': return "\\\\";
799             case '\'': return "\\'";
800             case '\"': return "\\\"";
801             default:   return String.format("\\u%04x", (int) c);
802         }
803     }
804 
805     private Set<String> getClassModifiers(AccessFlags flags) {
806         var flagSet = flagsReportUnknown(flags, cffv());
807         Set<AccessFlag> set;
808         if (flagSet.contains(AccessFlag.INTERFACE)) {
809             set = EnumSet.copyOf(flagSet);
810             set.remove(AccessFlag.ABSTRACT);
811         } else {
812             set = flagSet;
813         }
814         return getModifiers(set);
815     }
816 
817     private Set<String> getClassModifiers(AccessFlags flags, int majorVersion, int minorVersion) {
818         boolean previewClassFile = minorVersion == ClassFile.PREVIEW_MINOR_VERSION;
819         Set<AccessFlag> flagSet = flagsReportUnknown(flags, cffv());
820         if (flagSet.contains(AccessFlag.INTERFACE)) {
821             flagSet = EnumSet.copyOf(flagSet);
822             flagSet.remove(AccessFlag.ABSTRACT);
823         } else if (Source.isSupported(Source.Feature.VALUE_CLASSES, majorVersion) && previewClassFile) {
824             Set<String> classModifers = getModifiers(flagSet);
825             classModifers.add("value");
826             return classModifers;
827         }
828         return getModifiers(flagSet);
829     }
830 
831     private static Set<String> getModifiers(Set<AccessFlag> flags) {
832         Set<String> s = new LinkedHashSet<>();
833         for (var f : flags)
834             if (f.sourceModifier()) s.add(Modifier.toString(f.mask()));
835         return s;
836     }
837 
838     private Set<String> getClassFlags(AccessFlags flags) {
839         return getFlags(flags.flagsMask(), flagsReportUnknown(flags, cffv()));
840     }
841 
842     private static Set<String> getFlags(int mask, Set<AccessFlag> flags) {
843         Set<String> s = new LinkedHashSet<>();
844         for (var f: flags) {
845             s.add("ACC_" + f.name());
846             mask = mask & ~f.mask();
847         }
848         while (mask != 0) {
849             int bit = Integer.highestOneBit(mask);
850             s.add("0x" + Integer.toHexString(bit));
851             mask = mask & ~bit;
852         }
853         return s;
854     }
855 
856     private final Options options;
857     private final AttributeWriter attrWriter;
858     private final CodeWriter codeWriter;
859     private final ConstantWriter constantWriter;
860     private ClassModel classModel;
861     private URI uri;
862     private long lastModified;
863     private String digestName;
864     private byte[] digest;
865     private int size;
866     private MethodModel method;
867 }