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 // preview versions aren't explicitly supported, but latest is good enough for now 127 return ClassFileFormatVersion.latest(); 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(flagsReportUnknown(flags, cffv()).stream().filter(fl -> fl.sourceModifier()) 440 .map(fl -> Modifier.toString(fl.mask())).toList()); 441 print(() -> sigPrinter.print( 442 f.findAttribute(Attributes.signature()) 443 .map(SignatureAttribute::asTypeSignature) 444 .orElseGet(() -> Signature.of(f.fieldTypeSymbol())))); 445 print(" "); 446 print(() -> f.fieldName().stringValue()); 447 if (options.showConstants) { 448 var a = f.findAttribute(Attributes.constantValue()); 449 if (a.isPresent()) { 450 print(" = "); 451 var cv = a.get(); 452 print(() -> getConstantValue(f.fieldTypeSymbol(), cv.constant())); 453 } 454 } 455 print(";"); 456 println(); 457 458 indent(+1); 459 460 boolean showBlank = false; 461 462 if (options.showDescriptors) { 463 print("descriptor: ");println(() -> f.fieldType().stringValue()); 464 } 465 466 if (options.verbose) 467 writeList(String.format("flags: (0x%04x) ", flags.flagsMask()), 468 flagsReportUnknown(flags, cffv()).stream().map(fl -> "ACC_" + fl.name()).toList(), 469 "\n"); 470 471 if (options.showAllAttrs) { 472 attrWriter.write(f.attributes(), cffv()); 473 showBlank = true; 474 } 475 476 indent(-1); 477 478 if (showBlank || options.showDisassembled || options.showLineAndLocalVariableTables) 479 println(); 480 } 481 482 protected void writeMethods() { 483 for (MethodModel m: classModel.methods()) 484 writeMethod(m); 485 setPendingNewline(false); 486 } 487 488 private static final int DEFAULT_ALLOWED_MAJOR_VERSION = 52; 489 private static final int DEFAULT_ALLOWED_MINOR_VERSION = 0; 490 491 protected void writeMethod(MethodModel m) { 492 if (!options.checkAccess(m.flags().flagsMask())) 493 return; 494 495 method = m; 496 497 int flags = m.flags().flagsMask(); 498 499 var modifiers = new ArrayList<String>(); 500 for (var f : flagsReportUnknown(m.flags(), cffv())) 501 if (f.sourceModifier()) modifiers.add(Modifier.toString(f.mask())); 502 503 String name = "???"; 504 try { 505 name = m.methodName().stringValue(); 506 } catch (IllegalArgumentException e) { 507 report(e); 508 } 509 510 if ((classModel.flags().flagsMask() & ACC_INTERFACE) != 0 && 511 ((flags & ACC_ABSTRACT) == 0) && !name.equals("<clinit>")) { 512 if (classModel.majorVersion() > DEFAULT_ALLOWED_MAJOR_VERSION || 513 (classModel.majorVersion() == DEFAULT_ALLOWED_MAJOR_VERSION 514 && classModel.minorVersion() >= DEFAULT_ALLOWED_MINOR_VERSION)) { 515 if ((flags & (ACC_STATIC | ACC_PRIVATE)) == 0) { 516 modifiers.add("default"); 517 } 518 } 519 } 520 writeModifiers(modifiers); 521 522 try { 523 var sigAttr = m.findAttribute(Attributes.signature()); 524 MethodSignature d; 525 if (sigAttr.isEmpty()) { 526 d = MethodSignature.parseFrom(m.methodType().stringValue()); 527 } else { 528 d = sigAttr.get().asMethodSignature(); 529 } 530 531 if (!d.typeParameters().isEmpty()) { 532 print(sigPrinter.printTypeParams(d.typeParameters()) + " "); 533 } 534 switch (name) { 535 case "<init>": 536 print(getJavaName(classModel.thisClass().asInternalName())); 537 print(getJavaParameterTypes(d, flags)); 538 break; 539 case "<clinit>": 540 print("{}"); 541 break; 542 default: 543 print(getJavaName(sigPrinter.print(d.result()))); 544 print(" "); 545 print(name); 546 print(getJavaParameterTypes(d, flags)); 547 break; 548 } 549 550 var e_attr = m.findAttribute(Attributes.exceptions()); 551 // if there are generic exceptions, there must be erased exceptions 552 if (e_attr.isPresent()) { 553 var exceptions = e_attr.get(); 554 print(" throws "); 555 if (d != null && !d.throwableSignatures().isEmpty()) { // use generic list if available 556 print(() -> sigPrinter.printList("", d.throwableSignatures(), "")); 557 } else { 558 var exNames = exceptions.exceptions(); 559 for (int i = 0; i < exNames.size(); i++) { 560 if (i > 0) 561 print(", "); 562 int ii = i; 563 print(() -> getJavaName(exNames.get(ii).asInternalName())); 564 } 565 } 566 } 567 } catch (IllegalArgumentException e) { 568 report(e); 569 } 570 571 println(";"); 572 573 indent(+1); 574 575 if (options.showDescriptors) { 576 print("descriptor: ");println(() -> m.methodType().stringValue()); 577 } 578 579 if (options.verbose) { 580 StringBuilder sb = new StringBuilder(); 581 String sep = ""; 582 sb.append(String.format("flags: (0x%04x) ", flags)); 583 for (var f : flagsReportUnknown(m.flags(), cffv())) { 584 sb.append(sep).append("ACC_").append(f.name()); 585 sep = ", "; 586 } 587 println(sb.toString()); 588 } 589 590 var code = (CodeAttribute)m.code().orElse(null); 591 592 if (options.showAllAttrs) { 593 attrWriter.write(m.attributes(), cffv()); 594 } else if (code != null && options.showDisassembled) { 595 codeWriter.writeMinimal(code); 596 } 597 598 indent(-1); 599 600 // set pendingNewline to write a newline before the next method (if any) 601 // if a separator is desired 602 setPendingNewline( 603 options.showDisassembled || 604 options.showAllAttrs || 605 options.showDescriptors || 606 options.showLineAndLocalVariableTables || 607 options.verbose); 608 } 609 610 void writeModifiers(Collection<String> items) { 611 for (Object item: items) { 612 print(item); 613 print(" "); 614 } 615 } 616 617 public static final int ACC_TRANSITIVE = 0x0020; 618 public static final int ACC_STATIC_PHASE = 0x0040; 619 620 void writeDirectives() { 621 var attr = classModel.findAttribute(Attributes.module()); 622 if (attr.isEmpty()) 623 return; 624 625 var m = attr.get(); 626 for (var entry: m.requires()) { 627 print("requires"); 628 if ((entry.requiresFlagsMask() & ACC_STATIC_PHASE) != 0) 629 print(" static"); 630 if ((entry.requiresFlagsMask() & ACC_TRANSITIVE) != 0) 631 print(" transitive"); 632 print(" "); 633 String mname; 634 print(entry.requires().name().stringValue()); 635 println(";"); 636 } 637 638 for (var entry: m.exports()) { 639 print("exports"); 640 print(" "); 641 print(entry.exportedPackage().name().stringValue().replace('/', '.')); 642 boolean first = true; 643 for (var mod: entry.exportsTo()) { 644 if (first) { 645 println(" to"); 646 indent(+1); 647 first = false; 648 } else { 649 println(","); 650 } 651 print(mod.name().stringValue()); 652 } 653 println(";"); 654 if (!first) 655 indent(-1); 656 } 657 658 for (var entry: m.opens()) { 659 print("opens"); 660 print(" "); 661 print(entry.openedPackage().name().stringValue().replace('/', '.')); 662 boolean first = true; 663 for (var mod: entry.opensTo()) { 664 if (first) { 665 println(" to"); 666 indent(+1); 667 first = false; 668 } else { 669 println(","); 670 } 671 print(mod.name().stringValue()); 672 } 673 println(";"); 674 if (!first) 675 indent(-1); 676 } 677 678 for (var entry: m.uses()) { 679 print("uses "); 680 print(entry.asInternalName().replace('/', '.')); 681 println(";"); 682 } 683 684 for (var entry: m.provides()) { 685 print("provides "); 686 print(entry.provides().asInternalName().replace('/', '.')); 687 boolean first = true; 688 for (var ce: entry.providesWith()) { 689 if (first) { 690 println(" with"); 691 indent(+1); 692 first = false; 693 } else { 694 println(","); 695 } 696 print(ce.asInternalName().replace('/', '.')); 697 } 698 println(";"); 699 if (!first) 700 indent(-1); 701 } 702 } 703 704 void writeList(String prefix, Collection<?> items, String suffix) { 705 print(prefix); 706 String sep = ""; 707 for (Object item: items) { 708 print(sep); 709 print(item); 710 sep = ", "; 711 } 712 print(suffix); 713 } 714 715 void writeListIfNotEmpty(String prefix, List<?> items, String suffix) { 716 if (items != null && items.size() > 0) 717 writeList(prefix, items, suffix); 718 } 719 720 String adjustVarargs(int flags, String params) { 721 if ((flags & ACC_VARARGS) != 0) { 722 int i = params.lastIndexOf("[]"); 723 if (i > 0) 724 return params.substring(0, i) + "..." + params.substring(i+2); 725 } 726 727 return params; 728 } 729 730 String getJavaParameterTypes(MethodSignature d, int flags) { 731 return getJavaName(adjustVarargs(flags, 732 sigPrinter.printList("(", d.arguments(), ")"))); 733 } 734 735 static String getJavaName(String name) { 736 return name.replace('/', '.'); 737 } 738 739 /** 740 * Get the value of an entry in the constant pool as a Java constant. 741 * Characters and booleans are represented by CONSTANT_Intgere entries. 742 * Character and string values are processed to escape characters outside 743 * the basic printable ASCII set. 744 * @param d the descriptor, giving the expected type of the constant 745 * @param index the index of the value in the constant pool 746 * @return a printable string containing the value of the constant. 747 */ 748 String getConstantValue(ClassDesc d, ConstantValueEntry cpInfo) { 749 switch (cpInfo.tag()) { 750 case PoolEntry.TAG_INTEGER: { 751 var val = (Integer)cpInfo.constantValue(); 752 switch (d.descriptorString()) { 753 case "C": 754 // character 755 return getConstantCharValue((char)val.intValue()); 756 case "Z": 757 // boolean 758 return String.valueOf(val == 1); 759 default: 760 // other: assume integer 761 return String.valueOf(val); 762 } 763 } 764 case PoolEntry.TAG_STRING: 765 return getConstantStringValue(cpInfo.constantValue().toString()); 766 default: 767 return constantWriter.stringValue(cpInfo); 768 } 769 } 770 771 private String getConstantCharValue(char c) { 772 StringBuilder sb = new StringBuilder(); 773 sb.append('\''); 774 sb.append(esc(c, '\'')); 775 sb.append('\''); 776 return sb.toString(); 777 } 778 779 private String getConstantStringValue(String s) { 780 StringBuilder sb = new StringBuilder(); 781 sb.append("\""); 782 for (int i = 0; i < s.length(); i++) { 783 sb.append(esc(s.charAt(i), '"')); 784 } 785 sb.append("\""); 786 return sb.toString(); 787 } 788 789 private String esc(char c, char quote) { 790 if (32 <= c && c <= 126 && c != quote && c != '\\') 791 return String.valueOf(c); 792 else switch (c) { 793 case '\b': return "\\b"; 794 case '\n': return "\\n"; 795 case '\t': return "\\t"; 796 case '\f': return "\\f"; 797 case '\r': return "\\r"; 798 case '\\': return "\\\\"; 799 case '\'': return "\\'"; 800 case '\"': return "\\\""; 801 default: return String.format("\\u%04x", (int) c); 802 } 803 } 804 805 private Set<String> getClassModifiers(AccessFlags flags) { 806 var flagSet = flagsReportUnknown(flags, cffv()); 807 Set<AccessFlag> set; 808 if (flagSet.contains(AccessFlag.INTERFACE)) { 809 set = EnumSet.copyOf(flagSet); 810 set.remove(AccessFlag.ABSTRACT); 811 } else { 812 set = flagSet; 813 } 814 return getModifiers(set); 815 } 816 817 private Set<String> getClassModifiers(AccessFlags flags, int majorVersion, int minorVersion) { 818 boolean previewClassFile = minorVersion == ClassFile.PREVIEW_MINOR_VERSION; 819 Set<AccessFlag> flagSet = flagsReportUnknown(flags, cffv()); 820 if (flagSet.contains(AccessFlag.INTERFACE)) { 821 flagSet = EnumSet.copyOf(flagSet); 822 flagSet.remove(AccessFlag.ABSTRACT); 823 } else if (Source.isSupported(Source.Feature.VALUE_CLASSES, majorVersion) && previewClassFile) { 824 Set<String> classModifers = getModifiers(flagSet); 825 classModifers.add("value"); 826 return classModifers; 827 } 828 return getModifiers(flagSet); 829 } 830 831 private static Set<String> getModifiers(Set<AccessFlag> flags) { 832 Set<String> s = new LinkedHashSet<>(); 833 for (var f : flags) 834 if (f.sourceModifier()) s.add(Modifier.toString(f.mask())); 835 return s; 836 } 837 838 private Set<String> getClassFlags(AccessFlags flags) { 839 return getFlags(flags.flagsMask(), flagsReportUnknown(flags, cffv())); 840 } 841 842 private static Set<String> getFlags(int mask, Set<AccessFlag> flags) { 843 Set<String> s = new LinkedHashSet<>(); 844 for (var f: flags) { 845 s.add("ACC_" + f.name()); 846 mask = mask & ~f.mask(); 847 } 848 while (mask != 0) { 849 int bit = Integer.highestOneBit(mask); 850 s.add("0x" + Integer.toHexString(bit)); 851 mask = mask & ~bit; 852 } 853 return s; 854 } 855 856 private final Options options; 857 private final AttributeWriter attrWriter; 858 private final CodeWriter codeWriter; 859 private final ConstantWriter constantWriter; 860 private ClassModel classModel; 861 private URI uri; 862 private long lastModified; 863 private String digestName; 864 private byte[] digest; 865 private int size; 866 private MethodModel method; 867 }