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