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