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 }