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