1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.graphio; 26 27 import java.io.Closeable; 28 import java.io.IOException; 29 import java.net.URI; 30 import java.net.URISyntaxException; 31 import java.nio.ByteBuffer; 32 import java.nio.channels.WritableByteChannel; 33 import java.nio.charset.Charset; 34 import java.util.Collection; 35 import java.util.HashMap; 36 import java.util.Iterator; 37 import java.util.LinkedHashMap; 38 import java.util.LinkedList; 39 import java.util.Map; 40 import java.util.Objects; 41 42 abstract class GraphProtocol<Graph, Node, NodeClass, Edges, Block, ResolvedJavaMethod, ResolvedJavaField, Signature, NodeSourcePosition, Location> implements Closeable { 43 private static final Charset UTF8 = Charset.forName("UTF-8"); 44 45 private static final int CONSTANT_POOL_MAX_SIZE = 8000; 46 47 private static final int BEGIN_GROUP = 0x00; 48 private static final int BEGIN_GRAPH = 0x01; 49 private static final int CLOSE_GROUP = 0x02; 50 51 private static final int POOL_NEW = 0x00; 52 private static final int POOL_STRING = 0x01; 53 private static final int POOL_ENUM = 0x02; 54 private static final int POOL_CLASS = 0x03; 55 private static final int POOL_METHOD = 0x04; 56 private static final int POOL_NULL = 0x05; 57 private static final int POOL_NODE_CLASS = 0x06; 58 private static final int POOL_FIELD = 0x07; 59 private static final int POOL_SIGNATURE = 0x08; 60 private static final int POOL_NODE_SOURCE_POSITION = 0x09; 61 private static final int POOL_NODE = 0x0a; 62 63 private static final int PROPERTY_POOL = 0x00; 64 private static final int PROPERTY_INT = 0x01; 65 private static final int PROPERTY_LONG = 0x02; 66 private static final int PROPERTY_DOUBLE = 0x03; 67 private static final int PROPERTY_FLOAT = 0x04; 68 private static final int PROPERTY_TRUE = 0x05; 69 private static final int PROPERTY_FALSE = 0x06; 70 private static final int PROPERTY_ARRAY = 0x07; 71 private static final int PROPERTY_SUBGRAPH = 0x08; 72 73 private static final int KLASS = 0x00; 74 private static final int ENUM_KLASS = 0x01; 75 76 private static final byte[] MAGIC_BYTES = {'B', 'I', 'G', 'V'}; 77 78 private final ConstantPool constantPool; 79 private final ByteBuffer buffer; 80 private final WritableByteChannel channel; 81 final int versionMajor; 82 final int versionMinor; 83 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 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") 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 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 130 public final void endGroup() throws IOException { 131 writeByte(CLOSE_GROUP); 132 } 133 134 @Override 135 public final void close() { 136 try { 137 flush(); 138 channel.close(); 139 } catch (IOException ex) { 140 throw new Error(ex); 141 } 142 } 143 144 protected abstract Graph findGraph(Graph current, Object obj); 145 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 */ 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 */ 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 */ 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 */ 181 protected abstract Object findJavaClass(NodeClass clazz); 182 183 protected abstract Object findEnumClass(Object enumValue); 184 185 protected abstract String findNameTemplate(NodeClass clazz); 186 187 protected abstract Edges findClassEdges(NodeClass nodeClass, boolean dumpInputs); 188 189 protected abstract int findNodeId(Node n); 190 191 protected abstract void findExtraNodes(Node node, Collection<? super Node> extraNodes); 192 193 protected abstract boolean hasPredecessor(Node node); 194 195 protected abstract int findNodesCount(Graph info); 196 197 protected abstract Iterable<? extends Node> findNodes(Graph info); 198 199 protected abstract void findNodeProperties(Node node, Map<String, Object> props, Graph info); 200 201 protected abstract Collection<? extends Node> findBlockNodes(Graph info, Block block); 202 203 protected abstract int findBlockId(Block sux); 204 205 protected abstract Collection<? extends Block> findBlocks(Graph graph); 206 207 protected abstract Collection<? extends Block> findBlockSuccessors(Block block); 208 209 protected abstract String formatTitle(Graph graph, int id, String format, Object... args); 210 211 protected abstract int findSize(Edges edges); 212 213 protected abstract boolean isDirect(Edges edges, int i); 214 215 protected abstract String findName(Edges edges, int i); 216 217 protected abstract Object findType(Edges edges, int i); 218 219 protected abstract Collection<? extends Node> findNodes(Graph graph, Node node, Edges edges, int i); 220 221 protected abstract int findEnumOrdinal(Object obj); 222 223 protected abstract String[] findEnumTypeValues(Object clazz); 224 225 protected abstract String findJavaTypeName(Object obj); 226 227 protected abstract byte[] findMethodCode(ResolvedJavaMethod method); 228 229 protected abstract int findMethodModifiers(ResolvedJavaMethod method); 230 231 protected abstract Signature findMethodSignature(ResolvedJavaMethod method); 232 233 protected abstract String findMethodName(ResolvedJavaMethod method); 234 235 protected abstract Object findMethodDeclaringClass(ResolvedJavaMethod method); 236 237 protected abstract int findFieldModifiers(ResolvedJavaField field); 238 239 protected abstract String findFieldTypeName(ResolvedJavaField field); 240 241 protected abstract String findFieldName(ResolvedJavaField field); 242 243 protected abstract Object findFieldDeclaringClass(ResolvedJavaField field); 244 245 protected abstract ResolvedJavaField findJavaField(Object object); 246 247 protected abstract Signature findSignature(Object object); 248 249 protected abstract int findSignatureParameterCount(Signature signature); 250 251 protected abstract String findSignatureParameterTypeName(Signature signature, int index); 252 253 protected abstract String findSignatureReturnTypeName(Signature signature); 254 255 protected abstract NodeSourcePosition findNodeSourcePosition(Object object); 256 257 protected abstract ResolvedJavaMethod findNodeSourcePositionMethod(NodeSourcePosition pos); 258 259 protected abstract NodeSourcePosition findNodeSourcePositionCaller(NodeSourcePosition pos); 260 261 protected abstract int findNodeSourcePositionBCI(NodeSourcePosition pos); 262 263 protected abstract Iterable<Location> findLocation(ResolvedJavaMethod method, int bci, NodeSourcePosition pos); 264 265 protected abstract String findLocationFile(Location loc) throws IOException; 266 267 protected abstract int findLocationLine(Location loc); 268 269 protected abstract URI findLocationURI(Location loc) throws URISyntaxException; 270 271 protected abstract String findLocationLanguage(Location loc); 272 273 protected abstract int findLocationStart(Location loc); 274 275 protected abstract int findLocationEnd(Location loc); 276 277 private void writeVersion() throws IOException { 278 writeBytesRaw(MAGIC_BYTES); 279 writeByte(versionMajor); 280 writeByte(versionMinor); 281 } 282 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 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 307 private void writeByte(int b) throws IOException { 308 ensureAvailable(1); 309 buffer.put((byte) b); 310 } 311 312 private void writeInt(int b) throws IOException { 313 ensureAvailable(4); 314 buffer.putInt(b); 315 } 316 317 private void writeLong(long b) throws IOException { 318 ensureAvailable(8); 319 buffer.putLong(b); 320 } 321 322 private void writeDouble(double b) throws IOException { 323 ensureAvailable(8); 324 buffer.putDouble(b); 325 } 326 327 private void writeFloat(float b) throws IOException { 328 ensureAvailable(4); 329 buffer.putFloat(b); 330 } 331 332 private void writeShort(char b) throws IOException { 333 ensureAvailable(2); 334 buffer.putChar(b); 335 } 336 337 private void writeString(String str) throws IOException { 338 byte[] bytes = str.getBytes(UTF8); 339 writeBytes(bytes); 340 } 341 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 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 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 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 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 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 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 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 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 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 518 private void writeNodeRef(Node node) throws IOException { 519 writeInt(findNodeId(node)); 520 } 521 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 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") 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 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 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 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 795 ConstantPool() { 796 super(50, 0.65f); 797 availableIds = new LinkedList<>(); 798 } 799 800 @Override 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 809 private Character nextAvailableId() { 810 if (!availableIds.isEmpty()) { 811 return availableIds.removeFirst(); 812 } 813 return nextId++; 814 } 815 816 public char add(Object obj) { 817 Character id = nextAvailableId(); 818 put(obj, id); 819 return id; 820 } 821 } 822 823 }