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