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