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