1 /*
   2  * Copyright (c) 2022, 2024, 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 package jdk.internal.classfile.impl;
  26 
  27 import java.lang.classfile.*;
  28 import java.lang.classfile.AnnotationValue.*;
  29 import java.lang.classfile.attribute.*;
  30 import java.lang.classfile.attribute.StackMapFrameInfo.ObjectVerificationTypeInfo;
  31 import java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo;
  32 import java.lang.classfile.attribute.StackMapFrameInfo.UninitializedVerificationTypeInfo;
  33 import java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo;
  34 import jdk.internal.classfile.components.ClassPrinter.LeafNode;
  35 import jdk.internal.classfile.components.ClassPrinter.ListNode;
  36 import jdk.internal.classfile.components.ClassPrinter.MapNode;
  37 import jdk.internal.classfile.components.ClassPrinter.Node;
  38 import jdk.internal.classfile.components.ClassPrinter.Verbosity;
  39 import java.lang.classfile.constantpool.*;
  40 import java.lang.classfile.instruction.*;
  41 import java.lang.constant.ConstantDesc;
  42 import java.lang.constant.DirectMethodHandleDesc;
  43 import java.lang.reflect.AccessFlag;
  44 import java.util.*;
  45 import java.util.function.BiConsumer;
  46 import java.util.function.Consumer;
  47 import java.util.stream.IntStream;
  48 import java.util.stream.Stream;
  49 
  50 import static java.lang.classfile.constantpool.PoolEntry.TAG_CLASS;
  51 import static java.lang.classfile.constantpool.PoolEntry.TAG_DOUBLE;
  52 import static java.lang.classfile.constantpool.PoolEntry.TAG_FLOAT;
  53 import static java.lang.classfile.constantpool.PoolEntry.TAG_LONG;
  54 import static java.lang.classfile.constantpool.PoolEntry.TAG_STRING;
  55 import static java.lang.classfile.constantpool.PoolEntry.*;
  56 import static java.lang.constant.ConstantDescs.BSM_NULL_CONSTANT;
  57 import static java.util.Objects.requireNonNull;
  58 import static jdk.internal.classfile.impl.ClassPrinterImpl.Style.BLOCK;
  59 import static jdk.internal.classfile.impl.ClassPrinterImpl.Style.FLOW;
  60 
  61 public final class ClassPrinterImpl {
  62 
  63     public enum Style { BLOCK, FLOW }
  64 
  65     public record LeafNodeImpl(ConstantDesc name, ConstantDesc value) implements LeafNode {
  66 
  67         @Override
  68         public Stream<Node> walk() {
  69             return Stream.of(this);
  70         }
  71     }
  72 
  73     public static sealed class ListNodeImpl extends AbstractList<Node> implements ListNode {
  74 
  75         private final Style style;
  76         private final ConstantDesc name;
  77         protected final List<Node> nodes;
  78 
  79         public ListNodeImpl(Style style, ConstantDesc name, Stream<Node> nodes) {
  80             this.style = style;
  81             this.name = name;
  82             this.nodes = nodes.toList();
  83         }
  84 
  85         protected ListNodeImpl(Style style, ConstantDesc name, List<Node> nodes) {
  86             this.style = style;
  87             this.name = name;
  88             this.nodes = nodes;
  89         }
  90 
  91         @Override
  92         public ConstantDesc name() {
  93             return name;
  94         }
  95 
  96         @Override
  97         public Stream<Node> walk() {
  98             return Stream.concat(Stream.of(this), stream().flatMap(Node::walk));
  99         }
 100 
 101         public Style style() {
 102             return style;
 103         }
 104 
 105         @Override
 106         public Node get(int index) {
 107             return nodes.get(index);
 108         }
 109 
 110         @Override
 111         public int size() {
 112             return nodes.size();
 113         }
 114     }
 115 
 116     public static final class MapNodeImpl implements MapNode {
 117 
 118         private static final class PrivateListNodeImpl extends ListNodeImpl {
 119             PrivateListNodeImpl(Style style, ConstantDesc name, Node... n) {
 120                 super(style, name, new ArrayList<>(List.of(n)));
 121             }
 122         }
 123 
 124         private final Style style;
 125         private final ConstantDesc name;
 126         private final Map<ConstantDesc, Node> map;
 127 
 128         public MapNodeImpl(Style style, ConstantDesc name) {
 129             this.style = style;
 130             this.name = name;
 131             this.map = new LinkedHashMap<>();
 132         }
 133 
 134         @Override
 135         public ConstantDesc name() {
 136             return name;
 137         }
 138 
 139         @Override
 140         public Stream<Node> walk() {
 141             return Stream.concat(Stream.of(this), values().stream().flatMap(Node::walk));
 142         }
 143 
 144         public Style style() {
 145             return style;
 146         }
 147 
 148         @Override
 149         public int size() {
 150             return map.size();
 151         }
 152         @Override
 153         public boolean isEmpty() {
 154             return map.isEmpty();
 155         }
 156         @Override
 157         public boolean containsKey(Object key) {
 158             return map.containsKey(key);
 159         }
 160         @Override
 161         public boolean containsValue(Object value) {
 162             return map.containsValue(value);
 163         }
 164 
 165         @Override
 166         public Node get(Object key) {
 167             return map.get(key);
 168         }
 169 
 170         @Override
 171         public Node put(ConstantDesc key, Node value) {
 172             throw new UnsupportedOperationException();
 173         }
 174 
 175         @Override
 176         public Node remove(Object key) {
 177             throw new UnsupportedOperationException();
 178         }
 179 
 180         @Override
 181         public void putAll(Map<? extends ConstantDesc, ? extends Node> m) {
 182             throw new UnsupportedOperationException();
 183         }
 184 
 185         @Override
 186         public void clear() {
 187             throw new UnsupportedOperationException();
 188         }
 189 
 190         @Override
 191         public Set<ConstantDesc> keySet() {
 192             return Collections.unmodifiableSet(map.keySet());
 193         }
 194 
 195         @Override
 196         public Collection<Node> values() {
 197             return Collections.unmodifiableCollection(map.values());
 198         }
 199 
 200         @Override
 201         public Set<Entry<ConstantDesc, Node>> entrySet() {
 202             return Collections.unmodifiableSet(map.entrySet());
 203         }
 204 
 205 
 206         MapNodeImpl with(Node... nodes) {
 207             for (var n : nodes) {
 208                 if (n != null) {
 209                     var prev = map.putIfAbsent(n.name(), n);
 210                     if (prev != null) {
 211                         //nodes with duplicite keys are joined into a list
 212                         if (prev instanceof PrivateListNodeImpl list) {
 213                             list.nodes.add(n);
 214                         } else {
 215                             map.put(n.name(), new PrivateListNodeImpl(style, n.name(), prev, n));
 216                         }
 217                     }
 218                 }
 219             }
 220             return this;
 221         }
 222     }
 223 
 224     private static Node leaf(ConstantDesc name, ConstantDesc value) {
 225         return new LeafNodeImpl(name, value);
 226     }
 227 
 228     private static Node[] leafs(ConstantDesc... namesAndValues) {
 229         if ((namesAndValues.length & 1) > 0)
 230             throw new AssertionError("Odd number of arguments: " + Arrays.toString(namesAndValues));
 231         var nodes = new Node[namesAndValues.length >> 1];
 232         for (int i = 0, j = 0; i < nodes.length; i ++) {
 233             nodes[i] = leaf(namesAndValues[j++], namesAndValues[j++]);
 234         }
 235         return nodes;
 236     }
 237 
 238     private static Node list(ConstantDesc listName, ConstantDesc itemsName, Stream<ConstantDesc> values) {
 239         return new ListNodeImpl(FLOW, listName, values.map(v -> leaf(itemsName, v)));
 240     }
 241 
 242     private static Node map(ConstantDesc mapName, ConstantDesc... keysAndValues) {
 243         return new MapNodeImpl(FLOW, mapName).with(leafs(keysAndValues));
 244     }
 245 
 246     private static final String NL = System.lineSeparator();
 247 
 248     private static final char[] DIGITS = "0123456789ABCDEF".toCharArray();
 249 
 250     private static void escape(int c, StringBuilder sb) {
 251         switch (c) {
 252             case '\\'  -> sb.append('\\').append('\\');
 253             case '"' -> sb.append('\\').append('"');
 254             case '\b' -> sb.append('\\').append('b');
 255             case '\n' -> sb.append('\\').append('n');
 256             case '\t' -> sb.append('\\').append('t');
 257             case '\f' -> sb.append('\\').append('f');
 258             case '\r' -> sb.append('\\').append('r');
 259             default -> {
 260                 if (c >= 0x20 && c < 0x7f) {
 261                     sb.append((char)c);
 262                 } else {
 263                     sb.append('\\').append('u').append(DIGITS[(c >> 12) & 0xf])
 264                             .append(DIGITS[(c >> 8) & 0xf]).append(DIGITS[(c >> 4) & 0xf]).append(DIGITS[(c) & 0xf]);
 265                 }
 266             }
 267         }
 268     }
 269 
 270     public static void toYaml(Node node, Consumer<String> out) {
 271         toYaml(0, false, new ListNodeImpl(BLOCK, null, Stream.of(node)), out);
 272         out.accept(NL);
 273     }
 274 
 275     private static void toYaml(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) {
 276         switch (node) {
 277             case LeafNode leaf -> {
 278                 var v = leaf.value();
 279                 if (BSM_NULL_CONSTANT.equals(v)) {
 280                     out.accept("null");
 281                 } else {
 282                     out.accept(quoteAndEscapeYaml(v));
 283                 }
 284             }
 285             case ListNodeImpl list -> {
 286                 switch (list.style()) {
 287                     case FLOW -> {
 288                         out.accept("[");
 289                         boolean first = true;
 290                         for (var n : list) {
 291                             if (first) first = false;
 292                             else out.accept(", ");
 293                             toYaml(0, false, n, out);
 294                         }
 295                         out.accept("]");
 296                     }
 297                     case BLOCK -> {
 298                         for (var n : list) {
 299                             out.accept(NL + "    ".repeat(indent) + "  - ");
 300                             toYaml(indent + 1, true, n, out);
 301                         }
 302                     }
 303                 }
 304             }
 305             case MapNodeImpl map -> {
 306                 switch (map.style()) {
 307                     case FLOW -> {
 308                         out.accept("{");
 309                         boolean first = true;
 310                         for (var n : map.values()) {
 311                             if (first) first = false;
 312                             else out.accept(", ");
 313                             out.accept(quoteAndEscapeYaml(n.name()) + ": ");
 314                             toYaml(0, false, n, out);
 315                         }
 316                         out.accept("}");
 317                     }
 318                     case BLOCK -> {
 319                         for (var n : map.values()) {
 320                             if (skipFirstIndent) {
 321                                 skipFirstIndent = false;
 322                             } else {
 323                                 out.accept(NL + "    ".repeat(indent));
 324                             }
 325                             out.accept(quoteAndEscapeYaml(n.name()) + ": ");
 326                             toYaml(n instanceof ListNodeImpl pl && pl.style() == BLOCK ? indent : indent + 1, false, n, out);
 327                         }
 328                     }
 329                 }
 330             }
 331         }
 332     }
 333 
 334     private static String quoteAndEscapeYaml(ConstantDesc value) {
 335         String s = String.valueOf(value);
 336         if (value instanceof Number) return s;
 337         if (s.length() == 0) return "''";
 338         if (s.equalsIgnoreCase("null")) return "'" + s + "'";
 339         var sb = new StringBuilder(s.length() << 1);
 340         s.chars().forEach(c -> {
 341             switch (c) {
 342                 case '\''  -> sb.append("''");
 343                 default -> escape(c, sb);
 344             }});
 345         String esc = sb.toString();
 346         if (esc.length() != s.length()) return "'" + esc + "'";
 347         switch (esc.charAt(0)) {
 348             case '-', '?', ':', ',', '[', ']', '{', '}', '#', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`':
 349                 return "'" + esc + "'";
 350         }
 351         for (int i = 1; i < esc.length(); i++) {
 352             switch (esc.charAt(i)) {
 353                 case ',', '[', ']', '{', '}':
 354                     return "'" + esc + "'";
 355             }
 356         }
 357         return esc;
 358     }
 359 
 360     public static void toJson(Node node, Consumer<String> out) {
 361         toJson(1, true, node, out);
 362         out.accept(NL);
 363     }
 364 
 365     private static void toJson(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) {
 366         switch (node) {
 367             case LeafNode leaf -> {
 368                 var v = leaf.value();
 369                 if (BSM_NULL_CONSTANT.equals(v)) {
 370                     out.accept("null");
 371                 } else {
 372                     out.accept(quoteAndEscapeJson(v));
 373                 }
 374             }
 375             case ListNodeImpl list -> {
 376                 out.accept("[");
 377                 boolean first = true;
 378                 switch (list.style()) {
 379                     case FLOW -> {
 380                         for (var n : list) {
 381                             if (first) first = false;
 382                             else out.accept(", ");
 383                             toJson(0, false, n, out);
 384                         }
 385                     }
 386                     case BLOCK -> {
 387                         for (var n : list) {
 388                             if (first) first = false;
 389                             else out.accept(",");
 390                             out.accept(NL + "    ".repeat(indent));
 391                             toJson(indent + 1, true, n, out);
 392                         }
 393                     }
 394                 }
 395                 out.accept("]");
 396             }
 397             case MapNodeImpl map -> {
 398                 switch (map.style()) {
 399                     case FLOW -> {
 400                         out.accept("{");
 401                         boolean first = true;
 402                         for (var n : map.values()) {
 403                             if (first) first = false;
 404                             else out.accept(", ");
 405                             out.accept(quoteAndEscapeJson(n.name().toString()) + ": ");
 406                             toJson(0, false, n, out);
 407                         }
 408                     }
 409                     case BLOCK -> {
 410                         if (skipFirstIndent) out.accept("  { ");
 411                         else out.accept("{");
 412                         boolean first = true;
 413                         for (var n : map.values()) {
 414                             if (first) first = false;
 415                             else out.accept(",");
 416                             if (skipFirstIndent) skipFirstIndent = false;
 417                             else out.accept(NL + "    ".repeat(indent));
 418                             out.accept(quoteAndEscapeJson(n.name().toString()) + ": ");
 419                             toJson(indent + 1, false, n, out);
 420                         }
 421                     }
 422                 }
 423                 out.accept("}");
 424             }
 425         }
 426     }
 427 
 428     private static String quoteAndEscapeJson(ConstantDesc value) {
 429         String s = String.valueOf(value);
 430         if (value instanceof Number) return s;
 431         var sb = new StringBuilder(s.length() << 1);
 432         sb.append('"');
 433         s.chars().forEach(c -> escape(c, sb));
 434         sb.append('"');
 435         return sb.toString();
 436     }
 437 
 438     public static void toXml(Node node, Consumer<String> out) {
 439         out.accept("<?xml version = '1.0'?>");
 440         toXml(0, false, node, out);
 441         out.accept(NL);
 442     }
 443 
 444     private static void toXml(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) {
 445         var name = toXmlName(node.name().toString());
 446         switch (node) {
 447             case LeafNode leaf -> {
 448                 out.accept("<" + name + ">");
 449                 var v = leaf.value();
 450                 if (BSM_NULL_CONSTANT.equals(v)) {
 451                     out.accept("<null/>");
 452                 } else {
 453                     out.accept(xmlEscape(v));
 454                 }
 455             }
 456             case ListNodeImpl list -> {
 457                 switch (list.style()) {
 458                     case FLOW -> {
 459                         out.accept("<" + name + ">");
 460                         for (var n : list) {
 461                             toXml(0, false, n, out);
 462                         }
 463                     }
 464                     case BLOCK -> {
 465                         if (!skipFirstIndent)
 466                             out.accept(NL + "    ".repeat(indent));
 467                         out.accept("<" + name + ">");
 468                         for (var n : list) {
 469                             out.accept(NL + "    ".repeat(indent + 1));
 470                             toXml(indent + 1, true, n, out);
 471                         }
 472                     }
 473                 }
 474             }
 475             case MapNodeImpl map -> {
 476                 switch (map.style()) {
 477                     case FLOW -> {
 478                         out.accept("<" + name + ">");
 479                         for (var n : map.values()) {
 480                             toXml(0, false, n, out);
 481                         }
 482                     }
 483                     case BLOCK -> {
 484                         if (!skipFirstIndent)
 485                             out.accept(NL + "    ".repeat(indent));
 486                         out.accept("<" + name + ">");
 487                         for (var n : map.values()) {
 488                             out.accept(NL + "    ".repeat(indent + 1));
 489                             toXml(indent + 1, true, n, out);
 490                         }
 491                     }
 492                 }
 493             }
 494         }
 495         out.accept("</" + name + ">");
 496     }
 497 
 498     private static String xmlEscape(ConstantDesc value) {
 499         var s = String.valueOf(value);
 500         var sb = new StringBuilder(s.length() << 1);
 501         s.chars().forEach(c -> {
 502         switch (c) {
 503             case '<'  -> sb.append("&lt;");
 504             case '>'  -> sb.append("&gt;");
 505             case '"'  -> sb.append("&quot;");
 506             case '&'  -> sb.append("&amp;");
 507             case '\''  -> sb.append("&apos;");
 508             default -> escape(c, sb);
 509         }});
 510         return sb.toString();
 511     }
 512 
 513     private static String toXmlName(String name) {
 514         if (Character.isDigit(name.charAt(0)))
 515             name = "_" + name;
 516         return name.replaceAll("[^A-Za-z_0-9]", "_");
 517     }
 518 
 519     private static Node[] elementValueToTree(AnnotationValue v) {
 520         return switch (v) {
 521             case OfString cv -> leafs("string", String.valueOf(cv.stringValue()));
 522             case OfDouble cv -> leafs("double", String.valueOf(cv.doubleValue()));
 523             case OfFloat cv -> leafs("float", String.valueOf(cv.floatValue()));
 524             case OfLong cv -> leafs("long", String.valueOf(cv.longValue()));
 525             case OfInt cv -> leafs("int", String.valueOf(cv.intValue()));
 526             case OfShort cv -> leafs("short", String.valueOf(cv.shortValue()));
 527             case OfChar cv -> leafs("char", String.valueOf(cv.charValue()));
 528             case OfByte cv -> leafs("byte", String.valueOf(cv.byteValue()));
 529             case OfBoolean cv -> leafs("boolean", String.valueOf(cv.booleanValue()));
 530             case OfClass clv -> leafs("class", clv.className().stringValue());
 531             case OfEnum ev -> leafs("enum class", ev.className().stringValue(),
 532                                     "constant name", ev.constantName().stringValue());
 533             case OfAnnotation av -> leafs("annotation class", av.annotation().className().stringValue());
 534             case OfArray av -> new Node[]{new ListNodeImpl(FLOW, "array", av.values().stream().map(
 535                     ev -> new MapNodeImpl(FLOW, "value").with(elementValueToTree(ev))))};
 536         };
 537     }
 538 
 539     private static Node elementValuePairsToTree(List<AnnotationElement> evps) {
 540         return new ListNodeImpl(FLOW, "values", evps.stream().map(evp -> new MapNodeImpl(FLOW, "pair").with(
 541                 leaf("name", evp.name().stringValue()),
 542                 new MapNodeImpl(FLOW, "value").with(elementValueToTree(evp.value())))));
 543     }
 544 
 545     private static Stream<ConstantDesc> convertVTIs(CodeAttribute lr, List<VerificationTypeInfo> vtis) {
 546         return vtis.stream().mapMulti((vti, ret) -> {
 547             switch (vti) {
 548                 case SimpleVerificationTypeInfo s -> {
 549                     switch (s) {
 550                         case DOUBLE -> {
 551                             ret.accept("double");
 552                             ret.accept("double2");
 553                         }
 554                         case FLOAT ->
 555                             ret.accept("float");
 556                         case INTEGER ->
 557                             ret.accept("int");
 558                         case LONG ->  {
 559                             ret.accept("long");
 560                             ret.accept("long2");
 561                         }
 562                         case NULL -> ret.accept(BSM_NULL_CONSTANT);
 563                         case TOP -> ret.accept("?");
 564                         case UNINITIALIZED_THIS -> ret.accept("THIS");
 565                     }
 566                 }
 567                 case ObjectVerificationTypeInfo o ->
 568                     ret.accept(o.className().name().stringValue());
 569                 case UninitializedVerificationTypeInfo u ->
 570                     ret.accept("UNINITIALIZED @" + lr.labelToBci(u.newTarget()));
 571             }
 572         });
 573     }
 574 
 575     private record ExceptionHandler(int start, int end, int handler, String catchType) {}
 576 
 577     public static MapNode modelToTree(CompoundElement<?> model, Verbosity verbosity) {
 578         requireNonNull(verbosity); // we are using == checks in implementations
 579         return switch(model) {
 580             case ClassModel cm -> classToTree(cm, verbosity);
 581             case FieldModel fm -> fieldToTree(fm, verbosity);
 582             case MethodModel mm -> methodToTree(mm, verbosity);
 583             case CodeModel com -> codeToTree((CodeAttribute)com, verbosity);
 584         };
 585     }
 586 
 587     private static MapNode classToTree(ClassModel clm, Verbosity verbosity) {
 588         return new MapNodeImpl(BLOCK, "class")
 589                 .with(leaf("class name", clm.thisClass().asInternalName()),
 590                       leaf("version", clm.majorVersion() + "." + clm.minorVersion()),
 591                       list("flags", "flag", clm.flags().flags().stream().map(AccessFlag::name)),
 592                       leaf("superclass", clm.superclass().map(ClassEntry::asInternalName).orElse("")),
 593                       list("interfaces", "interface", clm.interfaces().stream().map(ClassEntry::asInternalName)),
 594                       list("attributes", "attribute", clm.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)))
 595                 .with(constantPoolToTree(clm.constantPool(), verbosity))
 596                 .with(attributesToTree(clm.attributes(), verbosity))
 597                 .with(new ListNodeImpl(BLOCK, "fields", clm.fields().stream().map(f ->
 598                     fieldToTree(f, verbosity))))
 599                 .with(new ListNodeImpl(BLOCK, "methods", clm.methods().stream().map(mm ->
 600                     methodToTree(mm, verbosity))));
 601     }
 602 
 603     private static Node[] constantPoolToTree(ConstantPool cp, Verbosity verbosity) {
 604         if (verbosity == Verbosity.TRACE_ALL) {
 605             var cpNode = new MapNodeImpl(BLOCK, "constant pool");
 606             for (PoolEntry e : cp) {
 607                 cpNode.with(new MapNodeImpl(FLOW, e.index())
 608                         .with(leaf("tag", switch (e.tag()) {
 609                             case TAG_UTF8 -> "Utf8";
 610                             case TAG_INTEGER -> "Integer";
 611                             case TAG_FLOAT -> "Float";
 612                             case TAG_LONG -> "Long";
 613                             case TAG_DOUBLE -> "Double";
 614                             case TAG_CLASS -> "Class";
 615                             case TAG_STRING -> "String";
 616                             case TAG_FIELDREF -> "Fieldref";
 617                             case TAG_METHODREF -> "Methodref";
 618                             case TAG_INTERFACE_METHODREF -> "InterfaceMethodref";
 619                             case TAG_NAME_AND_TYPE -> "NameAndType";
 620                             case TAG_METHOD_HANDLE -> "MethodHandle";
 621                             case TAG_METHOD_TYPE -> "MethodType";
 622                             case TAG_DYNAMIC -> "Dynamic";
 623                             case TAG_INVOKE_DYNAMIC -> "InvokeDynamic";
 624                             case TAG_MODULE -> "Module";
 625                             case TAG_PACKAGE -> "Package";
 626                             default -> throw new AssertionError("Unknown CP tag: " + e.tag());
 627                         }))
 628                         .with(switch (e) {
 629                             case ClassEntry ce -> leafs(
 630                                 "class name index", ce.name().index(),
 631                                 "class internal name", ce.asInternalName());
 632                             case ModuleEntry me -> leafs(
 633                                 "module name index", me.name().index(),
 634                                 "module name", me.name().stringValue());
 635                             case PackageEntry pe -> leafs(
 636                                 "package name index", pe.name().index(),
 637                                 "package name", pe.name().stringValue());
 638                             case StringEntry se -> leafs(
 639                                     "value index", se.utf8().index(),
 640                                     "value", se.stringValue());
 641                             case MemberRefEntry mre -> leafs(
 642                                     "owner index", mre.owner().index(),
 643                                     "name and type index", mre.nameAndType().index(),
 644                                     "owner", mre.owner().name().stringValue(),
 645                                     "name", mre.name().stringValue(),
 646                                     "type", mre.type().stringValue());
 647                             case NameAndTypeEntry nte -> leafs(
 648                                     "name index", nte.name().index(),
 649                                     "type index", nte.type().index(),
 650                                     "name", nte.name().stringValue(),
 651                                     "type", nte.type().stringValue());
 652                             case MethodHandleEntry mhe -> leafs(
 653                                     "reference kind", DirectMethodHandleDesc.Kind.valueOf(mhe.kind()).name(),
 654                                     "reference index", mhe.reference().index(),
 655                                     "owner", mhe.reference().owner().asInternalName(),
 656                                     "name", mhe.reference().name().stringValue(),
 657                                     "type", mhe.reference().type().stringValue());
 658                             case MethodTypeEntry mte -> leafs(
 659                                     "descriptor index", mte.descriptor().index(),
 660                                     "descriptor", mte.descriptor().stringValue());
 661                             case DynamicConstantPoolEntry dcpe -> new Node[] {
 662                                 leaf("bootstrap method handle index", dcpe.bootstrap().bootstrapMethod().index()),
 663                                 list("bootstrap method arguments indexes",
 664                                         "index", dcpe.bootstrap().arguments().stream().map(en -> en.index())),
 665                                 leaf("name and type index", dcpe.nameAndType().index()),
 666                                 leaf("name", dcpe.name().stringValue()),
 667                                 leaf("type", dcpe.type().stringValue())};
 668                             case AnnotationConstantValueEntry ve -> leafs(
 669                                 "value", String.valueOf(ve.constantValue())
 670                             );
 671                         }));
 672             }
 673             return new Node[]{cpNode};
 674         } else {
 675             return new Node[0];
 676         }
 677     }
 678 
 679     private static Node frameToTree(ConstantDesc name, CodeAttribute lr, StackMapFrameInfo f) {
 680         return new MapNodeImpl(FLOW, name).with(
 681                 list("locals", "item", convertVTIs(lr, f.locals())),
 682                 list("stack", "item", convertVTIs(lr, f.stack())));
 683     }
 684 
 685     private static MapNode fieldToTree(FieldModel f, Verbosity verbosity) {
 686         return new MapNodeImpl(BLOCK, "field")
 687                             .with(leaf("field name", f.fieldName().stringValue()),
 688                                   list("flags",
 689                                           "flag", f.flags().flags().stream().map(AccessFlag::name)),
 690                                   leaf("field type", f.fieldType().stringValue()),
 691                                   list("attributes",
 692                                           "attribute", f.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)))
 693                             .with(attributesToTree(f.attributes(), verbosity));
 694     }
 695 
 696     public static MapNode methodToTree(MethodModel m, Verbosity verbosity) {
 697         return new MapNodeImpl(BLOCK, "method")
 698                 .with(leaf("method name", m.methodName().stringValue()),
 699                       list("flags",
 700                               "flag", m.flags().flags().stream().map(AccessFlag::name)),
 701                       leaf("method type", m.methodType().stringValue()),
 702                       list("attributes",
 703                               "attribute", m.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)))
 704                 .with(attributesToTree(m.attributes(), verbosity))
 705                 .with(codeToTree((CodeAttribute)m.code().orElse(null), verbosity));
 706     }
 707 
 708     private static MapNode codeToTree(CodeAttribute com, Verbosity verbosity) {
 709         if (verbosity != Verbosity.MEMBERS_ONLY && com != null) {
 710             var codeNode = new MapNodeImpl(BLOCK, "code");
 711             codeNode.with(leaf("max stack", com.maxStack()));
 712             codeNode.with(leaf("max locals", com.maxLocals()));
 713             codeNode.with(list("attributes",
 714                     "attribute", com.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)));
 715             var stackMap = new MapNodeImpl(BLOCK, "stack map frames");
 716             var visibleTypeAnnos = new LinkedHashMap<Integer, List<TypeAnnotation>>();
 717             var invisibleTypeAnnos = new LinkedHashMap<Integer, List<TypeAnnotation>>();
 718             List<LocalVariableInfo> locals = List.of();
 719             for (var attr : com.attributes()) {
 720                 if (attr instanceof StackMapTableAttribute smta) {
 721                     codeNode.with(stackMap);
 722                     for (var smf : smta.entries()) {
 723                         stackMap.with(frameToTree(com.labelToBci(smf.target()), com, smf));
 724                     }
 725                 } else if (verbosity == Verbosity.TRACE_ALL && attr != null) switch (attr) {
 726                     case LocalVariableTableAttribute lvta -> {
 727                         locals = lvta.localVariables();
 728                         codeNode.with(new ListNodeImpl(BLOCK, "local variables",
 729                                 IntStream.range(0, locals.size()).mapToObj(i -> {
 730                                     var lv = lvta.localVariables().get(i);
 731                                     return map(i + 1,
 732                                         "start", lv.startPc(),
 733                                         "end", lv.startPc() + lv.length(),
 734                                         "slot", lv.slot(),
 735                                         "name", lv.name().stringValue(),
 736                                         "type", lv.type().stringValue());
 737                                 })));
 738                     }
 739                     case LocalVariableTypeTableAttribute lvtta -> {
 740                         codeNode.with(new ListNodeImpl(BLOCK, "local variable types",
 741                                 IntStream.range(0, lvtta.localVariableTypes().size()).mapToObj(i -> {
 742                                     var lvt = lvtta.localVariableTypes().get(i);
 743                                     return map(i + 1,
 744                                         "start", lvt.startPc(),
 745                                         "end", lvt.startPc() + lvt.length(),
 746                                         "slot", lvt.slot(),
 747                                         "name", lvt.name().stringValue(),
 748                                         "signature", lvt.signature().stringValue());
 749                                 })));
 750                     }
 751                     case LineNumberTableAttribute lnta -> {
 752                         codeNode.with(new ListNodeImpl(BLOCK, "line numbers",
 753                                 IntStream.range(0, lnta.lineNumbers().size()).mapToObj(i -> {
 754                                     var ln = lnta.lineNumbers().get(i);
 755                                     return map(i + 1,
 756                                         "start", ln.startPc(),
 757                                         "line number", ln.lineNumber());
 758                                 })));
 759                     }
 760                     case CharacterRangeTableAttribute crta -> {
 761                         codeNode.with(new ListNodeImpl(BLOCK, "character ranges",
 762                                 IntStream.range(0, crta.characterRangeTable().size()).mapToObj(i -> {
 763                                     var cr = crta.characterRangeTable().get(i);
 764                                     return map(i + 1,
 765                                         "start", cr.startPc(),
 766                                         "end", cr.endPc(),
 767                                         "range start", cr.characterRangeStart(),
 768                                         "range end", cr.characterRangeEnd(),
 769                                         "flags", cr.flags());
 770                                 })));
 771                     }
 772                     case RuntimeVisibleTypeAnnotationsAttribute rvtaa ->
 773                         rvtaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) ->
 774                                 visibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an)));
 775                     case RuntimeInvisibleTypeAnnotationsAttribute ritaa ->
 776                         ritaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) ->
 777                                 invisibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an)));
 778                     case Object o -> {}
 779                 }
 780             }
 781             codeNode.with(attributesToTree(com.attributes(), verbosity));
 782             if (!stackMap.containsKey(0)) {
 783                 codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @0").with(
 784                     list("locals", "item", convertVTIs(com, StackMapDecoder.initFrameLocals(com.parent().get()))),
 785                     list("stack", "item", Stream.of())));
 786             }
 787             var excHandlers = com.exceptionHandlers().stream().map(exc -> new ExceptionHandler(
 788                     com.labelToBci(exc.tryStart()),
 789                     com.labelToBci(exc.tryEnd()),
 790                     com.labelToBci(exc.handler()),
 791                     exc.catchType().map(ct -> ct.name().stringValue()).orElse(null))).toList();
 792             int bci = 0;
 793             for (var coe : com) {
 794                 if (coe instanceof Instruction ins) {
 795                     var frame = stackMap.get(bci);
 796                     if (frame != null) {
 797                         codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @" + bci)
 798                                 .with(((MapNodeImpl)frame).values().toArray(new Node[2])));
 799                     }
 800                     var annos = invisibleTypeAnnos.get(bci);
 801                     if (annos != null) {
 802                         codeNode.with(typeAnnotationsToTree(FLOW, "//invisible type annotations @" + bci, annos));
 803                     }
 804                     annos = visibleTypeAnnos.get(bci);
 805                     if (annos != null) {
 806                         codeNode.with(typeAnnotationsToTree(FLOW, "//visible type annotations @" + bci, annos));
 807                     }
 808                     for (int i = 0; i < excHandlers.size(); i++) {
 809                         var exc = excHandlers.get(i);
 810                         if (exc.start() == bci) {
 811                             codeNode.with(map("//try block " + (i + 1) + " start",
 812                                     "start", exc.start(),
 813                                     "end", exc.end(),
 814                                     "handler", exc.handler(),
 815                                     "catch type", exc.catchType()));
 816                         }
 817                         if (exc.end() == bci) {
 818                             codeNode.with(map("//try block " + (i + 1) + " end",
 819                                     "start", exc.start(),
 820                                     "end", exc.end(),
 821                                     "handler", exc.handler(),
 822                                     "catch type", exc.catchType()));
 823                         }
 824                         if (exc.handler() == bci) {
 825                             codeNode.with(map("//exception handler " + (i + 1) + " start",
 826                                     "start", exc.start(),
 827                                     "end", exc.end(),
 828                                     "handler", exc.handler(),
 829                                     "catch type", exc.catchType()));
 830                         }
 831                     }
 832                     var in = new MapNodeImpl(FLOW, bci).with(leaf("opcode", ins.opcode().name()));
 833                     codeNode.with(in);
 834                     switch (coe) {
 835                         case IncrementInstruction inc ->  in.with(leafs(
 836                                 "slot", inc.slot(),
 837                                 "const", inc.constant()))
 838                                 .with(localInfoToTree(locals, inc.slot(), bci));
 839                         case LoadInstruction lv ->  in.with(leaf(
 840                                 "slot", lv.slot()))
 841                                 .with(localInfoToTree(locals, lv.slot(), bci));
 842                         case StoreInstruction lv ->  in.with(leaf(
 843                                 "slot", lv.slot()))
 844                                 .with(localInfoToTree(locals, lv.slot(), bci));
 845                         case FieldInstruction fa -> in.with(leafs(
 846                                 "owner", fa.owner().name().stringValue(),
 847                                 "field name", fa.name().stringValue(),
 848                                 "field type", fa.type().stringValue()));
 849                         case InvokeInstruction inv -> in.with(leafs(
 850                                 "owner", inv.owner().name().stringValue(),
 851                                 "method name", inv.name().stringValue(),
 852                                 "method type", inv.type().stringValue()));
 853                         case InvokeDynamicInstruction invd -> {
 854                             in.with(leafs(
 855                                 "name", invd.name().stringValue(),
 856                                 "descriptor", invd.type().stringValue(),
 857                                 "bootstrap method", invd.bootstrapMethod().kind().name()
 858                                      + " " + Util.toInternalName(invd.bootstrapMethod().owner())
 859                                      + "::" + invd.bootstrapMethod().methodName()));
 860                             in.with(list("arguments", "arg", invd.bootstrapArgs().stream()));
 861                         }
 862                         case NewObjectInstruction newo -> in.with(leaf(
 863                                 "type", newo.className().name().stringValue()));
 864                         case NewPrimitiveArrayInstruction newa -> in.with(leafs(
 865                                 "dimensions", 1,
 866                                 "descriptor", newa.typeKind().upperBound().displayName()));
 867                         case NewReferenceArrayInstruction newa -> in.with(leafs(
 868                                 "dimensions", 1,
 869                                 "descriptor", newa.componentType().name().stringValue()));
 870                         case NewMultiArrayInstruction newa -> in.with(leafs(
 871                                 "dimensions", newa.dimensions(),
 872                                 "descriptor", newa.arrayType().name().stringValue()));
 873                         case TypeCheckInstruction tch -> in.with(leaf(
 874                                 "type", tch.type().name().stringValue()));
 875                         case ConstantInstruction cons -> in.with(leaf(
 876                                 "constant value", cons.constantValue()));
 877                         case BranchInstruction br -> in.with(leaf(
 878                                 "target", com.labelToBci(br.target())));
 879                         case LookupSwitchInstruction si -> in.with(list(
 880                                 "targets", "target", Stream.concat(Stream.of(si.defaultTarget())
 881                                         .map(com::labelToBci), si.cases().stream()
 882                                                 .map(sc -> com.labelToBci(sc.target())))));
 883                         case TableSwitchInstruction si -> in.with(list(
 884                                 "targets", "target", Stream.concat(Stream.of(si.defaultTarget())
 885                                         .map(com::labelToBci), si.cases().stream()
 886                                                 .map(sc -> com.labelToBci(sc.target())))));
 887                         case DiscontinuedInstruction.JsrInstruction jsr -> in.with(leaf(
 888                                 "target", com.labelToBci(jsr.target())));
 889                         case DiscontinuedInstruction.RetInstruction ret ->  in.with(leaf(
 890                                 "slot", ret.slot()));
 891                         default -> {}
 892                     }
 893                     bci += ins.sizeInBytes();
 894                 }
 895             }
 896             if (!excHandlers.isEmpty()) {
 897                 var handlersNode = new MapNodeImpl(BLOCK, "exception handlers");
 898                 codeNode.with(handlersNode);
 899                 for (int i = 0; i < excHandlers.size(); i++) {
 900                     var exc = excHandlers.get(i);
 901                     handlersNode.with(map("handler " + (i + 1),
 902                             "start", exc.start(),
 903                             "end", exc.end(),
 904                             "handler", exc.handler(),
 905                             "type", exc.catchType()));
 906                 }
 907             }
 908             return codeNode;
 909         }
 910         return null;
 911     }
 912 
 913     private static Node[] attributesToTree(List<Attribute<?>> attributes, Verbosity verbosity) {
 914         var nodes = new LinkedList<Node>();
 915         if (verbosity != Verbosity.MEMBERS_ONLY) for (var attr : attributes) {
 916             switch (attr) {
 917                 case BootstrapMethodsAttribute bma ->
 918                     nodes.add(new ListNodeImpl(BLOCK, "bootstrap methods", bma.bootstrapMethods().stream().map(
 919                     bm -> {
 920                         var mh = bm.bootstrapMethod();
 921                         var mref = mh.reference();
 922                         var bmNode = new MapNodeImpl(FLOW, "bm");
 923                         bmNode.with(leafs(
 924                                 "index", bm.bsmIndex(),
 925                                 "kind", DirectMethodHandleDesc.Kind.valueOf(mh.kind(),
 926                                         mref instanceof InterfaceMethodRefEntry).name(),
 927                                 "owner", mref.owner().asInternalName(),
 928                                 "name", mref.nameAndType().name().stringValue()));
 929                         bmNode.with(list("args", "arg", bm.arguments().stream().map(LoadableConstantEntry::constantValue)));
 930                         return bmNode;
 931                     })));
 932                 case ConstantValueAttribute cva ->
 933                     nodes.add(leaf("constant value", cva.constant().constantValue()));
 934                 case NestHostAttribute nha ->
 935                     nodes.add(leaf("nest host", nha.nestHost().name().stringValue()));
 936                 case NestMembersAttribute nma ->
 937                     nodes.add(list("nest members", "member", nma.nestMembers().stream()
 938                             .map(mp -> mp.name().stringValue())));
 939                 case PermittedSubclassesAttribute psa ->
 940                     nodes.add(list("permitted subclasses", "subclass", psa.permittedSubclasses().stream()
 941                             .map(e -> e.name().stringValue())));



 942                 default -> {}
 943             }
 944             if (verbosity == Verbosity.TRACE_ALL) switch (attr) {
 945                 case EnclosingMethodAttribute ema ->
 946                     nodes.add(map("enclosing method",
 947                             "class", ema.enclosingClass().name().stringValue(),
 948                             "method name", ema.enclosingMethodName()
 949                                     .<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT),
 950                             "method type", ema.enclosingMethodType()
 951                                     .<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)));
 952                 case ExceptionsAttribute exa ->
 953                     nodes.add(list("exceptions", "exc", exa.exceptions().stream()
 954                             .map(e -> e.name().stringValue())));
 955                 case InnerClassesAttribute ica ->
 956                     nodes.add(new ListNodeImpl(BLOCK, "inner classes", ica.classes().stream()
 957                             .map(ic -> new MapNodeImpl(FLOW, "cls").with(
 958                                 leaf("inner class", ic.innerClass().name().stringValue()),
 959                                 leaf("outer class", ic.outerClass()
 960                                         .map(cle -> (ConstantDesc)cle.name().stringValue()).orElse(BSM_NULL_CONSTANT)),
 961                                 leaf("inner name", ic.innerName().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
 962                                 list("flags", "flag", ic.flags().stream().map(AccessFlag::name))))));
 963                 case MethodParametersAttribute mpa -> {
 964                     var n = new MapNodeImpl(BLOCK, "method parameters");
 965                     for (int i = 0; i < mpa.parameters().size(); i++) {
 966                         var p = mpa.parameters().get(i);
 967                         n.with(new MapNodeImpl(FLOW, i + 1).with(
 968                                 leaf("name", p.name().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
 969                                 list("flags", "flag", p.flags().stream().map(AccessFlag::name))));
 970                     }
 971                 }
 972                 case ModuleAttribute ma ->
 973                     nodes.add(new MapNodeImpl(BLOCK, "module")
 974                             .with(leaf("name", ma.moduleName().name().stringValue()),
 975                                   list("flags","flag", ma.moduleFlags().stream().map(AccessFlag::name)),
 976                                   leaf("version", ma.moduleVersion().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
 977                                   list("uses", "class", ma.uses().stream().map(ce -> ce.name().stringValue())),
 978                                   new ListNodeImpl(BLOCK, "requires", ma.requires().stream().map(req ->
 979                                     new MapNodeImpl(FLOW, "req").with(
 980                                             leaf("name", req.requires().name().stringValue()),
 981                                             list("flags", "flag", req.requiresFlags().stream()
 982                                                     .map(AccessFlag::name)),
 983                                             leaf("version", req.requiresVersion()
 984                                                     .map(Utf8Entry::stringValue).orElse(null))))),
 985                                   new ListNodeImpl(BLOCK, "exports", ma.exports().stream().map(exp ->
 986                                     new MapNodeImpl(FLOW, "exp").with(
 987                                             leaf("package", exp.exportedPackage().asSymbol().name()),
 988                                             list("flags", "flag", exp.exportsFlags().stream()
 989                                                     .map(AccessFlag::name)),
 990                                             list("to", "module", exp.exportsTo().stream()
 991                                                     .map(me -> me.name().stringValue()))))),
 992                                   new ListNodeImpl(BLOCK, "opens", ma.opens().stream().map(opn ->
 993                                     new MapNodeImpl(FLOW, "opn").with(
 994                                             leaf("package", opn.openedPackage().asSymbol().name()),
 995                                             list("flags", "flag", opn.opensFlags().stream()
 996                                                     .map(AccessFlag::name)),
 997                                             list("to", "module", opn.opensTo().stream()
 998                                                     .map(me -> me.name().stringValue()))))),
 999                                   new ListNodeImpl(BLOCK, "provides", ma.provides().stream()
1000                                           .map(prov -> new MapNodeImpl(FLOW, "prov").with(
1001                                                   leaf("class", prov.provides().name().stringValue()),
1002                                                   list("with", "cls", prov.providesWith().stream()
1003                                                           .map(ce -> ce.name().stringValue())))))));
1004                 case ModulePackagesAttribute mopa ->
1005                     nodes.add(list("module packages", "subclass", mopa.packages().stream()
1006                             .map(mp -> mp.asSymbol().name())));
1007                 case ModuleMainClassAttribute mmca ->
1008                     nodes.add(leaf("module main class", mmca.mainClass().name().stringValue()));
1009                 case RecordAttribute ra ->
1010                     nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream()
1011                             .map(rc -> new MapNodeImpl(BLOCK, "component")
1012                                     .with(leafs(
1013                                         "name", rc.name().stringValue(),
1014                                         "type", rc.descriptor().stringValue()))
1015                                     .with(list("attributes", "attribute", rc.attributes().stream()
1016                                             .map(Attribute::attributeName).map(Utf8Entry::stringValue)))
1017                                     .with(attributesToTree(rc.attributes(), verbosity)))));
1018                 case AnnotationDefaultAttribute ada ->
1019                     nodes.add(new MapNodeImpl(FLOW, "annotation default").with(elementValueToTree(ada.defaultValue())));
1020                 case RuntimeInvisibleAnnotationsAttribute aa ->
1021                     nodes.add(annotationsToTree("invisible annotations", aa.annotations()));
1022                 case RuntimeVisibleAnnotationsAttribute aa ->
1023                     nodes.add(annotationsToTree("visible annotations", aa.annotations()));
1024                 case RuntimeInvisibleParameterAnnotationsAttribute aa ->
1025                     nodes.add(parameterAnnotationsToTree("invisible parameter annotations", aa.parameterAnnotations()));
1026                 case RuntimeVisibleParameterAnnotationsAttribute aa ->
1027                     nodes.add(parameterAnnotationsToTree("visible parameter annotations", aa.parameterAnnotations()));
1028                 case RuntimeInvisibleTypeAnnotationsAttribute aa ->
1029                     nodes.add(typeAnnotationsToTree(BLOCK, "invisible type annotations", aa.annotations()));
1030                 case RuntimeVisibleTypeAnnotationsAttribute aa ->
1031                     nodes.add(typeAnnotationsToTree(BLOCK, "visible type annotations", aa.annotations()));
1032                 case SignatureAttribute sa ->
1033                     nodes.add(leaf("signature", sa.signature().stringValue()));
1034                 case SourceFileAttribute sfa ->
1035                     nodes.add(leaf("source file", sfa.sourceFile().stringValue()));
1036                 default -> {}
1037             }
1038         }
1039         return nodes.toArray(Node[]::new);
1040     }
1041 
1042     private static Node annotationsToTree(String name, List<Annotation> annos) {
1043         return new ListNodeImpl(BLOCK, name, annos.stream().map(a ->
1044                 new MapNodeImpl(FLOW, "anno")
1045                         .with(leaf("annotation class", a.className().stringValue()))
1046                         .with(elementValuePairsToTree(a.elements()))));
1047 
1048     }
1049 
1050     private static Node typeAnnotationsToTree(Style style, String name, List<TypeAnnotation> annos) {
1051         return new ListNodeImpl(style, name, annos.stream().map(a ->
1052                 new MapNodeImpl(FLOW, "anno")
1053                         .with(leaf("annotation class", a.annotation().className().stringValue()),
1054                               leaf("target info", a.targetInfo().targetType().name()))
1055                         .with(elementValuePairsToTree(a.annotation().elements()))));
1056 
1057     }
1058 
1059     private static MapNodeImpl parameterAnnotationsToTree(String name, List<List<Annotation>> paramAnnotations) {
1060         var node = new MapNodeImpl(BLOCK, name);
1061         for (int i = 0; i < paramAnnotations.size(); i++) {
1062             var annos = paramAnnotations.get(i);
1063             if (!annos.isEmpty()) {
1064                 node.with(new ListNodeImpl(FLOW, "parameter " + (i + 1), annos.stream().map(a ->
1065                                 new MapNodeImpl(FLOW, "anno")
1066                                         .with(leaf("annotation class", a.className().stringValue()))
1067                                         .with(elementValuePairsToTree(a.elements())))));
1068             }
1069         }
1070         return node;
1071     }
1072 
1073     private static Node[] localInfoToTree(List<LocalVariableInfo> locals, int slot, int bci) {
1074         if (locals != null) {
1075             for (var l : locals) {
1076                 if (l.slot() == slot && l.startPc() <= bci && l.length() + l.startPc() >= bci) {
1077                     return leafs("type", l.type().stringValue(),
1078                                  "variable name", l.name().stringValue());
1079                 }
1080             }
1081         }
1082         return new Node[0];
1083     }
1084 
1085     private static void forEachOffset(TypeAnnotation ta, CodeAttribute lr, BiConsumer<Integer, TypeAnnotation> consumer) {
1086         switch (ta.targetInfo()) {
1087             case TypeAnnotation.OffsetTarget ot ->
1088                 consumer.accept(lr.labelToBci(ot.target()), ta);
1089             case TypeAnnotation.TypeArgumentTarget tat ->
1090                 consumer.accept(lr.labelToBci(tat.target()), ta);
1091             case TypeAnnotation.LocalVarTarget lvt ->
1092                 lvt.table().forEach(lvti -> consumer.accept(lr.labelToBci(lvti.startLabel()), ta));
1093             default -> {}
1094         }
1095     }
1096 }
--- EOF ---