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