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