Smile: adding symbol handling (similar to standard UTF-8); now smile decoding 25% faster than textual json (about same as size reduction)

diff --git a/src/java/org/codehaus/jackson/sym/BytesToNameCanonicalizer.java b/src/java/org/codehaus/jackson/sym/BytesToNameCanonicalizer.java
index dbbad75..2e8e215 100644
--- a/src/java/org/codehaus/jackson/sym/BytesToNameCanonicalizer.java
+++ b/src/java/org/codehaus/jackson/sym/BytesToNameCanonicalizer.java
@@ -477,6 +477,20 @@
     /**********************************************************
      */
 
+    /**
+     * @since 1.6.0
+     */
+    public Name addName(String symbolStr, int q1, int q2)
+    {
+        if (_intern) {
+            symbolStr = InternCache.instance.intern(symbolStr);
+        }
+        int hash = (q2 == 0) ? calcHash(q1) : calcHash(q1, q2);
+        Name symbol = constructName(hash, symbolStr, q1, q2);
+        _addSymbol(hash, symbol);
+        return symbol;
+    }
+    
     public Name addName(String symbolStr, int[] quads, int qlen)
     {
         if (_intern) {
@@ -487,7 +501,7 @@
         _addSymbol(hash, symbol);
         return symbol;
     }
-
+    
     /*
     /**********************************************************
     /* Helper methods
@@ -732,9 +746,9 @@
         Bucket[] oldBuckets = _collList;
         _collList = new Bucket[oldBuckets.length];
         for (int i = 0; i < oldEnd; ++i) {
-            for (Bucket curr = oldBuckets[i]; curr != null; curr = curr.mNext) {
+            for (Bucket curr = oldBuckets[i]; curr != null; curr = curr._next) {
                 ++symbolsSeen;
-                Name symbol = curr.mName;
+                Name symbol = curr._name;
                 int hash = symbol.hashCode();
                 int ix = (hash & _mainHashMask);
                 int val = _mainHash[ix];
@@ -862,7 +876,6 @@
     /**********************************************************
      */
 
-    /*
     private static Name constructName(int hash, String name, int q1, int q2)
     {     
         if (q2 == 0) { // one quad only?
@@ -870,7 +883,6 @@
         }
         return new Name2(name, hash, q1, q2);
     }
-    */
 
     private static Name constructName(int hash, String name, int[] quads, int qlen)
     {
@@ -901,19 +913,19 @@
 
     final static class Bucket
     {
-        final Name mName;
-        final Bucket mNext;
+        protected final Name _name;
+        protected final Bucket _next;
 
         Bucket(Name name, Bucket next)
         {
-            mName = name;
-            mNext = next;
+            _name = name;
+            _next = next;
         }
 
         public int length()
         {
             int len = 1;
-            for (Bucket curr = mNext; curr != null; curr = curr.mNext) {
+            for (Bucket curr = _next; curr != null; curr = curr._next) {
                 ++len;
             }
             return len;
@@ -921,13 +933,13 @@
 
         public Name find(int hash, int firstQuad, int secondQuad)
         {
-            if (mName.hashCode() == hash) {
-                if (mName.equals(firstQuad, secondQuad)) {
-                    return mName;
+            if (_name.hashCode() == hash) {
+                if (_name.equals(firstQuad, secondQuad)) {
+                    return _name;
                 }
             }
-            for (Bucket curr = mNext; curr != null; curr = curr.mNext) {
-                Name currName = curr.mName;
+            for (Bucket curr = _next; curr != null; curr = curr._next) {
+                Name currName = curr._name;
                 if (currName.hashCode() == hash) {
                     if (currName.equals(firstQuad, secondQuad)) {
                         return currName;
@@ -939,13 +951,13 @@
 
         public Name find(int hash, int[] quads, int qlen)
         {
-            if (mName.hashCode() == hash) {
-                if (mName.equals(quads, qlen)) {
-                    return mName;
+            if (_name.hashCode() == hash) {
+                if (_name.equals(quads, qlen)) {
+                    return _name;
                 }
             }
-            for (Bucket curr = mNext; curr != null; curr = curr.mNext) {
-                Name currName = curr.mName;
+            for (Bucket curr = _next; curr != null; curr = curr._next) {
+                Name currName = curr._name;
                 if (currName.hashCode() == hash) {
                     if (currName.equals(quads, qlen)) {
                         return currName;
diff --git a/src/java/org/codehaus/jackson/sym/Name.java b/src/java/org/codehaus/jackson/sym/Name.java
index b677876..4127ac8 100644
--- a/src/java/org/codehaus/jackson/sym/Name.java
+++ b/src/java/org/codehaus/jackson/sym/Name.java
@@ -21,9 +21,9 @@
     public String getName() { return mName; }
 
     /*
-    //////////////////////////////////////////////////////////
-    // Methods for package/core parser
-    //////////////////////////////////////////////////////////
+    /**********************************************************
+    /* Methods for package/core parser
+    /**********************************************************
      */
 
     public abstract boolean equals(int quad1);
@@ -33,9 +33,9 @@
     public abstract boolean equals(int[] quads, int qlen);
 
     /*
-    //////////////////////////////////////////////////////////
-    // Overridden standard methods
-    //////////////////////////////////////////////////////////
+    /**********************************************************
+    /* Overridden standard methods
+    /**********************************************************
      */
 
     @Override
diff --git a/src/perf/TestJsonPerf.java b/src/perf/TestJsonPerf.java
index f93d3b7..166c5a0 100644
--- a/src/perf/TestJsonPerf.java
+++ b/src/perf/TestJsonPerf.java
@@ -55,7 +55,7 @@
         while (true) {
             try {  Thread.sleep(100L); } catch (InterruptedException ie) { }
             // Use 9 to test all...
-            int round = (i++ % 5);
+            int round = (i++ % 2);
 
             long curr = System.currentTimeMillis();
             String msg;
@@ -64,31 +64,33 @@
             switch (round) {
 
             case 0:
-                msg = "Jackson, stream/byte";
-                sum += testJacksonStream(REPS, _jsonFactory, _jsonData, true);
-                break;
-            case 1:
-                msg = "Jackson, stream/char";
-                sum += testJacksonStream(REPS, _jsonFactory, _jsonData, false);
-                break;
-            case 2:
                 msg = "Jackson/smile, stream";
                 sum += testJacksonStream(REPS, _smileFactory, _smileData, true);
                 break;
-            case 3:
-                msg = "Noggit";
-                sum += testNoggit(REPS);
+            case 1:
+                msg = "Jackson, stream/byte";
+                sum += testJacksonStream(REPS, _jsonFactory, _jsonData, true);
+                break;
+            case 2:
+                msg = "Jackson, stream/char";
+                sum += testJacksonStream(REPS, _jsonFactory, _jsonData, false);
                 break;
 
-            case 4:
+            case 3:
                 msg = "Jackson, Java types";
                 sum += testJacksonJavaTypes(_mapper, REPS);
                 break;
 
-            case 5:
+            case 4:
                 msg = "Jackson, JSON types";
                 sum += testJacksonJsonTypes(_mapper, REPS);
                 break;
+
+            case 5:
+                msg = "Noggit";
+                sum += testNoggit(REPS);
+                break;
+
             case 6:
                 msg = "Json.org";
                 sum += testJsonOrg(REPS);
diff --git a/src/smile/java/org/codehaus/jackson/smile/SmileParser.java b/src/smile/java/org/codehaus/jackson/smile/SmileParser.java
index 37b89cb..11d78ec 100644
--- a/src/smile/java/org/codehaus/jackson/smile/SmileParser.java
+++ b/src/smile/java/org/codehaus/jackson/smile/SmileParser.java
@@ -11,6 +11,7 @@
 import org.codehaus.jackson.impl.StreamBasedParserBase;
 import org.codehaus.jackson.io.IOContext;
 import org.codehaus.jackson.sym.BytesToNameCanonicalizer;
+import org.codehaus.jackson.sym.Name;
 
 public class SmileParser
     extends StreamBasedParserBase
@@ -49,6 +50,8 @@
         public int getMask() { return _mask; }
     }
 
+    private static int[] NO_INTS = new int[0];
+    
     /*
     /**********************************************************
     /* Configuration
@@ -60,6 +63,11 @@
      */
     protected ObjectCodec _objectCodec;
 
+    /**
+     * Symbol table that contains field names encountered so far
+     */
+    final protected BytesToNameCanonicalizer _symbols;
+    
     /*
     /**********************************************************
     /* Additional parsing state
@@ -78,6 +86,13 @@
      * format does, and we want to retain that separation.
      */
     protected boolean _got32BitFloat;
+
+    /**
+     * Temporary buffer used for name parsing.
+     */
+    protected int[] _quadBuffer = NO_INTS;
+
+    protected int _quad1, _quad2;
     
     /*
     /**********************************************************
@@ -93,6 +108,7 @@
     {
         super(ctxt, parserFeatures, in, inputBuffer, start, end, bufferRecyclable);
         _objectCodec = codec;
+        _symbols = sym;
         _tokenInputRow = -1;
         _tokenInputCol = -1;
     }
@@ -147,7 +163,28 @@
         }
         return true;
 	}
-    
+
+    /*
+    /**********************************************************
+    /* Overridden methods
+    /**********************************************************
+     */
+
+    @Override
+    protected void _finishString() throws IOException, JsonParseException
+    {
+        // should never be called; but must be defined for superclass
+        _throwInternal();
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        super.close();
+        // Merge found symbols, if any:
+        _symbols.release();
+    }
+	
     /*
     /**********************************************************
     /* JsonParser impl
@@ -418,7 +455,7 @@
     
     /*
     /**********************************************************
-    /* Internal methods, parsing
+    /* Internal methods, field name parsing
     /**********************************************************
      */
 
@@ -468,45 +505,77 @@
             return JsonToken.FIELD_NAME;
         case 2: // short ASCII
 	        {
-	            int len = 1 + (ch & 0x3F);
-	            _loadToHaveAtLeast(len);
-	            int outPtr = 0;
-	            char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
-	            int inPtr = _inputPtr;
-	            _inputPtr += len;
-	            for (int end = inPtr + len; inPtr < end; ) {
-	                outBuf[outPtr++] = (char) _inputBuffer[inPtr++];
-	            }
-	            _textBuffer.setCurrentLength(len);
-	            _parsingContext.setCurrentName(_textBuffer.contentsAsString());
+	        	int len = (ch & 0x3f) + 1;
+	        	String name;
+	        	Name n = _findDecodedFromSymbols(len);
+	        	if (n != null) {
+	        		name = n.getName();
+	        		_inputPtr += len;
+	        	} else {
+	        		name = _decodeShortAsciiName(len);
+	        		name = _addDecodedToSymbols(len, name);
+	        	}
+	            _parsingContext.setCurrentName(name);
 	        }
             return JsonToken.FIELD_NAME;                
         case 3: // short Unicode
             // all valid, except for 0xBF and 0xFF
             if ((ch & 0x3F) != 0x3F) {
-                int len = 1 + (ch & 0x3F);
-                _decodeShortUnicode(len);
-                _parsingContext.setCurrentName(_textBuffer.contentsAsString());
-                return JsonToken.FIELD_NAME;                
+	        	int len = (ch & 0x3f) + 1;
+	        	String name;
+	        	Name n = _findDecodedFromSymbols(len);
+	        	if (n != null) {
+	        		name = n.getName();
+	        		_inputPtr += len;
+	        	} else {
+	        		name = _decodeShortUnicodeName(len);
+	        		name = _addDecodedToSymbols(len, name);
+	        	}
+	            _parsingContext.setCurrentName(name);
+	            return JsonToken.FIELD_NAME;                
             }
             break;
         }
         // Other byte values are illegal
         _reportError("Invalid type marker byte 0x"+Integer.toHexString(ch)+" for expected field name (or END_OBJECT marker)");
         return null;
-    }    
+    }
 
+    private final String _addDecodedToSymbols(int len, String name)
+    {
+	    if (len < 5) {
+    		return _symbols.addName(name, _quad1, 0).getName();
+		}
+	    if (len < 9) {
+    		return _symbols.addName(name, _quad1, _quad2).getName();
+		}
+		int qlen = (len + 3) >> 2;
+		return _symbols.addName(name, _quadBuffer, qlen).getName();
+	}
+
+    private final String _decodeShortAsciiName(int len)
+	    throws IOException, JsonParseException
+	{
+		int outPtr = 0;
+	    char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+	    int inPtr = _inputPtr;
+	    _inputPtr += len;
+	    for (int end = inPtr + len; inPtr < end; ) {
+	        outBuf[outPtr++] = (char) _inputBuffer[inPtr++];
+	    }
+	    _textBuffer.setCurrentLength(len);
+	    return _textBuffer.contentsAsString();
+	}
+    
     /**
      * Helper method used to decode short Unicode string, length for which actual
      * length (in bytes) is known
      * 
      * @param len Length between 1 and 64
      */
-    protected final void _decodeShortUnicode(int len)
+    private final String _decodeShortUnicodeName(int len)
         throws IOException, JsonParseException
     {
-        _loadToHaveAtLeast(len);
-
         int outPtr = 0;
         char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
         int inPtr = _inputPtr;
@@ -543,8 +612,111 @@
             outBuf[outPtr++] = (char) i;
         }
         _textBuffer.setCurrentLength(outPtr);
+	    return _textBuffer.contentsAsString();
     }
 
+    /**
+     * Helper method for trying to find specified encoded UTF-8 byte sequence
+     * from symbol table; if succesfull avoids actual decoding to String
+     */
+    private final Name _findDecodedFromSymbols(int len)
+    	throws IOException, JsonParseException
+	{
+	    if ((_inputEnd - _inputPtr) < len) {
+			_loadToHaveAtLeast(len);
+		}
+		// First: maybe we already have this name decoded?
+		if (len < 5) {
+	    	int inPtr = _inputPtr;
+	    	final byte[] inBuf = _inputBuffer;
+			int q = inBuf[inPtr];
+			if (--len > 0) {
+				q = (q << 8) + inBuf[++inPtr];
+				if (--len > 0) {
+					q = (q << 8) + inBuf[++inPtr];
+					if (--len > 0) {
+						q = (q << 8) + inBuf[++inPtr];
+					}
+				}
+			}
+			_quad1 = q;
+			return _symbols.findName(q);
+		}
+		if (len < 9) {
+	    	int inPtr = _inputPtr;
+	    	final byte[] inBuf = _inputBuffer;
+			// First quadbyte is easy
+			int q1 = inBuf[inPtr++] << 8;
+			q1 += inBuf[inPtr++];
+			q1 <<= 8;
+			q1 += inBuf[inPtr++];
+			q1 <<= 8;
+			q1 += inBuf[inPtr++];
+			int q2 = inBuf[inPtr++];
+			len -= 5;
+			if (len > 0) {
+				q2 = (q2 << 8) + inBuf[inPtr++];				
+				if (--len >= 0) {
+					q2 = (q2 << 8) + inBuf[inPtr++];				
+					if (--len >= 0) {
+						q2 = (q2 << 8) + inBuf[inPtr++];				
+					}
+				}
+			}
+			_quad1 = q1;
+			_quad2 = q2;
+			return _symbols.findName(q1, q2);
+		}
+		return _findDecodedLong(len);
+	}
+
+    private final Name _findDecodedLong(int len)
+		throws IOException, JsonParseException
+	{
+    	// first, need enough buffer to store bytes as ints:
+    	{
+			int bufLen = (len  + 3) >> 2;
+	        if (bufLen > _quadBuffer.length) {
+	            _quadBuffer = _growArrayTo(_quadBuffer, bufLen);
+	        }
+    	}
+    	// then decode, full quads first
+    	int offset = 0;
+    	int inPtr = _inputPtr;
+    	final byte[] inBuf = _inputBuffer;
+        do {
+			int q = inBuf[inPtr++] << 8;
+			q |= inBuf[inPtr++];
+			q <<= 8;
+			q |= inBuf[inPtr++];
+			q <<= 8;
+			q |= inBuf[inPtr++];
+			_quadBuffer[offset++] = q;
+        } while ((len -= 4) > 3);
+        // and then leftovers
+        if (len > 0) {
+			int q = inBuf[inPtr++];
+			if (--len >= 0) {
+				q = (q << 8) + inBuf[inPtr++];				
+				if (--len >= 0) {
+					q = (q << 8) + inBuf[inPtr++];				
+				}
+			}
+			_quadBuffer[offset++] = q;
+        }
+        return _symbols.findName(_quadBuffer, offset);
+	}
+    
+    public static int[] _growArrayTo(int[] arr, int minSize)
+    {
+    	int[] newArray = new int[minSize + 4];
+        if (arr != null) {
+        	// !!! TODO: JDK 1.6, Arrays.copyOf
+        	System.arraycopy(arr, 0, newArray, 0, arr.length);
+        }
+        return newArray;
+    }
+    
     /*
     /**********************************************************
     /* Internal methods, secondary parsing
@@ -574,24 +746,13 @@
         case 2: // tiny ascii
             // fall through
         case 3: // short ascii
-        	{
-        		int len = (tb - 0x3F);
-	            _loadToHaveAtLeast(len);
-	            int outPtr = 0;
-	            char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
-	            int inPtr = _inputPtr;
-	            _inputPtr += len;
-	            for (int end = inPtr + len; inPtr < end; ) {
-	                outBuf[outPtr++] = (char) _inputBuffer[inPtr++];
-	            }
-	            _textBuffer.setCurrentLength(len);
-        	}
+            _decodeShortAsciiValue(tb - 0x3F);
         	return;
 
         case 4: // tiny unicode
             // fall through
         case 5: // short unicode
-            _decodeShortUnicode(tb - 0x7F);
+            _decodeShortUnicodeValue(tb - 0x7F);
 	        return;
 
         case 7:
@@ -682,6 +843,67 @@
     	_throwInternal();
     }
 
+    protected final void _decodeShortAsciiValue(int len)
+	    throws IOException, JsonParseException
+	{
+    	if ((_inputEnd - _inputPtr) < len) {
+    		_loadToHaveAtLeast(len);
+    	}
+        int outPtr = 0;
+        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+        int inPtr = _inputPtr;
+        _inputPtr += len;
+        for (int end = inPtr + len; inPtr < end; ) {
+            outBuf[outPtr++] = (char) _inputBuffer[inPtr++];
+        }
+        _textBuffer.setCurrentLength(len);
+	}
+    
+    protected final void _decodeShortUnicodeValue(int len)
+	    throws IOException, JsonParseException
+	{
+    	if ((_inputEnd - _inputPtr) < len) {
+    		_loadToHaveAtLeast(len);
+    	}
+	
+	    int outPtr = 0;
+	    char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+	    int inPtr = _inputPtr;
+	    _inputPtr += len;
+	    final int[] codes = SmileConstants.sUtf8UnitLengths;
+	    for (int end = inPtr + len; inPtr < end; ) {
+	        int i = _inputBuffer[inPtr++] & 0xFF;
+	        int code = codes[i];
+	        if (code != 0) {
+	            // trickiest one, need surrogate handling
+	            switch (code) {
+	            case 1:
+	                i = ((i & 0x1F) << 6) | (_inputBuffer[inPtr++] & 0x3F);
+	                break;
+	            case 2:
+	                i = ((i & 0x0F) << 12)
+	                    | ((_inputBuffer[inPtr++] & 0x3F) << 6)
+	                    | (_inputBuffer[inPtr++] & 0x3F);
+	                break;
+	            case 3:
+	                i = ((i & 0x07) << 18)
+	                | ((_inputBuffer[inPtr++] & 0x3F) << 12)
+	                | ((_inputBuffer[inPtr++] & 0x3F) << 6)
+	                | (_inputBuffer[inPtr++] & 0x3F);
+	                // note: this is the codepoint value; need to split, too
+	                i -= 0x10000;
+	                outBuf[outPtr++] = (char) (0xD800 | (i >> 10));
+	                i = 0xDC00 | (i & 0x3FF);
+	                break;
+	            default: // invalid
+	                _reportError("Invalid byte "+Integer.toHexString(i)+" in short Unicode text block");
+	            }
+	        }
+	        outBuf[outPtr++] = (char) i;
+	    }
+	    _textBuffer.setCurrentLength(outPtr);
+	}
+    
     /**
      * Helper method that will update state for an int value
      */
@@ -888,13 +1110,6 @@
 	    }
     }
     
-    @Override
-    protected void _finishString() throws IOException, JsonParseException
-    {
-        // should never be called; but must be defined for superclass
-        _throwInternal();
-    }
-    
     /*
     /**********************************************************
     /* Internal methods, other
diff --git a/src/smile/test/org/codehaus/jackson/smile/SmileTestBase.java b/src/smile/test/org/codehaus/jackson/smile/SmileTestBase.java
index bdc866e..19c3af6 100644
--- a/src/smile/test/org/codehaus/jackson/smile/SmileTestBase.java
+++ b/src/smile/test/org/codehaus/jackson/smile/SmileTestBase.java
@@ -13,10 +13,15 @@
     protected SmileParser _parser(byte[] input)
         throws IOException
     {
-    	SmileFactory f = new SmileFactory();
-        return f.createJsonParser(input);
+    	return _parser(new SmileFactory(), input);
     }
 
+    protected SmileParser _parser(SmileFactory f, byte[] input)
+	    throws IOException
+	{
+	    return f.createJsonParser(input);
+	}
+    
     protected byte[] _smileDoc(String json) throws IOException
     {
     	return _smileDoc(json, true);
diff --git a/src/smile/test/org/codehaus/jackson/smile/TestSmileParser.java b/src/smile/test/org/codehaus/jackson/smile/TestSmileParser.java
index 9d6ff5c..6d7d64b 100644
--- a/src/smile/test/org/codehaus/jackson/smile/TestSmileParser.java
+++ b/src/smile/test/org/codehaus/jackson/smile/TestSmileParser.java
@@ -34,6 +34,35 @@
     	assertToken(JsonToken.END_ARRAY, p.nextToken());
     	p.close();
     }
+
+    public void testLongAsciiString() throws IOException
+    {
+    	final String DIGITS = "1234567890";
+    	String LONG = DIGITS + DIGITS + DIGITS + DIGITS;
+    	LONG = LONG + LONG + LONG + LONG;
+    	byte[] data = _smileDoc(quote(LONG));
+
+    	SmileParser p = _parser(data);
+    	assertNull(p.getCurrentToken());
+    	assertToken(JsonToken.VALUE_STRING, p.nextToken());
+    	assertEquals(LONG, p.getText());
+    	assertNull(p.nextToken());
+    }
+
+    public void testTrivialObject() throws IOException
+    {
+    	byte[] data = _smileDoc("{\"abc\":13}");
+    	SmileParser p = _parser(data);
+    	assertNull(p.getCurrentToken());
+
+    	assertToken(JsonToken.START_OBJECT, p.nextToken());
+    	assertToken(JsonToken.FIELD_NAME, p.nextToken());
+    	assertEquals("abc", p.getCurrentName());
+    	assertEquals("abc", p.getText());
+    	assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+    	assertEquals(13, p.getIntValue());    	
+    	assertToken(JsonToken.END_OBJECT, p.nextToken());
+    }
     
     public void testSimpleObject() throws IOException
     {
diff --git a/src/smile/test/org/codehaus/jackson/smile/TestSmileParserSymbolHandling.java b/src/smile/test/org/codehaus/jackson/smile/TestSmileParserSymbolHandling.java
new file mode 100644
index 0000000..58ae00a
--- /dev/null
+++ b/src/smile/test/org/codehaus/jackson/smile/TestSmileParserSymbolHandling.java
@@ -0,0 +1,75 @@
+package org.codehaus.jackson.smile;
+
+import java.io.*;
+
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.sym.BytesToNameCanonicalizer;
+
+/**
+ * Unit tests for verifying that symbol handling works as planned, including
+ * efficient reuse of names encountered during parsing.
+ */
+public class TestSmileParserSymbolHandling
+	extends SmileTestBase
+{
+	public void testSimple() throws IOException
+    {
+		final String STR1 = "a";
+		
+    	byte[] data = _smileDoc("{ "+quote(STR1)+":1, \"foobar\":2, \"longername\":3 }");
+    	SmileFactory f = new SmileFactory();
+    	SmileParser p = _parser(f, data);
+    	final BytesToNameCanonicalizer symbols1 = p._symbols;
+    	assertEquals(0, symbols1.size());
+
+    	
+    	
+    	assertEquals(JsonToken.START_OBJECT, p.nextToken());
+    	assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+    	// field names are interned:
+    	assertSame(STR1, p.getCurrentName());
+    	assertEquals(1, symbols1.size());
+    	assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+    	assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+    	assertSame("foobar", p.getCurrentName());
+    	assertEquals(2, symbols1.size());
+    	assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+    	assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+    	assertSame("longername", p.getCurrentName());
+    	assertEquals(3, symbols1.size());
+    	assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+    	assertEquals(JsonToken.END_OBJECT, p.nextToken());
+    	assertNull(p.nextToken());
+    	assertEquals(3, symbols1.size());
+        p.close();
+
+        // but let's verify that symbol table gets reused properly
+    	p = _parser(f, data);
+    	BytesToNameCanonicalizer symbols2 = p._symbols;
+    	// symbol tables are not reused, but contents are:
+    	assertNotSame(symbols1, symbols2);
+    	assertEquals(3, symbols2.size());
+
+    	assertEquals(JsonToken.START_OBJECT, p.nextToken());
+    	assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+    	// field names are interned:
+    	assertSame(STR1, p.getCurrentName());
+    	assertEquals(3, symbols2.size());
+    	assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+    	assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+    	assertSame("foobar", p.getCurrentName());
+    	assertEquals(3, symbols2.size());
+    	assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+    	assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+    	assertSame("longername", p.getCurrentName());
+    	assertEquals(3, symbols2.size());
+    	assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+    	assertEquals(JsonToken.END_OBJECT, p.nextToken());
+    	assertNull(p.nextToken());
+    	assertEquals(3, symbols2.size());
+        p.close();
+
+        assertEquals(3, symbols2.size());
+    	p.close();
+    }
+}