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