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