1 /*
   2  * Copyright (c) 2011, 2018, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.graphio;
  26 
  27 import java.io.Closeable;
  28 import java.io.IOException;
  29 import java.net.URI;
  30 import java.net.URISyntaxException;
  31 import java.nio.ByteBuffer;
  32 import java.nio.channels.WritableByteChannel;
  33 import java.nio.charset.Charset;
  34 import java.util.Collection;
  35 import java.util.HashMap;
  36 import java.util.Iterator;
  37 import java.util.LinkedHashMap;
  38 import java.util.LinkedList;
  39 import java.util.Map;
  40 import java.util.Objects;
  41 
  42 abstract class GraphProtocol<Graph, Node, NodeClass, Edges, Block, ResolvedJavaMethod, ResolvedJavaField, Signature, NodeSourcePosition, Location> implements Closeable {
  43     private static final Charset UTF8 = Charset.forName("UTF-8");
  44 
  45     private static final int CONSTANT_POOL_MAX_SIZE = 8000;
  46 
  47     private static final int BEGIN_GROUP = 0x00;
  48     private static final int BEGIN_GRAPH = 0x01;
  49     private static final int CLOSE_GROUP = 0x02;
  50 
  51     private static final int POOL_NEW = 0x00;
  52     private static final int POOL_STRING = 0x01;
  53     private static final int POOL_ENUM = 0x02;
  54     private static final int POOL_CLASS = 0x03;
  55     private static final int POOL_METHOD = 0x04;
  56     private static final int POOL_NULL = 0x05;
  57     private static final int POOL_NODE_CLASS = 0x06;
  58     private static final int POOL_FIELD = 0x07;
  59     private static final int POOL_SIGNATURE = 0x08;
  60     private static final int POOL_NODE_SOURCE_POSITION = 0x09;
  61     private static final int POOL_NODE = 0x0a;
  62 
  63     private static final int PROPERTY_POOL = 0x00;
  64     private static final int PROPERTY_INT = 0x01;
  65     private static final int PROPERTY_LONG = 0x02;
  66     private static final int PROPERTY_DOUBLE = 0x03;
  67     private static final int PROPERTY_FLOAT = 0x04;
  68     private static final int PROPERTY_TRUE = 0x05;
  69     private static final int PROPERTY_FALSE = 0x06;
  70     private static final int PROPERTY_ARRAY = 0x07;
  71     private static final int PROPERTY_SUBGRAPH = 0x08;
  72 
  73     private static final int KLASS = 0x00;
  74     private static final int ENUM_KLASS = 0x01;
  75 
  76     private static final byte[] MAGIC_BYTES = {'B', 'I', 'G', 'V'};
  77 
  78     private final ConstantPool constantPool;
  79     private final ByteBuffer buffer;
  80     private final WritableByteChannel channel;
  81     final int versionMajor;
  82     final int versionMinor;
  83 
  84     GraphProtocol(WritableByteChannel channel, int major, int minor) throws IOException {
  85         if (major > 6 || (major == 6 && minor > 0)) {
  86             throw new IllegalArgumentException("Unrecognized version " + major + "." + minor);
  87         }
  88         this.versionMajor = major;
  89         this.versionMinor = minor;
  90         this.constantPool = new ConstantPool();
  91         this.buffer = ByteBuffer.allocateDirect(256 * 1024);
  92         this.channel = channel;
  93         writeVersion();
  94     }
  95 
  96     GraphProtocol(GraphProtocol<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> parent) {
  97         this.versionMajor = parent.versionMajor;
  98         this.versionMinor = parent.versionMinor;
  99         this.constantPool = parent.constantPool;
 100         this.buffer = parent.buffer;
 101         this.channel = parent.channel;
 102     }
 103 
 104     @SuppressWarnings("all")
 105     public final void print(Graph graph, Map<? extends Object, ? extends Object> properties, int id, String format, Object... args) throws IOException {
 106         writeByte(BEGIN_GRAPH);
 107         if (versionMajor >= 3) {
 108             writeInt(id);
 109             writeString(format);
 110             writeInt(args.length);
 111             for (Object a : args) {
 112                 writePropertyObject(graph, a);
 113             }
 114         } else {
 115             writePoolObject(formatTitle(graph, id, format, args));
 116         }
 117         writeGraph(graph, properties);
 118         flush();
 119     }
 120 
 121     public final void beginGroup(Graph noGraph, String name, String shortName, ResolvedJavaMethod method, int bci, Map<? extends Object, ? extends Object> properties) throws IOException {
 122         writeByte(BEGIN_GROUP);
 123         writePoolObject(name);
 124         writePoolObject(shortName);
 125         writePoolObject(method);
 126         writeInt(bci);
 127         writeProperties(noGraph, properties);
 128     }
 129 
 130     public final void endGroup() throws IOException {
 131         writeByte(CLOSE_GROUP);
 132     }
 133 
 134     @Override
 135     public final void close() {
 136         try {
 137             flush();
 138             channel.close();
 139         } catch (IOException ex) {
 140             throw new Error(ex);
 141         }
 142     }
 143 
 144     protected abstract Graph findGraph(Graph current, Object obj);
 145 
 146     protected abstract ResolvedJavaMethod findMethod(Object obj);
 147 
 148     /**
 149      * Attempts to recognize the provided object as a node. Used to encode it with
 150      * {@link #POOL_NODE} pool type.
 151      *
 152      * @param obj any object
 153      * @return <code>null</code> if it is not a node object, non-null otherwise
 154      */
 155     protected abstract Node findNode(Object obj);
 156 
 157     /**
 158      * Determines whether the provided object is node class or not.
 159      *
 160      * @param obj object to check
 161      * @return {@code null} if {@code obj} does not represent a NodeClass otherwise the NodeClass
 162      *         represented by {@code obj}
 163      */
 164     protected abstract NodeClass findNodeClass(Object obj);
 165 
 166     /**
 167      * Returns the NodeClass for a given Node {@code obj}.
 168      *
 169      * @param obj instance of node
 170      * @return non-{@code null} instance of the node's class object
 171      */
 172     protected abstract NodeClass findClassForNode(Node obj);
 173 
 174     /**
 175      * Find a Java class. The returned object must be acceptable by
 176      * {@link #findJavaTypeName(java.lang.Object)} and return valid name for the class.
 177      *
 178      * @param clazz node class object
 179      * @return object representing the class, for example {@link Class}
 180      */
 181     protected abstract Object findJavaClass(NodeClass clazz);
 182 
 183     protected abstract Object findEnumClass(Object enumValue);
 184 
 185     protected abstract String findNameTemplate(NodeClass clazz);
 186 
 187     protected abstract Edges findClassEdges(NodeClass nodeClass, boolean dumpInputs);
 188 
 189     protected abstract int findNodeId(Node n);
 190 
 191     protected abstract void findExtraNodes(Node node, Collection<? super Node> extraNodes);
 192 
 193     protected abstract boolean hasPredecessor(Node node);
 194 
 195     protected abstract int findNodesCount(Graph info);
 196 
 197     protected abstract Iterable<? extends Node> findNodes(Graph info);
 198 
 199     protected abstract void findNodeProperties(Node node, Map<String, Object> props, Graph info);
 200 
 201     protected abstract Collection<? extends Node> findBlockNodes(Graph info, Block block);
 202 
 203     protected abstract int findBlockId(Block sux);
 204 
 205     protected abstract Collection<? extends Block> findBlocks(Graph graph);
 206 
 207     protected abstract Collection<? extends Block> findBlockSuccessors(Block block);
 208 
 209     protected abstract String formatTitle(Graph graph, int id, String format, Object... args);
 210 
 211     protected abstract int findSize(Edges edges);
 212 
 213     protected abstract boolean isDirect(Edges edges, int i);
 214 
 215     protected abstract String findName(Edges edges, int i);
 216 
 217     protected abstract Object findType(Edges edges, int i);
 218 
 219     protected abstract Collection<? extends Node> findNodes(Graph graph, Node node, Edges edges, int i);
 220 
 221     protected abstract int findEnumOrdinal(Object obj);
 222 
 223     protected abstract String[] findEnumTypeValues(Object clazz);
 224 
 225     protected abstract String findJavaTypeName(Object obj);
 226 
 227     protected abstract byte[] findMethodCode(ResolvedJavaMethod method);
 228 
 229     protected abstract int findMethodModifiers(ResolvedJavaMethod method);
 230 
 231     protected abstract Signature findMethodSignature(ResolvedJavaMethod method);
 232 
 233     protected abstract String findMethodName(ResolvedJavaMethod method);
 234 
 235     protected abstract Object findMethodDeclaringClass(ResolvedJavaMethod method);
 236 
 237     protected abstract int findFieldModifiers(ResolvedJavaField field);
 238 
 239     protected abstract String findFieldTypeName(ResolvedJavaField field);
 240 
 241     protected abstract String findFieldName(ResolvedJavaField field);
 242 
 243     protected abstract Object findFieldDeclaringClass(ResolvedJavaField field);
 244 
 245     protected abstract ResolvedJavaField findJavaField(Object object);
 246 
 247     protected abstract Signature findSignature(Object object);
 248 
 249     protected abstract int findSignatureParameterCount(Signature signature);
 250 
 251     protected abstract String findSignatureParameterTypeName(Signature signature, int index);
 252 
 253     protected abstract String findSignatureReturnTypeName(Signature signature);
 254 
 255     protected abstract NodeSourcePosition findNodeSourcePosition(Object object);
 256 
 257     protected abstract ResolvedJavaMethod findNodeSourcePositionMethod(NodeSourcePosition pos);
 258 
 259     protected abstract NodeSourcePosition findNodeSourcePositionCaller(NodeSourcePosition pos);
 260 
 261     protected abstract int findNodeSourcePositionBCI(NodeSourcePosition pos);
 262 
 263     protected abstract Iterable<Location> findLocation(ResolvedJavaMethod method, int bci, NodeSourcePosition pos);
 264 
 265     protected abstract String findLocationFile(Location loc) throws IOException;
 266 
 267     protected abstract int findLocationLine(Location loc);
 268 
 269     protected abstract URI findLocationURI(Location loc) throws URISyntaxException;
 270 
 271     protected abstract String findLocationLanguage(Location loc);
 272 
 273     protected abstract int findLocationStart(Location loc);
 274 
 275     protected abstract int findLocationEnd(Location loc);
 276 
 277     private void writeVersion() throws IOException {
 278         writeBytesRaw(MAGIC_BYTES);
 279         writeByte(versionMajor);
 280         writeByte(versionMinor);
 281     }
 282 
 283     private void flush() throws IOException {
 284         buffer.flip();
 285         /*
 286          * Try not to let interrupted threads abort the write. There's still a race here but an
 287          * interrupt that's been pending for a long time shouldn't stop this writing.
 288          */
 289         boolean interrupted = Thread.interrupted();
 290         try {
 291             channel.write(buffer);
 292         } finally {
 293             if (interrupted) {
 294                 Thread.currentThread().interrupt();
 295             }
 296         }
 297         buffer.compact();
 298     }
 299 
 300     private void ensureAvailable(int i) throws IOException {
 301         assert buffer.capacity() >= i : "Can not make " + i + " bytes available, buffer is too small";
 302         while (buffer.remaining() < i) {
 303             flush();
 304         }
 305     }
 306 
 307     private void writeByte(int b) throws IOException {
 308         ensureAvailable(1);
 309         buffer.put((byte) b);
 310     }
 311 
 312     private void writeInt(int b) throws IOException {
 313         ensureAvailable(4);
 314         buffer.putInt(b);
 315     }
 316 
 317     private void writeLong(long b) throws IOException {
 318         ensureAvailable(8);
 319         buffer.putLong(b);
 320     }
 321 
 322     private void writeDouble(double b) throws IOException {
 323         ensureAvailable(8);
 324         buffer.putDouble(b);
 325     }
 326 
 327     private void writeFloat(float b) throws IOException {
 328         ensureAvailable(4);
 329         buffer.putFloat(b);
 330     }
 331 
 332     private void writeShort(char b) throws IOException {
 333         ensureAvailable(2);
 334         buffer.putChar(b);
 335     }
 336 
 337     private void writeString(String str) throws IOException {
 338         byte[] bytes = str.getBytes(UTF8);
 339         writeBytes(bytes);
 340     }
 341 
 342     private void writeBytes(byte[] b) throws IOException {
 343         if (b == null) {
 344             writeInt(-1);
 345         } else {
 346             writeInt(b.length);
 347             writeBytesRaw(b);
 348         }
 349     }
 350 
 351     private void writeBytesRaw(byte[] b) throws IOException {
 352         int bytesWritten = 0;
 353         while (bytesWritten < b.length) {
 354             int toWrite = Math.min(b.length - bytesWritten, buffer.capacity());
 355             ensureAvailable(toWrite);
 356             buffer.put(b, bytesWritten, toWrite);
 357             bytesWritten += toWrite;
 358         }
 359     }
 360 
 361     private void writeInts(int[] b) throws IOException {
 362         if (b == null) {
 363             writeInt(-1);
 364         } else {
 365             writeInt(b.length);
 366             int sizeInBytes = b.length * 4;
 367             ensureAvailable(sizeInBytes);
 368             buffer.asIntBuffer().put(b);
 369             buffer.position(buffer.position() + sizeInBytes);
 370         }
 371     }
 372 
 373     private void writeDoubles(double[] b) throws IOException {
 374         if (b == null) {
 375             writeInt(-1);
 376         } else {
 377             writeInt(b.length);
 378             int sizeInBytes = b.length * 8;
 379             ensureAvailable(sizeInBytes);
 380             buffer.asDoubleBuffer().put(b);
 381             buffer.position(buffer.position() + sizeInBytes);
 382         }
 383     }
 384 
 385     private void writePoolObject(Object obj) throws IOException {
 386         Object object = obj;
 387         if (object == null) {
 388             writeByte(POOL_NULL);
 389             return;
 390         }
 391         Character id = constantPool.get(object);
 392         if (id == null) {
 393             addPoolEntry(object);
 394         } else {
 395             int type = findPoolType(object, null);
 396             writeByte(type);
 397             writeShort(id.charValue());
 398         }
 399     }
 400 
 401     private int findPoolType(Object obj, Object[] found) throws IOException {
 402         Object object = obj;
 403         if (object == null) {
 404             return POOL_NULL;
 405         }
 406         if (isFound(findJavaField(object), found)) {
 407             return POOL_FIELD;
 408         } else if (isFound(findSignature(object), found)) {
 409             return POOL_SIGNATURE;
 410         } else if (versionMajor >= 4 && isFound(findNodeSourcePosition(object), found)) {
 411             return POOL_NODE_SOURCE_POSITION;
 412         } else {
 413             final Node node = findNode(object);
 414             if (versionMajor == 4 && node != null) {
 415                 object = classForNode(node);
 416             }
 417             if (isFound(findNodeClass(object), found)) {
 418                 return POOL_NODE_CLASS;
 419             } else if (versionMajor >= 5 && isFound(node, found)) {
 420                 return POOL_NODE;
 421             } else if (isFound(findMethod(object), found)) {
 422                 return POOL_METHOD;
 423             } else if (object instanceof Enum<?>) {
 424                 if (found != null) {
 425                     found[0] = ((Enum<?>) object).ordinal();
 426                 }
 427                 return POOL_ENUM;
 428             } else {
 429                 int val = findEnumOrdinal(object);
 430                 if (val >= 0) {
 431                     if (found != null) {
 432                         found[0] = val;
 433                     }
 434                     return POOL_ENUM;
 435                 } else if (object instanceof Class<?>) {
 436                     if (found != null) {
 437                         found[0] = ((Class<?>) object).getName();
 438                     }
 439                     return POOL_CLASS;
 440                 } else if (isFound(findJavaTypeName(object), found)) {
 441                     return POOL_CLASS;
 442                 } else {
 443                     return POOL_STRING;
 444                 }
 445             }
 446         }
 447     }
 448 
 449     private void writeGraph(Graph graph, Map<? extends Object, ? extends Object> properties) throws IOException {
 450         writeProperties(graph, properties);
 451         writeNodes(graph);
 452         writeBlocks(findBlocks(graph), graph);
 453     }
 454 
 455     private void writeNodes(Graph info) throws IOException {
 456         Map<String, Object> props = new HashMap<>();
 457 
 458         final int size = findNodesCount(info);
 459         writeInt(size);
 460         int cnt = 0;
 461         for (Node node : findNodes(info)) {
 462             NodeClass nodeClass = classForNode(node);
 463             findNodeProperties(node, props, info);
 464 
 465             writeInt(findNodeId(node));
 466             writePoolObject(nodeClass);
 467             writeByte(hasPredecessor(node) ? 1 : 0);
 468             writeProperties(info, props);
 469             writeEdges(info, node, true);
 470             writeEdges(info, node, false);
 471 
 472             props.clear();
 473             cnt++;
 474         }
 475         if (size != cnt) {
 476             throw new IOException("Expecting " + size + " nodes, but found " + cnt);
 477         }
 478     }
 479 
 480     private void writeEdges(Graph graph, Node node, boolean dumpInputs) throws IOException {
 481         NodeClass clazz = classForNode(node);
 482         Edges edges = findClassEdges(clazz, dumpInputs);
 483         int size = findSize(edges);
 484         for (int i = 0; i < size; i++) {
 485             Collection<? extends Node> list = findNodes(graph, node, edges, i);
 486             if (isDirect(edges, i)) {
 487                 if (list != null && list.size() != 1) {
 488                     throw new IOException("Edge " + i + " in " + edges + " is direct, but list isn't singleton: " + list);
 489                 }
 490                 Node n = null;
 491                 if (list != null && !list.isEmpty()) {
 492                     n = list.iterator().next();
 493                 }
 494                 writeNodeRef(n);
 495             } else {
 496                 if (list == null) {
 497                     writeShort((char) 0);
 498                 } else {
 499                     int listSize = list.size();
 500                     assert listSize == ((char) listSize);
 501                     writeShort((char) listSize);
 502                     for (Node edge : list) {
 503                         writeNodeRef(edge);
 504                     }
 505                 }
 506             }
 507         }
 508     }
 509 
 510     private NodeClass classForNode(Node node) throws IOException {
 511         NodeClass clazz = findClassForNode(node);
 512         if (clazz == null) {
 513             throw new IOException("No class for " + node);
 514         }
 515         return clazz;
 516     }
 517 
 518     private void writeNodeRef(Node node) throws IOException {
 519         writeInt(findNodeId(node));
 520     }
 521 
 522     private void writeBlocks(Collection<? extends Block> blocks, Graph info) throws IOException {
 523         if (blocks != null) {
 524             for (Block block : blocks) {
 525                 Collection<? extends Node> nodes = findBlockNodes(info, block);
 526                 if (nodes == null) {
 527                     writeInt(0);
 528                     return;
 529                 }
 530             }
 531             writeInt(blocks.size());
 532             for (Block block : blocks) {
 533                 Collection<? extends Node> nodes = findBlockNodes(info, block);
 534                 writeInt(findBlockId(block));
 535                 writeInt(nodes.size());
 536                 for (Node node : nodes) {
 537                     writeInt(findNodeId(node));
 538                 }
 539                 final Collection<? extends Block> successors = findBlockSuccessors(block);
 540                 writeInt(successors.size());
 541                 for (Block sux : successors) {
 542                     writeInt(findBlockId(sux));
 543                 }
 544             }
 545         } else {
 546             writeInt(0);
 547         }
 548     }
 549 
 550     private void writeEdgesInfo(NodeClass nodeClass, boolean dumpInputs) throws IOException {
 551         Edges edges = findClassEdges(nodeClass, dumpInputs);
 552         int size = findSize(edges);
 553         writeShort((char) size);
 554         for (int i = 0; i < size; i++) {
 555             writeByte(isDirect(edges, i) ? 0 : 1);
 556             writePoolObject(findName(edges, i));
 557             if (dumpInputs) {
 558                 writePoolObject(findType(edges, i));
 559             }
 560         }
 561     }
 562 
 563     @SuppressWarnings("unchecked")
 564     private void addPoolEntry(Object obj) throws IOException {
 565         Object object = obj;
 566         char index = constantPool.add(object);
 567         writeByte(POOL_NEW);
 568         writeShort(index);
 569 
 570         Object[] found = {null};
 571         int type = findPoolType(object, found);
 572         writeByte(type);
 573         switch (type) {
 574             case POOL_FIELD: {
 575                 ResolvedJavaField field = (ResolvedJavaField) found[0];
 576                 Objects.nonNull(field);
 577                 writePoolObject(findFieldDeclaringClass(field));
 578                 writePoolObject(findFieldName(field));
 579                 writePoolObject(findFieldTypeName(field));
 580                 writeInt(findFieldModifiers(field));
 581                 break;
 582             }
 583             case POOL_SIGNATURE: {
 584                 Signature signature = (Signature) found[0];
 585                 int args = findSignatureParameterCount(signature);
 586                 writeShort((char) args);
 587                 for (int i = 0; i < args; i++) {
 588                     writePoolObject(findSignatureParameterTypeName(signature, i));
 589                 }
 590                 writePoolObject(findSignatureReturnTypeName(signature));
 591                 break;
 592             }
 593             case POOL_NODE_SOURCE_POSITION: {
 594                 NodeSourcePosition pos = (NodeSourcePosition) found[0];
 595                 Objects.nonNull(pos);
 596                 ResolvedJavaMethod method = findNodeSourcePositionMethod(pos);
 597                 writePoolObject(method);
 598                 final int bci = findNodeSourcePositionBCI(pos);
 599                 writeInt(bci);
 600                 Iterator<Location> ste = findLocation(method, bci, pos).iterator();
 601                 if (versionMajor >= 6) {
 602                     while (ste.hasNext()) {
 603                         Location loc = ste.next();
 604                         URI uri;
 605                         try {
 606                             uri = findLocationURI(loc);
 607                         } catch (URISyntaxException ex) {
 608                             throw new IOException(ex);
 609                         }
 610                         if (uri == null) {
 611                             continue;
 612                         }
 613                         String l = findLocationLanguage(loc);
 614                         if (l == null) {
 615                             continue;
 616                         }
 617                         writePoolObject(uri.toString());
 618                         writeString(l);
 619                         writeInt(findLocationLine(loc));
 620                         writeInt(findLocationStart(loc));
 621                         writeInt(findLocationEnd(loc));
 622                     }
 623                     writePoolObject(null);
 624                 } else {
 625                     Location first = ste.hasNext() ? ste.next() : null;
 626                     String fileName = first != null ? findLocationFile(first) : null;
 627                     if (fileName != null) {
 628                         writePoolObject(fileName);
 629                         writeInt(findLocationLine(first));
 630                     } else {
 631                         writePoolObject(null);
 632                     }
 633                 }
 634                 writePoolObject(findNodeSourcePositionCaller(pos));
 635                 break;
 636             }
 637             case POOL_NODE: {
 638                 Node node = (Node) found[0];
 639                 Objects.nonNull(node);
 640                 writeInt(findNodeId(node));
 641                 writePoolObject(classForNode(node));
 642                 break;
 643             }
 644             case POOL_NODE_CLASS: {
 645                 NodeClass nodeClass = (NodeClass) found[0];
 646                 final Object clazz = findJavaClass(nodeClass);
 647                 if (versionMajor >= 3) {
 648                     writePoolObject(clazz);
 649                     writeString(findNameTemplate(nodeClass));
 650                 } else {
 651                     writeString(((Class<?>) clazz).getSimpleName());
 652                     String nameTemplate = findNameTemplate(nodeClass);
 653                     writeString(nameTemplate);
 654                 }
 655                 writeEdgesInfo(nodeClass, true);
 656                 writeEdgesInfo(nodeClass, false);
 657                 break;
 658             }
 659             case POOL_CLASS: {
 660                 String typeName = (String) found[0];
 661                 Objects.nonNull(typeName);
 662                 writeString(typeName);
 663                 String[] enumValueNames = findEnumTypeValues(object);
 664                 if (enumValueNames != null) {
 665                     writeByte(ENUM_KLASS);
 666                     writeInt(enumValueNames.length);
 667                     for (String o : enumValueNames) {
 668                         writePoolObject(o);
 669                     }
 670                 } else {
 671                     writeByte(KLASS);
 672                 }
 673                 break;
 674             }
 675             case POOL_METHOD: {
 676                 ResolvedJavaMethod method = (ResolvedJavaMethod) found[0];
 677                 Objects.nonNull(method);
 678                 writePoolObject(findMethodDeclaringClass(method));
 679                 writePoolObject(findMethodName(method));
 680                 final Signature methodSignature = findMethodSignature(method);
 681                 if (findSignature(methodSignature) == null) {
 682                     throw new IOException("Should be recognized as signature: " + methodSignature + " for " + method);
 683                 }
 684                 writePoolObject(methodSignature);
 685                 writeInt(findMethodModifiers(method));
 686                 writeBytes(findMethodCode(method));
 687                 break;
 688             }
 689             case POOL_ENUM: {
 690                 int enumOrdinal = (int) found[0];
 691                 writePoolObject(findEnumClass(object));
 692                 writeInt(enumOrdinal);
 693                 break;
 694             }
 695             case POOL_STRING: {
 696                 writeString(object.toString());
 697                 break;
 698             }
 699             default:
 700                 throw new IllegalStateException();
 701         }
 702     }
 703 
 704     private void writePropertyObject(Graph graph, Object obj) throws IOException {
 705         if (obj instanceof Integer) {
 706             writeByte(PROPERTY_INT);
 707             writeInt(((Integer) obj).intValue());
 708         } else if (obj instanceof Long) {
 709             writeByte(PROPERTY_LONG);
 710             writeLong(((Long) obj).longValue());
 711         } else if (obj instanceof Double) {
 712             writeByte(PROPERTY_DOUBLE);
 713             writeDouble(((Double) obj).doubleValue());
 714         } else if (obj instanceof Float) {
 715             writeByte(PROPERTY_FLOAT);
 716             writeFloat(((Float) obj).floatValue());
 717         } else if (obj instanceof Boolean) {
 718             if (((Boolean) obj).booleanValue()) {
 719                 writeByte(PROPERTY_TRUE);
 720             } else {
 721                 writeByte(PROPERTY_FALSE);
 722             }
 723         } else if (obj != null && obj.getClass().isArray()) {
 724             Class<?> componentType = obj.getClass().getComponentType();
 725             if (componentType.isPrimitive()) {
 726                 if (componentType == Double.TYPE) {
 727                     writeByte(PROPERTY_ARRAY);
 728                     writeByte(PROPERTY_DOUBLE);
 729                     writeDoubles((double[]) obj);
 730                 } else if (componentType == Integer.TYPE) {
 731                     writeByte(PROPERTY_ARRAY);
 732                     writeByte(PROPERTY_INT);
 733                     writeInts((int[]) obj);
 734                 } else {
 735                     writeByte(PROPERTY_POOL);
 736                     writePoolObject(obj);
 737                 }
 738             } else {
 739                 writeByte(PROPERTY_ARRAY);
 740                 writeByte(PROPERTY_POOL);
 741                 Object[] array = (Object[]) obj;
 742                 writeInt(array.length);
 743                 for (Object o : array) {
 744                     writePoolObject(o);
 745                 }
 746             }
 747         } else {
 748             Graph g = findGraph(graph, obj);
 749             if (g == null) {
 750                 writeByte(PROPERTY_POOL);
 751                 writePoolObject(obj);
 752             } else {
 753                 writeByte(PROPERTY_SUBGRAPH);
 754                 writeGraph(g, null);
 755             }
 756         }
 757     }
 758 
 759     private void writeProperties(Graph graph, Map<? extends Object, ? extends Object> props) throws IOException {
 760         if (props == null) {
 761             writeShort((char) 0);
 762             return;
 763         }
 764         final int size = props.size();
 765         // properties
 766         writeShort((char) size);
 767         int cnt = 0;
 768         for (Map.Entry<? extends Object, ? extends Object> entry : props.entrySet()) {
 769             String key = entry.getKey().toString();
 770             writePoolObject(key);
 771             writePropertyObject(graph, entry.getValue());
 772             cnt++;
 773         }
 774         if (size != cnt) {
 775             throw new IOException("Expecting " + size + " properties, but found only " + cnt);
 776         }
 777     }
 778 
 779     private static boolean isFound(Object obj, Object[] found) {
 780         if (obj == null) {
 781             return false;
 782         }
 783         if (found != null) {
 784             found[0] = obj;
 785         }
 786         return true;
 787     }
 788 
 789     private static final class ConstantPool extends LinkedHashMap<Object, Character> {
 790 
 791         private final LinkedList<Character> availableIds;
 792         private char nextId;
 793         private static final long serialVersionUID = -2676889957907285681L;
 794 
 795         ConstantPool() {
 796             super(50, 0.65f);
 797             availableIds = new LinkedList<>();
 798         }
 799 
 800         @Override
 801         protected boolean removeEldestEntry(java.util.Map.Entry<Object, Character> eldest) {
 802             if (size() > CONSTANT_POOL_MAX_SIZE) {
 803                 availableIds.addFirst(eldest.getValue());
 804                 return true;
 805             }
 806             return false;
 807         }
 808 
 809         private Character nextAvailableId() {
 810             if (!availableIds.isEmpty()) {
 811                 return availableIds.removeFirst();
 812             }
 813             return nextId++;
 814         }
 815 
 816         public char add(Object obj) {
 817             Character id = nextAvailableId();
 818             put(obj, id);
 819             return id;
 820         }
 821     }
 822 
 823 }