package lux.xml.tinybin; import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import lux.exception.LuxException; import net.sf.saxon.Configuration; import net.sf.saxon.om.NamePool; import net.sf.saxon.om.NamespaceBinding; import net.sf.saxon.tree.tiny.AppendableCharSequence; import net.sf.saxon.tree.tiny.CharSlice; import net.sf.saxon.tree.tiny.LargeStringBuffer; import net.sf.saxon.tree.tiny.TinyDocumentImpl; import net.sf.saxon.tree.tiny.TinyTree; import net.sf.saxon.tree.util.FastStringBuffer; import net.sf.saxon.type.Type; import org.apache.lucene.store.ByteArrayDataInput; import org.apache.lucene.store.ByteArrayDataOutput; /** * format version 0 corresponds to release 0.9.x * format version 1 is 0.10.x * */ // TODO: baseURI public class TinyBinary { public final static byte CURRENT_FORMAT = 1; private final static int TIN0 = ('T' << 24) | ('I' << 16) | ('N' << 8); private final static int TINY(byte version) { return TIN0 | version; } private final ByteBuffer byteBuffer; private final byte formatVersion; private int charBufferLength; private int commentBufferLength; private int nodeCount; // number of (non-attribute) nodes private int attCount; // number of attributes private int nsCount; // number of namespace declarations private int nameCount; // number of element/attribute names private int nsNameCount; // number of distinct prefixes and namespaces private int attValueCount; // number of distinct attribute values private LinkedHashMap<CharSequence, Integer> namespaces; private LinkedHashMap<CharSequence, Integer> names; private LinkedHashMap<CharSequence, Integer> attValues; private HashMap<Integer, Integer> nameCodeMap; private CharsetDecoder charsetDecoder; private CharsetEncoder charsetEncoder; private TinyDocumentImpl document; private static Field fsbUsed; /** To read a TinyTree from a byte array in which characters are encoded as they are in * Java (ie 16-bit values for normal chars, 32-bit for those in the supplemental planes) * @param buf a byte array containing a binary-encoded tiny tree. */ public TinyBinary(byte[] buf) { this(buf, null); } /** To read a TinyTree from a byte array in which characters are encoded according to the * given Charset. * @param buf a byte array containing a binary-encoded tiny tree. * @param charset the charset that defines a mapping between characters (unicode code points) * and bytes. */ public TinyBinary(byte[] buf, Charset charset) { this.byteBuffer = ByteBuffer.wrap(buf); int signature = byteBuffer.getInt(); if ((signature & 0xffffff00) != TIN0) { throw new LuxException ("bytes lack TINY signature"); } byte v = (byte) (signature & 0xff); formatVersion = (v == 'Y') ? 0 : v; // the first version (version 0) was labeled 'Y' if (formatVersion > CURRENT_FORMAT) { throw new LuxException ("Uknown tiny binary format version: " + formatVersion); } charBufferLength = byteBuffer.getInt(); commentBufferLength = byteBuffer.getInt(); nodeCount = byteBuffer.getInt(); attCount = byteBuffer.getInt(); nsCount = byteBuffer.getInt(); nameCount = byteBuffer.getInt(); nsNameCount = byteBuffer.getInt(); attValueCount = byteBuffer.getInt(); if (charset != null) { this.charsetDecoder = charset.newDecoder(); } // TODO: merge this w/getTinyDocument, which just picks up where this leaves off } public TinyDocumentImpl getTinyDocument(Configuration config) { if (document != null) { return document; } // Allocate TinyTree storage byte[] nodeKind = new byte[nodeCount]; short[] depth = new short[nodeCount]; int[] next = new int[nodeCount]; int[] alpha = new int[nodeCount]; int[] beta = new int[nodeCount]; int[] nameCode = new int[nodeCount]; int[] attParent = new int[attCount]; int[] attNameCode = new int[attCount]; int[] nsParent = new int[nsCount]; int[] nsNameCode = new int[nsCount]; // TODO: don't allocate this; stream in the values int[] attValueIndex = new int[attCount]; NamespaceBinding[] binding = new NamespaceBinding[nsCount]; AppendableCharSequence charBuffer; if (charBufferLength > 65000) { charBuffer = new LargeStringBuffer(); } else { charBuffer = new FastStringBuffer(charBufferLength); } FastStringBuffer commentBuffer = null; if (commentBufferLength > 0) { commentBuffer = new FastStringBuffer(commentBufferLength); } // System.out.println ("nodeCount: " + nodeCount); byteBuffer.get(nodeKind); ByteArrayDataInput in = new ByteArrayDataInput(byteBuffer.array(), byteBuffer.arrayOffset()+ byteBuffer.position(), byteBuffer.remaining()); readVInts(in, next, nodeCount); readAlpha(in, alpha, nodeKind, nodeCount); readBeta(in, beta, nodeKind, nodeCount); readMappedNameCodes(in, nameCode, nodeCount); readDeltas(in, attParent, attCount); readMappedNameCodes(in, attNameCode, attCount); readDeltas(in, nsParent, nsCount); readMappedNameCodes(in, nsNameCode, nsCount); readVInts(in, attValueIndex, attCount); readShortDeltas(in, depth, nodeCount); byteBuffer.position(in.getPosition() - byteBuffer.arrayOffset()); readChars(charBuffer, charBufferLength); if (commentBufferLength > 0) { readChars(commentBuffer, commentBufferLength); } String[] nameTable = readStrings(nameCount, charsetDecoder); String[] nsTable = readStrings(nsNameCount, charsetDecoder); CharSequence[] attValueDict = readCharSequences(attValueCount, charsetDecoder); // dereference attValue pointers CharSequence[] attValue = new CharSequence [attCount]; for (int i = 0; i < attCount; i++) { int idx = attValueIndex[i]; if (idx == 0) { attValue[i] = ""; } else { attValue[i] = attValueDict[idx - 1]; } } NamePool namePool = config.getNamePool(); // TODO: Would it be faster to save the list of distinct nameCodes // for efficiently recreating the pool rather than attempting to // allocate every node's name? allocateNames(nameCode, nameTable, nsTable, namePool); allocateNames(attNameCode, nameTable, nsTable, namePool); for (int i = 0; i < nsCount; i++) { int nsCode = nsNameCode[i]; int prefixCode = ((nsCode >> 16) & 0xffff) - 1; String prefix = prefixCode < 0 ? "" : nsTable[prefixCode]; int uriCode = (nsCode & 0xffff) - 1; binding[i] = new NamespaceBinding(prefix, nsTable[uriCode]); } resetByteBuffer(); /* * TinyTree tree = new TinyTree (config, nodeCount, nodeKind, depth, * next, alpha, beta, nameCode, attCount, attParent, attNameCode, * attValue, nsCount, nsParent, binding, charBuffer, commentBuffer, * config.getDocumentNumberAllocator().allocateDocumentNumber() ); */ TinyTree tree = new TinyTree(config); try { Field documentList = TinyTree.class.getDeclaredField("documentList"); documentList.setAccessible(true); @SuppressWarnings("unchecked") ArrayList<TinyDocumentImpl> tinyDocuments = (ArrayList<TinyDocumentImpl>) documentList.get(tree); tinyDocuments.add(new TinyDocumentImpl(tree)); setFieldValue(tree, "numberOfNodes", nodeCount); setFieldValue(tree, "nodeKind", nodeKind); setFieldValue(tree, "depth", depth); setFieldValue(tree, "next", next); setFieldValue(tree, "alpha", alpha); setFieldValue(tree, "beta", beta); setFieldValue(tree, "nameCode", nameCode); setFieldValue(tree, "numberOfAttributes", attCount); setFieldValue(tree, "attParent", attParent); setFieldValue(tree, "attCode", attNameCode); setFieldValue(tree, "attValue", attValue); setFieldValue(tree, "numberOfNamespaces", nsCount); setFieldValue(tree, "namespaceParent", nsParent); setFieldValue(tree, "namespaceBinding", binding); setFieldValue(tree, "charBuffer", charBuffer); setFieldValue(tree, "commentBuffer", commentBuffer); setFieldValue(tree, "documentNumber", config .getDocumentNumberAllocator().allocateDocumentNumber()); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } document = (TinyDocumentImpl) tree.getNode(0); return document; } private void setFieldValue(TinyTree tree, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Field f = TinyTree.class.getDeclaredField(fieldName); f.setAccessible(true); f.set(tree, fieldValue); } /** * decode character from the bytes ByteBuffer into the given Character * storage. * * @param charBuffer * the character storage * @param len * the number of characters to decode */ private void readChars(AppendableCharSequence charBuffer, int len) { if (charsetDecoder == null) { CharBuffer chars = byteBuffer.asCharBuffer(); chars.limit(len); if (charBuffer instanceof LargeStringBuffer) { // TODO: don't copy all these chars - implement // LargeStringBuffer.append(CharBuffer) charBuffer.append(chars.toString()); } else { charBuffer.append(chars); } byteBuffer.position(byteBuffer.position() + len * 2); } else { if (charBuffer instanceof FastStringBuffer) { FastStringBuffer buffer = ((FastStringBuffer) charBuffer); CharBuffer chars = CharBuffer.wrap(buffer.getCharArray(), 0, len); CoderResult result = charsetDecoder.decode(byteBuffer, chars, false); // we expect Overflow to occur if (result.isError() || result.isUnderflow()) { throw new LuxException ("character mapping error: " + result.toString()); } setStringBufferUsed (buffer, len); } else { CharBuffer chars = CharBuffer.wrap(new char[len]); charsetDecoder.decode(byteBuffer, chars, false); charBuffer.append(new CharSlice(chars.array())); } } } private void allocateNames(int[] nameCode, String[] nameArr, String[] nsArr, NamePool namePool) { for (int i = 0; i < nameCode.length; i++) { int code = nameCode[i]; if (code < 0) { continue; } int localNameCode = (code & 0xffff) - 1; String localName = localNameCode < 0 ? "" : nameArr[localNameCode]; int prefixCode = ((code >> 24) & 0xff) - 1; String prefix = prefixCode < 0 ? "" : nsArr[prefixCode]; int uriCode = ((code >> 16) & 0xff) - 1; String uri = uriCode < 0 ? "" : nsArr[uriCode]; // System.out.println ("allocateName " + i + " " + prefix + ":" + // localName); int poolCode = namePool.allocate(prefix, uri, localName); nameCode[i] = poolCode; } } private CharSequence[] readCharSequences(final int count, CharsetDecoder decoder) { CharSequence[] csqs = new CharSequence[count]; for (int i = 0; i < count; i++) { short len = byteBuffer.getShort(); CharBuffer chars; if (decoder == null) { chars = byteBuffer.asCharBuffer(); chars.limit(len); byteBuffer.position(byteBuffer.position() + len * 2); } else { chars = CharBuffer.wrap(new char[len]); decoder.decode(byteBuffer, chars, false); chars.position(0); } csqs[i] = chars; } return csqs; } private String[] readStrings(final int count, CharsetDecoder decoder) { String[] strings = new String[count]; for (int i = 0; i < count; i++) { short len = byteBuffer.getShort(); CharBuffer chars; if (decoder == null) { chars = byteBuffer.asCharBuffer(); chars.limit(len); byteBuffer.position(byteBuffer.position() + len * 2); strings[i] = chars.toString(); } else { // TODO: re-use this char[] chars = CharBuffer.wrap(new char[len]); decoder.decode(byteBuffer, chars, false); strings[i] = new String(chars.array(), 0, chars.position()); } } return strings; } // To write a TinyTree public TinyBinary(TinyTree tree) { this(tree, null); } public TinyBinary(TinyTree tree, Charset charset) { this (tree, charset, CURRENT_FORMAT); } public TinyBinary(TinyTree tree, Charset charset, byte formatVersion) { names = new LinkedHashMap<CharSequence, Integer>(); namespaces = new LinkedHashMap<CharSequence, Integer>(); attValues = new LinkedHashMap<CharSequence, Integer>(); nameCodeMap = new HashMap<Integer, Integer>(); if (charset != null) { charsetEncoder = charset.newEncoder(); } this.formatVersion = formatVersion; int totalSize = calculateTotalSize(tree); byteBuffer = ByteBuffer.allocate(totalSize); byteBuffer.putInt(TINY(formatVersion)); // signature of the current TINY format byteBuffer.putInt(tree.getCharacterBuffer().length()); if (tree.getCommentBuffer() == null) byteBuffer.putInt(0); else byteBuffer.putInt(tree.getCommentBuffer().length()); byteBuffer.putInt(nodeCount); byteBuffer.putInt(attCount); byteBuffer.putInt(nsCount); byteBuffer.putInt(names.size()); byteBuffer.putInt(namespaces.size()); byteBuffer.putInt(attValues.size()); byteBuffer.put(tree.nodeKind, 0, nodeCount); ByteArrayDataOutput out = new ByteArrayDataOutput(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining()); writeVInts(out, tree.getNextPointerArray(), nodeCount); writeAlpha(out, tree.getAlphaArray(), tree.nodeKind, nodeCount); writeBeta(out, tree.getBetaArray(), tree.nodeKind, nodeCount); writeMappedNameCodes(out, tree.getNameCodeArray(), nodeCount); writeDeltas(out, tree.getAttributeParentArray(), attCount); writeMappedNameCodes(out, tree.getAttributeNameCodeArray(), attCount); writeDeltas(out, tree.getNamespaceParentArray(), nsCount); // put ns decl string references NamespaceBinding[] bindings = tree.getNamespaceCodeArray(); try { // write namespace binding pointers: these are pairs of indexes into the namespaces string array for (int i = 0; i < nsCount; i++) { NamespaceBinding binding = bindings[i]; int a = binding.getPrefix().length() == 0 ? 0 : namespaces .get(binding.getPrefix()); int b = namespaces.get(binding.getURI()); out.writeVInt((a << 16) | b); } // write attribute value pointers: these are indexes into the attValues string array for (int i = 0; i < attCount; i++) { Integer index = attValues.get(tree.getAttributeValueArray()[i]); // index is always 1-based: the 0 value is reserved for the empty string out.writeVInt(index == null ? 0 : index.intValue()); } } catch (IOException e) { } writeShortDeltas(out, tree.getNodeDepthArray(), nodeCount); byteBuffer.position(out.getPosition() - byteBuffer.arrayOffset()); putCharacterBuffer(tree.getCharacterBuffer(), charsetEncoder); if (tree.getCommentBuffer() != null) { putCharacterBuffer(tree.getCommentBuffer(), charsetEncoder); } writeStrings(names, charsetEncoder); writeStrings(namespaces, charsetEncoder); writeStrings(attValues, charsetEncoder); resetByteBuffer(); } private void resetByteBuffer() { // leave the buffer positioned at zero and properly limited so that consumers of the // raw buffer will see all the right bytes byteBuffer.limit(byteBuffer.position()); byteBuffer.position(0); } private void writeAlpha(ByteArrayDataOutput out, int[] alpha, byte[] nodeKind, int count) { // the alpha array stores offset into the character buffer for text, // offset into the // comment buffer for comments and pis, and index of the first attribute // for elements (or -1) int textOffset = 0, commentOffset = 0, attrIndex = -1; try { int k; for (int i = 0; i < count; i++) { switch (nodeKind[i]) { case Type.TEXT: k = alpha[i] - textOffset; textOffset = alpha[i]; break; case Type.ELEMENT: if (alpha[i] < 0) { k = 0; } else { k = alpha[i] - attrIndex; attrIndex = alpha[i]; } break; case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: k = alpha[i] - commentOffset; commentOffset = alpha[i]; break; case Type.WHITESPACE_TEXT: out.writeInt(alpha[i]); continue; case Type.PARENT_POINTER: case Type.STOPPER: case Type.DOCUMENT: k = alpha[i]; break; default: throw new IllegalStateException("unexpected node kind: " + nodeKind[i]); } out.writeVInt(k); } } catch (IOException e) { } } private void readAlpha(ByteArrayDataInput in, int[] alpha, byte[] nodeKind, int count) { int textOffset = 0, commentOffset = 0, attrIndex = -1; for (int i = 0; i < count; i++) { int k; switch (nodeKind[i]) { case Type.TEXT: k = in.readVInt(); textOffset += k; alpha[i] = textOffset; break; case Type.ELEMENT: k = in.readVInt(); if (k == 0) { alpha[i] = -1; } else { attrIndex += k; alpha[i] = attrIndex; } break; case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: k = in.readVInt(); commentOffset += k; alpha[i] = commentOffset; break; case Type.WHITESPACE_TEXT: alpha[i] = in.readInt(); break; case Type.PARENT_POINTER: case Type.STOPPER: case Type.DOCUMENT: alpha[i] = in.readVInt(); break; default: throw new IllegalStateException("unexpected node kind: " + nodeKind[i]); } } } private void writeBeta(ByteArrayDataOutput out, int[] beta, byte[] nodeKind, int count) { // the beta array stores length of text, comment and pi nodes, // and index of the first namespace decl for elements (or -1) int nsIndex = -1; try { int k; for (int i = 0; i < count; i++) { switch (nodeKind[i]) { case Type.ELEMENT: if (beta[i] < 0) { k = 0; } else { k = beta[i] - nsIndex; nsIndex = beta[i]; } out.writeVInt(k); break; case Type.PARENT_POINTER: case Type.STOPPER: case Type.DOCUMENT: case Type.TEXT: case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: case Type.WHITESPACE_TEXT: out.writeVInt(beta[i]); break; default: throw new IllegalStateException("unexpected node kind: " + nodeKind[i]); } } } catch (IOException e) { } } private void readBeta(ByteArrayDataInput in, int[] beta, byte[] nodeKind, int count) { int attrIndex = -1; for (int i = 0; i < count; i++) { switch (nodeKind[i]) { case Type.ELEMENT: attrIndex += in.readVInt(); beta[i] = attrIndex; break; case Type.PARENT_POINTER: case Type.STOPPER: case Type.DOCUMENT: case Type.TEXT: case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: case Type.WHITESPACE_TEXT: beta[i] = in.readVInt(); break; default: throw new IllegalStateException("unexpected node kind: " + nodeKind[i]); } } } private void readVInts(ByteArrayDataInput in, int[] ints, int count) { for (int i = 0; i < count; i++) { ints[i] = in.readVInt(); } } private void readDeltas(ByteArrayDataInput in, int[] ints, int count) { int k = 0; if (formatVersion < 1) { for (int i = 0; i < count; i++) { k = k + in.readByte(); ints[i] = k; } } else { for (int i = 0; i < count; i++) { k = k + in.readVInt(); ints[i] = k; } } // System.out.println ("after readDeltas " + count + " pos=" + // bytes.position()); } private void readShortDeltas(ByteArrayDataInput in, short[] shorts, int count) { short k = 0; if (formatVersion < 1) { for (int i = 0; i < count; i++) { k = (short) (k + in.readByte()); shorts[i] = k; } } else { for (int i = 0; i < count; i++) { k = (short) (k + in.readVInt()); shorts[i] = k; } } } private void writeVInts(ByteArrayDataOutput out, int[] ints, int count) { try { for (int i = 0; i < count; i++) { out.writeVInt(ints[i]); } } catch (IOException e) { } } private void writeDeltas(ByteArrayDataOutput out, int[] ints, int count) { int k = 0; try { if (formatVersion < 1) { for (int i = 0; i < count; i++) { k = ints[i] - k; out.writeByte((byte) (k & 0xff)); k = ints[i]; } } else { for (int i = 0; i < count; i++) { k = ints[i] - k; out.writeVInt(k); k = ints[i]; } } } catch (IOException e) { } // System.out.println ("after writeDeltas " + count + " pos=" + // bytes.position()); } private void writeShortDeltas(ByteArrayDataOutput out, short[] shorts, int count) { int k = 0; try { if (formatVersion < 1) { for (int i = 0; i < count; i++) { k = shorts[i] - k; out.writeByte((byte) (k & 0xff)); k = shorts[i]; } } else { for (int i = 0; i < count; i++) { k = shorts[i] - k; out.writeVInt(k); k = shorts[i]; } } } catch (IOException e) { } } private void putCharacterBuffer(CharSequence characterBuffer, CharsetEncoder encoder) { if (encoder == null) { CharBuffer chars = byteBuffer.asCharBuffer(); chars.put(characterBuffer.toString()); byteBuffer.position(byteBuffer.position() + chars.position() * 2); } else if (characterBuffer instanceof FastStringBuffer) { CharBuffer chars = CharBuffer.wrap( ((FastStringBuffer) characterBuffer).getCharArray(), 0, characterBuffer.length()); encoder.encode(chars, byteBuffer, false); } else { CharBuffer chars = CharBuffer.wrap(characterBuffer, 0, characterBuffer.length()); encoder.encode(chars, byteBuffer, false); } } private void writeMappedNameCodes(ByteArrayDataOutput out, int[] nameCodes, int count) { try { for (int i = 0; i < count; i++) { int nameCode = nameCodes[i]; if (nameCode >= 0) { out.writeVInt(nameCodeMap.get(nameCode)); } else { // out.writeByte (0)? out.writeVInt(0); } } } catch (IOException e) { } } private void readMappedNameCodes(ByteArrayDataInput in, int[] nameCodes, int count) { for (int i = 0; i < count; i++) { int nameCode = in.readVInt(); if (nameCode > 0) { nameCodes[i] = nameCode; } else { nameCodes[i] = -1; } } } private void writeStrings(LinkedHashMap<CharSequence, Integer> map, CharsetEncoder encoder) { CharBuffer chars = byteBuffer.asCharBuffer(); for (CharSequence name : map.keySet()) { int len = name.length(); byteBuffer.putShort((short) len); if (encoder == null) { chars.position(chars.position() + 1); chars.put(name.toString()); byteBuffer.position(byteBuffer.position() + len * 2); } else { chars = CharBuffer.wrap(name); encoder.encode(chars, byteBuffer, false); } } } /** * FIXME: lurking bug here for buffers filled entirely with supplemental chars, * since we allocate a buffer based on an assumption that chars will be 2 bytes on average, * and this could be badly wrong. We should allocate smaller (assume 1 byte per char) * and grow as needed. * * @param tree * a Saxon TinyTree * @return an estimate of the size required to store the tree in TinyBinary * format */ private int calculateTotalSize(TinyTree tree) { nodeCount = tree.getNumberOfNodes(); attCount = tree.getNumberOfAttributes(); nsCount = tree.getNumberOfNamespaces(); getStrings(tree); int stringLen = 12; // 3 * 4 ints = lengths of the string arrays for (CharSequence s : names.keySet()) { stringLen += s.length() * 2; stringLen += 2; } for (CharSequence s : namespaces.keySet()) { stringLen += s.length() * 2; stringLen += 2; } for (CharSequence s : attValues.keySet()) { stringLen += s.length() * 2; stringLen += 2; } return 36 + nodeCount * 19 + attCount * 8 + nsCount * 8 + tree.getCharacterBuffer().length() * 2 + (tree.getCommentBuffer() == null ? 0 : tree .getCommentBuffer().length() * 2) + stringLen; } private void getStrings(TinyTree tree) { NamePool namePool = tree.getNamePool(); for (int i = 0; i < nodeCount; i++) { int nameCode = tree.getNameCode(i); internNameCodeStrings(nameCode, namePool); } int[] attNameCode = tree.getAttributeNameCodeArray(); for (int i = 0; i < attCount; i++) { int nameCode = attNameCode[i]; internNameCodeStrings(nameCode, namePool); } NamespaceBinding[] bindings = tree.getNamespaceCodeArray(); for (int i = 0; i < nsCount; i++) { internString(namespaces, bindings[i].getPrefix()); internString(namespaces, bindings[i].getURI()); } CharSequence[] attValueArray = tree.getAttributeValueArray(); for (int i = 0; i < attCount; i++) { internString(attValues, attValueArray[i]); } } private void internNameCodeStrings(int nameCode, NamePool namePool) { if (nameCode >= 0 && !nameCodeMap.containsKey(nameCode)) { int a = internString(names, namePool.getLocalName(nameCode)); int b = internString(namespaces, namePool.getPrefix(nameCode)); int c = internString(namespaces, namePool.getURI(nameCode)); nameCodeMap.put(nameCode, a | (b << 24) | (c << 16)); } } /* * Intern char sequence values in a symbol table. The symbols are indexed, * starting at 1. The index value of 0 indicates a null value. */ private int internString(HashMap<CharSequence, Integer> symbolTable, CharSequence s) { if (s == null || s.length() == 0) { return 0; } Integer n = symbolTable.get(s); if (n == null) { n = symbolTable.size() + 1; symbolTable.put(s, n); } return n; } /** * @return the internal storage buffer */ public ByteBuffer getByteBuffer() { return byteBuffer; } /** * @return the byte array from the internal storage buffer. This will usually be larger * than required, and contains unused trailing bytes. */ public byte[] getBytes() { return byteBuffer.array(); } public int length() { return byteBuffer.limit(); } /* * Use reflection to set value of FastStringBuffer.used since we want to write directly into its buffer */ private void setStringBufferUsed (FastStringBuffer buffer, int used) { if (fsbUsed == null) { try { fsbUsed = FastStringBuffer.class.getDeclaredField ("used"); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } fsbUsed.setAccessible(true); } try { fsbUsed.set(buffer, used); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }