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 }