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