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