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