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