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