Merge branch '2.5' into 2.6
diff --git a/README.md b/README.md
index 44a6b42..b76d839 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
 
 Project contains versions 2.0 and above: source code for earlier (1.x) versions is available from [Codehaus](http://jackson.codehaus.org) SVN repository.
 
-[![Build Status](https://travis-ci.org/FasterXML/jackson-core.png?branch=master)](https://travis-ci.org/FasterXML/jackson-core) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.jackson.core/jackson-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.jackson.core/jackson-core)
+[![Build Status](https://travis-ci.org/FasterXML/jackson-core.svg?branch=master)](https://travis-ci.org/FasterXML/jackson-core) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.jackson.core/jackson-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.jackson.core/jackson-core)
 
 # Get it!
 
@@ -38,7 +38,7 @@
 </dependency>
 ```
 
-or download jars from Maven repository or links on [Wiki]](../../wiki).
+or download jars from Maven repository or links on [Wiki](../../wiki).
 Core jar is a functional OSGi bundle, with proper import/export declarations.
 
 Package has no external dependencies, except for testing (which uses `JUnit`).
diff --git a/pom.xml b/pom.xml
index 6510db2..f58af18 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,18 +3,18 @@
   <parent>
     <groupId>com.fasterxml.jackson</groupId>
     <artifactId>jackson-parent</artifactId>
-    <version>2.5.1</version>
+    <version>2.6.2</version>
   </parent>
 
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <name>Jackson-core</name>
-  <version>2.5.6-SNAPSHOT</version>
+  <version>2.6.6-SNAPSHOT</version>
   <packaging>bundle</packaging>
-  <description>Core Jackson abstractions, basic JSON streaming API implementation
-  </description>
+  <description>Core Jackson abstractions, basic JSON streaming API implementation</description>
+  <inceptionYear>2008</inceptionYear>
 
-  <url>https://github.com/FasterXML/jackson</url>
+  <url>https://github.com/FasterXML/jackson-core</url>
   <scm>
     <connection>scm:git:git@github.com:FasterXML/jackson-core.git</connection>
     <developerConnection>scm:git:git@github.com:FasterXML/jackson-core.git</developerConnection>
@@ -65,6 +65,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
+        <version>${version.plugin.surefire}</version>
         <configuration>
           <redirectTestOutputToFile>${surefire.redirectTestOutputToFile}</redirectTestOutputToFile>
           <excludes>
diff --git a/release-notes/CREDITS b/release-notes/CREDITS
index ef23c1f..4e01f69 100644
--- a/release-notes/CREDITS
+++ b/release-notes/CREDITS
@@ -54,3 +54,19 @@
   * Reported #184: WRITE_NUMBERS_AS_STRINGS disables WRITE_BIGDECIMAL_AS_PLAIN
    (2.4.6 / 2.5.2)
 
+Masaru Hasegawa (masaruh@github):
+  * Reported, contributed fix for#182: Inconsistent TextBuffer#getTextBuffer behavior
+   (2.6.0)
+
+Ruediger Moeller (RuedigerMoeller@github)
+  * Requested #195: Add `JsonGenerator.getOutputBuffered()` to find out amount of content buffered,
+     not yet flushed.
+   (2.6.0)
+
+Florian Schoppmann (fschopp@github@github)
+  * Reported #207: `ArrayIndexOutOfBoundsException` in `ByteQuadsCanonicalizer`
+   (2.6.1)
+
+Iskren Ivov Chernev (ichernev@github)
+  * Reported #213: Parser is sometimes wrong when using CANONICALIZE_FIELD_NAMES
+   (2.6.2)
diff --git a/release-notes/VERSION b/release-notes/VERSION
index f381045..00053a7 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -14,6 +14,58 @@
 === Releases ===
 ------------------------------------------------------------------------
 
+2.6.6 (not yet released)
+
+#248: VersionUtil.versionFor() unexpectedly return null instead of Version.unknownVersion()
+ (reported by sammyhk@github)
+
+2.6.5 (19-Jan-2015)
+2.6.4 (07-Dec-2015)
+
+No changes since 2.6.3.
+
+2.6.3 (12-Oct-2015)
+
+#220: Problem with `JsonParser.nextFieldName(SerializableString)` for byte-backed parser
+
+2.6.2 (14-Sep-2015)
+
+#213: Parser is sometimes wrong when using CANONICALIZE_FIELD_NAMES
+ (reported by ichernev@github)
+#216: ArrayIndexOutOfBoundsException: 128 when repeatedly serializing to a byte array
+ (reported by geekbeast@github)
+
+2.6.1 (09-Aug-2015)
+
+#207: `ArrayIndexOutOfBoundsException` in `ByteQuadsCanonicalizer`
+ (reported by Florian S, fschopp@github)
+
+2.6.0 (17-Jul-2015)
+
+#137: Allow filtering content read via `JsonParser` by specifying `JsonPointer`;
+  uses new class `com.fasterxml.jackson.core.filter.FilteringParserDelegate`
+  (and related, `TokenFilter`)
+#177: Add a check so `JsonGenerator.writeString()` won't work if `writeFieldName()` expected.
+#182: Inconsistent TextBuffer#getTextBuffer behavior
+ (contributed by Masaru H)
+#185: Allow filtering content written via `JsonGenerator` by specifying `JsonPointer`;
+  uses new class `com.fasterxml.jackson.core.filter.FilteringGeneratorDelegate`
+  (and related, `TokenFilter`)
+#188: `JsonParser.getValueAsString()` should return field name for `JsonToken.FIELD_NAME`, not `null`
+#189: Add `JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING` (default: true), which may
+  be disabled to prevent use of ThreadLocal-based buffer recyling.
+ (suggested by soldierkam@github)
+#195: Add `JsonGenerator.getOutputBuffered()` to find out amount of content buffered,
+  not yet flushed.
+ (requested by Ruediger M)
+#196: Add support for `FormatFeature` extension, for format-specifc Enum-backed
+  parser/generator options
+- Minor improvement to construction of "default PrettyPrinter": now overridable by data format
+  modules
+- Implement a new yet more optimized symbol table for byte-backed parsers
+- Add `JsonParser.Feature.IGNORE_UNDEFINED`, useful for data formats like protobuf
+- Optimize writing of String names (remove intermediate copy; with JDK7 no speed benefit)
+
 2.5.5 (07-Dec-2015)
 
 #220: Problem with `JsonParser.nextFieldName(SerializableString)` for byte-backed parser
@@ -63,7 +115,7 @@
 - Added `JsonParser.hasTokenId(id)` convenience method
 - Added `JsonParser.nextFieldName()` (no args)
 
-2.4.6 (not released yet)
+2.4.6 (23-Apr-2015)
 
 #184: WRITE_NUMBERS_AS_STRINGS disables WRITE_BIGDECIMAL_AS_PLAIN
  (reported by Derek C)
@@ -108,6 +160,13 @@
   of `String` input as well.
 - Refactor `BufferRecycler` to eliminate helper enums
 
+2.3.5 (13-Jan-2015)
+
+#152: Exception for property names longer than 256k
+#173: An exception is thrown for a valid JsonPointer expression
+#176: `JsonPointer` should not consider "00" to be valid index
+
+2.3.4 (17-Jul-2014)
 2.3.3 (10-Apr-2014)
 
 No changes since 2.3.2.
diff --git a/src/main/java/com/fasterxml/jackson/core/FormatFeature.java b/src/main/java/com/fasterxml/jackson/core/FormatFeature.java
new file mode 100644
index 0000000..e775e4c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/FormatFeature.java
@@ -0,0 +1,33 @@
+package com.fasterxml.jackson.core;
+
+/**
+ * Marker interface that is to be implemented by data format - specific features.
+ * Interface used since Java Enums can not extend classes or other Enums, but
+ * they can implement interfaces; and as such we may be able to use limited
+ * amount of generic functionality.
+ *<p>
+ * Note that this type is only implemented by non-JSON formats:
+ * types {@link JsonParser.Feature} and {@link JsonGenerator.Feature} do NOT
+ * implement it. This is to make it easier to avoid ambiguity with method
+ * calls.
+ * 
+ * @since 2.6 (to be fully used in 2.7 and beyond)
+ */
+public interface FormatFeature
+{
+    /**
+     * Accessor for checking whether this feature is enabled by default.
+     */
+    public boolean enabledByDefault();
+    
+    /**
+     * Returns bit mask for this feature instance; must be a single bit,
+     * that is of form <code>(1 &lt;&lt; N)</code>
+     */
+    public int getMask();
+
+    /**
+     * Convenience method for checking whether feature is enabled in given bitmask
+     */
+    public boolean enabledIn(int flags);
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
index 730413f..08037aa 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
@@ -12,7 +12,7 @@
 import com.fasterxml.jackson.core.format.MatchStrength;
 import com.fasterxml.jackson.core.io.*;
 import com.fasterxml.jackson.core.json.*;
-import com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer;
+import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
 import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
 import com.fasterxml.jackson.core.util.BufferRecycler;
 import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
@@ -42,10 +42,7 @@
     implements Versioned,
         java.io.Serializable // since 2.1 (for Android, mostly)
 {
-    /**
-     * Computed for Jackson 2.4.0 release
-     */
-    private static final long serialVersionUID = 3306684576057132431L;
+    private static final long serialVersionUID = 1; // since 2.6.0
 
     /*
     /**********************************************************
@@ -104,6 +101,21 @@
          */
         FAIL_ON_SYMBOL_HASH_OVERFLOW(true),
 
+        /**
+         * Feature that determines whether we will use {@link BufferRecycler} with
+         * {@link ThreadLocal} and {@link SoftReference}, for efficient reuse of
+         * underlying input/output buffers.
+         * This usually makes sense on normal J2SE/J2EE server-side processing;
+         * but may not make sense on platforms where {@link SoftReference} handling
+         * is broken (like Android), or if there are retention issues due to
+         * {@link ThreadLocal} (see
+         * <a href="https://github.com/FasterXML/jackson-core/issues/189">Issue #189</a>
+         * for a possible case)
+         *
+         * @since 2.6
+         */
+        USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING(true)
+        
         ;
 
         /**
@@ -188,8 +200,20 @@
      *<p>
      * TODO: should clean up this; looks messy having 2 alternatives
      * with not very clear differences.
+     * 
+     * @since 2.6.0
      */
-    protected final transient BytesToNameCanonicalizer _rootByteSymbols = BytesToNameCanonicalizer.createRoot();
+    protected final transient ByteQuadsCanonicalizer _byteSymbolCanonicalizer = ByteQuadsCanonicalizer.createRoot();
+
+    /**
+     * Earlier byte-based symbol table; replaced with 2.6 with a new implementation.
+     * Left in for version 2.6.0: will be removed in 2.7 or later.
+     *
+     * @deprecated Since 2.6.0, only use {@link #_byteSymbolCanonicalizer}
+     */
+    @Deprecated
+    protected final transient com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer _rootByteSymbols
+        = com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer.createRoot();
 
     /*
     /**********************************************************
@@ -389,10 +413,31 @@
      * @since 2.4
      */
     public boolean canUseCharArrays() { return true; }
-    
+
+    /**
+     * Method for accessing kind of {@link FormatFeature} that a parser
+     * {@link JsonParser} produced by this factory would accept, if any;
+     * <code>null</code> returned if none.
+     *
+     * @since 2.6
+     */
+    public Class<? extends FormatFeature> getFormatReadFeatureType() {
+        return null;
+    }
+
+    /**
+     * Method for accessing kind of {@link FormatFeature} that a parser
+     * {@link JsonGenerator} produced by this factory would accept, if any;
+     * <code>null</code> returned if none.
+     *
+     * @since 2.6
+     */
+    public Class<? extends FormatFeature> getFormatWriteFeatureType() {
+        return null;
+    }
     /*
     /**********************************************************
-    /* Format detection functionality (since 1.8)
+    /* Format detection functionality
     /**********************************************************
      */
 
@@ -430,6 +475,10 @@
         return null;
     }
 
+    /**
+     * Convenience method for trying to determine whether input via given accessor
+     * is of format type supported by this factory.
+     */
     public MatchStrength hasFormat(InputAccessor acc) throws IOException
     {
         // since we can't keep this abstract, only implement for "vanilla" instance
@@ -1189,7 +1238,7 @@
     protected JsonParser _createParser(InputStream in, IOContext ctxt) throws IOException {
         // As per [JACKSON-259], may want to fully disable canonicalization:
         return new ByteSourceJsonBootstrapper(ctxt, in).constructParser(_parserFeatures,
-                _objectCodec, _rootByteSymbols, _rootCharSymbols, _factoryFeatures);
+                _objectCodec, _byteSymbolCanonicalizer, _rootCharSymbols, _factoryFeatures);
     }
 
     /**
@@ -1236,7 +1285,7 @@
     protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException
     {
         return new ByteSourceJsonBootstrapper(ctxt, data, offset, len).constructParser(_parserFeatures,
-                _objectCodec, _rootByteSymbols, _rootCharSymbols, _factoryFeatures);
+                _objectCodec, _byteSymbolCanonicalizer, _rootCharSymbols, _factoryFeatures);
     }
 
     /*
@@ -1375,12 +1424,22 @@
      */
     public BufferRecycler _getBufferRecycler()
     {
-        SoftReference<BufferRecycler> ref = _recyclerRef.get();
-        BufferRecycler br = (ref == null) ? null : ref.get();
+        BufferRecycler br;
 
-        if (br == null) {
+        /* 23-Apr-2015, tatu: Let's allow disabling of buffer recycling
+         *   scheme, for cases where it is considered harmful (possibly
+         *   on Android, for example)
+         */
+        if (isEnabled(Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING)) {
+            SoftReference<BufferRecycler> ref = _recyclerRef.get();
+            br = (ref == null) ? null : ref.get();
+    
+            if (br == null) {
+                br = new BufferRecycler();
+                _recyclerRef.set(new SoftReference<BufferRecycler>(br));
+            }
+        } else {
             br = new BufferRecycler();
-            _recyclerRef.set(new SoftReference<BufferRecycler>(br));
         }
         return br;
     }
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
index 9167f3c..7d57513 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
@@ -4,19 +4,7 @@
  */
 package com.fasterxml.jackson.core;
 
-import static com.fasterxml.jackson.core.JsonTokenId.ID_EMBEDDED_OBJECT;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_END_ARRAY;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_END_OBJECT;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_FALSE;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_FIELD_NAME;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_NOT_AVAILABLE;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_NULL;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_NUMBER_FLOAT;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_NUMBER_INT;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_START_ARRAY;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_START_OBJECT;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_STRING;
-import static com.fasterxml.jackson.core.JsonTokenId.ID_TRUE;
+import static com.fasterxml.jackson.core.JsonTokenId.*;
 
 import java.io.*;
 import java.math.BigDecimal;
@@ -43,6 +31,8 @@
      * Enumeration that defines all togglable features for generators.
      */
     public enum Feature {
+        // // Low-level I/O / content features
+        
         /**
          * Feature that determines whether generator will automatically
          * close underlying output target that is NOT owned by the
@@ -71,6 +61,21 @@
         AUTO_CLOSE_JSON_CONTENT(true),
 
         /**
+         * Feature that specifies that calls to {@link #flush} will cause
+         * matching <code>flush()</code> to underlying {@link OutputStream}
+         * or {@link Writer}; if disabled this will not be done.
+         * Main reason to disable this feature is to prevent flushing at
+         * generator level, if it is not possible to prevent method being
+         * called by other code (like <code>ObjectMapper</code> or third
+         * party libraries).
+         *<p>
+         * Feature is enabled by default.
+         */
+        FLUSH_PASSED_TO_STREAM(true),
+
+        // // Quoting-related features
+        
+        /**
          * Feature that determines whether JSON Object field names are
          * quoted using double-quotes, as specified by JSON specification
          * or not. Ability to disable quoting was added to support use
@@ -125,19 +130,6 @@
         WRITE_BIGDECIMAL_AS_PLAIN(false),
         
         /**
-         * Feature that specifies that calls to {@link #flush} will cause
-         * matching <code>flush()</code> to underlying {@link OutputStream}
-         * or {@link Writer}; if disabled this will not be done.
-         * Main reason to disable this feature is to prevent flushing at
-         * generator level, if it is not possible to prevent method being
-         * called by other code (like <code>ObjectMapper</code> or third
-         * party libraries).
-         *<p>
-         * Feature is enabled by default.
-         */
-        FLUSH_PASSED_TO_STREAM(true),
-        
-        /**
          * Feature that specifies that all characters beyond 7-bit ASCII
          * range (i.e. code points of 128 and above) need to be output
          * using format-specific escapes (for JSON, backslash escapes),
@@ -154,6 +146,8 @@
          */
         ESCAPE_NON_ASCII(false),
 
+        // // Schema/Validity support features
+
         /**
          * Feature that determines whether {@link JsonGenerator} will explicitly
          * check that no duplicate JSON Object field names are written.
@@ -170,7 +164,7 @@
          * @since 2.3
          */
         STRICT_DUPLICATE_DETECTION(false),
-
+        
         /**
          * Feature that determines what to do if the underlying data format requires knowledge
          * of all properties to output, and if no definition is found for a property that
@@ -270,53 +264,6 @@
     @Override
     public abstract Version version();
 
-    /**
-     * Method that can be used to get access to object that is used
-     * as target for generated output; this is usually either
-     * {@link OutputStream} or {@link Writer}, depending on what
-     * generator was constructed with.
-     * Note that returned value may be null in some cases; including
-     * case where implementation does not want to exposed raw
-     * source to caller.
-     * In cases where output has been decorated, object returned here
-     * is the decorated version; this allows some level of interaction
-     * between users of generator and decorator object.
-     *<p>
-     * In general use of this accessor should be considered as
-     * "last effort", i.e. only used if no other mechanism is applicable.
-     */
-    public Object getOutputTarget() {
-        return null;
-    }
-
-    /**
-     * Helper method, usually equivalent to:
-     *<code>
-     *   getOutputContext().getCurrentValue();
-     *</code>
-     * 
-     * @since 2.5
-     */
-    public Object getCurrentValue() {
-        JsonStreamContext ctxt = getOutputContext();
-        return (ctxt == null) ? null : ctxt.getCurrentValue();
-    }
-
-    /**
-     * Helper method, usually equivalent to:
-     *<code>
-     *   getOutputContext().setCurrentValue(v);
-     *</code>
-     * 
-     * @since 2.5
-     */
-    public void setCurrentValue(Object v) {
-        JsonStreamContext ctxt = getOutputContext();
-        if (ctxt != null) {
-            ctxt.setCurrentValue(v);
-        }
-    }
-
     /*
     /**********************************************************
     /* Public API, Feature configuration
@@ -358,9 +305,10 @@
 
 
     /**
-     * Bulk access method for getting state of all standard {@link Feature}s.
+     * Bulk access method for getting state of all standard (non-dataformat-specific)
+     * {@link JsonGenerator.Feature}s.
      * 
-     * @return Bit mask that defines current states of all standard {@link Feature}s.
+     * @return Bit mask that defines current states of all standard {@link JsonGenerator.Feature}s.
      * 
      * @since 2.3
      */
@@ -378,6 +326,60 @@
      */
     public abstract JsonGenerator setFeatureMask(int values);
 
+    /**
+     * Bulk set method for (re)setting states of features specified by <code>mask</code>.
+     * Functionally equivalent to
+     *<code>
+     *    int oldState = getFeatureMask();
+     *    int newState = (oldState &amp; ~mask) | (values &amp; mask);
+     *    setFeatureMask(newState);
+     *</code>
+     * 
+     * @param values Bit mask of set/clear state for features to change
+     * @param mask Bit mask of features to change
+     * 
+     * @since 2.6
+     */
+    public JsonGenerator overrideStdFeatures(int values, int mask) {
+        int oldState = getFeatureMask();
+        int newState = (oldState & ~mask) | (values & mask);
+        return setFeatureMask(newState);
+    }
+
+    /**
+     * Bulk access method for getting state of all {@link FormatFeature}s, format-specific
+     * on/off configuration settings.
+     * 
+     * @return Bit mask that defines current states of all standard {@link FormatFeature}s.
+     * 
+     * @since 2.6
+     */
+    public int getFormatFeatures() {
+        return 0;
+    }
+    
+    /**
+     * Bulk set method for (re)setting states of {@link FormatFeature}s,
+     * by specifying values (set / clear) along with a mask, to determine
+     * which features to change, if any.
+     *<p>
+     * Default implementation will simply throw an exception to indicate that
+     * the generator implementation does not support any {@link FormatFeature}s.
+     * 
+     * @param values Bit mask of set/clear state for features to change
+     * @param mask Bit mask of features to change
+     * 
+     * @since 2.6
+     */
+    public JsonGenerator overrideFormatFeatures(int values, int mask) {
+        throw new IllegalArgumentException("No FormatFeatures defined for generator of type "+getClass().getName());
+        /*
+        int oldState = getFeatureMask();
+        int newState = (oldState & ~mask) | (values & mask);
+        return setFeatureMask(newState);
+        */
+    }
+    
     /*
     /**********************************************************
     /* Public API, Schema configuration
@@ -521,6 +523,86 @@
 
     /*
     /**********************************************************
+    /* Public API, output state access
+    /**********************************************************
+     */
+    
+    /**
+     * Method that can be used to get access to object that is used
+     * as target for generated output; this is usually either
+     * {@link OutputStream} or {@link Writer}, depending on what
+     * generator was constructed with.
+     * Note that returned value may be null in some cases; including
+     * case where implementation does not want to exposed raw
+     * source to caller.
+     * In cases where output has been decorated, object returned here
+     * is the decorated version; this allows some level of interaction
+     * between users of generator and decorator object.
+     *<p>
+     * In general use of this accessor should be considered as
+     * "last effort", i.e. only used if no other mechanism is applicable.
+     */
+    public Object getOutputTarget() {
+        return null;
+    }
+
+    /**
+     * Method for verifying amount of content that is buffered by generator
+     * but not yet flushed to the underlying target (stream, writer),
+     * in units (byte, char) that the generator implementation uses for buffering;
+     * or -1 if this information is not available.
+     * Unit used is often the same as the unit of underlying target (that is,
+     * `byte` for {@link java.io.OutputStream}, `char` for {@link java.io.Writer}),
+     * but may differ if buffering is done before encoding.
+     * Default JSON-backed implementations do use matching units.
+     *<p>
+     * Note: non-JSON implementations will be retrofitted for 2.6 and beyond;
+     * please report if you see -1 (missing override)
+     *
+     * @return Amount of content buffered in internal units, if amount known and
+     *    accessible; -1 if not accessible.
+     *
+     * @since 2.6
+     */
+    public int getOutputBuffered() {
+        return -1;
+    }
+
+    /**
+     * Helper method, usually equivalent to:
+     *<code>
+     *   getOutputContext().getCurrentValue();
+     *</code>
+     *<p>
+     * Note that "current value" is NOT populated (or used) by Streaming parser;
+     * it is only used by higher-level data-binding functionality.
+     * The reason it is included here is that it can be stored and accessed hierarchically,
+     * and gets passed through data-binding.
+     * 
+     * @since 2.5
+     */
+    public Object getCurrentValue() {
+        JsonStreamContext ctxt = getOutputContext();
+        return (ctxt == null) ? null : ctxt.getCurrentValue();
+    }
+
+    /**
+     * Helper method, usually equivalent to:
+     *<code>
+     *   getOutputContext().setCurrentValue(v);
+     *</code>
+     * 
+     * @since 2.5
+     */
+    public void setCurrentValue(Object v) {
+        JsonStreamContext ctxt = getOutputContext();
+        if (ctxt != null) {
+            ctxt.setCurrentValue(v);
+        }
+    }
+    
+    /*
+    /**********************************************************
     /* Public API, capability introspection methods
     /**********************************************************
      */
@@ -644,8 +726,7 @@
      * are allowed: meaning everywhere except for when
      * a field name is expected.
      */
-    public abstract void writeStartObject()
-        throws IOException;
+    public abstract void writeStartObject() throws IOException;
 
     /**
      * Method for writing closing marker of a JSON Object value
@@ -657,8 +738,7 @@
      * complete value, or START-OBJECT marker (see JSON specification
      * for more details).
      */
-    public abstract void writeEndObject()
-        throws IOException;
+    public abstract void writeEndObject() throws IOException;
 
     /**
      * Method for writing a field name (JSON String surrounded by
@@ -669,8 +749,7 @@
      * JSON specification for details), when field name is expected
      * (field names alternate with values).
      */
-    public abstract void writeFieldName(String name)
-        throws IOException;
+    public abstract void writeFieldName(String name) throws IOException;
 
     /**
      * Method similar to {@link #writeFieldName(String)}, main difference
@@ -683,8 +762,7 @@
      * serialized String; implementations are strongly encouraged to make
      * use of more efficient methods argument object has.
      */
-    public abstract void writeFieldName(SerializableString name)
-        throws IOException;
+    public abstract void writeFieldName(SerializableString name) throws IOException;
 
     /*
     /**********************************************************
@@ -699,8 +777,7 @@
      * surrounded in double quotes, and contents will be properly
      * escaped as required by JSON specification.
      */
-    public abstract void writeString(String text)
-        throws IOException;
+    public abstract void writeString(String text) throws IOException;
 
     /**
      * Method for outputting a String value. Depending on context
@@ -709,8 +786,7 @@
      * surrounded in double quotes, and contents will be properly
      * escaped as required by JSON specification.
      */
-    public abstract void writeString(char[] text, int offset, int len)
-        throws IOException;
+    public abstract void writeString(char[] text, int offset, int len) throws IOException;
 
     /**
      * Method similar to {@link #writeString(String)}, but that takes
@@ -722,8 +798,7 @@
      * sub-classes should override it with more efficient implementation
      * if possible.
      */
-    public abstract void writeString(SerializableString text)
-        throws IOException;
+    public abstract void writeString(SerializableString text) throws IOException;
 
     /**
      * Method similar to {@link #writeString(String)} but that takes as
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonParser.java b/src/main/java/com/fasterxml/jackson/core/JsonParser.java
index 1241164..0af1d43 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonParser.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonParser.java
@@ -185,7 +185,31 @@
           * @since 2.3
           */
          STRICT_DUPLICATE_DETECTION(false),
-            ;
+
+         /**
+          * Feature that determines what to do if the underlying data format requires knowledge
+          * of all properties to decode (usually via a Schema), and if no definition is
+          * found for a property that input content contains.
+          * Typically most textual data formats do NOT require schema information (although
+          * some do, such as CSV), whereas many binary data formats do require definitions
+          * (such as Avro, protobuf), although not all (Smile, CBOR, BSON and MessagePack do not).
+          * Further note that some formats that do require schema information will not be able
+          * to ignore undefined properties: for example, Avro is fully positional and there is
+          * no possibility of undefined data. This leaves formats like Protobuf that have identifiers
+          * that may or may not map; and as such Protobuf format does make use of this feature.
+          *<p>
+          * Note that support for this feature is implemented by individual data format
+          * module, if (and only if) it makes sense for the format in question. For JSON,
+          * for example, this feature has no effect as properties need not be pre-defined.
+          *<p>
+          * Feature is disabled by default, meaning that if the underlying data format
+          * requires knowledge of all properties to output, attempts to read an unknown
+          * property will result in a {@link JsonProcessingException}
+          *
+          * @since 2.6
+          */
+         IGNORE_UNDEFINED(false)
+         ;
 
         /**
          * Whether feature is enabled or disabled by default.
@@ -282,6 +306,11 @@
      *<code>
      *   getParsingContext().getCurrentValue();
      *</code>
+     *<p>
+     * Note that "current value" is NOT populated (or used) by Streaming parser;
+     * it is only used by higher-level data-binding functionality.
+     * The reason it is included here is that it can be stored and accessed hierarchically,
+     * and gets passed through data-binding.
      * 
      * @since 2.5
      */
@@ -496,16 +525,67 @@
 
     /**
      * Bulk set method for (re)setting states of all standard {@link Feature}s
-     *
-     * @since 2.3
      * 
      * @return This parser object, to allow chaining of calls
+     * 
+     * @since 2.3
      */
     public JsonParser setFeatureMask(int mask) {
         _features = mask;
         return this;
     }
+
+    /**
+     * Bulk set method for (re)setting states of features specified by <code>mask</code>.
+     * Functionally equivalent to
+     *<code>
+     *    int oldState = getFeatureMask();
+     *    int newState = (oldState &amp; ~mask) | (values &amp; mask);
+     *    setFeatureMask(newState);
+     *</code>
+     * 
+     * @param values Bit mask of set/clear state for features to change
+     * @param mask Bit mask of features to change
+     * 
+     * @since 2.6
+     */
+    public JsonParser overrideStdFeatures(int values, int mask) {
+        _features = (_features & ~mask) | (values & mask);
+        return this;
+    }
+
+    /**
+     * Bulk access method for getting state of all {@link FormatFeature}s, format-specific
+     * on/off configuration settings.
+     * 
+     * @return Bit mask that defines current states of all standard {@link FormatFeature}s.
+     * 
+     * @since 2.6
+     */
+    public int getFormatFeatures() {
+        return 0;
+    }
     
+    /**
+     * Bulk set method for (re)setting states of {@link FormatFeature}s,
+     * by specifying values (set / clear) along with a mask, to determine
+     * which features to change, if any.
+     *<p>
+     * Default implementation will simply throw an exception to indicate that
+     * the generator implementation does not support any {@link FormatFeature}s.
+     * 
+     * @param values Bit mask of set/clear state for features to change
+     * @param mask Bit mask of features to change
+     * 
+     * @since 2.6
+     */
+    public JsonParser overrideFormatFeatures(int values, int mask) {
+        throw new IllegalArgumentException("No FormatFeatures defined for parser of type "+getClass().getName());
+        /*
+        _formatFeatures = (_formatFeatures & ~mask) | (values & mask);
+        */
+    }
+
     /*
     /**********************************************************
     /* Public API, traversal
@@ -568,8 +648,7 @@
      * @since 2.5
      */
     public String nextFieldName() throws IOException, JsonParseException {
-        return (nextToken() == JsonToken.FIELD_NAME)
-                ? getCurrentName() : null;
+        return (nextToken() == JsonToken.FIELD_NAME) ? getCurrentName() : null;
     }
 
     /**
@@ -727,6 +806,21 @@
     public abstract boolean hasTokenId(int id);
 
     /**
+     * Method that is functionally equivalent to:
+     *<code>
+     *  return getCurrentTokenId() == id
+     *</code>
+     * but may be more efficiently implemented.
+     *<p>
+     * Note that no traversal or conversion is performed; so in some
+     * cases calling method like {@link #isExpectedStartArrayToken()}
+     * is necessary instead.
+     *
+     * @since 2.6
+     */
+    public abstract boolean hasToken(JsonToken t);
+    
+    /**
      * Method that can be called to get the name associated with
      * the current token: for {@link JsonToken#FIELD_NAME}s it will
      * be the same as what {@link #getText} returns;
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonPointer.java b/src/main/java/com/fasterxml/jackson/core/JsonPointer.java
index 59ef9ae..ddbab6d 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonPointer.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonPointer.java
@@ -208,10 +208,10 @@
     }
     
     public JsonPointer matchProperty(String name) {
-        if (_nextSegment == null || !_matchingPropertyName.equals(name)) {
-            return null;
+        if ((_nextSegment != null) && _matchingPropertyName.equals(name)) {
+            return _nextSegment;
         }
-        return _nextSegment;
+        return null;
     }
 
     /**
@@ -225,6 +225,16 @@
     }
 
     /**
+     * @since 2.6
+     */
+    public JsonPointer matchElement(int index) {
+        if ((index != _matchingElementIndex) || (index < 0)) {
+            return null;
+        }
+        return _nextSegment;
+    }
+
+    /**
      * Accessor for getting a "sub-pointer", instance where current segment
      * has been removed and pointer includes rest of segments.
      * For matching state, will return null.
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java
index 9e0e31f..5e8ff17 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java
@@ -108,9 +108,11 @@
      * Method for accessing currently active value being used by data-binding
      * (as the source of streaming data to write, or destination of data being
      * read), at this level in hierarchy.
-     * The value may not exist or be available due to various limitations (at
-     * least during reading of data, as target value object may not have yet
-     * been constructed).
+     *<p>
+     * Note that "current value" is NOT populated (or used) by Streaming parser or generator;
+     * it is only used by higher-level data-binding functionality.
+     * The reason it is included here is that it can be stored and accessed hierarchically,
+     * and gets passed through data-binding.
      * 
      * @return Currently active value, if one has been assigned.
      * 
diff --git a/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java b/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java
index 17716ae..8246453 100644
--- a/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java
+++ b/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java
@@ -57,7 +57,7 @@
      * to output a curly bracket as well, but can surround that
      * with other (white-space) decoration.
      */
-    void writeStartObject(JsonGenerator jg)
+    void writeStartObject(JsonGenerator gen)
         throws IOException, JsonGenerationException;
 
     /**
@@ -73,7 +73,7 @@
      * @param nrOfEntries Number of direct members of the array that
      *   have been output
      */
-    void writeEndObject(JsonGenerator jg, int nrOfEntries)
+    void writeEndObject(JsonGenerator gen, int nrOfEntries)
         throws IOException, JsonGenerationException;
 
     /**
@@ -85,7 +85,7 @@
      * to output a comma as well, but can surround that with other
      * (white-space) decoration.
      */
-    void writeObjectEntrySeparator(JsonGenerator jg)
+    void writeObjectEntrySeparator(JsonGenerator gen)
         throws IOException, JsonGenerationException;
 
     /**
@@ -97,7 +97,7 @@
      * to output a colon as well, but can surround that with other
      * (white-space) decoration.
      */
-    void writeObjectFieldValueSeparator(JsonGenerator jg)
+    void writeObjectFieldValueSeparator(JsonGenerator gen)
         throws IOException, JsonGenerationException;
 
     // // // Array handling
@@ -112,7 +112,7 @@
      * to output a bracket as well, but can surround that
      * with other (white-space) decoration.
      */
-    void writeStartArray(JsonGenerator jg)
+    void writeStartArray(JsonGenerator gen)
         throws IOException, JsonGenerationException;
 
     /**
@@ -128,7 +128,7 @@
      * @param nrOfValues Number of direct members of the array that
      *   have been output
      */
-    void writeEndArray(JsonGenerator jg, int nrOfValues)
+    void writeEndArray(JsonGenerator gen, int nrOfValues)
         throws IOException, JsonGenerationException;
 
     /**
@@ -140,7 +140,7 @@
      * to output a comma as well, but can surround that with other
      * (white-space) decoration.
      */
-    void writeArrayValueSeparator(JsonGenerator jg)
+    void writeArrayValueSeparator(JsonGenerator gen)
         throws IOException, JsonGenerationException;
 
     /*
@@ -159,7 +159,7 @@
      * Default handling does not output anything, but pretty-printer
      * is free to add any white space decoration.
      */
-    void beforeArrayValues(JsonGenerator jg)
+    void beforeArrayValues(JsonGenerator gen)
         throws IOException, JsonGenerationException;
 
     /**
@@ -171,7 +171,7 @@
      * Default handling does not output anything, but pretty-printer
      * is free to add any white space decoration.
      */
-    void beforeObjectEntries(JsonGenerator jg)
+    void beforeObjectEntries(JsonGenerator gen)
         throws IOException, JsonGenerationException;
 }
 
diff --git a/src/main/java/com/fasterxml/jackson/core/Version.java b/src/main/java/com/fasterxml/jackson/core/Version.java
index e57a7c5..884d3d7 100644
--- a/src/main/java/com/fasterxml/jackson/core/Version.java
+++ b/src/main/java/com/fasterxml/jackson/core/Version.java
@@ -66,7 +66,7 @@
 
     public boolean isUknownVersion() { return (this == UNKNOWN_VERSION); }
 
-    // Added in patch 2.5.6 (added for good in 2.7)
+    // Added in patch 2.6.6 (added for good in 2.7)
     public boolean isUnknownVersion() { return (this == UNKNOWN_VERSION); }
 
     public boolean isSnapshot() { return (_snapshotInfo != null && _snapshotInfo.length() > 0); }
diff --git a/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java b/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java
index 68f699b..f806bdf 100644
--- a/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java
+++ b/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java
@@ -31,6 +31,15 @@
             | Feature.STRICT_DUPLICATE_DETECTION.getMask()
             ;
 
+    // // // Constants for validation messages (since 2.6)
+
+    protected final String WRITE_BINARY = "write a binary value";
+    protected final String WRITE_BOOLEAN = "write a boolean value";
+    protected final String WRITE_NULL = "write a null";
+    protected final String WRITE_NUMBER = "write a number";
+    protected final String WRITE_RAW = "write a raw (unencoded) value";
+    protected final String WRITE_STRING = "write a string";
+
     /*
     /**********************************************************
     /* Configuration
@@ -100,8 +109,9 @@
     }
 
     /**
-     * Implemented with detection that tries to find "VERSION.txt" in same
-     * package as the implementation class.
+     * Implemented with standard version number detection algorithm, typically using
+     * a simple generated class, with information extracted from Maven project file
+     * during build.
      */
     @Override public Version version() { return VersionUtil.versionFor(getClass()); }
 
@@ -193,7 +203,7 @@
         if (getPrettyPrinter() != null) {
             return this;
         }
-        return setPrettyPrinter(new DefaultPrettyPrinter());
+        return setPrettyPrinter(_constructDefaultPrettyPrinter());
     }
     
     @Override public JsonGenerator setCodec(ObjectCodec oc) {
@@ -363,6 +373,16 @@
      */
     protected abstract void _verifyValueWrite(String typeMsg) throws IOException;
 
+    /**
+     * Overridable factory method called to instantiate an appropriate {@link PrettyPrinter}
+     * for case of "just use the default one", when {@link #useDefaultPrettyPrinter()} is called.
+     *
+     * @since 2.6
+     */
+    protected PrettyPrinter _constructDefaultPrettyPrinter() {
+        return new DefaultPrettyPrinter();
+    }
+
     /*
     /**********************************************************
     /* UTF-8 related helper method(s)
diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
index a197c38..7c97e2a 100644
--- a/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
+++ b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
@@ -281,8 +281,7 @@
      */
 
     protected ParserBase(IOContext ctxt, int features) {
-        super();
-        _features = features;
+        super(features);
         _ioContext = ctxt;
         _textBuffer = ctxt.constructTextBuffer();
         DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(features)
@@ -654,7 +653,7 @@
     {
         if ((_numTypesValid & NR_INT) == 0) {
             if (_numTypesValid == NR_UNKNOWN) { // not parsed at all
-                _parseNumericValue(NR_INT); // will also check event type
+                return _parseIntValue();
             }
             if ((_numTypesValid & NR_INT) == 0) { // wasn't an int natively?
                 convertNumberToInt(); // let's make it so, if possible
@@ -799,7 +798,38 @@
         }
         _reportError("Current token ("+_currToken+") not numeric, can not use numeric value accessors");
     }
-    
+
+    /**
+     * @since 2.6
+     */
+    protected int _parseIntValue() throws IOException
+    {
+        // Inlined variant of: _parseNumericValue(NR_INT)
+
+        if (_currToken == JsonToken.VALUE_NUMBER_INT) {
+            char[] buf = _textBuffer.getTextBuffer();
+            int offset = _textBuffer.getTextOffset();
+            int len = _intLength;
+            if (_numberNegative) {
+                ++offset;
+            }
+            if (len <= 9) {
+                int i = NumberInput.parseInt(buf, offset, len);
+                if (_numberNegative) {
+                    i = -i;
+                }
+                _numberInt = i;
+                _numTypesValid = NR_INT;
+                return i;
+            }
+        }
+        _parseNumericValue(NR_INT);
+        if ((_numTypesValid & NR_INT) == 0) {
+            convertNumberToInt();
+        }
+        return _numberInt;
+    }
+
     private void _parseSlowFloat(int expType) throws IOException
     {
         /* Nope: floating point. Here we need to be careful to get
diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java
index f030a3f..310ee4e 100644
--- a/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java
+++ b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java
@@ -49,7 +49,7 @@
     /* Minimal generally useful state
     /**********************************************************
      */
-    
+
     /**
      * Last token retrieved via {@link #nextToken}, if any.
      * Null before the first call to <code>nextToken()</code>,
@@ -62,7 +62,7 @@
      * effect when {@link #clearCurrentToken} was called.
      */
     protected JsonToken _lastClearedToken;
-    
+
     /*
     /**********************************************************
     /* Life-cycle
@@ -74,7 +74,7 @@
 
     // NOTE: had base impl in 2.3 and before; but shouldn't
     // public abstract Version version();
-    
+
     /*
     /**********************************************************
     /* Configuration overrides if any
@@ -97,7 +97,7 @@
     @Override public abstract JsonToken nextToken() throws IOException;
     @Override public JsonToken getCurrentToken() { return _currToken; }
 
-    @Override public final int getCurrentTokenId() {
+    @Override public int getCurrentTokenId() {
         final JsonToken t = _currToken;
         return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id();
     }
@@ -111,6 +111,10 @@
         return t.id() == id;
     }
 
+    @Override public boolean hasToken(JsonToken t) {
+        return (_currToken == t);
+    }
+    
     @Override public boolean isExpectedStartArrayToken() { return _currToken == JsonToken.START_ARRAY; }
     @Override public boolean isExpectedStartObjectToken() { return _currToken == JsonToken.START_OBJECT; }
     
@@ -258,9 +262,28 @@
     }
 
     @Override
+    public int getValueAsInt() throws IOException
+    {
+        JsonToken t = _currToken;
+        if (t == JsonToken.VALUE_NUMBER_INT) {
+            return getIntValue();
+        }
+        if (t == JsonToken.VALUE_NUMBER_FLOAT) {
+            return getIntValue();
+        }
+        return getValueAsInt(0);
+    }
+
+    @Override
     public int getValueAsInt(int defaultValue) throws IOException
     {
         JsonToken t = _currToken;
+        if (t == JsonToken.VALUE_NUMBER_INT) {
+            return getIntValue();
+        }
+        if (t == JsonToken.VALUE_NUMBER_FLOAT) {
+            return getIntValue();
+        }
         if (t != null) {
             switch (t.id()) {
             case ID_STRING:
@@ -269,9 +292,6 @@
                     return 0;
                 }
                 return NumberInput.parseAsInt(str, defaultValue);
-            case ID_NUMBER_INT:
-            case ID_NUMBER_FLOAT:
-                return getIntValue();
             case ID_TRUE:
                 return 1;
             case ID_FALSE:
@@ -287,11 +307,30 @@
         }
         return defaultValue;
     }
+
+    @Override
+    public long getValueAsLong() throws IOException
+    {
+        JsonToken t = _currToken;
+        if (t == JsonToken.VALUE_NUMBER_INT) {
+            return getLongValue();
+        }
+        if (t == JsonToken.VALUE_NUMBER_FLOAT) {
+            return getLongValue();
+        }
+        return getValueAsLong(0L);
+    }
     
     @Override
     public long getValueAsLong(long defaultValue) throws IOException
     {
         JsonToken t = _currToken;
+        if (t == JsonToken.VALUE_NUMBER_INT) {
+            return getLongValue();
+        }
+        if (t == JsonToken.VALUE_NUMBER_FLOAT) {
+            return getLongValue();
+        }
         if (t != null) {
             switch (t.id()) {
             case ID_STRING:
@@ -300,9 +339,6 @@
                     return 0L;
                 }
                 return NumberInput.parseAsLong(str, defaultValue);
-            case ID_NUMBER_INT:
-            case ID_NUMBER_FLOAT:
-                return getLongValue();
             case ID_TRUE:
                 return 1L;
             case ID_FALSE:
@@ -349,11 +385,26 @@
     }
 
     @Override
+    public String getValueAsString() throws IOException {
+        if (_currToken == JsonToken.VALUE_STRING) {
+            return getText();
+        }
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return getCurrentName();
+        }
+        return getValueAsString(null);
+    }
+    
+    @Override
     public String getValueAsString(String defaultValue) throws IOException {
-        if (_currToken != JsonToken.VALUE_STRING) {
-            if (_currToken == null || _currToken == JsonToken.VALUE_NULL || !_currToken.isScalarValue()) {
-                return defaultValue;
-            }
+        if (_currToken == JsonToken.VALUE_STRING) {
+            return getText();
+        }
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return getCurrentName();
+        }
+        if (_currToken == null || _currToken == JsonToken.VALUE_NULL || !_currToken.isScalarValue()) {
+            return defaultValue;
         }
         return getText();
     }
@@ -378,42 +429,6 @@
         }
     }
 
-    /**
-     * @param bindex Relative index within base64 character unit; between 0
-     *   and 3 (as unit has exactly 4 characters)
-     *   
-     * @deprecated in 2.2.3; should migrate away
-     */
-    @Deprecated
-    protected void _reportInvalidBase64(Base64Variant b64variant, char ch, int bindex, String msg)
-        throws JsonParseException
-    {
-        String base;
-        if (ch <= INT_SPACE) {
-            base = "Illegal white space character (code 0x"+Integer.toHexString(ch)+") as character #"+(bindex+1)+" of 4-char base64 unit: can only used between units";
-        } else if (b64variant.usesPaddingChar(ch)) {
-            base = "Unexpected padding character ('"+b64variant.getPaddingChar()+"') as character #"+(bindex+1)+" of 4-char base64 unit: padding only legal as 3rd or 4th character";
-        } else if (!Character.isDefined(ch) || Character.isISOControl(ch)) {
-            // Not sure if we can really get here... ? (most illegal xml chars are caught at lower level)
-            base = "Illegal character (code 0x"+Integer.toHexString(ch)+") in base64 content";
-        } else {
-            base = "Illegal character '"+ch+"' (code 0x"+Integer.toHexString(ch)+") in base64 content";
-        }
-        if (msg != null) {
-            base = base + ": " + msg;
-        }
-        throw _constructError(base);
-    }
-
-    /**
-     *   
-     * @deprecated in 2.2.3; should migrate away
-     */
-    @Deprecated
-    protected void _reportBase64EOF() throws JsonParseException {
-        throw _constructError("Unexpected end-of-String in base64 content");
-    }
-
     /*
     /**********************************************************
     /* Coercion helper methods (overridable)
diff --git a/src/main/java/com/fasterxml/jackson/core/filter/FilteringGeneratorDelegate.java b/src/main/java/com/fasterxml/jackson/core/filter/FilteringGeneratorDelegate.java
new file mode 100644
index 0000000..8468a37
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/filter/FilteringGeneratorDelegate.java
@@ -0,0 +1,868 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.JsonGeneratorDelegate;
+
+/**
+ * Specialized {@link JsonGeneratorDelegate} that allows use of
+ * {@link TokenFilter} for outputting a subset of content that
+ * caller tries to generate.
+ * 
+ * @since 2.6
+ */
+public class FilteringGeneratorDelegate extends JsonGeneratorDelegate
+{
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+    
+    /**
+     * Object consulted to determine whether to write parts of content generator
+     * is asked to write or not.
+     */
+    protected TokenFilter rootFilter;
+
+    /**
+     * Flag that determines whether filtering will continue after the first
+     * match is indicated or not: if `false`, output is based on just the first
+     * full match (returning {@link TokenFilter#INCLUDE_ALL}) and no more
+     * checks are made; if `true` then filtering will be applied as necessary
+     * until end of content.
+     */
+    protected boolean _allowMultipleMatches;
+
+    /**
+     * Flag that determines whether path leading up to included content should
+     * also be automatically included or not. If `false`, no path inclusion is
+     * done and only explicitly included entries are output; if `true` then
+     * path from main level down to match is also included as necessary.
+     */
+    protected boolean _includePath;
+
+    /* NOTE: this feature is included in the first version (2.6), but
+     * there is no public API to enable it, yet, since there isn't an
+     * actual use case. But it seemed possible need could arise, which
+     * is feature has not yet been removed. If no use is found within
+     * first version or two, just remove.
+     * 
+     * Marked as deprecated since its status is uncertain.
+     */
+    @Deprecated
+    protected boolean _includeImmediateParent = false;
+
+    /*
+    /**********************************************************
+    /* Additional state
+    /**********************************************************
+     */
+
+    /**
+     * Although delegate has its own output context it is not sufficient since we actually
+     * have to keep track of excluded (filtered out) structures as well as ones delegate
+     * actually outputs.
+     */
+    protected TokenFilterContext _filterContext;
+
+    /**
+     * State that applies to the item within container, used where applicable.
+     * Specifically used to pass inclusion state between property name and
+     * property, and also used for array elements.
+     */
+    protected TokenFilter _itemFilter;
+    
+    /**
+     * Number of tokens for which {@link TokenFilter#INCLUDE_ALL}
+     * has been returned
+     */
+    protected int _matchCount;
+
+    /*
+    /**********************************************************
+    /* Construction, initialization
+    /**********************************************************
+     */
+
+    public FilteringGeneratorDelegate(JsonGenerator d, TokenFilter f,
+            boolean includePath, boolean allowMultipleMatches)
+    {
+        // By default, do NOT delegate copy methods
+        super(d, false);
+        rootFilter = f;
+        // and this is the currently active filter for root values
+        _itemFilter = f;
+        _filterContext = TokenFilterContext.createRootContext(f);
+        _includePath = includePath;
+        _allowMultipleMatches = allowMultipleMatches;
+    }
+
+    /*
+    /**********************************************************
+    /* Extended API
+    /**********************************************************
+     */
+
+    public TokenFilter getFilter() { return rootFilter; }
+
+    public JsonStreamContext getFilterContext() {
+        return _filterContext;
+    }
+    
+    /**
+     * Accessor for finding number of matches, where specific token and sub-tree
+     * starting (if structured type) are passed.
+     */
+    public int getMatchCount() {
+        return _matchCount;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, accessors
+    /**********************************************************
+     */
+    
+    @Override
+    public JsonStreamContext getOutputContext() {
+        /* 11-Apr-2015, tatu: Choice is between pre- and post-filter context;
+         *   let's expose post-filter context that correlates with the view
+         *   of caller.
+         */
+        return _filterContext;
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, write methods, structural
+    /**********************************************************
+     */
+    
+    @Override
+    public void writeStartArray() throws IOException
+    {
+        // First things first: whole-sale skipping easy
+        if (_itemFilter == null) {
+            _filterContext = _filterContext.createChildArrayContext(null, false);
+            return;
+        }
+        if (_itemFilter == TokenFilter.INCLUDE_ALL) { // include the whole sub-tree?
+            _filterContext = _filterContext.createChildArrayContext(_itemFilter, true);
+            delegate.writeStartArray();
+            return;
+        }
+        // Ok; regular checking state then
+        _itemFilter = _filterContext.checkValue(_itemFilter);
+        if (_itemFilter == null) {
+            _filterContext = _filterContext.createChildArrayContext(null, false);
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            _itemFilter = _itemFilter.filterStartArray();
+        }
+        if (_itemFilter == TokenFilter.INCLUDE_ALL) {
+            _checkParentPath();
+            _filterContext = _filterContext.createChildArrayContext(_itemFilter, true);
+            delegate.writeStartArray();
+        } else {
+            _filterContext = _filterContext.createChildArrayContext(_itemFilter, false);
+        }
+    }
+        
+    @Override
+    public void writeStartArray(int size) throws IOException
+    {
+        if (_itemFilter == null) {
+            _filterContext = _filterContext.createChildArrayContext(null, false);
+            return;
+        }
+        if (_itemFilter == TokenFilter.INCLUDE_ALL) {
+            _filterContext = _filterContext.createChildArrayContext(_itemFilter, true);
+            delegate.writeStartArray(size);
+            return;
+        }
+        _itemFilter = _filterContext.checkValue(_itemFilter);
+        if (_itemFilter == null) {
+            _filterContext = _filterContext.createChildArrayContext(null, false);
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            _itemFilter = _itemFilter.filterStartArray();
+        }
+        if (_itemFilter == TokenFilter.INCLUDE_ALL) {
+            _checkParentPath();
+            _filterContext = _filterContext.createChildArrayContext(_itemFilter, true);
+            delegate.writeStartArray(size);
+        } else {
+            _filterContext = _filterContext.createChildArrayContext(_itemFilter, false);
+        }
+    }
+    
+    @Override
+    public void writeEndArray() throws IOException
+    {
+        _filterContext = _filterContext.closeArray(delegate);
+
+        if (_filterContext != null) {
+            _itemFilter = _filterContext.getFilter();
+        }
+    }
+
+    @Override
+    public void writeStartObject() throws IOException
+    {
+        if (_itemFilter == null) {
+            _filterContext = _filterContext.createChildObjectContext(_itemFilter, false);
+            return;
+        }
+        if (_itemFilter == TokenFilter.INCLUDE_ALL) {
+            _filterContext = _filterContext.createChildObjectContext(_itemFilter, true);
+            delegate.writeStartObject();
+            return;
+        }
+
+        TokenFilter f = _filterContext.checkValue(_itemFilter);
+        if (f == null) {
+            return;
+        }
+        
+        if (f != TokenFilter.INCLUDE_ALL) {
+            f = f.filterStartObject();
+        }
+        if (f == TokenFilter.INCLUDE_ALL) {
+            _checkParentPath();
+            _filterContext = _filterContext.createChildObjectContext(f, true);
+            delegate.writeStartObject();
+        } else { // filter out
+            _filterContext = _filterContext.createChildObjectContext(f, false);
+        }
+    }
+    
+    @Override
+    public void writeEndObject() throws IOException
+    {
+        _filterContext = _filterContext.closeObject(delegate);
+        if (_filterContext != null) {
+            _itemFilter = _filterContext.getFilter();
+        }
+    }
+
+    @Override
+    public void writeFieldName(String name) throws IOException
+    {
+        TokenFilter state = _filterContext.setFieldName(name);
+        if (state == null) {
+            _itemFilter = null;
+            return;
+        }
+        if (state == TokenFilter.INCLUDE_ALL) {
+            _itemFilter = state;
+            delegate.writeFieldName(name);
+            return;
+        }
+        state = state.includeProperty(name);
+        _itemFilter = state;
+        if (state == TokenFilter.INCLUDE_ALL) {
+            _checkPropertyParentPath();
+        }
+    }
+
+    @Override
+    public void writeFieldName(SerializableString name) throws IOException
+    {
+        TokenFilter state = _filterContext.setFieldName(name.getValue());
+        if (state == null) {
+            _itemFilter = null;
+            return;
+        }
+        if (state == TokenFilter.INCLUDE_ALL) {
+            _itemFilter = state;
+            delegate.writeFieldName(name);
+            return;
+        }
+        state = state.includeProperty(name.getValue());
+        _itemFilter = state;
+        if (state == TokenFilter.INCLUDE_ALL) {
+            _checkPropertyParentPath();
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, text/String values
+    /**********************************************************
+     */
+
+    @Override
+    public void writeString(String value) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeString(value)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeString(value);
+    }
+
+    @Override
+    public void writeString(char[] text, int offset, int len) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            String value = new String(text, offset, len);
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeString(value)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeString(text, offset, len);
+    }
+
+    @Override
+    public void writeString(SerializableString value) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeString(value.getValue())) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeString(value);
+    }
+
+    @Override
+    public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRawUTF8String(text, offset, length);
+        }
+    }
+
+    @Override
+    public void writeUTF8String(byte[] text, int offset, int length) throws IOException
+    {
+        // not exact match, but best we can do
+        if (_checkRawValueWrite()) {
+            delegate.writeRawUTF8String(text, offset, length);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, binary/raw content
+    /**********************************************************
+     */
+
+    @Override
+    public void writeRaw(String text) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(text);
+        }
+    }
+
+    @Override
+    public void writeRaw(String text, int offset, int len) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(text);
+        }
+    }
+
+    @Override
+    public void writeRaw(SerializableString text) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(text);
+        }
+    }
+
+    @Override
+    public void writeRaw(char[] text, int offset, int len) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(text, offset, len);
+        }
+    }
+
+    @Override
+    public void writeRaw(char c) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(c);
+        }
+    }
+
+    @Override
+    public void writeRawValue(String text) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(text);
+        }
+    }
+
+    @Override
+    public void writeRawValue(String text, int offset, int len) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(text, offset, len);
+        }
+    }
+
+    @Override
+    public void writeRawValue(char[] text, int offset, int len) throws IOException
+    {
+        if (_checkRawValueWrite()) {
+            delegate.writeRaw(text, offset, len);
+        }
+    }
+
+    @Override
+    public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException
+    {
+        if (_checkBinaryWrite()) {
+            delegate.writeBinary(b64variant, data, offset, len);
+        }
+    }
+
+    @Override
+    public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength) throws IOException
+    {
+        if (_checkBinaryWrite()) {
+            return delegate.writeBinary(b64variant, data, dataLength);
+        }
+        return -1;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, other value types
+    /**********************************************************
+     */
+
+    @Override
+    public void writeNumber(short v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNumber(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(int v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNumber(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(long v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNumber(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(BigInteger v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNumber(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(double v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNumber(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(float v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNumber(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(BigDecimal v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNumber(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(String encodedValue) throws IOException, UnsupportedOperationException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeRawValue()) { // close enough?
+                    return;
+                }
+            }
+            _checkParentPath();
+        }
+        delegate.writeNumber(encodedValue);
+    }
+
+    @Override
+    public void writeBoolean(boolean v) throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeBoolean(v)) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeBoolean(v);
+    }
+
+    @Override
+    public void writeNull() throws IOException
+    {
+        if (_itemFilter == null) {
+            return;
+        }
+        if (_itemFilter != TokenFilter.INCLUDE_ALL) {
+            TokenFilter state = _filterContext.checkValue(_itemFilter);
+            if (state == null) {
+                return;
+            }
+            if (state != TokenFilter.INCLUDE_ALL) {
+                if (!state.includeNull()) {
+                    return;
+                }
+            }
+            _checkParentPath();
+        } 
+        delegate.writeNull();
+    }
+
+    /*
+    /**********************************************************
+    /* Overridden field methods
+    /**********************************************************
+     */
+
+    @Override
+    public void writeOmittedField(String fieldName) throws IOException {
+        // Hmmh. Not sure how this would work but...
+        if (_itemFilter != null) {
+            delegate.writeOmittedField(fieldName);
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, write methods, Native Ids
+    /**********************************************************
+     */
+
+    // 25-Mar-2015, tatu: These are tricky as they sort of predate actual filtering calls.
+    //   Let's try to use current state as a clue at least...
+    
+    @Override
+    public void writeObjectId(Object id) throws IOException {
+        if (_itemFilter != null) {
+            delegate.writeObjectId(id);
+        }
+    }
+
+    @Override
+    public void writeObjectRef(Object id) throws IOException {
+        if (_itemFilter != null) {
+            delegate.writeObjectRef(id);
+        }
+    }
+    
+    @Override
+    public void writeTypeId(Object id) throws IOException {
+        if (_itemFilter != null) {
+            delegate.writeTypeId(id);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, serializing Java objects
+    /**********************************************************
+     */
+
+    // Base class definitions for these seems correct to me, iff not directly delegating:
+
+    /*
+    @Override
+    public void writeObject(Object pojo) throws IOException,JsonProcessingException {
+        if (delegateCopyMethods) {
+            delegate.writeObject(pojo);
+            return;
+        }
+        // NOTE: copied from 
+        if (pojo == null) {
+            writeNull();
+        } else {
+            if (getCodec() != null) {
+                getCodec().writeValue(this, pojo);
+                return;
+            }
+            _writeSimpleObject(pojo);
+        }
+    }
+    
+    @Override
+    public void writeTree(TreeNode rootNode) throws IOException {
+        if (delegateCopyMethods) {
+            delegate.writeTree(rootNode);
+            return;
+        }
+        // As with 'writeObject()', we are not check if write would work
+        if (rootNode == null) {
+            writeNull();
+        } else {
+            if (getCodec() == null) {
+                throw new IllegalStateException("No ObjectCodec defined");
+            }
+            getCodec().writeValue(this, rootNode);
+        }
+    }
+    */
+
+    /*
+    /**********************************************************
+    /* Public API, copy-through methods
+    /**********************************************************
+     */
+
+    // Base class definitions for these seems correct to me, iff not directly delegating:
+
+    /*
+    @Override
+    public void copyCurrentEvent(JsonParser jp) throws IOException {
+        if (delegateCopyMethods) delegate.copyCurrentEvent(jp);
+        else super.copyCurrentEvent(jp);
+    }
+
+    @Override
+    public void copyCurrentStructure(JsonParser jp) throws IOException {
+        if (delegateCopyMethods) delegate.copyCurrentStructure(jp);
+        else super.copyCurrentStructure(jp);
+    }
+    */
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    protected void _checkParentPath() throws IOException
+    {
+        ++_matchCount;
+        // only need to construct path if parent wasn't written
+        if (_includePath) {
+            _filterContext.writePath(delegate);
+        }
+        // also: if no multiple matches desired, short-cut checks
+        if (!_allowMultipleMatches) {
+            // Mark parents as "skip" so that further check calls are not made
+            _filterContext.skipParentChecks();
+        }
+    }
+
+    /**
+     * Specialized variant of {@link #_checkParentPath} used when checking
+     * parent for a property name to be included with value: rules are slightly
+     * different.
+     */
+    protected void _checkPropertyParentPath() throws IOException
+    {
+        ++_matchCount;
+        if (_includePath) {
+            _filterContext.writePath(delegate);
+        } else if (_includeImmediateParent) {
+            // 21-Apr-2015, tatu: Note that there is no API to enable this currently...
+            //    retained for speculative future use
+            _filterContext.writeImmediatePath(delegate);
+        }
+
+        // also: if no multiple matches desired, short-cut checks
+        if (!_allowMultipleMatches) {
+            // Mark parents as "skip" so that further check calls are not made
+            _filterContext.skipParentChecks();
+        }
+    }
+    
+    protected boolean _checkBinaryWrite() throws IOException
+    {
+        if (_itemFilter == null) {
+            return false;
+        }
+        if (_itemFilter == TokenFilter.INCLUDE_ALL) {
+            return true;
+        }
+        if (_itemFilter.includeBinary()) { // close enough?
+            _checkParentPath();
+            return true;
+        }
+        return false;
+    }
+    
+    protected boolean _checkRawValueWrite() throws IOException
+    {
+        if (_itemFilter == null) {
+            return false;
+        }
+        if (_itemFilter == TokenFilter.INCLUDE_ALL) {
+            return true;
+        }
+        if (_itemFilter.includeRawValue()) { // close enough?
+            _checkParentPath();
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/filter/FilteringParserDelegate.java b/src/main/java/com/fasterxml/jackson/core/filter/FilteringParserDelegate.java
new file mode 100644
index 0000000..0d65bff
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/filter/FilteringParserDelegate.java
@@ -0,0 +1,880 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.JsonParserDelegate;
+
+import static com.fasterxml.jackson.core.JsonTokenId.*;
+
+/**
+ * Specialized {@link JsonParserDelegate} that allows use of
+ * {@link TokenFilter} for outputting a subset of content that
+ * is visible to caller
+ * 
+ * @since 2.6
+ */
+public class FilteringParserDelegate extends JsonParserDelegate
+{
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+    
+    /**
+     * Object consulted to determine whether to write parts of content generator
+     * is asked to write or not.
+     */
+    protected TokenFilter rootFilter;
+
+    /**
+     * Flag that determines whether filtering will continue after the first
+     * match is indicated or not: if `false`, output is based on just the first
+     * full match (returning {@link TokenFilter#INCLUDE_ALL}) and no more
+     * checks are made; if `true` then filtering will be applied as necessary
+     * until end of content.
+     */
+    protected boolean _allowMultipleMatches;
+
+    /**
+     * Flag that determines whether path leading up to included content should
+     * also be automatically included or not. If `false`, no path inclusion is
+     * done and only explicitly included entries are output; if `true` then
+     * path from main level down to match is also included as necessary.
+     */
+    protected boolean _includePath;
+
+    /* NOTE: this feature is included in the first version (2.6), but
+     * there is no public API to enable it, yet, since there isn't an
+     * actual use case. But it seemed possible need could arise, which
+     * is feature has not yet been removed. If no use is found within
+     * first version or two, just remove.
+     * 
+     * Marked as deprecated since its status is uncertain.
+     */
+    @Deprecated
+    protected boolean _includeImmediateParent = false;
+    
+    /*
+    /**********************************************************
+    /* State
+    /**********************************************************
+     */
+
+    /**
+     * Last token retrieved via {@link #nextToken}, if any.
+     * Null before the first call to <code>nextToken()</code>,
+     * as well as if token has been explicitly cleared
+     */
+    protected JsonToken _currToken;
+
+    /**
+     * Last cleared token, if any: that is, value that was in
+     * effect when {@link #clearCurrentToken} was called.
+     */
+    protected JsonToken _lastClearedToken;
+    
+    /**
+     * During traversal this is the actual "open" parse tree, which sometimes
+     * is the same as {@link #_exposedContext}, and at other times is ahead
+     * of it. Note that this context is never null.
+     */
+    protected TokenFilterContext _headContext;
+
+    /**
+     * In cases where {@link #_headContext} is "ahead" of context exposed to
+     * caller, this context points to what is currently exposed to caller.
+     * When the two are in sync, this context reference will be <code>null</code>.
+     */
+    protected TokenFilterContext _exposedContext;
+
+    /**
+     * State that applies to the item within container, used where applicable.
+     * Specifically used to pass inclusion state between property name and
+     * property, and also used for array elements.
+     */
+    protected TokenFilter _itemFilter;
+    
+    /**
+     * Number of tokens for which {@link TokenFilter#INCLUDE_ALL}
+     * has been returned.
+     */
+    protected int _matchCount;
+
+    /*
+    /**********************************************************
+    /* Construction, initialization
+    /**********************************************************
+     */
+
+    public FilteringParserDelegate(JsonParser p, TokenFilter f,
+            boolean includePath, boolean allowMultipleMatches)
+    {
+        super(p);
+        rootFilter = f;
+        // and this is the currently active filter for root values
+        _itemFilter = f;
+        _headContext = TokenFilterContext.createRootContext(f);
+        _includePath = includePath;
+        _allowMultipleMatches = allowMultipleMatches;
+    }
+
+    /*
+    /**********************************************************
+    /* Extended API
+    /**********************************************************
+     */
+
+    public TokenFilter getFilter() { return rootFilter; }
+
+    /**
+     * Accessor for finding number of matches, where specific token and sub-tree
+     * starting (if structured type) are passed.
+     */
+    public int getMatchCount() {
+        return _matchCount;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, token accessors
+    /**********************************************************
+     */
+
+    @Override public JsonToken getCurrentToken() { return _currToken; }
+
+    @Override public final int getCurrentTokenId() {
+        final JsonToken t = _currToken;
+        return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id();
+    }
+
+    @Override public boolean hasCurrentToken() { return _currToken != null; }
+    @Override public boolean hasTokenId(int id) {
+        final JsonToken t = _currToken;
+        if (t == null) {
+            return (JsonTokenId.ID_NO_TOKEN == id);
+        }
+        return t.id() == id;
+    }
+
+    @Override public final boolean hasToken(JsonToken t) {
+        return (_currToken == t);
+    }
+    
+    @Override public boolean isExpectedStartArrayToken() { return _currToken == JsonToken.START_ARRAY; }
+    @Override public boolean isExpectedStartObjectToken() { return _currToken == JsonToken.START_OBJECT; }
+
+    @Override public JsonLocation getCurrentLocation() { return delegate.getCurrentLocation(); }
+
+    @Override
+    public JsonStreamContext getParsingContext() {
+        return _filterContext();
+    }
+    
+    // !!! TODO: Verify it works as expected: copied from standard JSON parser impl
+    @Override
+    public String getCurrentName() throws IOException {
+        JsonStreamContext ctxt = _filterContext();
+        if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
+            JsonStreamContext parent = ctxt.getParent();
+            return (parent == null) ? null : parent.getCurrentName();
+        }
+        return ctxt.getCurrentName();
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, token state overrides
+    /**********************************************************
+     */
+
+    @Override
+    public void clearCurrentToken() {
+        if (_currToken != null) {
+            _lastClearedToken = _currToken;
+            _currToken = null;
+        }
+    }
+
+    @Override
+    public JsonToken getLastClearedToken() { return _lastClearedToken; }
+
+    @Override
+    public void overrideCurrentName(String name) {
+        /* 14-Apr-2015, tatu: Not sure whether this can be supported, and if so,
+         *    what to do with it... Delegation won't work for sure, so let's for
+         *    now throw an exception
+         */
+        throw new UnsupportedOperationException("Can not currently override name during filtering read");
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, traversal
+    /**********************************************************
+     */
+
+    @Override
+    public JsonToken nextToken() throws IOException
+    {
+        // Anything buffered?
+        TokenFilterContext ctxt = _exposedContext;
+
+        if (ctxt != null) {
+            while (true) {
+                JsonToken t = ctxt.nextTokenToRead();
+                if (t != null) {
+                    _currToken = t;
+                    return t;
+                }
+                // all done with buffered stuff?
+                if (ctxt == _headContext) {
+                    _exposedContext = null;
+                    if (ctxt.inArray()) {
+                        t = delegate.getCurrentToken();
+// Is this guaranteed to work without further checks?
+//                        if (t != JsonToken.START_ARRAY) {
+                        _currToken = t;
+                        return t;
+                    }
+
+                    // Almost! Most likely still have the current token;
+                    // with the sole exception of 
+                    /*
+                    t = delegate.getCurrentToken();
+                    if (t != JsonToken.FIELD_NAME) {
+                        _currToken = t;
+                        return t;
+                    }
+                    */
+                    break;
+                }
+                // If not, traverse down the context chain
+                ctxt = _headContext.findChildOf(ctxt);
+                _exposedContext = ctxt;
+                if (ctxt == null) { // should never occur
+                    throw _constructError("Unexpected problem: chain of filtered context broken");
+                }
+            }
+        }
+
+        // If not, need to read more. If we got any:
+        JsonToken t = delegate.nextToken();
+        if (t == null) {
+            // no strict need to close, since we have no state here
+            return (_currToken = t);
+        }
+
+        // otherwise... to include or not?
+        TokenFilter f;
+        
+        switch (t.id()) {
+        case ID_START_ARRAY:
+            f = _itemFilter;
+            if (f == TokenFilter.INCLUDE_ALL) {
+                _headContext = _headContext.createChildArrayContext(f, true);
+                return (_currToken = t);
+            }
+            if (f == null) { // does this occur?
+                delegate.skipChildren();
+                break;
+            }
+            // Otherwise still iffy, need to check
+            f = _headContext.checkValue(f);
+            if (f == null) {
+                delegate.skipChildren();
+                break;
+            }
+            if (f != TokenFilter.INCLUDE_ALL) {
+                f = f.filterStartArray();
+            }
+            _itemFilter = f;
+            if (f == TokenFilter.INCLUDE_ALL) {
+                _headContext = _headContext.createChildArrayContext(f, true);
+                return (_currToken = t);
+            }
+            _headContext = _headContext.createChildArrayContext(f, false);
+            
+            // Also: only need buffering if parent path to be included
+            if (_includePath) {
+                t = _nextTokenWithBuffering(_headContext);
+                if (t != null) {
+                    _currToken = t;
+                    return t;
+                }
+            }
+            break;
+
+        case ID_START_OBJECT:
+            f = _itemFilter;
+            if (f == TokenFilter.INCLUDE_ALL) {
+                _headContext = _headContext.createChildObjectContext(f, true);
+                return (_currToken = t);
+            }
+            if (f == null) { // does this occur?
+                delegate.skipChildren();
+                break;
+            }
+            // Otherwise still iffy, need to check
+            f = _headContext.checkValue(f);
+            if (f == null) {
+                delegate.skipChildren();
+                break;
+            }
+            if (f != TokenFilter.INCLUDE_ALL) {
+                f = f.filterStartObject();
+            }
+            _itemFilter = f;
+            if (f == TokenFilter.INCLUDE_ALL) {
+                _headContext = _headContext.createChildObjectContext(f, true);
+                return (_currToken = t);
+            }
+            _headContext = _headContext.createChildObjectContext(f, false);
+            // Also: only need buffering if parent path to be included
+            if (_includePath) {
+                t = _nextTokenWithBuffering(_headContext);
+                if (t != null) {
+                    _currToken = t;
+                    return t;
+                }
+            }
+            // note: inclusion of surrounding Object handled separately via
+            // FIELD_NAME
+            break;
+
+        case ID_END_ARRAY:
+        case ID_END_OBJECT:
+            {
+                boolean returnEnd = _headContext.isStartHandled();
+                f = _headContext.getFilter();
+                if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
+                    f.filterFinishArray();
+                }
+                _headContext = _headContext.getParent();
+                _itemFilter = _headContext.getFilter();
+                if (returnEnd) {
+                    return (_currToken = t);
+                }
+            }
+            break;
+
+        case ID_FIELD_NAME:
+            {
+                final String name = delegate.getCurrentName();
+                // note: this will also set 'needToHandleName'
+                f = _headContext.setFieldName(name);
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _itemFilter = f;
+                    if (!_includePath) {
+                        // Minor twist here: if parent NOT included, may need to induce output of
+                        // surrounding START_OBJECT/END_OBJECT
+                        if (_includeImmediateParent && !_headContext.isStartHandled()) {
+                            t = _headContext.nextTokenToRead(); // returns START_OBJECT but also marks it handled
+                            _exposedContext = _headContext;
+                        }
+                    }
+                    return (_currToken = t);
+                }
+                if (f == null) {
+                    delegate.nextToken();
+                    delegate.skipChildren();
+                    break;
+                }
+                f = f.includeProperty(name);
+                if (f == null) {
+                    delegate.nextToken();
+                    delegate.skipChildren();
+                    break;
+                }
+                _itemFilter = f;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    if (_includePath) {
+                        return (_currToken = t);
+                    }
+                }
+                if (_includePath) {
+                    t = _nextTokenWithBuffering(_headContext);
+                    if (t != null) {
+                        _currToken = t;
+                        return t;
+                    }
+                }
+                break;
+            }
+
+        default: // scalar value
+            f = _itemFilter;
+            if (f == TokenFilter.INCLUDE_ALL) {
+                return (_currToken = t);
+            }
+            if (f != null) {
+                f = _headContext.checkValue(f);
+                if ((f == TokenFilter.INCLUDE_ALL)
+                        || ((f != null) && f.includeValue(delegate))) {
+                    return (_currToken = t);
+                }
+            }
+            // Otherwise not included (leaves must be explicitly included)
+            break;
+        }
+
+        // We get here if token was not yet found; offlined handling
+        return _nextToken2();
+    }
+
+    /**
+     * Offlined handling for cases where there was no buffered token to
+     * return, and the token read next could not be returned as-is,
+     * at least not yet, but where we have not yet established that
+     * buffering is needed.
+     */
+    protected final JsonToken _nextToken2() throws IOException
+    {
+        main_loop:
+        while (true) {
+            JsonToken t = delegate.nextToken();
+            if (t == null) { // is this even legal?
+                return (_currToken = t);
+            }
+            TokenFilter f;
+
+            switch (t.id()) {
+            case ID_START_ARRAY:
+                f = _itemFilter;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _headContext = _headContext.createChildArrayContext(f, true);
+                    return (_currToken = t);
+                }
+                if (f == null) { // does this occur?
+                    delegate.skipChildren();
+                    continue main_loop;
+                }
+                // Otherwise still iffy, need to check
+                f = _headContext.checkValue(f);
+                if (f == null) {
+                    delegate.skipChildren();
+                    continue main_loop;
+                }
+                if (f != TokenFilter.INCLUDE_ALL) {
+                    f = f.filterStartArray();
+                }
+                _itemFilter = f;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _headContext = _headContext.createChildArrayContext(f, true);
+                    return (_currToken = t);
+                }
+                _headContext = _headContext.createChildArrayContext(f, false);
+                // but if we didn't figure it out yet, need to buffer possible events
+                if (_includePath) {
+                    t = _nextTokenWithBuffering(_headContext);
+                    if (t != null) {
+                        _currToken = t;
+                        return t;
+                    }
+                }
+                continue main_loop;
+
+            case ID_START_OBJECT:
+                f = _itemFilter;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _headContext = _headContext.createChildObjectContext(f, true);
+                    return (_currToken = t);
+                }
+                if (f == null) { // does this occur?
+                    delegate.skipChildren();
+                    continue main_loop;
+                }
+                // Otherwise still iffy, need to check
+                f = _headContext.checkValue(f);
+                if (f == null) {
+                    delegate.skipChildren();
+                    continue main_loop;
+                }
+                if (f != TokenFilter.INCLUDE_ALL) {
+                    f = f.filterStartObject();
+                }
+                _itemFilter = f;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _headContext = _headContext.createChildObjectContext(f, true);
+                    return (_currToken = t);
+                }
+                _headContext = _headContext.createChildObjectContext(f, false);
+                if (_includePath) {
+                    t = _nextTokenWithBuffering(_headContext);
+                    if (t != null) {
+                        _currToken = t;
+                        return t;
+                    }
+                }
+                continue main_loop;
+
+            case ID_END_ARRAY:
+            case ID_END_OBJECT:
+                {
+                    boolean returnEnd = _headContext.isStartHandled();
+                    f = _headContext.getFilter();
+                    if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
+                        f.filterFinishArray();
+                    }
+                    _headContext = _headContext.getParent();
+                    _itemFilter = _headContext.getFilter();
+                    if (returnEnd) {
+                        return (_currToken = t);
+                    }
+                }
+                continue main_loop;
+
+            case ID_FIELD_NAME:
+                {
+                    final String name = delegate.getCurrentName();
+                    f = _headContext.setFieldName(name);
+                    if (f == TokenFilter.INCLUDE_ALL) {
+                        _itemFilter = f;
+                        return (_currToken = t);
+                    }
+                    if (f == null) { // filter out the value
+                        delegate.nextToken();
+                        delegate.skipChildren();
+                        continue main_loop;
+                    }
+                    f = f.includeProperty(name);
+                    if (f == null) { // filter out the value
+                        delegate.nextToken();
+                        delegate.skipChildren();
+                        continue main_loop;
+                    }
+                    _itemFilter = f;
+                    if (f == TokenFilter.INCLUDE_ALL) {
+                        if (_includePath) {
+                            return (_currToken = t);
+                        }
+//                        if (_includeImmediateParent) { ...
+                        continue main_loop;
+                    }
+                    if (_includePath) {
+                        t = _nextTokenWithBuffering(_headContext);
+                        if (t != null) {
+                            _currToken = t;
+                            return t;
+                        }
+                    }
+                }
+                continue main_loop;
+
+            default: // scalar value
+                f = _itemFilter;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    return (_currToken = t);
+                }
+                if (f != null) {
+                    f = _headContext.checkValue(f);
+                    if ((f == TokenFilter.INCLUDE_ALL)
+                            || ((f != null) && f.includeValue(delegate))) {
+                        return (_currToken = t);
+                    }
+                }
+                // Otherwise not included (leaves must be explicitly included)
+                break;
+            }
+        }
+    }
+
+    /**
+     * Method called when a new potentially included context is found.
+     */
+    protected final JsonToken _nextTokenWithBuffering(final TokenFilterContext buffRoot)
+        throws IOException
+    {
+        main_loop:
+        while (true) {
+            JsonToken t = delegate.nextToken();
+            if (t == null) { // is this even legal?
+                return t;
+            }
+            TokenFilter f;
+
+            // One simplification here: we know for a fact that the item filter is
+            // neither null nor 'include all', for most cases; the only exception
+            // being FIELD_NAME handling
+
+            switch (t.id()) {
+            case ID_START_ARRAY:
+                f = _headContext.checkValue(_itemFilter);
+                if (f == null) {
+                    delegate.skipChildren();
+                    continue main_loop;
+                }
+                if (f != TokenFilter.INCLUDE_ALL) {
+                    f = f.filterStartArray();
+                }
+                _itemFilter = f;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _headContext = _headContext.createChildArrayContext(f, true);
+                    return _nextBuffered(buffRoot);
+                }
+                _headContext = _headContext.createChildArrayContext(f, false);
+                continue main_loop;
+
+            case ID_START_OBJECT:
+                f = _itemFilter;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _headContext = _headContext.createChildObjectContext(f, true);
+                    return t;
+                }
+                if (f == null) { // does this occur?
+                    delegate.skipChildren();
+                    continue main_loop;
+                }
+                // Otherwise still iffy, need to check
+                f = _headContext.checkValue(f);
+                if (f == null) {
+                    delegate.skipChildren();
+                    continue main_loop;
+                }
+                if (f != TokenFilter.INCLUDE_ALL) {
+                    f = f.filterStartObject();
+                }
+                _itemFilter = f;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    _headContext = _headContext.createChildObjectContext(f, true);
+                    return _nextBuffered(buffRoot);
+                }
+                _headContext = _headContext.createChildObjectContext(f, false);
+                continue main_loop;
+
+            case ID_END_ARRAY:
+            case ID_END_OBJECT:
+                {
+                    // Unlike with other loops, here we know that content was NOT
+                    // included (won't get this far otherwise)
+                    f = _headContext.getFilter();
+                    if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
+                        f.filterFinishArray();
+                    }
+                    boolean gotEnd = (_headContext == buffRoot);
+                    boolean returnEnd = gotEnd && _headContext.isStartHandled();
+
+                    _headContext = _headContext.getParent();
+                    _itemFilter = _headContext.getFilter();
+
+                    if (returnEnd) {
+                        return t;
+                    }
+                    // Hmmh. Do we need both checks, or should above suffice?
+                    if (gotEnd || (_headContext == buffRoot)) {
+                        return null;
+                    }
+                }
+                continue main_loop;
+
+            case ID_FIELD_NAME:
+                {
+                    final String name = delegate.getCurrentName();
+                    f = _headContext.setFieldName(name);
+                    if (f == TokenFilter.INCLUDE_ALL) {
+                        _itemFilter = f;
+                        return _nextBuffered(buffRoot);
+                    }
+                    if (f == null) { // filter out the value
+                        delegate.nextToken();
+                        delegate.skipChildren();
+                        continue main_loop;
+                    }
+                    f = f.includeProperty(name);
+                    if (f == null) { // filter out the value
+                        delegate.nextToken();
+                        delegate.skipChildren();
+                        continue main_loop;
+                    }
+                    _itemFilter = f;
+                    if (f == TokenFilter.INCLUDE_ALL) {
+                        return _nextBuffered(buffRoot);
+                    }
+                }
+                continue main_loop;
+
+            default: // scalar value
+                f = _itemFilter;
+                if (f == TokenFilter.INCLUDE_ALL) {
+                    return _nextBuffered(buffRoot);
+                }
+                if (f != null) {
+                    f = _headContext.checkValue(f);
+                    if ((f == TokenFilter.INCLUDE_ALL)
+                            || ((f != null) && f.includeValue(delegate))) {
+                        return _nextBuffered(buffRoot);
+                    }
+                }
+                // Otherwise not included (leaves must be explicitly included)
+                continue main_loop;
+            }
+        }
+    }
+
+    private JsonToken _nextBuffered(TokenFilterContext buffRoot) throws IOException
+    {
+        _exposedContext = buffRoot;
+        TokenFilterContext ctxt = buffRoot;
+        JsonToken t = ctxt.nextTokenToRead();
+        if (t != null) {
+            return t;
+        }
+        while (true) {
+            // all done with buffered stuff?
+            if (ctxt == _headContext) {
+                throw _constructError("Internal error: failed to locate expected buffered tokens");
+                /*
+                _exposedContext = null;
+                break;
+                */
+            }
+            // If not, traverse down the context chain
+            ctxt = _exposedContext.findChildOf(ctxt);
+            _exposedContext = ctxt;
+            if (ctxt == null) { // should never occur
+                throw _constructError("Unexpected problem: chain of filtered context broken");
+            }
+            t = _exposedContext.nextTokenToRead();
+            if (t != null) {
+                return t;
+            }
+        }
+    }
+    
+    @Override
+    public JsonToken nextValue() throws IOException {
+        // Re-implemented same as ParserMinimalBase:
+        JsonToken t = nextToken();
+        if (t == JsonToken.FIELD_NAME) {
+            t = nextToken();
+        }
+        return t;
+    }
+
+    /**
+     * Need to override, re-implement similar to how method defined in
+     * {@link com.fasterxml.jackson.core.base.ParserMinimalBase}, to keep
+     * state correct here.
+     */
+    @Override
+    public JsonParser skipChildren() throws IOException
+    {
+        if ((_currToken != JsonToken.START_OBJECT)
+            && (_currToken != JsonToken.START_ARRAY)) {
+            return this;
+        }
+        int open = 1;
+
+        // Since proper matching of start/end markers is handled
+        // by nextToken(), we'll just count nesting levels here
+        while (true) {
+            JsonToken t = nextToken();
+            if (t == null) { // not ideal but for now, just return
+                return this;
+            }
+            if (t.isStructStart()) {
+                ++open;
+            } else if (t.isStructEnd()) {
+                if (--open == 0) {
+                    return this;
+                }
+            }
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, access to token information, text
+    /**********************************************************
+     */
+
+    @Override public String getText() throws IOException { return delegate.getText();  }
+    @Override public boolean hasTextCharacters() { return delegate.hasTextCharacters(); }
+    @Override public char[] getTextCharacters() throws IOException { return delegate.getTextCharacters(); }
+    @Override public int getTextLength() throws IOException { return delegate.getTextLength(); }
+    @Override public int getTextOffset() throws IOException { return delegate.getTextOffset(); }
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, numeric
+    /**********************************************************
+     */
+    
+    @Override
+    public BigInteger getBigIntegerValue() throws IOException { return delegate.getBigIntegerValue(); }
+
+    @Override
+    public boolean getBooleanValue() throws IOException { return delegate.getBooleanValue(); }
+    
+    @Override
+    public byte getByteValue() throws IOException { return delegate.getByteValue(); }
+
+    @Override
+    public short getShortValue() throws IOException { return delegate.getShortValue(); }
+
+    @Override
+    public BigDecimal getDecimalValue() throws IOException { return delegate.getDecimalValue(); }
+
+    @Override
+    public double getDoubleValue() throws IOException { return delegate.getDoubleValue(); }
+
+    @Override
+    public float getFloatValue() throws IOException { return delegate.getFloatValue(); }
+
+    @Override
+    public int getIntValue() throws IOException { return delegate.getIntValue(); }
+
+    @Override
+    public long getLongValue() throws IOException { return delegate.getLongValue(); }
+
+    @Override
+    public NumberType getNumberType() throws IOException { return delegate.getNumberType(); }
+
+    @Override
+    public Number getNumberValue() throws IOException { return delegate.getNumberValue(); }
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, coercion/conversion
+    /**********************************************************
+     */
+    
+    @Override public int getValueAsInt() throws IOException { return delegate.getValueAsInt(); }
+    @Override public int getValueAsInt(int defaultValue) throws IOException { return delegate.getValueAsInt(defaultValue); }
+    @Override public long getValueAsLong() throws IOException { return delegate.getValueAsLong(); }
+    @Override public long getValueAsLong(long defaultValue) throws IOException { return delegate.getValueAsLong(defaultValue); }
+    @Override public double getValueAsDouble() throws IOException { return delegate.getValueAsDouble(); }
+    @Override public double getValueAsDouble(double defaultValue) throws IOException { return delegate.getValueAsDouble(defaultValue); }
+    @Override public boolean getValueAsBoolean() throws IOException { return delegate.getValueAsBoolean(); }
+    @Override public boolean getValueAsBoolean(boolean defaultValue) throws IOException { return delegate.getValueAsBoolean(defaultValue); }
+    @Override public String getValueAsString() throws IOException { return delegate.getValueAsString(); }
+    @Override public String getValueAsString(String defaultValue) throws IOException { return delegate.getValueAsString(defaultValue); }
+    
+    /*
+    /**********************************************************
+    /* Public API, access to token values, other
+    /**********************************************************
+     */
+
+    @Override public Object getEmbeddedObject() throws IOException { return delegate.getEmbeddedObject(); }
+    @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException { return delegate.getBinaryValue(b64variant); }
+    @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException { return delegate.readBinaryValue(b64variant, out); }
+    @Override public JsonLocation getTokenLocation() { return delegate.getTokenLocation(); }
+
+    /*
+    /**********************************************************
+    /* Internal helper methods
+    /**********************************************************
+     */
+
+    protected JsonStreamContext _filterContext() {
+        if (_exposedContext != null) {
+            return _exposedContext;
+        }
+        return _headContext;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/filter/JsonPointerBasedFilter.java b/src/main/java/com/fasterxml/jackson/core/filter/JsonPointerBasedFilter.java
new file mode 100644
index 0000000..d3dedee
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/filter/JsonPointerBasedFilter.java
@@ -0,0 +1,69 @@
+package com.fasterxml.jackson.core.filter;
+
+import com.fasterxml.jackson.core.JsonPointer;
+
+/**
+ * Simple {@link TokenFilter} implementation that takes a single
+ * {@link JsonPointer} and matches a single value accordingly.
+ * Instances are immutable and fully thread-safe, shareable,
+ * and efficient to use.
+ * 
+ * @since 2.6
+ */
+public class JsonPointerBasedFilter extends TokenFilter
+{
+    protected final JsonPointer _pathToMatch;
+
+    public JsonPointerBasedFilter(String ptrExpr) {
+        this(JsonPointer.compile(ptrExpr));
+    }
+
+    public JsonPointerBasedFilter(JsonPointer match) {
+        _pathToMatch = match;
+    }
+
+    @Override
+    public TokenFilter includeElement(int index) {
+        JsonPointer next = _pathToMatch.matchElement(index);
+        if (next == null) {
+            return null;
+        }
+        if (next.matches()) {
+            return TokenFilter.INCLUDE_ALL;
+        }
+        return new JsonPointerBasedFilter(next);
+    }
+
+    @Override
+    public TokenFilter includeProperty(String name) {
+        JsonPointer next = _pathToMatch.matchProperty(name);
+        if (next == null) {
+            return null;
+        }
+        if (next.matches()) {
+            return TokenFilter.INCLUDE_ALL;
+        }
+        return new JsonPointerBasedFilter(next);
+    }
+
+    @Override
+    public TokenFilter filterStartArray() {
+        return this;
+    }
+    
+    @Override
+    public TokenFilter filterStartObject() {
+        return this;
+    }
+    
+    @Override
+    protected boolean _includeScalar() {
+        // should only occur for root-level scalars, path "/"
+        return _pathToMatch.matches();
+    }
+
+    @Override
+    public String toString() {
+        return "[JsonPointerFilter at: "+_pathToMatch+"]";
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/filter/TokenFilter.java b/src/main/java/com/fasterxml/jackson/core/filter/TokenFilter.java
new file mode 100644
index 0000000..4546fbf
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/filter/TokenFilter.java
@@ -0,0 +1,363 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+
+/**
+ * Strategy class that can be implemented to specify actual inclusion/exclusion
+ * criteria for filtering, used by {@link FilteringGeneratorDelegate}.
+ *
+ * @since 2.6
+ */
+public class TokenFilter
+{
+
+    // // Marker values
+
+    /**
+     * Marker value that should be used to indicate inclusion of a structured
+     * value (sub-tree representing Object or Array), or value of a named
+     * property (regardless of type).
+     * Note that if this instance is returned, it will used as a marker, and 
+     * no actual callbacks need to be made. For this reason, it is more efficient
+     * to return this instance if the whole sub-tree is to be included, instead
+     * of implementing similar filter functionality explicitly.
+     */
+    public final static TokenFilter INCLUDE_ALL = new TokenFilter();
+
+    // Life-cycle
+
+    protected TokenFilter() { }
+
+    /*
+    /**********************************************************
+    /* API, structured values
+    /**********************************************************
+     */
+
+    /**
+     * Method called to check whether Object value at current output
+     * location should be included in output.
+     * Three kinds of return values may be used as follows:
+     *<ul>
+     * <li><code>null</code> to indicate that the Object should be skipped
+     *   </li>
+     * <li>{@link #INCLUDE_ALL} to indicate that the Object should be included
+     * completely in output
+     *   </li>
+     * <li>Any other {@link TokenFilter} implementation (possibly this one) to mean
+     *  that further inclusion calls on return filter object need to be made
+     *  on contained properties, as necessary. {@link #filterFinishObject()} will
+     *  also be called on returned filter object
+     *   </li>
+     * </ul>
+     *<p>
+     * Default implementation returns <code>this</code>, which means that checks
+     * are made recursively for properties of the Object to determine possible inclusion.
+     * 
+     * @return TokenFilter to use for further calls within Array, unless return value
+     *   is <code>null</code> or {@link #INCLUDE_ALL} (which have simpler semantics)
+     */
+    public TokenFilter filterStartObject() {
+        return this;
+    }
+
+    /**
+     * Method called to check whether Array value at current output
+     * location should be included in output.
+     * Three kinds of return values may be used as follows:
+     *<ul>
+     * <li><code>null</code> to indicate that the Array should be skipped
+     *   </li>
+     * <li>{@link #INCLUDE_ALL} to indicate that the Array should be included
+     * completely in output
+     *   </li>
+     * <li>Any other {@link TokenFilter} implementation (possibly this one) to mean
+     *  that further inclusion calls on return filter object need to be made
+     *  on contained element values, as necessary. {@link #filterFinishArray()} will
+     *  also be called on returned filter object
+     *   </li>
+     * </ul>
+     *<p>
+     * Default implementation returns <code>this</code>, which means that checks
+     * are made recursively for elements of the array to determine possible inclusion.
+     * 
+     * @return TokenFilter to use for further calls within Array, unless return value
+     *   is <code>null</code> or {@link #INCLUDE_ALL} (which have simpler semantics)
+     */
+    public TokenFilter filterStartArray() {
+        return this;
+    }
+
+    /**
+     * Method called to indicate that output of non-filtered Object (one that may
+     * have been included either completely, or in part) is completed,
+     * in cases where filter other that {@link #INCLUDE_ALL} was returned.
+     * This occurs when {@link JsonGenerator#writeEndObject()} is called.
+     */
+    public void filterFinishObject() { }
+
+    /**
+     * Method called to indicate that output of non-filtered Array (one that may
+     * have been included either completely, or in part) is completed,
+     * in cases where filter other that {@link #INCLUDE_ALL} was returned.
+     * This occurs when {@link JsonGenerator#writeEndArray()} is called.
+     */
+    public void filterFinishArray() { }
+
+    /*
+    /**********************************************************
+    /* API, properties/elements
+    /**********************************************************
+     */
+
+    /**
+     * Method called to check whether property value with specified name,
+     * at current output location, should be included in output.
+     * Three kinds of return values may be used as follows:
+     *<ul>
+     * <li><code>null</code> to indicate that the property and its value should be skipped
+     *   </li>
+     * <li>{@link #INCLUDE_ALL} to indicate that the property and its value should be included
+     * completely in output
+     *   </li>
+     * <li>Any other {@link TokenFilter} implementation (possibly this one) to mean
+     *  that further inclusion calls on returned filter object need to be made
+     *  as necessary, to determine inclusion.
+     *   </li>
+     * </ul>
+     *<p>
+     * The default implementation simply returns <code>this</code> to continue calling
+     * methods on this filter object, without full inclusion or exclusion.
+     * 
+     * @return TokenFilter to use for further calls within property value, unless return value
+     *   is <code>null</code> or {@link #INCLUDE_ALL} (which have simpler semantics)
+     */
+    public TokenFilter includeProperty(String name) {
+        return this;
+    }
+
+    /**
+     * Method called to check whether array element with specified index (zero-based),
+     * at current output location, should be included in output.
+     * Three kinds of return values may be used as follows:
+     *<ul>
+     * <li><code>null</code> to indicate that the Array element should be skipped
+     *   </li>
+     * <li>{@link #INCLUDE_ALL} to indicate that the Array element should be included
+     * completely in output
+     *   </li>
+     * <li>Any other {@link TokenFilter} implementation (possibly this one) to mean
+     *  that further inclusion calls on returned filter object need to be made
+     *  as necessary, to determine inclusion.
+     *   </li>
+     * </ul>
+     *<p>
+     * The default implementation simply returns <code>this</code> to continue calling
+     * methods on this filter object, without full inclusion or exclusion.
+     * 
+     * @return TokenFilter to use for further calls within element value, unless return value
+     *   is <code>null</code> or {@link #INCLUDE_ALL} (which have simpler semantics)
+     */
+    public TokenFilter includeElement(int index) {
+        return this;
+    }
+
+    /**
+     * Method called to check whether root-level value,
+     * at current output location, should be included in output.
+     * Three kinds of return values may be used as follows:
+     *<ul>
+     * <li><code>null</code> to indicate that the root value should be skipped
+     *   </li>
+     * <li>{@link #INCLUDE_ALL} to indicate that the root value should be included
+     * completely in output
+     *   </li>
+     * <li>Any other {@link TokenFilter} implementation (possibly this one) to mean
+     *  that further inclusion calls on returned filter object need to be made
+     *  as necessary, to determine inclusion.
+     *   </li>
+     * </ul>
+     *<p>
+     * The default implementation simply returns <code>this</code> to continue calling
+     * methods on this filter object, without full inclusion or exclusion.
+     * 
+     * @return TokenFilter to use for further calls within root value, unless return value
+     *   is <code>null</code> or {@link #INCLUDE_ALL} (which have simpler semantics)
+     */
+    public TokenFilter includeRootValue(int index) {
+        return this;
+    }
+
+    /*
+    /**********************************************************
+    /* API, scalar values (being read)
+    /**********************************************************
+     */
+
+    /**
+     * Call made when verifying whether a scaler value is being
+     * read from a parser.
+     *<p>
+     * Default action is to call <code>_includeScalar()</code> and return
+     * whatever it indicates.
+     */
+    public boolean includeValue(JsonParser p) throws IOException {
+        return _includeScalar();
+    }
+
+    /*
+    /**********************************************************
+    /* API, scalar values (being written)
+    /**********************************************************
+     */
+
+    /**
+     * Call made to verify whether leaf-level
+     * boolean value
+     * should be included in output or not.
+     */
+    public boolean includeBoolean(boolean value) {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * null value
+     * should be included in output or not.
+     */
+    public boolean includeNull() {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * String value
+     * should be included in output or not.
+     */
+    public boolean includeString(String value) {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * <code>int</code> value
+     * should be included in output or not.
+     * 
+     * NOTE: also called for `short`, `byte`
+     */
+    public boolean includeNumber(int v) {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * <code>long</code> value
+     * should be included in output or not.
+     */
+    public boolean includeNumber(long v) {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * <code>float</code> value
+     * should be included in output or not.
+     */
+    public boolean includeNumber(float v) {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * <code>double</code> value
+     * should be included in output or not.
+     */
+    public boolean includeNumber(double v) {
+        return _includeScalar();
+    }
+    
+    /**
+     * Call made to verify whether leaf-level
+     * {@link BigDecimal} value
+     * should be included in output or not.
+     */
+    public boolean includeNumber(BigDecimal v) {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * {@link BigInteger} value
+     * should be included in output or not.
+     */
+    public boolean includeNumber(BigInteger v) {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * Binary value
+     * should be included in output or not.
+     *<p>
+     * NOTE: no binary payload passed; assumption is this won't be of much use.
+     */
+    public boolean includeBinary() {
+        return _includeScalar();
+    }
+
+    /**
+     * Call made to verify whether leaf-level
+     * raw (pre-encoded, not quoted by generator) value
+     * should be included in output or not.
+     *<p>
+     * NOTE: value itself not passed since it may come on multiple forms
+     * and is unlikely to be of much use in determining inclusion
+     * criteria.
+     */
+    public boolean includeRawValue() {
+        return _includeScalar();
+    }
+    
+    /**
+     * Call made to verify whether leaf-level
+     * embedded (Opaque) value
+     * should be included in output or not.
+     */
+    public boolean includeEmbeddedValue(Object ob) {
+        return _includeScalar();
+    }
+
+    /*
+    /**********************************************************
+    /* Overrides
+    /**********************************************************
+     */
+
+    @Override
+    public String toString() {
+        if (this == INCLUDE_ALL) {
+            return "TokenFilter.INCLUDE_ALL";
+        }
+        return super.toString();
+    }
+
+    /*
+    /**********************************************************
+    /* Other methods
+    /**********************************************************
+     */
+    
+    /**
+     * Overridable default implementation delegated to all scalar value
+     * inclusion check methods.
+     * The default implementation simply includes all leaf values.
+     */
+    protected boolean _includeScalar() {
+        return true;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/filter/TokenFilterContext.java b/src/main/java/com/fasterxml/jackson/core/filter/TokenFilterContext.java
new file mode 100644
index 0000000..fcca317
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/filter/TokenFilterContext.java
@@ -0,0 +1,353 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Alternative variant of {@link JsonStreamContext}, used when filtering
+ * content being read or written (based on {@link TokenFilter}).
+ * 
+ * @since 2.6
+ */
+public class TokenFilterContext extends JsonStreamContext
+{
+    /**
+     * Parent context for this context; null for root context.
+     */
+    protected final TokenFilterContext _parent;
+
+    /*
+    /**********************************************************
+    /* Simple instance reuse slots; speed up things
+    /* a bit (10-15%) for docs with lots of small
+    /* arrays/objects
+    /**********************************************************
+     */
+
+    protected TokenFilterContext _child = null;
+
+    /*
+    /**********************************************************
+    /* Location/state information
+    /**********************************************************
+     */
+    
+    /**
+     * Name of the field of which value is to be parsed; only
+     * used for OBJECT contexts
+     */
+    protected String _currentName;
+
+    /**
+     * Filter to use for items in this state (for properties of Objects,
+     * elements of Arrays, and root-level values of root context)
+     */
+    protected TokenFilter _filter;
+
+    /**
+     * Flag that indicates that start token has been read/written,
+     * so that matching close token needs to be read/written as well
+     * when context is getting closed.
+     */
+    protected boolean _startHandled;
+
+    /**
+     * Flag that indicates that the current name of this context
+     * still needs to be read/written, if path from root down to
+     * included leaf is to be exposed.
+     */
+    protected boolean _needToHandleName;
+    
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    protected TokenFilterContext(int type, TokenFilterContext parent,
+            TokenFilter filter, boolean startHandled)
+    {
+        super();
+        _type = type;
+        _parent = parent;
+        _filter = filter;
+        _index = -1;
+        _startHandled = startHandled;
+        _needToHandleName = false;
+    }
+
+    protected TokenFilterContext reset(int type,
+            TokenFilter filter, boolean startWritten)
+    {
+        _type = type;
+        _filter = filter;
+        _index = -1;
+        _currentName = null;
+        _startHandled = startWritten;
+        _needToHandleName = false;
+        return this;
+    }
+
+    /*
+    /**********************************************************
+    /* Factory methods
+    /**********************************************************
+     */
+
+    public static TokenFilterContext createRootContext(TokenFilter filter) {
+        // true -> since we have no start/end marker, consider start handled
+        return new TokenFilterContext(TYPE_ROOT, null, filter, true);
+    }
+
+    public TokenFilterContext createChildArrayContext(TokenFilter filter, boolean writeStart) {
+        TokenFilterContext ctxt = _child;
+        if (ctxt == null) {
+            _child = ctxt = new TokenFilterContext(TYPE_ARRAY, this, filter, writeStart);
+            return ctxt;
+        }
+        return ctxt.reset(TYPE_ARRAY, filter, writeStart);
+    }
+
+    public TokenFilterContext createChildObjectContext(TokenFilter filter, boolean writeStart) {
+        TokenFilterContext ctxt = _child;
+        if (ctxt == null) {
+            _child = ctxt = new TokenFilterContext(TYPE_OBJECT, this, filter, writeStart);
+            return ctxt;
+        }
+        return ctxt.reset(TYPE_OBJECT, filter, writeStart);
+    }
+
+    /*
+    /**********************************************************
+    /* State changes
+    /**********************************************************
+     */
+    
+    public TokenFilter setFieldName(String name) throws JsonProcessingException {
+        _currentName = name;
+        _needToHandleName = true;
+        return _filter;
+    }
+
+    /**
+     * Method called to check whether value is to be included at current output
+     * position, either as Object property, Array element, or root value.
+     */
+    public TokenFilter checkValue(TokenFilter filter) {
+        // First, checks for Object properties have been made earlier:
+        if (_type == TYPE_OBJECT) {
+            return filter;
+        }
+        // We increase it first because at the beginning of array, value is -1
+        int ix = ++_index;
+        if (_type == TYPE_ARRAY) {
+            return filter.includeElement(ix);
+        }
+        return filter.includeRootValue(ix);
+    }
+
+    /**
+     * Method called to ensure that parent path from root is written up to
+     * and including this node.
+     */
+    public void writePath(JsonGenerator gen) throws IOException
+    {
+        if ((_filter == null) || (_filter == TokenFilter.INCLUDE_ALL)) {
+            return;
+        }
+        if (_parent != null) {
+            _parent._writePath(gen);
+        }
+        if (_startHandled) {
+            // even if Object started, need to start leaf-level name
+            if (_needToHandleName) {
+                gen.writeFieldName(_currentName);
+            }
+        } else {
+            _startHandled = true;
+            if (_type == TYPE_OBJECT) {
+                gen.writeStartObject();
+                gen.writeFieldName(_currentName); // we know name must be written
+            } else if (_type == TYPE_ARRAY) {
+                gen.writeStartArray();
+            }
+        }
+    }
+
+    /**
+     * Variant of {@link #writePath(JsonGenerator)} called when all we
+     * need is immediately surrounding Object. Method typically called
+     * when including a single property but not including full path
+     * to root.
+     */
+    public void writeImmediatePath(JsonGenerator gen) throws IOException
+    {
+        if ((_filter == null) || (_filter == TokenFilter.INCLUDE_ALL)) {
+            return;
+        }
+        if (_startHandled) {
+            // even if Object started, need to start leaf-level name
+            if (_needToHandleName) {
+                gen.writeFieldName(_currentName);
+            }
+        } else {
+            _startHandled = true;
+            if (_type == TYPE_OBJECT) {
+                gen.writeStartObject();
+                if (_needToHandleName) {
+                    gen.writeFieldName(_currentName);
+                }
+            } else if (_type == TYPE_ARRAY) {
+                gen.writeStartArray();
+            }
+        }
+    }
+
+    private void _writePath(JsonGenerator gen) throws IOException
+    {
+        if ((_filter == null) || (_filter == TokenFilter.INCLUDE_ALL)) {
+            return;
+        }
+        if (_parent != null) {
+            _parent._writePath(gen);
+        }
+        if (_startHandled) {
+            // even if Object started, need to start leaf-level name
+            if (_needToHandleName) {
+                _needToHandleName = false; // at parent must explicitly clear
+                gen.writeFieldName(_currentName);
+            }
+        } else {
+            _startHandled = true;
+            if (_type == TYPE_OBJECT) {
+                gen.writeStartObject();
+                if (_needToHandleName) {
+                    _needToHandleName = false; // at parent must explicitly clear
+                    gen.writeFieldName(_currentName);
+                }
+            } else if (_type == TYPE_ARRAY) {
+                gen.writeStartArray();
+            }
+        }
+    }
+
+    public TokenFilterContext closeArray(JsonGenerator gen) throws IOException
+    {
+        if (_startHandled) {
+            gen.writeEndArray();
+        }
+        if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
+            _filter.filterFinishArray();
+        }
+        return _parent;
+    }
+
+    public TokenFilterContext closeObject(JsonGenerator gen) throws IOException
+    {
+        if (_startHandled) {
+            gen.writeEndObject();
+        }
+        if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
+            _filter.filterFinishObject();
+        }
+        return _parent;
+    }
+    
+    public void skipParentChecks() {
+        _filter = null;
+        for (TokenFilterContext ctxt = _parent; ctxt != null; ctxt = ctxt._parent) {
+            _parent._filter = null;
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Accessors, mutators
+    /**********************************************************
+     */
+
+    @Override
+    public Object getCurrentValue() { return null; }
+
+    @Override
+    public void setCurrentValue(Object v) { }
+
+    @Override public final TokenFilterContext getParent() { return _parent; }
+    @Override public final String getCurrentName() { return _currentName; }
+
+    public TokenFilter getFilter() { return _filter; }
+    public boolean isStartHandled() { return _startHandled; }
+
+    public JsonToken nextTokenToRead() {
+        if (!_startHandled) {
+            _startHandled = true;
+            if (_type == TYPE_OBJECT) {
+                return JsonToken.START_OBJECT;
+            }
+            // Note: root should never be unhandled
+            return JsonToken.START_ARRAY;
+        }
+        // But otherwise at most might have FIELD_NAME
+        if (_needToHandleName && (_type == TYPE_OBJECT)) {
+            _needToHandleName = false;
+            return JsonToken.FIELD_NAME;
+        }
+        return null;
+    }
+
+    public TokenFilterContext findChildOf(TokenFilterContext parent) {
+        if (_parent == parent) {
+            return this;
+        }
+        TokenFilterContext curr = _parent;
+        while (curr != null) {
+            TokenFilterContext p = curr._parent;
+            if (p == parent) {
+                return curr;
+            }
+            curr = p;
+        }
+        // should never occur but...
+        return null;
+    }
+    
+    // // // Internally used abstract methods
+
+    protected void appendDesc(StringBuilder sb) {
+        if (_parent != null) {
+            _parent.appendDesc(sb);
+        }
+        if (_type == TYPE_OBJECT) {
+            sb.append('{');
+            if (_currentName != null) {
+                sb.append('"');
+                // !!! TODO: Name chars should be escaped?
+                sb.append(_currentName);
+                sb.append('"');
+            } else {
+                sb.append('?');
+            }
+            sb.append('}');
+        } else if (_type == TYPE_ARRAY) {
+            sb.append('[');
+            sb.append(getCurrentIndex());
+            sb.append(']');
+        } else {
+            // nah, ROOT:
+            sb.append("/");
+        }
+    }
+
+    // // // Overridden standard methods
+
+    /**
+     * Overridden to provide developer writeable "JsonPath" representation
+     * of the context.
+     */
+    @Override public String toString() {
+        StringBuilder sb = new StringBuilder(64);
+        appendDesc(sb);
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/IOContext.java b/src/main/java/com/fasterxml/jackson/core/io/IOContext.java
index 7229da2..84fefc1 100644
--- a/src/main/java/com/fasterxml/jackson/core/io/IOContext.java
+++ b/src/main/java/com/fasterxml/jackson/core/io/IOContext.java
@@ -109,6 +109,14 @@
         _encoding = enc;
     }
 
+    /**
+     * @since 1.6
+     */
+    public IOContext withEncoding(JsonEncoding enc) {
+        _encoding = enc;
+        return this;
+    }
+    
     /*
     /**********************************************************
     /* Public API, accessors
diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java
index bbe0c08..523c401 100644
--- a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java
+++ b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java
@@ -316,6 +316,13 @@
         return Double.toString(v);
     }
 
+    /**
+     * @since 2.6.0
+     */
+    public static String toString(float v) {
+        return Float.toString(v);
+    }
+
     /*
     /**********************************************************
     /* Internal methods
diff --git a/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java b/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java
index 3dfda9d..f59e032 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java
@@ -6,7 +6,7 @@
 import com.fasterxml.jackson.core.format.InputAccessor;
 import com.fasterxml.jackson.core.format.MatchStrength;
 import com.fasterxml.jackson.core.io.*;
-import com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer;
+import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
 import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
 
 /**
@@ -209,7 +209,7 @@
     }
 
     public JsonParser constructParser(int parserFeatures, ObjectCodec codec,
-            BytesToNameCanonicalizer rootByteSymbols, CharsToNameCanonicalizer rootCharSymbols,
+            ByteQuadsCanonicalizer rootByteSymbols, CharsToNameCanonicalizer rootCharSymbols,
             int factoryFeatures) throws IOException
     {
         JsonEncoding enc = detectEncoding();
@@ -219,7 +219,7 @@
              * (which is ok for larger input; not so hot for smaller; but this is not a common case)
              */
             if (JsonFactory.Feature.CANONICALIZE_FIELD_NAMES.enabledIn(factoryFeatures)) {
-                BytesToNameCanonicalizer can = rootByteSymbols.makeChild(factoryFeatures);
+                ByteQuadsCanonicalizer can = rootByteSymbols.makeChild(factoryFeatures);
                 return new UTF8StreamJsonParser(_context, parserFeatures, _in, codec, can,
                         _inputBuffer, _inputPtr, _inputEnd, _bufferRecyclable);
             }
diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java
index dbe7c9d..9ce93e4 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java
@@ -163,8 +163,7 @@
     // // Overrides just to make things final, to possibly help with inlining
     
     @Override
-    public final void writeStringField(String fieldName, String value)
-        throws IOException, JsonGenerationException
+    public final void writeStringField(String fieldName, String value) throws IOException
     {
         writeFieldName(fieldName);
         writeString(value);
diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java
index 4ff12d7..f15bd54 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java
@@ -133,16 +133,12 @@
         return ctxt.reset(TYPE_OBJECT);
     }
 
-    // // // Shared API
-
     @Override public final JsonWriteContext getParent() { return _parent; }
     @Override public final String getCurrentName() { return _currentName; }
 
     public DupDetector getDupDetector() {
         return _dups;
     }
-    
-    // // // API sub-classes are to implement
 
     /**
      * Method that writer is to call before it writes a field name.
@@ -166,6 +162,9 @@
     public int writeValue() {
         // Most likely, object:
         if (_type == TYPE_OBJECT) {
+            if (!_gotName) {
+                return STATUS_EXPECT_NAME;
+            }
             _gotName = false;
             ++_index;
             return STATUS_OK_AFTER_COLON;
diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
index cf8085a..1bc9d34 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
@@ -250,6 +250,9 @@
             }
             return _textBuffer.contentsAsString();
         }
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return getCurrentName();
+        }
         return super.getValueAsString(null);
     }
     
@@ -263,6 +266,9 @@
             }
             return _textBuffer.contentsAsString();
         }
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return getCurrentName();
+        }
         return super.getValueAsString(defValue);
     }
 
@@ -544,10 +550,10 @@
     }
 
     /*
-   /**********************************************************
-   /* Public API, traversal
-   /**********************************************************
-    */
+    /**********************************************************
+    /* Public API, traversal
+    /**********************************************************
+     */
 
     /**
      * @return Next token from the stream, if any found, or null
@@ -707,11 +713,168 @@
     }
 
     /*
-    @Override
-    public boolean nextFieldName(SerializableString str)
-         throws IOException
+    /**********************************************************
+    /* Public API, nextXxx() overrides
+    /**********************************************************
      */
 
+    /*
+    @Override
+    public boolean nextFieldName(SerializableString str)
+        throws IOException
+    {
+    
+    }
+    */
+
+    @Override
+    public String nextFieldName() throws IOException
+    {
+        // // // Note: this is almost a verbatim copy of nextToken() (minus comments)
+
+        _numTypesValid = NR_UNKNOWN;
+        if (_currToken == JsonToken.FIELD_NAME) {
+            _nextAfterName();
+            return null;
+        }
+        if (_tokenIncomplete) {
+            _skipString();
+        }
+        int i = _skipWSOrEnd();
+        if (i < 0) {
+            close();
+            _currToken = null;
+            return null;
+        }
+        _tokenInputTotal = _currInputProcessed + _inputPtr - 1;
+        _tokenInputRow = _currInputRow;
+        _tokenInputCol = _inputPtr - _currInputRowStart - 1;
+        _binaryValue = null;
+        if (i == INT_RBRACKET) {
+            if (!_parsingContext.inArray()) {
+                _reportMismatchedEndMarker(i, '}');
+            }
+            _parsingContext = _parsingContext.getParent();
+            _currToken = JsonToken.END_ARRAY;
+            return null;
+        }
+        if (i == INT_RCURLY) {
+            if (!_parsingContext.inObject()) {
+                _reportMismatchedEndMarker(i, ']');
+            }
+            _parsingContext = _parsingContext.getParent();
+            _currToken = JsonToken.END_OBJECT;
+            return null;
+        }
+        if (_parsingContext.expectComma()) {
+            i = _skipComma(i);
+        }
+
+        if (!_parsingContext.inObject()) {
+            _nextTokenNotInObject(i);
+            return null;
+        }
+
+        String name = (i == INT_QUOTE) ? _parseName() : _handleOddName(i);
+        _parsingContext.setCurrentName(name);
+        _currToken = JsonToken.FIELD_NAME;
+        i = _skipColon();
+
+        if (i == INT_QUOTE) {
+            _tokenIncomplete = true;
+            _nextToken = JsonToken.VALUE_STRING;
+            return name;
+        }
+        
+        // Ok: we must have a value... what is it?
+
+        JsonToken t;
+
+        switch (i) {
+        case '-':
+            t = _parseNegNumber();
+            break;
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            t = _parsePosNumber(i);
+            break;
+        case 'f':
+            _matchFalse();
+            t = JsonToken.VALUE_FALSE;
+            break;
+        case 'n':
+            _matchNull();
+            t = JsonToken.VALUE_NULL;
+            break;
+        case 't':
+            _matchTrue();
+            t = JsonToken.VALUE_TRUE;
+            break;
+        case '[':
+            t = JsonToken.START_ARRAY;
+            break;
+        case '{':
+            t = JsonToken.START_OBJECT;
+            break;
+        default:
+            t = _handleOddValue(i);
+            break;
+        }
+        _nextToken = t;
+        return name;
+    }
+
+    private final JsonToken _nextTokenNotInObject(int i) throws IOException
+    {
+        if (i == INT_QUOTE) {
+            _tokenIncomplete = true;
+            return (_currToken = JsonToken.VALUE_STRING);
+        }
+        switch (i) {
+        case '[':
+            _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            return (_currToken = JsonToken.START_ARRAY);
+        case '{':
+            _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            return (_currToken = JsonToken.START_OBJECT);
+        case 't':
+            _matchToken("true", 1);
+            return (_currToken = JsonToken.VALUE_TRUE);
+        case 'f':
+            _matchToken("false", 1);
+            return (_currToken = JsonToken.VALUE_FALSE);
+        case 'n':
+            _matchToken("null", 1);
+            return (_currToken = JsonToken.VALUE_NULL);
+        case '-':
+            return (_currToken = _parseNegNumber());
+            /* Should we have separate handling for plus? Although
+             * it is not allowed per se, it may be erroneously used,
+             * and could be indicated by a more specific error message.
+             */
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            return (_currToken = _parsePosNumber(i));
+        }
+        return (_currToken = _handleOddValue(i));
+    }
+    
     // note: identical to one in UTF8StreamJsonParser
     @Override
     public final String nextTextValue() throws IOException
diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java
index 6f34187..6d32010 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java
@@ -13,12 +13,12 @@
     private final static byte BYTE_u = (byte) 'u';
 
     private final static byte BYTE_0 = (byte) '0';
-    
+
     private final static byte BYTE_LBRACKET = (byte) '[';
     private final static byte BYTE_RBRACKET = (byte) ']';
     private final static byte BYTE_LCURLY = (byte) '{';
     private final static byte BYTE_RCURLY = (byte) '}';
- 
+
     private final static byte BYTE_BACKSLASH = (byte) '\\';
     private final static byte BYTE_COMMA = (byte) ',';
     private final static byte BYTE_COLON = (byte) ':';
@@ -26,7 +26,7 @@
 
     // intermediate copies only made up to certain length...
     private final static int MAX_BYTES_TO_BUFFER = 512;
-    
+
     final static byte[] HEX_CHARS = CharTypes.copyHexBytes();
 
     private final static byte[] NULL_BYTES = { 'n', 'u', 'l', 'l' };
@@ -38,7 +38,7 @@
     /* Output buffering
     /**********************************************************
      */
-    
+
     /**
      * Underlying output stream used for writing JSON content.
      */
@@ -67,18 +67,18 @@
      * in the output buffer after escaping
      */
     protected final int _outputMaxContiguous;
-    
+
     /**
      * Intermediate buffer in which characters of a String are copied
      * before being encoded.
      */
     protected char[] _charBuffer;
-    
+
     /**
      * Length of <code>_charBuffer</code>
      */
     protected final int _charBufferLength;
-    
+
     /**
      * 6 character temporary buffer allocated if needed, for constructing
      * escape sequences
@@ -145,7 +145,7 @@
         _outputBuffer = outputBuffer;
         _outputEnd = _outputBuffer.length;
         // up to 6 bytes per char (see above), rounded up to 1/8
-        _outputMaxContiguous = _outputEnd >> 3;
+        _outputMaxContiguous = (_outputEnd >> 3);
         _charBuffer = ctxt.allocConcatBuffer();
         _charBufferLength = _charBuffer.length;
         _cfgUnqNames = !Feature.QUOTE_FIELD_NAMES.enabledIn(features);
@@ -162,6 +162,12 @@
         return _outputStream;
     }
 
+    @Override
+    public int getOutputBuffered() {
+        // Assuming tail is always valid, set to 0 on close
+        return _outputTail;
+    }
+
     /*
     /**********************************************************
     /* Overridden methods
@@ -202,15 +208,14 @@
             _flushBuffer();
         }
         _outputBuffer[_outputTail++] = BYTE_QUOTE;
-        name.getChars(0, len, _charBuffer, 0);
         // But as one segment, or multiple?
         if (len <= _outputMaxContiguous) {
             if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
                 _flushBuffer();
             }
-            _writeStringSegment(_charBuffer, 0, len);
+            _writeStringSegment(name, 0, len);
         } else {
-            _writeStringSegments(_charBuffer, 0, len);
+            _writeStringSegments(name, 0, len);
         }
         // and closing quotes; need room for one more char:
         if (_outputTail >= _outputEnd) {
@@ -416,45 +421,22 @@
     @Override
     public void writeString(String text) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (text == null) {
             _writeNull();
             return;
         }
-        // First: can we make a local copy of chars that make up text?
+        // First: if we can't guarantee it all fits, quoted, within output, offline
         final int len = text.length();
-        if (len > _charBufferLength) { // nope: off-line handling
+        if (len > _outputMaxContiguous) { // nope: off-line handling
             _writeStringSegments(text, true);
             return;
         }
-        // yes: good.
-        text.getChars(0, len, _charBuffer, 0);
-        // Output: if we can't guarantee it fits in output buffer, off-line as well:
-        if (len > _outputMaxContiguous) {
-            _writeLongString(_charBuffer, 0, len);
-            return;
-        }
         if ((_outputTail + len) >= _outputEnd) {
             _flushBuffer();
         }
         _outputBuffer[_outputTail++] = BYTE_QUOTE;
-        _writeStringSegment(_charBuffer, 0, len); // we checked space already above
-        /* [JACKSON-462] But that method may have had to expand multi-byte Unicode
-         *   chars, so we must check again
-         */
-        if (_outputTail >= _outputEnd) {
-            _flushBuffer();
-        }
-        _outputBuffer[_outputTail++] = BYTE_QUOTE;
-    }
-
-    private void _writeLongString(char[] text, int offset, int len) throws IOException
-    {
-        if (_outputTail >= _outputEnd) {
-            _flushBuffer();
-        }
-        _outputBuffer[_outputTail++] = BYTE_QUOTE;
-        _writeStringSegments(text, 0, len);
+        _writeStringSegment(text, 0, len); // we checked space already above
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
         }
@@ -464,7 +446,7 @@
     @Override
     public void writeString(char[] text, int offset, int len) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
         }
@@ -488,7 +470,7 @@
     @Override
     public final void writeString(SerializableString text) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
         }
@@ -504,11 +486,11 @@
         }
         _outputBuffer[_outputTail++] = BYTE_QUOTE;
     }
-
+    
     @Override
     public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
         }
@@ -523,7 +505,7 @@
     @Override
     public void writeUTF8String(byte[] text, int offset, int len) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
         }
@@ -590,7 +572,7 @@
     // since 2.5
     @Override
     public void writeRawValue(SerializableString text) throws IOException {
-        _verifyValueWrite("write raw value");
+        _verifyValueWrite(WRITE_RAW);
         byte[] raw = text.asUnquotedUTF8();
         if (raw.length > 0) {
             _writeBytes(raw);
@@ -710,7 +692,7 @@
             byte[] data, int offset, int len)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write binary value");
+        _verifyValueWrite(WRITE_BINARY);
         // Starting quotes
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
@@ -729,7 +711,7 @@
             InputStream data, int dataLength)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write binary value");
+        _verifyValueWrite(WRITE_BINARY);
         // Starting quotes
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
@@ -768,7 +750,7 @@
     public void writeNumber(short s)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         // up to 5 digits and possible minus sign
         if ((_outputTail + 6) >= _outputEnd) {
             _flushBuffer();
@@ -793,7 +775,7 @@
     public void writeNumber(int i)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         // up to 10 digits and possible minus sign
         if ((_outputTail + 11) >= _outputEnd) {
             _flushBuffer();
@@ -819,7 +801,7 @@
     public void writeNumber(long l)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (_cfgNumbersAsStrings) {
             _writeQuotedLong(l);
             return;
@@ -845,7 +827,7 @@
     public void writeNumber(BigInteger value)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (value == null) {
             _writeNull();
         } else if (_cfgNumbersAsStrings) {
@@ -868,7 +850,7 @@
             return;
         }
         // What is the max length for doubles? 40 chars?
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         writeRaw(String.valueOf(d));
     }
 
@@ -884,7 +866,7 @@
             return;
         }
         // What is the max length for floats?
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         writeRaw(String.valueOf(f));
     }
 
@@ -893,7 +875,7 @@
         throws IOException, JsonGenerationException
     {
         // Don't really know max length for big decimal, no point checking
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (value == null) {
             _writeNull();
         } else if (_cfgNumbersAsStrings) {
@@ -910,7 +892,7 @@
     public void writeNumber(String encodedValue)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (_cfgNumbersAsStrings) {
             _writeQuotedRaw(encodedValue);            
         } else {
@@ -935,7 +917,7 @@
     public void writeBoolean(boolean state)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write boolean value");
+        _verifyValueWrite(WRITE_BOOLEAN);
         if ((_outputTail + 5) >= _outputEnd) {
             _flushBuffer();
         }
@@ -949,7 +931,7 @@
     public void writeNull()
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write null value");
+        _verifyValueWrite(WRITE_NULL);
         _writeNull();
     }
 
@@ -960,8 +942,7 @@
      */
 
     @Override
-    protected final void _verifyValueWrite(String typeMsg)
-        throws IOException, JsonGenerationException
+    protected final void _verifyValueWrite(String typeMsg) throws IOException
     {
         int status = _writeContext.writeValue();
         if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
@@ -999,8 +980,7 @@
         _verifyPrettyValueWrite(typeMsg, status);
     }
 
-    protected final void _verifyPrettyValueWrite(String typeMsg, int status)
-        throws IOException, JsonGenerationException
+    protected final void _verifyPrettyValueWrite(String typeMsg, int status) throws IOException
     {
         // If we have a pretty printer, it knows what to do:
         switch (status) {
@@ -1034,8 +1014,7 @@
      */
 
     @Override
-    public void flush()
-        throws IOException
+    public void flush() throws IOException
     {
         _flushBuffer();
         if (_outputStream != null) {
@@ -1046,8 +1025,7 @@
     }
 
     @Override
-    public void close()
-        throws IOException
+    public void close() throws IOException
     {
         super.close();
 
@@ -1055,7 +1033,7 @@
          *   scopes.
          */
         // First: let's see that we still have buffers...
-        if (_outputBuffer != null
+        if ((_outputBuffer != null)
             && isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
             while (true) {
                 JsonStreamContext ctxt = getOutputContext();
@@ -1069,6 +1047,7 @@
             }
         }
         _flushBuffer();
+        _outputTail = 0; // just to ensure we don't think there's anything buffered
 
         /* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
          *   on the underlying Reader, unless we "own" it, or auto-closing
@@ -1162,15 +1141,13 @@
 
         int left = text.length();
         int offset = 0;
-        final char[] cbuf = _charBuffer;
 
         while (left > 0) {
             int len = Math.min(_outputMaxContiguous, left);
-            text.getChars(offset, offset+len, cbuf, 0);
             if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
                 _flushBuffer();
             }
-            _writeStringSegment(cbuf, 0, len);
+            _writeStringSegment(text, offset, len);
             offset += len;
             left -= len;
         }
@@ -1189,8 +1166,7 @@
      * the output buffer. If so, we will need to choose smaller output
      * chunks to write at a time.
      */
-    private final void _writeStringSegments(char[] cbuf, int offset, int totalLen)
-        throws IOException, JsonGenerationException
+    private final void _writeStringSegments(char[] cbuf, int offset, int totalLen) throws IOException
     {
         do {
             int len = Math.min(_outputMaxContiguous, totalLen);
@@ -1203,6 +1179,19 @@
         } while (totalLen > 0);
     }
 
+    private final void _writeStringSegments(String text, int offset, int totalLen) throws IOException
+    {
+        do {
+            int len = Math.min(_outputMaxContiguous, totalLen);
+            if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
+                _flushBuffer();
+            }
+            _writeStringSegment(text, offset, len);
+            offset += len;
+            totalLen -= len;
+        } while (totalLen > 0);
+    }
+
     /*
     /**********************************************************
     /* Internal methods, low-level writing, text segments
@@ -1218,7 +1207,7 @@
      * potentially enough space for other cases (but not necessarily flushed)
      */
     private final void _writeStringSegment(char[] cbuf, int offset, int len)
-        throws IOException, JsonGenerationException
+        throws IOException
     {
         // note: caller MUST ensure (via flushing) there's room for ASCII only
         
@@ -1253,12 +1242,42 @@
         }
     }
 
+    private final void _writeStringSegment(String text, int offset, int len) throws IOException
+    {
+        // note: caller MUST ensure (via flushing) there's room for ASCII only
+        // Fast+tight loop for ASCII-only, no-escaping-needed output
+        len += offset; // becomes end marker, then
+
+        int outputPtr = _outputTail;
+        final byte[] outputBuffer = _outputBuffer;
+        final int[] escCodes = _outputEscapes;
+
+        while (offset < len) {
+            int ch = text.charAt(offset);
+            // note: here we know that (ch > 0x7F) will cover case of escaping non-ASCII too:
+            if (ch > 0x7F || escCodes[ch] != 0) {
+                break;
+            }
+            outputBuffer[outputPtr++] = (byte) ch;
+            ++offset;
+        }
+        _outputTail = outputPtr;
+        if (offset < len) {
+            if (_characterEscapes != null) {
+                _writeCustomStringSegment2(text, offset, len);
+            } else if (_maximumNonEscapedChar == 0) {
+                _writeStringSegment2(text, offset, len);
+            } else {
+                _writeStringSegmentASCII2(text, offset, len);
+            }
+        }
+    }
+
     /**
      * Secondary method called when content contains characters to escape,
      * and/or multi-byte UTF-8 characters.
      */
-    private final void _writeStringSegment2(final char[] cbuf, int offset, final int end)
-        throws IOException, JsonGenerationException
+    private final void _writeStringSegment2(final char[] cbuf, int offset, final int end) throws IOException
     {
         // Ok: caller guarantees buffer can have room; but that may require flushing:
         if ((_outputTail +  6 * (end - offset)) > _outputEnd) {
@@ -1297,6 +1316,44 @@
         _outputTail = outputPtr;
     }
 
+    private final void _writeStringSegment2(final String text, int offset, final int end) throws IOException
+    {
+        if ((_outputTail +  6 * (end - offset)) > _outputEnd) {
+            _flushBuffer();
+        }
+
+        int outputPtr = _outputTail;
+
+        final byte[] outputBuffer = _outputBuffer;
+        final int[] escCodes = _outputEscapes;
+        
+        while (offset < end) {
+            int ch = text.charAt(offset++);
+            if (ch <= 0x7F) {
+                 if (escCodes[ch] == 0) {
+                     outputBuffer[outputPtr++] = (byte) ch;
+                     continue;
+                 }
+                 int escape = escCodes[ch];
+                 if (escape > 0) { // 2-char escape, fine
+                     outputBuffer[outputPtr++] = BYTE_BACKSLASH;
+                     outputBuffer[outputPtr++] = (byte) escape;
+                 } else {
+                     // ctrl-char, 6-byte escape...
+                     outputPtr = _writeGenericEscape(ch, outputPtr);
+                }
+                continue;
+            }
+            if (ch <= 0x7FF) { // fine, just needs 2 byte output
+                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
+                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
+            } else {
+                outputPtr = _outputMultiByteChar(ch, outputPtr);
+            }
+        }
+        _outputTail = outputPtr;
+    }
+    
     /*
     /**********************************************************
     /* Internal methods, low-level writing, text segment
@@ -1308,8 +1365,7 @@
      * Same as <code>_writeStringSegment2(char[], ...)</code., but with
      * additional escaping for high-range code points
      */
-    private final void _writeStringSegmentASCII2(final char[] cbuf, int offset, final int end)
-        throws IOException, JsonGenerationException
+    private final void _writeStringSegmentASCII2(final char[] cbuf, int offset, final int end) throws IOException
     {
         // Ok: caller guarantees buffer can have room; but that may require flushing:
         if ((_outputTail +  6 * (end - offset)) > _outputEnd) {
@@ -1353,6 +1409,50 @@
         _outputTail = outputPtr;
     }
 
+    private final void _writeStringSegmentASCII2(final String text, int offset, final int end) throws IOException
+    {
+        // Ok: caller guarantees buffer can have room; but that may require flushing:
+        if ((_outputTail +  6 * (end - offset)) > _outputEnd) {
+            _flushBuffer();
+        }
+    
+        int outputPtr = _outputTail;
+    
+        final byte[] outputBuffer = _outputBuffer;
+        final int[] escCodes = _outputEscapes;
+        final int maxUnescaped = _maximumNonEscapedChar;
+        
+        while (offset < end) {
+            int ch = text.charAt(offset++);
+            if (ch <= 0x7F) {
+                 if (escCodes[ch] == 0) {
+                     outputBuffer[outputPtr++] = (byte) ch;
+                     continue;
+                 }
+                 int escape = escCodes[ch];
+                 if (escape > 0) { // 2-char escape, fine
+                     outputBuffer[outputPtr++] = BYTE_BACKSLASH;
+                     outputBuffer[outputPtr++] = (byte) escape;
+                 } else {
+                     // ctrl-char, 6-byte escape...
+                     outputPtr = _writeGenericEscape(ch, outputPtr);
+                 }
+                 continue;
+            }
+            if (ch > maxUnescaped) { // [JACKSON-102] Allow forced escaping if non-ASCII (etc) chars:
+                outputPtr = _writeGenericEscape(ch, outputPtr);
+                continue;
+            }
+            if (ch <= 0x7FF) { // fine, just needs 2 byte output
+                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
+                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
+            } else {
+                outputPtr = _outputMultiByteChar(ch, outputPtr);
+            }
+        }
+        _outputTail = outputPtr;
+    }
+    
     /*
     /**********************************************************
     /* Internal methods, low-level writing, text segment
@@ -1364,8 +1464,7 @@
      * Same as <code>_writeStringSegmentASCII2(char[], ...)</code., but with
      * additional checking for completely custom escapes
      */
-    private final void _writeCustomStringSegment2(final char[] cbuf, int offset, final int end)
-        throws IOException, JsonGenerationException
+    private final void _writeCustomStringSegment2(final char[] cbuf, int offset, final int end) throws IOException
     {
         // Ok: caller guarantees buffer can have room; but that may require flushing:
         if ((_outputTail +  6 * (end - offset)) > _outputEnd) {
@@ -1422,6 +1521,63 @@
         _outputTail = outputPtr;
     }
 
+    private final void _writeCustomStringSegment2(final String text, int offset, final int end) throws IOException
+    {
+        // Ok: caller guarantees buffer can have room; but that may require flushing:
+        if ((_outputTail +  6 * (end - offset)) > _outputEnd) {
+            _flushBuffer();
+        }
+        int outputPtr = _outputTail;
+    
+        final byte[] outputBuffer = _outputBuffer;
+        final int[] escCodes = _outputEscapes;
+        // may or may not have this limit
+        final int maxUnescaped = (_maximumNonEscapedChar <= 0) ? 0xFFFF : _maximumNonEscapedChar;
+        final CharacterEscapes customEscapes = _characterEscapes; // non-null
+        
+        while (offset < end) {
+            int ch = text.charAt(offset++);
+            if (ch <= 0x7F) {
+                 if (escCodes[ch] == 0) {
+                     outputBuffer[outputPtr++] = (byte) ch;
+                     continue;
+                 }
+                 int escape = escCodes[ch];
+                 if (escape > 0) { // 2-char escape, fine
+                     outputBuffer[outputPtr++] = BYTE_BACKSLASH;
+                     outputBuffer[outputPtr++] = (byte) escape;
+                 } else if (escape == CharacterEscapes.ESCAPE_CUSTOM) {
+                     SerializableString esc = customEscapes.getEscapeSequence(ch);
+                     if (esc == null) {
+                         _reportError("Invalid custom escape definitions; custom escape not found for character code 0x"
+                                 +Integer.toHexString(ch)+", although was supposed to have one");
+                     }
+                     outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end-offset);
+                 } else {
+                     // ctrl-char, 6-byte escape...
+                     outputPtr = _writeGenericEscape(ch, outputPtr);
+                 }
+                 continue;
+            }
+            if (ch > maxUnescaped) { // [JACKSON-102] Allow forced escaping if non-ASCII (etc) chars:
+                outputPtr = _writeGenericEscape(ch, outputPtr);
+                continue;
+            }
+            SerializableString esc = customEscapes.getEscapeSequence(ch);
+            if (esc != null) {
+                outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end-offset);
+                continue;
+            }
+            if (ch <= 0x7FF) { // fine, just needs 2 byte output
+                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
+                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
+            } else {
+                outputPtr = _outputMultiByteChar(ch, outputPtr);
+            }
+        }
+        _outputTail = outputPtr;
+    }
+
     private final int _writeCustomEscape(byte[] outputBuffer, int outputPtr, SerializableString esc, int remainingChars)
         throws IOException, JsonGenerationException
     {
diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
index 8df7c35..cc04868 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
@@ -7,7 +7,7 @@
 import com.fasterxml.jackson.core.base.ParserBase;
 import com.fasterxml.jackson.core.io.CharTypes;
 import com.fasterxml.jackson.core.io.IOContext;
-import com.fasterxml.jackson.core.sym.*;
+import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
 import com.fasterxml.jackson.core.util.*;
 
 import static com.fasterxml.jackson.core.JsonTokenId.*;
@@ -46,14 +46,14 @@
     /**
      * Symbol table that contains field names encountered so far
      */
-    final protected BytesToNameCanonicalizer _symbols;
-    
+    final protected ByteQuadsCanonicalizer _symbols;
+
     /*
     /**********************************************************
     /* Parsing state
     /**********************************************************
      */
-    
+
     /**
      * Temporary buffer used for name parsing.
      */
@@ -70,13 +70,13 @@
      * Temporary storage for partially parsed name bytes.
      */
     private int _quad1;
-    
+
     /*
     /**********************************************************
     /* Input buffering (from former 'StreamBasedParserBase')
     /**********************************************************
      */
-    
+
     protected InputStream _inputStream;
 
     /*
@@ -108,7 +108,7 @@
      */
 
     public UTF8StreamJsonParser(IOContext ctxt, int features, InputStream in,
-            ObjectCodec codec, BytesToNameCanonicalizer sym,
+            ObjectCodec codec, ByteQuadsCanonicalizer sym,
             byte[] inputBuffer, int start, int end,
             boolean bufferRecyclable)
     {
@@ -277,13 +277,12 @@
      */
 
     @Override
-    public String getText()
-        throws IOException, JsonParseException
+    public String getText() throws IOException
     {
         if (_currToken == JsonToken.VALUE_STRING) {
             if (_tokenIncomplete) {
                 _tokenIncomplete = false;
-                _finishString(); // only strings can be incomplete
+                return _finishAndReturnString(); // only strings can be incomplete
             }
             return _textBuffer.contentsAsString();
         }
@@ -294,31 +293,77 @@
     
     // @since 2.1
     @Override
-    public String getValueAsString() throws IOException, JsonParseException
+    public String getValueAsString() throws IOException
     {
         if (_currToken == JsonToken.VALUE_STRING) {
             if (_tokenIncomplete) {
                 _tokenIncomplete = false;
-                _finishString(); // only strings can be incomplete
+                return _finishAndReturnString(); // only strings can be incomplete
             }
             return _textBuffer.contentsAsString();
         }
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return getCurrentName();
+        }
         return super.getValueAsString(null);
     }
     
     // @since 2.1
     @Override
-    public String getValueAsString(String defValue) throws IOException, JsonParseException
+    public String getValueAsString(String defValue) throws IOException
     {
         if (_currToken == JsonToken.VALUE_STRING) {
             if (_tokenIncomplete) {
                 _tokenIncomplete = false;
-                _finishString(); // only strings can be incomplete
+                return _finishAndReturnString(); // only strings can be incomplete
             }
             return _textBuffer.contentsAsString();
         }
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return getCurrentName();
+        }
         return super.getValueAsString(defValue);
     }
+
+    // since 2.6
+    @Override
+    public int getValueAsInt() throws IOException
+    {
+        JsonToken t = _currToken;
+        if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) {
+            // inlined 'getIntValue()'
+            if ((_numTypesValid & NR_INT) == 0) {
+                if (_numTypesValid == NR_UNKNOWN) {
+                    return _parseIntValue();
+                }
+                if ((_numTypesValid & NR_INT) == 0) {
+                    convertNumberToInt();
+                }
+            }
+            return _numberInt;
+        }
+        return super.getValueAsInt(0);
+    }
+
+    // since 2.6
+    @Override
+    public int getValueAsInt(int defValue) throws IOException
+    {
+        JsonToken t = _currToken;
+        if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) {
+            // inlined 'getIntValue()'
+            if ((_numTypesValid & NR_INT) == 0) {
+                if (_numTypesValid == NR_UNKNOWN) {
+                    return _parseIntValue();
+                }
+                if ((_numTypesValid & NR_INT) == 0) {
+                    convertNumberToInt();
+                }
+            }
+            return _numberInt;
+        }
+        return super.getValueAsInt(defValue);
+    }
     
     protected final String _getText2(JsonToken t)
     {
@@ -340,8 +385,7 @@
     }
 
     @Override
-    public char[] getTextCharacters()
-        throws IOException, JsonParseException
+    public char[] getTextCharacters() throws IOException
     {
         if (_currToken != null) { // null only before/after document
             switch (_currToken.id()) {
@@ -378,8 +422,7 @@
     }
 
     @Override
-    public int getTextLength()
-        throws IOException, JsonParseException
+    public int getTextLength() throws IOException
     {
         if (_currToken != null) { // null only before/after document
             switch (_currToken.id()) {
@@ -404,7 +447,7 @@
     }
 
     @Override
-    public int getTextOffset() throws IOException, JsonParseException
+    public int getTextOffset() throws IOException
     {
         // Most have offset of 0, only some may have other values:
         if (_currToken != null) {
@@ -427,8 +470,7 @@
     }
     
     @Override
-    public byte[] getBinaryValue(Base64Variant b64variant)
-        throws IOException, JsonParseException
+    public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
     {
         if (_currToken != JsonToken.VALUE_STRING &&
                 (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT || _binaryValue == null)) {
@@ -459,8 +501,7 @@
     }
 
     @Override
-    public int readBinaryValue(Base64Variant b64variant, OutputStream out)
-        throws IOException, JsonParseException
+    public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException
     {
         // if we have already read the token, just use whatever we may have
         if (!_tokenIncomplete || _currToken != JsonToken.VALUE_STRING) {
@@ -478,8 +519,7 @@
     }
 
     protected int _readBinary(Base64Variant b64variant, OutputStream out,
-                              byte[] buffer)
-        throws IOException, JsonParseException
+                              byte[] buffer) throws IOException
     {
         int outputPtr = 0;
         final int outputEnd = buffer.length - 3;
@@ -697,8 +737,8 @@
             return _nextTokenNotInObject(i);
         }
         // So first parse the field name itself:
-        Name n = _parseName(i);
-        _parsingContext.setCurrentName(n.getName());
+        String n = _parseName(i);
+        _parsingContext.setCurrentName(n);
         _currToken = JsonToken.FIELD_NAME;
 
         i = _skipColon();
@@ -894,7 +934,8 @@
                     while (true) {
                         if (ptr == end) { // yes, match!
                             _parsingContext.setCurrentName(str.getValue());
-                            _isNextTokenNameYes(_skipColonFast(ptr+1));
+                            i = _skipColonFast(ptr+1);
+                            _isNextTokenNameYes(i);
                             return true;
                         }
                         if (nameBytes[offset] != _inputBuffer[ptr]) {
@@ -964,8 +1005,7 @@
             return null;
         }
 
-        Name n = _parseName(i);
-        final String nameStr = n.getName();
+        final String nameStr = _parseName(i);
         _parsingContext.setCurrentName(nameStr);
         _currToken = JsonToken.FIELD_NAME;
 
@@ -980,11 +1020,6 @@
         case '-':
             t = _parseNegNumber();
             break;
-
-            /* Should we have separate handling for plus? Although
-             * it is not allowed per se, it may be erroneously used,
-             * and could be indicate by a more specific error message.
-             */
         case '0':
         case '1':
         case '2':
@@ -1122,14 +1157,10 @@
     private final boolean _isNextTokenNameMaybe(int i, SerializableString str) throws IOException
     {
         // // // and this is back to standard nextToken()
-            
-        Name n = _parseName(i);
-        final boolean match;
-        {
-            String nameStr = n.getName();
-            _parsingContext.setCurrentName(nameStr);
-            match = nameStr.equals(str.getValue());
-        }
+
+        String n = _parseName(i);
+        _parsingContext.setCurrentName(n);
+        final boolean match = n.equals(str.getValue());
         _currToken = JsonToken.FIELD_NAME;
         i = _skipColon();
 
@@ -1194,7 +1225,7 @@
             if (t == JsonToken.VALUE_STRING) {
                 if (_tokenIncomplete) {
                     _tokenIncomplete = false;
-                    _finishString();
+                    return _finishAndReturnString();
                 }
                 return _textBuffer.contentsAsString();
             }
@@ -1278,16 +1309,16 @@
             return null;
         }
 
-        switch (nextToken().id()) {
-        case ID_TRUE:
+        JsonToken t = nextToken();
+        if (t == JsonToken.VALUE_TRUE) {
             return Boolean.TRUE;
-        case ID_FALSE:
-            return Boolean.FALSE;
-        default:
-            return null;
         }
+        if (t == JsonToken.VALUE_FALSE) {
+            return Boolean.FALSE;
+        }
+        return null;
     }
-    
+
     /*
     /**********************************************************
     /* Internal methods, number parsing
@@ -1613,13 +1644,13 @@
     /**********************************************************
      */
     
-    protected final Name _parseName(int i) throws IOException
+    protected final String _parseName(int i) throws IOException
     {
         if (i != INT_QUOTE) {
             return _handleOddName(i);
         }
         // First: can we optimize out bounds checks?
-        if ((_inputPtr + 9) > _inputEnd) { // Need 8 chars, plus one trailing (quote)
+        if ((_inputPtr + 13) > _inputEnd) { // Need up to 12 chars, plus one trailing (quote)
             return slowParseName();
         }
 
@@ -1670,12 +1701,12 @@
             return parseName(q, i, 1);
         }     
         if (q == INT_QUOTE) { // special case, ""
-            return BytesToNameCanonicalizer.getEmptyName();
+            return "";
         }
         return parseName(0, q, 0); // quoting or invalid char
     }
 
-    protected final Name parseMediumName(int q2) throws IOException
+    protected final String parseMediumName(int q2) throws IOException
     {
         final byte[] input = _inputBuffer;
         final int[] codes = _icLatin1;
@@ -1712,18 +1743,62 @@
             }
             return parseName(_quad1, q2, i, 4);
         }
-        return parseLongName(i, q2);
+        return parseMediumName2(i, q2);
     }
 
-    protected final Name parseLongName(int q, final int q2) throws IOException
+    /**
+     * @since 2.6
+     */
+    protected final String parseMediumName2(int q3, final int q2) throws IOException
+    {
+        final byte[] input = _inputBuffer;
+        final int[] codes = _icLatin1;
+
+        // Got 9 name bytes so far
+        int i = input[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 9 bytes
+                return findName(_quad1, q2, q3, 1);
+            }
+            return parseName(_quad1, q2, q3, i, 1);
+        }
+        q3 = (q3 << 8) | i;
+        i = input[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 10 bytes
+                return findName(_quad1, q2, q3, 2);
+            }
+            return parseName(_quad1, q2, q3, i, 2);
+        }
+        q3 = (q3 << 8) | i;
+        i = input[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 11 bytes
+                return findName(_quad1, q2, q3, 3);
+            }
+            return parseName(_quad1, q2, q3, i, 3);
+        }
+        q3 = (q3 << 8) | i;
+        i = input[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 12 bytes
+                return findName(_quad1, q2, q3, 4);
+            }
+            return parseName(_quad1, q2, q3, i, 4);
+        }
+        return parseLongName(i, q2, q3);
+    }
+    
+    protected final String parseLongName(int q, final int q2, int q3) throws IOException
     {
         _quadBuffer[0] = _quad1;
         _quadBuffer[1] = q2;
+        _quadBuffer[2] = q3;
 
         // As explained above, will ignore UTF-8 encoding at this point
         final byte[] input = _inputBuffer;
         final int[] codes = _icLatin1;
-        int qlen = 2;
+        int qlen = 3;
 
         while ((_inputPtr + 4) <= _inputEnd) {
             int i = input[_inputPtr++] & 0xFF;
@@ -1778,10 +1853,10 @@
 
     /**
      * Method called when not even first 8 bytes are guaranteed
-     * to come consecutively. Happens rarely, so this is off-lined;
+     * to come consequtively. Happens rarely, so this is offlined;
      * plus we'll also do full checks for escaping etc.
      */
-    protected Name slowParseName() throws IOException
+    protected String slowParseName() throws IOException
     {
         if (_inputPtr >= _inputEnd) {
             if (!loadMore()) {
@@ -1790,28 +1865,34 @@
         }
         int i = _inputBuffer[_inputPtr++] & 0xFF;
         if (i == INT_QUOTE) { // special case, ""
-            return BytesToNameCanonicalizer.getEmptyName();
+            return "";
         }
         return parseEscapedName(_quadBuffer, 0, 0, i, 0);
     }
 
-    private final Name parseName(int q1, int ch, int lastQuadBytes) throws IOException {
+    private final String parseName(int q1, int ch, int lastQuadBytes) throws IOException {
         return parseEscapedName(_quadBuffer, 0, q1, ch, lastQuadBytes);
     }
 
-    private final Name parseName(int q1, int q2, int ch, int lastQuadBytes) throws IOException {
+    private final String parseName(int q1, int q2, int ch, int lastQuadBytes) throws IOException {
         _quadBuffer[0] = q1;
         return parseEscapedName(_quadBuffer, 1, q2, ch, lastQuadBytes);
     }
 
+    private final String parseName(int q1, int q2, int q3, int ch, int lastQuadBytes) throws IOException {
+        _quadBuffer[0] = q1;
+        _quadBuffer[1] = q2;
+        return parseEscapedName(_quadBuffer, 2, q3, ch, lastQuadBytes);
+    }
+    
     /**
      * Slower parsing method which is generally branched to when
      * an escape sequence is detected (or alternatively for long
-     * names, or ones crossing input buffer boundary). In any case,
-     * needs to be able to handle more exceptional cases, gets
-     * slower, and hance is offlined to a separate method.
+     * names, one crossing input buffer boundary).
+     * Needs to be able to handle more exceptional cases, gets slower,
+     * and hance is offlined to a separate method.
      */
-    protected final Name parseEscapedName(int[] quads, int qlen, int currQuad, int ch,
+    protected final String parseEscapedName(int[] quads, int qlen, int currQuad, int ch,
             int currQuadBytes) throws IOException
     {
         /* 25-Nov-2008, tatu: This may seem weird, but here we do not want to worry about
@@ -1890,13 +1971,14 @@
             }
             ch = _inputBuffer[_inputPtr++] & 0xFF;
         }
+
         if (currQuadBytes > 0) {
             if (qlen >= quads.length) {
                 _quadBuffer = quads = growArrayBy(quads, quads.length);
             }
             quads[qlen++] = pad(currQuad, currQuadBytes);
         }
-        Name name = _symbols.findName(quads, qlen);
+        String name = _symbols.findName(quads, qlen);
         if (name == null) {
             name = addName(quads, qlen, currQuadBytes);
         }
@@ -1909,7 +1991,7 @@
      * In standard mode will just throw an expection; but
      * in non-standard modes may be able to parse name.
      */
-    protected Name _handleOddName(int ch) throws IOException
+    protected String _handleOddName(int ch) throws IOException
     {
         // [JACKSON-173]: allow single quotes
         if (ch == '\'' && isEnabled(Feature.ALLOW_SINGLE_QUOTES)) {
@@ -1970,7 +2052,7 @@
             }
             quads[qlen++] = currQuad;
         }
-        Name name = _symbols.findName(quads, qlen);
+        String name = _symbols.findName(quads, qlen);
         if (name == null) {
             name = addName(quads, qlen, currQuadBytes);
         }
@@ -1982,7 +2064,7 @@
      * for valid JSON -- more alternatives, more code, generally
      * bit slower execution.
      */
-    protected Name _parseAposName() throws IOException
+    protected String _parseAposName() throws IOException
     {
         if (_inputPtr >= _inputEnd) {
             if (!loadMore()) {
@@ -1991,7 +2073,7 @@
         }
         int ch = _inputBuffer[_inputPtr++] & 0xFF;
         if (ch == '\'') { // special case, ''
-            return BytesToNameCanonicalizer.getEmptyName();
+            return "";
         }
         int[] quads = _quadBuffer;
         int qlen = 0;
@@ -2080,7 +2162,7 @@
             }
             quads[qlen++] = pad(currQuad, currQuadBytes);
         }
-        Name name = _symbols.findName(quads, qlen);
+        String name = _symbols.findName(quads, qlen);
         if (name == null) {
             name = addName(quads, qlen, currQuadBytes);
         }
@@ -2093,12 +2175,11 @@
     /**********************************************************
      */
 
-    private final Name findName(int q1, int lastQuadBytes)
-        throws JsonParseException
+    private final String findName(int q1, int lastQuadBytes) throws JsonParseException
     {
         q1 = pad(q1, lastQuadBytes);
         // Usually we'll find it from the canonical symbol table already
-        Name name = _symbols.findName(q1);
+        String name = _symbols.findName(q1);
         if (name != null) {
             return name;
         }
@@ -2107,12 +2188,11 @@
         return addName(_quadBuffer, 1, lastQuadBytes);
     }
 
-    private final Name findName(int q1, int q2, int lastQuadBytes)
-        throws JsonParseException
+    private final String findName(int q1, int q2, int lastQuadBytes) throws JsonParseException
     {
         q2 = pad(q2, lastQuadBytes);
         // Usually we'll find it from the canonical symbol table already
-        Name name = _symbols.findName(q1, q2);
+        String name = _symbols.findName(q1, q2);
         if (name != null) {
             return name;
         }
@@ -2122,14 +2202,27 @@
         return addName(_quadBuffer, 2, lastQuadBytes);
     }
 
-    private final Name findName(int[] quads, int qlen, int lastQuad, int lastQuadBytes)
-        throws JsonParseException
+    private final String findName(int q1, int q2, int q3, int lastQuadBytes) throws JsonParseException
+    {
+        q3 = pad(q3, lastQuadBytes);
+        String name = _symbols.findName(q1, q2, q3);
+        if (name != null) {
+            return name;
+        }
+        int[] quads = _quadBuffer;
+        quads[0] = q1;
+        quads[1] = q2;
+        quads[2] = pad(q3, lastQuadBytes);
+        return addName(quads, 3, lastQuadBytes);
+    }
+    
+    private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadBytes) throws JsonParseException
     {
         if (qlen >= quads.length) {
             _quadBuffer = quads = growArrayBy(quads, quads.length);
         }
         quads[qlen++] = pad(lastQuad, lastQuadBytes);
-        Name name = _symbols.findName(quads, qlen);
+        String name = _symbols.findName(quads, qlen);
         if (name == null) {
             return addName(quads, qlen, lastQuadBytes);
         }
@@ -2142,8 +2235,7 @@
      * multi-byte chars (if any), and then construct Name instance
      * and add it to the symbol table.
      */
-    private final Name addName(int[] quads, int qlen, int lastQuadBytes)
-        throws JsonParseException
+    private final String addName(int[] quads, int qlen, int lastQuadBytes) throws JsonParseException
     {
         /* Ok: must decode UTF-8 chars. No other validation is
          * needed, since unescaping has been done earlier as necessary
@@ -2289,6 +2381,40 @@
         _finishString2(outBuf, outPtr);
     }
 
+    /**
+     * @since 2.6
+     */
+    protected String _finishAndReturnString() throws IOException
+    {
+        // First, single tight loop for ASCII content, not split across input buffer boundary:        
+        int ptr = _inputPtr;
+        if (ptr >= _inputEnd) {
+            loadMoreGuaranteed();
+            ptr = _inputPtr;
+        }
+        int outPtr = 0;
+        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+        final int[] codes = _icUTF8;
+
+        final int max = Math.min(_inputEnd, (ptr + outBuf.length));
+        final byte[] inputBuffer = _inputBuffer;
+        while (ptr < max) {
+            int c = (int) inputBuffer[ptr] & 0xFF;
+            if (codes[c] != 0) {
+                if (c == INT_QUOTE) {
+                    _inputPtr = ptr+1;
+                    return _textBuffer.setCurrentAndReturn(outPtr);
+                }
+                break;
+            }
+            ++ptr;
+            outBuf[outPtr++] = (char) c;
+        }
+        _inputPtr = ptr;
+        _finishString2(outBuf, outPtr);
+        return _textBuffer.contentsAsString();
+    }
+    
     private final void _finishString2(char[] outBuf, int outPtr)
         throws IOException
     {
@@ -2857,7 +2983,7 @@
         }
         return _skipColon2(false);
     }
-    
+
     private final int _skipColon2(boolean gotColon) throws IOException
     {
         while (_inputPtr < _inputEnd || loadMore()) {
diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java
index 0e8b6c5..4e91969 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java
@@ -87,7 +87,14 @@
     public Object getOutputTarget() {
         return _writer;
     }
-    
+
+    @Override
+    public int getOutputBuffered() {
+        // Assuming tail and head are kept but... trust and verify:
+        int len = _outputTail - _outputHead;
+        return Math.max(0, len);
+    }
+
     /*
     /**********************************************************
     /* Overridden methods
@@ -319,7 +326,7 @@
     @Override
     public void writeString(String text) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (text == null) {
             _writeNull();
             return;
@@ -339,7 +346,7 @@
     @Override
     public void writeString(char[] text, int offset, int len) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
         }
@@ -355,7 +362,7 @@
     @Override
     public void writeString(SerializableString sstr) throws IOException
     {
-        _verifyValueWrite("write text value");
+        _verifyValueWrite(WRITE_STRING);
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
         }
@@ -507,7 +514,7 @@
     public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write binary value");
+        _verifyValueWrite(WRITE_BINARY);
         // Starting quotes
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
@@ -526,7 +533,7 @@
             InputStream data, int dataLength)
         throws IOException, JsonGenerationException
     {
-        _verifyValueWrite("write binary value");
+        _verifyValueWrite(WRITE_BINARY);
         // Starting quotes
         if (_outputTail >= _outputEnd) {
             _flushBuffer();
@@ -564,7 +571,7 @@
     @Override
     public void writeNumber(short s) throws IOException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (_cfgNumbersAsStrings) {
             _writeQuotedShort(s);
             return;
@@ -588,7 +595,7 @@
     @Override
     public void writeNumber(int i) throws IOException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (_cfgNumbersAsStrings) {
             _writeQuotedInt(i);
             return;
@@ -612,7 +619,7 @@
     @Override
     public void writeNumber(long l) throws IOException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (_cfgNumbersAsStrings) {
             _writeQuotedLong(l);
             return;
@@ -638,7 +645,7 @@
     @Override
     public void writeNumber(BigInteger value) throws IOException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (value == null) {
             _writeNull();
         } else if (_cfgNumbersAsStrings) {
@@ -659,7 +666,7 @@
             return;
         }
         // What is the max length for doubles? 40 chars?
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         writeRaw(String.valueOf(d));
     }
 
@@ -673,7 +680,7 @@
             return;
         }
         // What is the max length for floats?
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         writeRaw(String.valueOf(f));
     }
 
@@ -681,7 +688,7 @@
     public void writeNumber(BigDecimal value) throws IOException
     {
         // Don't really know max length for big decimal, no point checking
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (value == null) {
             _writeNull();
         } else if (_cfgNumbersAsStrings) {
@@ -697,7 +704,7 @@
     @Override
     public void writeNumber(String encodedValue) throws IOException
     {
-        _verifyValueWrite("write number");
+        _verifyValueWrite(WRITE_NUMBER);
         if (_cfgNumbersAsStrings) {
             _writeQuotedRaw(encodedValue);            
         } else {
@@ -721,7 +728,7 @@
     @Override
     public void writeBoolean(boolean state) throws IOException
     {
-        _verifyValueWrite("write boolean value");
+        _verifyValueWrite(WRITE_BOOLEAN);
         if ((_outputTail + 5) >= _outputEnd) {
             _flushBuffer();
         }
@@ -744,7 +751,7 @@
 
     @Override
     public void writeNull() throws IOException {
-        _verifyValueWrite("write null value");
+        _verifyValueWrite(WRITE_NULL);
         _writeNull();
     }
 
@@ -840,8 +847,7 @@
     }
 
     @Override
-    public void close()
-        throws IOException
+    public void close() throws IOException
     {
         super.close();
 
@@ -863,6 +869,8 @@
             }
         }
         _flushBuffer();
+        _outputHead = 0;
+        _outputTail = 0;
 
         /* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
          *   on the underlying Reader, unless we "own" it, or auto-closing
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/ByteQuadsCanonicalizer.java b/src/main/java/com/fasterxml/jackson/core/sym/ByteQuadsCanonicalizer.java
new file mode 100644
index 0000000..d9e6c4f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/ByteQuadsCanonicalizer.java
@@ -0,0 +1,1272 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.util.InternCache;
+
+/**
+ * Replacement for <code>BytesToNameCanonicalizer</code> which aims at more localized
+ * memory access due to flattening of name quad data.
+ * Performance improvement modest for simple JSON document data binding (maybe 3%),
+ * but should help more for larger symbol tables, or for binary formats like Smile.
+ *
+ * @since 2.6
+ */
+public final class ByteQuadsCanonicalizer
+{
+    /**
+     * Initial size of the primary hash area. Each entry consumes 4 ints (16 bytes),
+     * and secondary area is same as primary; so default size will use 2kB of memory_tertiaryStart
+     * (plus 64x4 or 64x8 (256/512 bytes) for references to Strings, and Strings
+     * themselves).
+     */
+    private static final int DEFAULT_T_SIZE = 64;
+//    private static final int DEFAULT_T_SIZE = 256;
+
+    /**
+     * Let's not expand symbol tables past some maximum size;
+     * this should protected against OOMEs caused by large documents
+     * with unique (~= random) names.
+     * Size is in 
+     */
+    private static final int MAX_T_SIZE = 0x10000; // 64k entries == 2M mem hash area
+
+    /**
+     * No point in trying to construct tiny tables, just need to resize soon.
+     */
+    final static int MIN_HASH_SIZE = 16;
+    
+    /**
+     * Let's only share reasonably sized symbol tables. Max size set to 3/4 of 8k;
+     * this corresponds to 256k main hash index. This should allow for enough distinct
+     * names for almost any case, while preventing ballooning for cases where names
+     * are unique (or close thereof).
+     */
+    final static int MAX_ENTRIES_FOR_REUSE = 6000;
+
+    /*
+    /**********************************************************
+    /* Linkage, needed for merging symbol tables
+    /**********************************************************
+     */
+
+    /**
+     * Reference to the root symbol table, for child tables, so
+     * that they can merge table information back as necessary.
+     */
+    final protected ByteQuadsCanonicalizer _parent;
+
+    /**
+     * Member that is only used by the root table instance: root
+     * passes immutable state into child instances, and children
+     * may return new state if they add entries to the table.
+     * Child tables do NOT use the reference.
+     */
+    final protected AtomicReference<TableInfo> _tableInfo;
+    
+    /**
+     * Seed value we use as the base to make hash codes non-static between
+     * different runs, but still stable for lifetime of a single symbol table
+     * instance.
+     * This is done for security reasons, to avoid potential DoS attack via
+     * hash collisions.
+     */
+    final private int _seed;
+    
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    /**
+     * Whether canonical symbol Strings are to be intern()ed before added
+     * to the table or not.
+     *<p>
+     * NOTE: non-final to allow disabling intern()ing in case of excessive
+     * collisions.
+     */
+    protected boolean _intern;
+
+    /**
+     * Flag that indicates whether we should throw an exception if enough 
+     * hash collisions are detected (true); or just worked around (false).
+     * 
+     * @since 2.4
+     */
+    protected final boolean _failOnDoS;
+    
+    /*
+    /**********************************************************
+    /* First, main hash area info
+    /**********************************************************
+     */
+
+    /**
+     * Primary hash information area: consists of <code>2 * _hashSize</code>
+     * entries of 16 bytes (4 ints), arranged in a cascading lookup
+     * structure (details of which may be tweaked depending on expected rates
+     * of collisions).
+     */
+    protected int[] _hashArea;
+
+    /**
+     * Number of slots for primary entries within {@link #_hashArea}; which is
+     * at most <code>1/8</code> of actual size of the underlying array (4-int slots,
+     * primary covers only half of the area; plus, additional area for longer
+     * symbols after hash area).
+     */
+    protected int _hashSize;
+
+    /**
+     * Offset within {@link #_hashArea} where secondary entries start
+     */
+    protected int _secondaryStart;
+
+    /**
+     * Offset within {@link #_hashArea} where tertiary entries start
+     */
+    protected int _tertiaryStart;
+    
+    /**
+     * Constant that determines size of buckets for tertiary entries:
+     * <code>1 &lt;&lt; _tertiaryShift</code> is the size, and shift value
+     * is also used for translating from primary offset into
+     * tertiary bucket (shift right by <code>4 + _tertiaryShift</code>).
+     *<p>
+     * Default value is 2, for buckets of 4 slots; grows bigger with
+     * bigger table sizes.
+     */
+    protected int _tertiaryShift;
+
+    /**
+     * Total number of Strings in the symbol table; only used for child tables.
+     */
+    protected int _count;
+
+    /**
+     * Array that contains <code>String</code> instances matching
+     * entries in {@link #_hashArea}.
+     * Contains nulls for unused entries. Note that this size is twice
+     * that of {@link #_hashArea}
+     */
+    protected String[] _names;
+
+    /*
+    /**********************************************************
+    /* Then information on collisions etc
+    /**********************************************************
+     */
+
+    /**
+     * Pointer to the offset within spill-over area where there is room
+     * for more spilled over entries (if any).
+     * Spill over area is within fixed-size portion of {@link #_hashArea}.
+     */
+    protected int _spilloverEnd;
+
+    /**
+     * Offset within {@link #_hashArea} that follows main slots and contains
+     * quads for longer names (13 bytes or longers), and points to the
+     * first available int that may be used for appending quads of the next
+     * long name.
+     * Note that long name area follows immediately after the fixed-size
+     * main hash area ({@link #_hashArea}).
+     */
+    protected int _longNameOffset;
+
+    /**
+     * This flag is set if, after adding a new entry, it is deemed
+     * that a rehash is warranted if any more entries are to be added.
+     */
+    private transient boolean _needRehash;
+
+    /*
+    /**********************************************************
+    /* Sharing, versioning
+    /**********************************************************
+     */
+
+    // // // Which of the buffers may be shared (and are copy-on-write)?
+
+    /**
+     * Flag that indicates whether underlying data structures for
+     * the main hash area are shared or not. If they are, then they
+     * need to be handled in copy-on-write way, i.e. if they need
+     * to be modified, a copy needs to be made first; at this point
+     * it will not be shared any more, and can be modified.
+     *<p>
+     * This flag needs to be checked both when adding new main entries,
+     * and when adding new collision list queues (i.e. creating a new
+     * collision list head entry)
+     */
+    private boolean _hashShared;
+
+    /*
+    /**********************************************************
+    /* Life-cycle: constructors
+    /**********************************************************
+     */
+
+    /**
+     * Constructor used for creating per-<code>JsonFactory</code> "root"
+     * symbol tables: ones used for merging and sharing common symbols
+     * 
+     * @param sz Initial primary hash area size
+     * @param intern Whether Strings contained should be {@link String#intern}ed
+     * @param seed Random seed valued used to make it more difficult to cause
+     *   collisions (used for collision-based DoS attacks).
+     */
+    private ByteQuadsCanonicalizer(int sz, boolean intern, int seed, boolean failOnDoS) {
+        _parent = null;
+        _seed = seed;
+        _intern = intern;
+        _failOnDoS = failOnDoS;
+        // Sanity check: let's now allow hash sizes below certain minimum value
+        if (sz < MIN_HASH_SIZE) {
+            sz = MIN_HASH_SIZE;
+        } else {
+            // Also; size must be 2^N; otherwise hash algorithm won't
+            // work... so let's just pad it up, if so
+            if ((sz & (sz - 1)) != 0) { // only true if it's 2^N
+                int curr = MIN_HASH_SIZE;
+                while (curr < sz) {
+                    curr += curr;
+                }
+                sz = curr;
+            }
+        }
+        _tableInfo = new AtomicReference<TableInfo>(TableInfo.createInitial(sz));
+    }
+
+    /**
+     * Constructor used when creating a child instance
+     */
+    private ByteQuadsCanonicalizer(ByteQuadsCanonicalizer parent, boolean intern,
+            int seed, boolean failOnDoS, TableInfo state)
+    {
+        _parent = parent;
+        _seed = seed;
+        _intern = intern;
+        _failOnDoS = failOnDoS;
+        _tableInfo = null; // not used by child tables
+
+        // Then copy shared state
+        _count = state.count;
+        _hashSize = state.size;
+        _secondaryStart = _hashSize << 2; // right after primary area
+        _tertiaryStart = _secondaryStart + (_secondaryStart >> 1); // right after secondary
+        _tertiaryShift = state.tertiaryShift;
+        
+        _hashArea = state.mainHash;
+        _names = state.names;
+
+        _spilloverEnd = state.spilloverEnd;
+        _longNameOffset = state.longNameOffset;
+
+        // and then set other state to reflect sharing status
+        _needRehash = false;
+        _hashShared = true;
+    }
+
+    /*
+    /**********************************************************
+    /* Life-cycle: factory methods, merging
+    /**********************************************************
+     */
+    
+    /**
+     * Factory method to call to create a symbol table instance with a
+     * randomized seed value.
+     */
+    public static ByteQuadsCanonicalizer createRoot() {
+        /* [Issue-21]: Need to use a variable seed, to thwart hash-collision
+         * based attacks.
+         */
+        long now = System.currentTimeMillis();
+        // ensure it's not 0; and might as well require to be odd so:
+        int seed = (((int) now) + ((int) (now >>> 32))) | 1;
+        return createRoot(seed);
+    }
+
+    /**
+     * Factory method that should only be called from unit tests, where seed
+     * value should remain the same.
+     */
+    protected static ByteQuadsCanonicalizer createRoot(int seed) {
+        return new ByteQuadsCanonicalizer(DEFAULT_T_SIZE, true, seed, true);
+    }
+    
+    /**
+     * Factory method used to create actual symbol table instance to
+     * use for parsing.
+     */
+    public ByteQuadsCanonicalizer makeChild(int flags) {
+        return new ByteQuadsCanonicalizer(this,
+                JsonFactory.Feature.INTERN_FIELD_NAMES.enabledIn(flags),
+                _seed,
+                JsonFactory.Feature.FAIL_ON_SYMBOL_HASH_OVERFLOW.enabledIn(flags),
+                _tableInfo.get());
+    }
+
+    /**
+     * Method called by the using code to indicate it is done
+     * with this instance. This lets instance merge accumulated
+     * changes into parent (if need be), safely and efficiently,
+     * and without calling code having to know about parent
+     * information
+     */
+    public void release()
+    {
+        // we will try to merge if child table has new entries
+        if (_parent != null && maybeDirty()) {
+            _parent.mergeChild(new TableInfo(this));
+            /* Let's also mark this instance as dirty, so that just in
+             * case release was too early, there's no corruption of possibly shared data.
+             */
+            _hashShared = true;
+        }
+    }
+
+    private void mergeChild(TableInfo childState)
+    {
+        final int childCount = childState.count;
+        TableInfo currState = _tableInfo.get();
+
+        // Should usually grow; but occasionally could also shrink if (but only if)
+        // collision list overflow ends up clearing some collision lists.
+        if (childCount == currState.count) {
+            return;
+        }
+
+        // One caveat: let's try to avoid problems with degenerate cases of documents with
+        // generated "random" names: for these, symbol tables would bloat indefinitely.
+        // One way to do this is to just purge tables if they grow
+        // too large, and that's what we'll do here.
+        if (childCount > MAX_ENTRIES_FOR_REUSE) {
+            // At any rate, need to clean up the tables
+            childState = TableInfo.createInitial(DEFAULT_T_SIZE);
+        }
+        _tableInfo.compareAndSet(currState, childState);
+    }
+
+    /*
+    /**********************************************************
+    /* API, accessors
+    /**********************************************************
+     */
+
+    public int size()
+    {
+        if (_tableInfo != null) { // root table
+            return _tableInfo.get().count;
+        }
+        // nope, child table
+        return _count;
+    }
+
+    /**
+     * Returns number of primary slots table has currently
+     */
+    public int bucketCount() { return _hashSize; }
+
+    /**
+     * Method called to check to quickly see if a child symbol table
+     * may have gotten additional entries. Used for checking to see
+     * if a child table should be merged into shared table.
+     */
+    public boolean maybeDirty() { return !_hashShared; }
+
+    public int hashSeed() { return _seed; }
+    
+    /**
+     * Method mostly needed by unit tests; calculates number of
+     * entries that are in the primary slot set. These are
+     * "perfect" entries, accessible with a single lookup
+     */
+    public int primaryCount()
+    {
+        int count = 0;
+        for (int offset = 3, end = _secondaryStart; offset < end; offset += 4) {
+            if (_hashArea[offset] != 0) {
+                ++count;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Method mostly needed by unit tests; calculates number of entries
+     * in secondary buckets
+     */
+    public int secondaryCount() {
+        int count = 0;
+        int offset = _secondaryStart + 3;
+        for (int end = _tertiaryStart; offset < end; offset += 4) {
+            if (_hashArea[offset] != 0) {
+                ++count;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Method mostly needed by unit tests; calculates number of entries
+     * in tertiary buckets
+     */
+    public int tertiaryCount() {
+        int count = 0;
+        int offset = _tertiaryStart + 3; // to 1.5x, starting point of tertiary
+        for (int end = offset + _hashSize; offset < end; offset += 4) {
+            if (_hashArea[offset] != 0) {
+                ++count;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Method mostly needed by unit tests; calculates number of entries
+     * in shared spillover area
+     */
+    public int spilloverCount() {
+        // difference between spillover end, start, divided by 4 (four ints per slot)
+        return (_spilloverEnd - _spilloverStart()) >> 2;
+    }
+
+    public int totalCount()
+    {
+        int count = 0;
+        for (int offset = 3, end = (_hashSize << 3); offset < end; offset += 4) {
+            if (_hashArea[offset] != 0) {
+                ++count;
+            }
+        }
+        return count;
+    }
+
+    @Override
+    public String toString() {
+        int pri = primaryCount();
+        int sec = secondaryCount();
+        int tert = tertiaryCount();
+        int spill = spilloverCount();
+        int total = totalCount();
+        return String.format("[%s: size=%d, hashSize=%d, %d/%d/%d/%d pri/sec/ter/spill (=%s), total:%d]",
+                getClass().getName(), _count, _hashSize,
+                pri, sec, tert, spill, total, (pri+sec+tert+spill), total);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, accessing symbols
+    /**********************************************************
+     */
+
+    public String findName(int q1)
+    {
+        int offset = _calcOffset(calcHash(q1));
+        // first: primary match?
+        final int[] hashArea = _hashArea;
+
+        int len = hashArea[offset+3];
+
+        if (len == 1) {
+            if (hashArea[offset] == q1) {
+                return _names[offset >> 2];
+            }
+        } else if (len == 0) { // empty slot; unlikely but avoid further lookups if so
+            return null;
+        }
+        // secondary? single slot shared by N/2 primaries
+        int offset2 = _secondaryStart + ((offset >> 3) << 2);
+
+        len = hashArea[offset2+3];
+
+        if (len == 1) {
+            if (hashArea[offset2] == q1) {
+                return _names[offset2 >> 2];
+            }
+        } else if (len == 0) { // empty slot; unlikely but avoid further lookups if so
+            return null;
+        }
+
+        // tertiary lookup & spillovers best to offline
+        return _findSecondary(offset, q1);
+    }
+
+    public String findName(int q1, int q2)
+    {
+        int offset = _calcOffset(calcHash(q1, q2));
+
+        final int[] hashArea = _hashArea;
+
+        int len = hashArea[offset+3];
+
+        if (len == 2) {
+            if ((q1 == hashArea[offset]) && (q2 == hashArea[offset+1])) {
+                return _names[offset >> 2];
+            }
+        } else if (len == 0) { // empty slot; unlikely but avoid further lookups if so
+            return null;
+        }
+        // secondary?
+        int offset2 = _secondaryStart + ((offset >> 3) << 2);
+
+        len = hashArea[offset2+3];
+
+        if (len == 2) {
+            if ((q1 == hashArea[offset2]) && (q2 == hashArea[offset2+1])) {
+                return _names[offset2 >> 2];
+            }
+        } else if (len == 0) { // empty slot? Short-circuit if no more spillovers
+            return null;
+        }
+        return _findSecondary(offset, q1, q2);
+    }
+
+    public String findName(int q1, int q2, int q3)
+    {
+        int offset = _calcOffset(calcHash(q1, q2, q3));
+        final int[] hashArea = _hashArea;
+        int len = hashArea[offset+3];
+
+        if (len == 3) {
+            if ((q1 == hashArea[offset]) && (hashArea[offset+1] == q2) && (hashArea[offset+2] == q3)) {
+                return _names[offset >> 2];
+            }
+        } else if (len == 0) { // empty slot; unlikely but avoid further lookups if so
+            return null;
+        }
+        // secondary?
+        int offset2 = _secondaryStart + ((offset >> 3) << 2);
+
+        len = hashArea[offset2+3];
+
+        if (len == 3) {
+            if ((q1 == hashArea[offset2]) && (hashArea[offset2+1] == q2) && (hashArea[offset2+2] == q3)) {
+                return _names[offset2 >> 2];
+            }
+        } else if (len == 0) { // empty slot? Short-circuit if no more spillovers
+            return null;
+        }
+        return _findSecondary(offset, q1, q2, q3);
+    }
+
+    public String findName(int[] q, int qlen)
+    {
+        /* This version differs significantly, because longer names do not fit within cell.
+         * Rather, they contain hash in main slot, and offset+length to extension area
+         * that contains actual quads.
+         */
+        if (qlen < 4) { // another sanity check
+            if (qlen == 3) {
+                return findName(q[0], q[1], q[2]);
+            }
+            if (qlen == 2) {
+                return findName(q[0], q[1]);
+            }
+            return findName(q[0]);
+        }
+        final int hash = calcHash(q, qlen);
+        int offset = _calcOffset(hash);
+
+        final int[] hashArea = _hashArea;
+
+        final int len = hashArea[offset+3];
+        
+        if ((hash == hashArea[offset]) && (len == qlen)) {
+            // probable but not guaranteed: verify
+            if (_verifyLongName(q, qlen, hashArea[offset+1])) {
+                return _names[offset >> 2];
+            }
+        }
+        if (len == 0) { // empty slot; unlikely but avoid further lookups if so
+            return null;
+        }
+        // secondary?
+        int offset2 = _secondaryStart + ((offset >> 3) << 2);
+
+        final int len2 = hashArea[offset2+3];
+        if ((hash == hashArea[offset2]) && (len2 == qlen)) {
+            if (_verifyLongName(q, qlen, hashArea[offset2+1])) {
+                return _names[offset2 >> 2];
+            }
+        }
+        if (len == 0) { // empty slot? Short-circuit if no more spillovers
+            return null;
+        }
+        return _findSecondary(offset, hash, q, qlen);
+    }
+    
+    private final int _calcOffset(int hash)
+    {
+        // NOTE: simple for initial impl, but we may want to interleave it a bit
+        // in near future
+        // So: first, hash into primary hash index
+        int ix = hash & (_hashSize-1);
+        // keeping in mind we have 4 ints per entry
+        return (ix << 2);
+    }
+
+    /*
+    /**********************************************************
+    /* Access from spill-over areas
+    /**********************************************************
+     */
+
+    private String _findSecondary(int origOffset, int q1)
+    {
+        // tertiary area division is dynamic. First; its size is N/4 compared to
+        // primary hash size; and offsets are for 4 int slots. So to get to logical
+        // index would shift by 4. But! Tertiary area is further split into buckets,
+        // determined by shift value. And finally, from bucket back into physical offsets
+        int offset = _tertiaryStart + ((origOffset >> (_tertiaryShift + 2)) << _tertiaryShift);
+        final int[] hashArea = _hashArea;
+        final int bucketSize = (1 << _tertiaryShift);
+        for (int end = offset + bucketSize; offset < end; offset += 4) {
+            int len = hashArea[offset+3];
+            if ((q1 == hashArea[offset]) && (1 == len)) {
+                return _names[offset >> 2];
+            }
+            if (len == 0) {
+                return null;
+            }
+        }
+        // but if tertiary full, check out spill-over area as last resort
+        // shared spillover starts at 7/8 of the main hash area
+        // (which is sized at 2 * _hashSize), so:
+        for (offset = _spilloverStart(); offset < _spilloverEnd; offset += 4) {
+            if ((q1 == hashArea[offset]) && (1 == hashArea[offset+3])) {
+                return _names[offset >> 2];
+            }
+        }
+        return null;
+    }
+
+    private String _findSecondary(int origOffset, int q1, int q2)
+    {
+        int offset = _tertiaryStart + ((origOffset >> (_tertiaryShift + 2)) << _tertiaryShift);
+        final int[] hashArea = _hashArea;
+
+        final int bucketSize = (1 << _tertiaryShift);
+        for (int end = offset + bucketSize; offset < end; offset += 4) {
+            int len = hashArea[offset+3];
+            if ((q1 == hashArea[offset]) && (q2 == hashArea[offset+1]) && (2 == len)) {
+                return _names[offset >> 2];
+            }
+            if (len == 0) {
+                return null;
+            }
+        }
+        for (offset = _spilloverStart(); offset < _spilloverEnd; offset += 4) {
+            if ((q1 == hashArea[offset]) && (q2 == hashArea[offset+1]) && (2 == hashArea[offset+3])) {
+                return _names[offset >> 2];
+            }
+        }
+        return null;
+    }
+
+    private String _findSecondary(int origOffset, int q1, int q2, int q3)
+    {
+        int offset = _tertiaryStart + ((origOffset >> (_tertiaryShift + 2)) << _tertiaryShift);
+        final int[] hashArea = _hashArea;
+
+        final int bucketSize = (1 << _tertiaryShift);
+        for (int end = offset + bucketSize; offset < end; offset += 4) {
+            int len = hashArea[offset+3];
+            if ((q1 == hashArea[offset]) && (q2 == hashArea[offset+1]) && (q3 == hashArea[offset+2]) && (3 == len)) {
+                return _names[offset >> 2];
+            }
+            if (len == 0) {
+                return null;
+            }
+        }
+        for (offset = _spilloverStart(); offset < _spilloverEnd; offset += 4) {
+            if ((q1 == hashArea[offset]) && (q2 == hashArea[offset+1]) && (q3 == hashArea[offset+2])
+                    && (3 == hashArea[offset+3])) {
+                return _names[offset >> 2];
+            }
+        }
+        return null;
+    }
+
+    private String _findSecondary(int origOffset, int hash, int[] q, int qlen)
+    {
+        int offset = _tertiaryStart + ((origOffset >> (_tertiaryShift + 2)) << _tertiaryShift);
+        final int[] hashArea = _hashArea;
+
+        final int bucketSize = (1 << _tertiaryShift);
+        for (int end = offset + bucketSize; offset < end; offset += 4) {
+            int len = hashArea[offset+3];
+            if ((hash == hashArea[offset]) && (qlen == len)) {
+                if (_verifyLongName(q, qlen, hashArea[offset+1])) {
+                    return _names[offset >> 2];
+                }
+            }
+            if (len == 0) {
+                return null;
+            }
+        }
+        for (offset = _spilloverStart(); offset < _spilloverEnd; offset += 4) {
+            if ((hash == hashArea[offset]) && (qlen == hashArea[offset+3])) {
+                if (_verifyLongName(q, qlen, hashArea[offset+1])) {
+                    return _names[offset >> 2];
+                }
+            }
+        }
+        return null;
+    }
+    
+    private boolean _verifyLongName(int[] q, int qlen, int spillOffset)
+    {
+        final int[] hashArea = _hashArea;
+        // spillOffset assumed to be physical index right into quad string
+        int ix = 0;
+
+        switch (qlen) {
+        default:
+            return _verifyLongName2(q, qlen, spillOffset);
+        case 8:
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+        case 7:
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+        case 6:
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+        case 5:
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+        case 4: // always at least 4
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+            if (q[ix++] != hashArea[spillOffset++]) return false;
+        }
+        return true;
+    }
+
+    private boolean _verifyLongName2(int[] q, int qlen, int spillOffset)
+    {
+        int ix = 0;
+        do {
+            if (q[ix++] != _hashArea[spillOffset++]) {
+                return false;
+            }
+        } while (ix < qlen);
+        return true;
+    }
+
+    /*
+    /**********************************************************
+    /* API, mutators
+    /**********************************************************
+     */
+
+    public String addName(String name, int q1) {
+        _verifySharing();
+        if (_intern) {
+            name = InternCache.instance.intern(name);
+        }
+        int offset = _findOffsetForAdd(calcHash(q1));
+        _hashArea[offset] = q1;
+        _hashArea[offset+3] = 1;
+        _names[offset >> 2] = name;
+        ++_count;
+        _verifyNeedForRehash();
+        return name;
+    }
+
+    public String addName(String name, int q1, int q2) {
+        _verifySharing();
+        if (_intern) {
+            name = InternCache.instance.intern(name);
+        }
+        int hash = (q2 == 0) ? calcHash(q1) : calcHash(q1, q2);
+        int offset = _findOffsetForAdd(hash);
+        _hashArea[offset] = q1;
+        _hashArea[offset+1] = q2;
+        _hashArea[offset+3] = 2;
+        _names[offset >> 2] = name;
+        ++_count;
+        _verifyNeedForRehash();
+        return name;
+    }
+
+    public String addName(String name, int q1, int q2, int q3) {
+        _verifySharing();
+        if (_intern) {
+            name = InternCache.instance.intern(name);
+        }
+        int offset = _findOffsetForAdd(calcHash(q1, q2, q3));
+        _hashArea[offset] = q1;
+        _hashArea[offset+1] = q2;
+        _hashArea[offset+2] = q3;
+        _hashArea[offset+3] = 3;
+        _names[offset >> 2] = name;
+        ++_count;
+        _verifyNeedForRehash();
+        return name;
+    }
+
+    public String addName(String name, int[] q, int qlen)
+    {
+        _verifySharing();
+        if (_intern) {
+            name = InternCache.instance.intern(name);
+        }
+        int offset;
+        
+        switch (qlen) {
+        case 1:
+        {
+                offset = _findOffsetForAdd(calcHash(q[0]));
+                _hashArea[offset] = q[0];
+                _hashArea[offset+3] = 1;
+            }
+            break;
+        case 2:
+            {
+                offset = _findOffsetForAdd(calcHash(q[0], q[1]));
+                _hashArea[offset] = q[0];
+                _hashArea[offset+1] = q[1];
+                _hashArea[offset+3] = 2;
+            }
+            break;
+        case 3:
+            {
+                offset = _findOffsetForAdd(calcHash(q[0], q[1], q[2]));
+                _hashArea[offset] = q[0];
+                _hashArea[offset+1] = q[1];
+                _hashArea[offset+2] = q[2];
+                _hashArea[offset+3] = 3;
+            }
+            break;
+        default:
+            final int hash = calcHash(q, qlen);
+            offset = _findOffsetForAdd(hash);
+
+            _hashArea[offset] = hash;
+            int longStart = _appendLongName(q, qlen);
+            _hashArea[offset+1] = longStart;
+            _hashArea[offset+3] = qlen;
+        }
+        // plus add the actual String
+        _names[offset >> 2] = name;
+
+        // and finally; see if we really should rehash.
+        ++_count;
+        _verifyNeedForRehash();
+        return name;
+    }
+
+    private void _verifyNeedForRehash() {
+        // Yes if above 80%, or above 50% AND have ~1% spill-overs
+        if (_count > (_hashSize >> 1)) { // over 50%
+            int spillCount = (_spilloverEnd - _spilloverStart()) >> 2;
+            if ((spillCount > (1 + _count >> 7))
+                    || (_count > (_hashSize * 0.80))) {
+                _needRehash = true;
+            }
+        }
+    }
+
+    private void _verifySharing()
+    {
+        if (_hashShared) {
+            _hashArea = Arrays.copyOf(_hashArea, _hashArea.length);
+            _names = Arrays.copyOf(_names, _names.length);
+            _hashShared = false;
+            // 09-Sep-2015, tatu: As per [jackson-core#216], also need to ensure
+            //    we rehash as needed, as need-rehash flag is not copied from parent
+            _verifyNeedForRehash();
+        }
+        if (_needRehash) {
+            rehash();
+        }
+    }
+
+    /**
+     * Method called to find the location within hash table to add a new symbol in.
+     */
+    private int _findOffsetForAdd(int hash)
+    {
+        // first, check the primary:
+        int offset = _calcOffset(hash);
+        final int[] hashArea = _hashArea;
+        if (hashArea[offset+3] == 0) {
+//System.err.printf(" PRImary slot #%d, hash %X\n", (offset>>2), hash & 0x7F);
+            return offset;
+        }
+        // then secondary
+        int offset2 = _secondaryStart + ((offset >> 3) << 2);
+        if (hashArea[offset2+3] == 0) {
+//System.err.printf(" SECondary slot #%d (start x%X), hash %X\n",(offset >> 3), _secondaryStart, (hash & 0x7F));
+            return offset2;
+        }
+        // if not, tertiary?
+
+        offset2 = _tertiaryStart + ((offset >> (_tertiaryShift + 2)) << _tertiaryShift);
+        final int bucketSize = (1 << _tertiaryShift);
+        for (int end = offset2 + bucketSize; offset2 < end; offset2 += 4) {
+            if (hashArea[offset2+3] == 0) {
+//System.err.printf(" TERtiary slot x%X (from x%X, start x%X), hash %X.\n", offset2, ((offset >> (_tertiaryShift + 2)) << _tertiaryShift), _tertiaryStart, (hash & 0x7F));
+                return offset2;
+            }
+        }
+
+        // and if even tertiary full, append at the end of spill area
+        offset = _spilloverEnd;
+        _spilloverEnd += 4;
+
+//System.err.printf(" SPIll-over at x%X; start x%X; end x%X, hash %X\n", offset, _spilloverStart(), _hashArea.length, (hash & 0x7F));
+        
+        // one caveat: in the unlikely event if spill-over filling up,
+        // check if that could be considered a DoS attack; handle appropriately
+        // (NOTE: approximate for now; we could verify details if that becomes necessary)
+        /* 31-Jul-2015, tatu: Note that spillover area does NOT end at end of array,
+         *   since "long names" area follows. Instead, need to calculate from hash size.
+         */
+        final int end = (_hashSize << 3);
+        if (_spilloverEnd >= end) {
+            if (_failOnDoS) {
+                _reportTooManyCollisions();
+            }
+            // and if we didn't fail, we'll simply force rehash for next add
+            // (which, in turn, may double up or nuke contents, depending on size etc)
+            _needRehash = true;
+        }
+        return offset;
+    }
+
+    private int _appendLongName(int[] quads, int qlen)
+    {
+        int start = _longNameOffset;
+        
+        // note: at this point we must already be shared. But may not have enough space
+        if ((start + qlen) > _hashArea.length) {
+            // try to increment in reasonable chunks; at least space that we need
+            int toAdd = (start + qlen) - _hashArea.length;
+            // but at least 1/8 of regular hash area size or 16kB (whichever smaller)
+            int minAdd = Math.min(4096, _hashSize);
+
+            int newSize = _hashArea.length + Math.max(toAdd, minAdd);
+            _hashArea = Arrays.copyOf(_hashArea, newSize);
+        }
+        System.arraycopy(quads, 0, _hashArea, start, qlen);
+        _longNameOffset += qlen;
+        return start;
+    }
+
+    /*
+    /**********************************************************
+    /* Hash calculation
+    /**********************************************************
+     */
+
+    /* Note on hash calculation: we try to make it more difficult to
+     * generate collisions automatically; part of this is to avoid
+     * simple "multiply-add" algorithm (like JDK String.hashCode()),
+     * and add bit of shifting. And other part is to make this
+     * non-linear, at least for shorter symbols.
+     */
+    
+    // JDK uses 31; other fine choices are 33 and 65599, let's use 33
+    // as it seems to give fewest collisions for us
+    // (see [http://www.cse.yorku.ca/~oz/hash.html] for details)
+    private final static int MULT = 33;
+    private final static int MULT2 = 65599;
+    private final static int MULT3 = 31;
+    
+    public int calcHash(int q1)
+    {
+        int hash = q1 ^ _seed;
+        /* 29-Mar-2015, tatu: Earlier used 15 + 9 right shifts, which worked ok
+         *    except for one specific problem case: numbers. So needed to make sure
+         *    that all 4 least-significant bits participate in hash. Couple of ways
+         *    to work it out, but this is the simplest, fast and seems to do ok.
+         */
+        hash += (hash >>> 16); // to xor hi- and low- 16-bits
+        hash ^= (hash << 3); // shuffle back a bit
+        hash += (hash >>> 12); // and bit more
+        return hash;
+    }
+
+    public int calcHash(int q1, int q2)
+    {
+        // For two quads, let's change algorithm a bit, to spice
+        // things up (can do bit more processing anyway)
+        int hash = q1;
+
+        hash += (hash >>> 15); // try mixing first and second byte pairs first
+        hash ^= (hash >>> 9); // as well as lowest 2 bytes
+        hash += (q2 * MULT); // then add second quad
+        hash ^= _seed;
+        hash += (hash >>> 16); // and shuffle some more
+        hash ^= (hash >>> 4);
+        hash += (hash << 3);
+        
+        return hash;
+    }
+
+    public int calcHash(int q1, int q2, int q3)
+    { // use same algorithm as multi-byte, tested to work well
+        int hash = q1 ^ _seed;
+        hash += (hash >>> 9);
+        hash *= MULT3;
+        hash += q2;
+        hash *= MULT;
+        hash += (hash >>> 15);
+        hash ^= q3;
+        // 26-Mar-2015, tatu: As per two-quad case, a short shift seems to help more here
+        hash += (hash >>> 4);
+
+        hash += (hash >>> 15);
+        hash ^= (hash << 9);
+
+        return hash;
+    }
+
+    public int calcHash(int[] q, int qlen)
+    {
+        if (qlen < 4) {
+            throw new IllegalArgumentException();
+        }
+        /* And then change handling again for "multi-quad" case; mostly
+         * to make calculation of collisions less fun. For example,
+         * add seed bit later in the game, and switch plus/xor around,
+         * use different shift lengths.
+         */
+        int hash = q[0] ^ _seed;
+        hash += (hash >>> 9);
+        hash += q[1];
+        hash += (hash >>> 15);
+        hash *= MULT;
+        hash ^= q[2];
+        hash += (hash >>> 4);
+
+        for (int i = 3; i < qlen; ++i) {
+            int next = q[i];
+            next = next ^ (next >> 21);
+            hash += next;
+        }
+        hash *= MULT2;
+        
+        // and finally shuffle some more once done
+        hash += (hash >>> 19);
+        hash ^= (hash << 5);
+        return hash;
+    }
+
+    /*
+    /**********************************************************
+    /* Rehashing
+    /**********************************************************
+     */
+
+    private void rehash()
+    {
+        _needRehash = false;
+        // Note: since we'll make copies, no need to unshare, can just mark as such:
+        _hashShared = false;
+
+        // And then we can first deal with the main hash area. Since we are expanding
+        // linearly (double up), we know there'll be no collisions during this phase.
+        final int[] oldHashArea = _hashArea;
+        final String[] oldNames = _names;
+        final int oldSize = _hashSize;
+        final int oldCount = _count;
+        final int newSize = oldSize + oldSize;
+        final int oldEnd = _spilloverEnd;
+
+        /* 13-Mar-2010, tatu: Let's guard against OOME that could be caused by
+         *    large documents with unique (or mostly so) names
+         */
+        if (newSize > MAX_T_SIZE) {
+            nukeSymbols(true);
+            return;
+        }
+        // double up main hash area, but do not expand long-name area:
+        _hashArea = new int[oldHashArea.length + (oldSize<<3)];
+        _hashSize = newSize;
+        _secondaryStart = (newSize << 2); // 4 ints per entry
+        _tertiaryStart = _secondaryStart + (_secondaryStart >> 1); // right after secondary
+        _tertiaryShift = _calcTertiaryShift(newSize);
+        
+        // and simply double up name array
+        _names = new String[oldNames.length << 1];
+        nukeSymbols(false);
+
+        // Plus we can scan only through the primary hash area, looking for non-empty
+        // slots, without worrying about ordering. This should never reduce priority
+        // of existing entries: primaries remain primaries; however, due to increased
+        // space, secondaries may become primaries etc
+
+        int copyCount = 0;
+        int[] q = new int[16];
+        for (int offset = 0, end = oldEnd; offset < end; offset += 4) {
+            int len = oldHashArea[offset+3];
+            if (len == 0) { // empty slot, skip
+                continue;
+            }
+            ++copyCount;
+            String name = oldNames[offset>>2];
+            switch (len) {
+            case 1:
+                q[0] = oldHashArea[offset];
+                addName(name, q, 1);
+                break;
+            case 2:
+                q[0] = oldHashArea[offset];
+                q[1] = oldHashArea[offset+1];
+                addName(name, q, 2);
+                break;
+            case 3:
+                q[0] = oldHashArea[offset];
+                q[1] = oldHashArea[offset+1];
+                q[2] = oldHashArea[offset+2];
+                addName(name, q, 3);
+                break;
+            default:
+                if (len > q.length) {
+                    q = new int[len];
+                }
+                // #0 is hash, #1 offset
+                int qoff = oldHashArea[offset+1];
+                System.arraycopy(oldHashArea, qoff, q, 0, len);
+                addName(name, q, len);
+                break;
+            }
+        }
+
+        // Sanity checks: since corruption difficult to detect, assert explicitly
+        // with production code
+        if (copyCount != oldCount) {
+            throw new IllegalStateException("Failed rehash(): old count="+oldCount+", copyCount="+copyCount);
+        }
+    }
+
+    /**
+     * Helper method called to empty all shared symbols, but to leave
+     * arrays allocated
+     */
+    private void nukeSymbols(boolean fill) {
+        _count = 0;
+        // reset spill-over to empty (starting at 7/8 of hash area)
+        _spilloverEnd = _spilloverStart();
+        // and long name area to empty, starting immediately after hash area
+        _longNameOffset = _hashSize << 3;
+        if (fill) {
+            Arrays.fill(_hashArea, 0);
+            Arrays.fill(_names, null);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    /**
+     * Helper method that calculates start of the spillover area
+     */
+    private final int _spilloverStart() {
+        // we'll need slot at 1.75x of hashSize, but with 4-ints per slot.
+        // So basically multiply by 7
+        int offset = _hashSize;
+        return (offset << 3) - offset;
+    }
+
+    protected void _reportTooManyCollisions()
+    {
+        // First: do not fuzz about small symbol tables; may get balanced by doubling up
+        if (_hashSize <= 1024) { // would have spill-over area of 128 entries
+            return;
+        }
+        throw new IllegalStateException("Spill-over slots in symbol table with "+_count
+                +" entries, hash area of "+_hashSize+" slots is now full (all "
+                +(_hashSize >> 3)+" slots -- suspect a DoS attack based on hash collisions."
+                +" You can disable the check via `JsonFactory.Feature.FAIL_ON_SYMBOL_HASH_OVERFLOW`");
+    }
+
+    static int _calcTertiaryShift(int primarySlots)
+    {
+        // first: we only get 1/4 of slots of primary, to divide
+        int tertSlots = (primarySlots) >> 2;
+        
+        // default is for buckets of 4 slots (each 4 ints, i.e. 1 << 4)
+        if (tertSlots < 64) {
+            return 4;
+        }
+        if (tertSlots <= 256) { // buckets of 8 slots (up to 256 == 32 x 8)
+            return 5;
+        }
+        if (tertSlots <= 1024) { // buckets of 16 slots (up to 1024 == 64 x 16)
+            return 6;
+        }
+        // and biggest buckets have 32 slots
+        return 7;
+    }
+
+    /*
+    /**********************************************************
+    /* Helper classes
+    /**********************************************************
+     */
+
+    /**
+     * Immutable value class used for sharing information as efficiently
+     * as possible, by only require synchronization of reference manipulation
+     * but not access to contents.
+     * 
+     * @since 2.1
+     */
+    private final static class TableInfo
+    {
+        public final int size;
+        public final int count;
+        public final int tertiaryShift;
+        public final int[] mainHash;
+        public final String[] names;
+        public final int spilloverEnd;
+        public final int longNameOffset;
+
+        public TableInfo(int size, int count, int tertiaryShift, 
+                int[] mainHash, String[] names, int spilloverEnd, int longNameOffset)
+        {
+            this.size = size;
+            this.count = count;
+            this.tertiaryShift = tertiaryShift;
+            this.mainHash = mainHash;
+            this.names = names;
+            this.spilloverEnd = spilloverEnd;
+            this.longNameOffset = longNameOffset;
+        }
+
+        public TableInfo(ByteQuadsCanonicalizer src)
+        {
+            size = src._hashSize;
+            count = src._count;
+            tertiaryShift = src._tertiaryShift;
+            mainHash = src._hashArea;
+            names = src._names;
+            spilloverEnd = src._spilloverEnd;
+            longNameOffset = src._longNameOffset;
+        }
+
+        public static TableInfo createInitial(int sz) {
+            int hashAreaSize = sz << 3;
+            int tertShift = _calcTertiaryShift(sz);
+
+            return new TableInfo(sz, // hashSize
+                    0, // count
+                    tertShift,
+                    new int[hashAreaSize], // mainHash, 2x slots, 4 ints per slot
+                    new String[sz << 1], // names == 2x slots
+                    hashAreaSize - sz, // at 7/8 of the total area
+                    hashAreaSize // longNameOffset, immediately after main hashes
+            );
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java b/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java
index 0de6edf..7d610c1 100644
--- a/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java
+++ b/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java
@@ -15,8 +15,9 @@
  * symbol tables, to be able to make use of usually shared vocabulary
  * of subsequent parsing runs.
  *
- * @author Tatu Saloranta
+ * @deprecated Since 2.6, replaced by {@link ByteQuadsCanonicalizer}
  */
+@Deprecated
 public final class BytesToNameCanonicalizer
 {
     private static final int DEFAULT_T_SIZE = 64;
@@ -33,7 +34,7 @@
      * this corresponds to 64k main hash index. This should allow for enough distinct
      * names for almost any case.
      */
-    private final static int MAX_ENTRIES_FOR_REUSE = 6000;
+    final static int MAX_ENTRIES_FOR_REUSE = 6000;
 
     /**
      * Also: to thwart attacks based on hash collisions (which may or may not
@@ -333,13 +334,13 @@
                 0 // longestCollisionList
         );
     }
-    
+
     /*
     /**********************************************************
     /* Life-cycle: factory methods, merging
     /**********************************************************
      */
-    
+
     /**
      * Factory method to call to create a symbol table instance with a
      * randomized seed value.
@@ -361,7 +362,7 @@
     protected static BytesToNameCanonicalizer createRoot(int seed) {
         return new BytesToNameCanonicalizer(DEFAULT_T_SIZE, true, seed, true);
     }
-    
+
     /**
      * Factory method used to create actual symbol table instance to
      * use for parsing.
@@ -592,6 +593,37 @@
         return null;
     }
 
+    public Name findName(int q1, int q2, int q3)
+    {
+        int hash = calcHash(q1, q2, q3);
+        int ix = (hash & _hashMask);
+        int val = _hash[ix];
+        
+        if ((((val >> 8) ^ hash) << 8) == 0) { // match
+            // Ok, but do we have an actual match?
+            Name name = _mainNames[ix];
+            if (name == null) { // main slot empty; can't find
+                return null;
+            }
+            if (name.equals(q1, q2, q3)) {
+                return name;
+            }
+        } else if (val == 0) { // empty slot? no match
+            return null;
+        }
+        // Maybe a spill-over?
+        val &= 0xFF;
+        if (val > 0) { // 0 means 'empty'
+            val -= 1; // to convert from 1-based to 0...
+            Bucket bucket = _collList[val];
+            if (bucket != null) {
+                return bucket.find(hash, q1, q2, q3);
+            }
+        }
+        // Nope, no match whatsoever
+        return null;
+    }
+    
     /**
      * Finds and returns name matching the specified symbol, if such
      * name already exists in the table; or if not, creates name object,
@@ -611,7 +643,10 @@
      */
     public Name findName(int[] q, int qlen)
     {
-        if (qlen < 3) { // another sanity check
+        if (qlen < 4) { // another sanity check
+            if (qlen == 3) {
+                return findName(q[0], q[1], q[2]);
+            }
             return findName(q[0], (qlen < 2) ? 0 : q[1]);
         }
         int hash = calcHash(q, qlen);
@@ -661,8 +696,14 @@
             name = InternCache.instance.intern(name);
         }
         int hash;
-        if (qlen < 3) {
-            hash = (qlen == 1) ? calcHash(q[0]) : calcHash(q[0], q[1]);
+        if (qlen < 4) {
+            if (qlen == 1) {
+                hash = calcHash(q[0]);
+            } else if (qlen == 2) {
+                hash = calcHash(q[0], q[1]);
+            } else {
+                hash = calcHash(q[0], q[1], q[2]);
+            }
         } else {
             hash = calcHash(q, qlen);
         }
@@ -715,10 +756,28 @@
         return hash;
     }
 
+    public int calcHash(int q1, int q2, int q3)
+    {
+        // use same algorithm as multi-byte, tested to work well
+        int hash = q1 ^ _seed;
+        hash += (hash >>> 9);
+        hash *= MULT;
+        hash += q2;
+        hash *= MULT2;
+        hash += (hash >>> 15);
+        hash ^= q3;
+        hash += (hash >>> 17);
+
+        // and finally shuffle some more once done
+        hash += (hash >>> 15); // to get high-order bits to mix more
+        hash ^= (hash << 9); // as well as lowest 2 bytes
+
+        return hash;
+    }
+    
     public int calcHash(int[] q, int qlen)
     {
-        // Note: may be called for qlen < 3; but has at least one int
-        if (qlen < 3) {
+        if (qlen < 4) {
             throw new IllegalArgumentException();
         }
 
@@ -1123,8 +1182,8 @@
             case 2:
                 return new Name2(name, hash, quads[0], quads[1]);
             case 3:
-                return new Name3(name, hash, quads[0], quads[1], quads[2]);
             default:
+                return new Name3(name, hash, quads[0], quads[1], quads[2]);
             }
         }
         return NameN.construct(name, hash, quads, qlen);
@@ -1135,7 +1194,7 @@
     /* Other helper methods
     /**********************************************************
      */
-    
+
     /**
      * @since 2.1
      */
@@ -1144,7 +1203,7 @@
         throw new IllegalStateException("Longest collision chain in symbol table (of size "+_count
                 +") now exceeds maximum, "+maxLen+" -- suspect a DoS attack based on hash collisions");
     }
-    
+
     /*
     /**********************************************************
     /* Helper classes
@@ -1226,6 +1285,23 @@
             return null;
         }
 
+        public Name find(int h, int q1, int q2, int q3) {
+            if (hash == h) {
+                if (name.equals(q1, q2, q3)) {
+                    return name;
+                }
+            }
+            for (Bucket curr = next; curr != null; curr = curr.next) {
+                if (curr.hash == h) {
+                    Name currName = curr.name;
+                    if (currName.equals(q1, q2, q3)) {
+                        return currName;
+                    }
+                }
+            }
+            return null;
+        }
+        
         public Name find(int h, int[] quads, int qlen) {
             if (hash == h) {
                 if (name.equals(quads, qlen)) {
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java b/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java
index 43fa7c7..a668f2e 100644
--- a/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java
+++ b/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java
@@ -554,7 +554,10 @@
      * and truncates to be used as the index.
      */
     public int _hashToIndex(int rawHash) {
-        rawHash += (rawHash >>> 15); // this seems to help quite a bit, at least for our tests
+        // doing these seems to help a bit
+        rawHash += (rawHash >>> 15);
+        rawHash ^= (rawHash << 7);
+        rawHash += (rawHash >>> 3);
         return (rawHash & _indexMask);
     }
     
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name.java b/src/main/java/com/fasterxml/jackson/core/sym/Name.java
index d26f55d..3546008 100644
--- a/src/main/java/com/fasterxml/jackson/core/sym/Name.java
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name.java
@@ -26,9 +26,14 @@
     /**********************************************************
      */
 
-    public abstract boolean equals(int quad1);
+    public abstract boolean equals(int q1);
 
-    public abstract boolean equals(int quad1, int quad2);
+    public abstract boolean equals(int q1, int q2);
+
+    /**
+     * @since 2.6
+     */
+    public abstract boolean equals(int q1, int q2, int q3);
 
     public abstract boolean equals(int[] quads, int qlen);
 
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name1.java b/src/main/java/com/fasterxml/jackson/core/sym/Name1.java
index 7a9830f..fee4255 100644
--- a/src/main/java/com/fasterxml/jackson/core/sym/Name1.java
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name1.java
@@ -23,5 +23,7 @@
 
     @Override public boolean equals(int quad) { return (quad == q); }
     @Override public boolean equals(int quad1, int quad2) { return (quad1 == q) && (quad2 == 0); }
+    @Override public boolean equals(int q1, int q2, int q3) { return false; }
+
     @Override public boolean equals(int[] quads, int qlen) { return (qlen == 1 && quads[0] == q); }
 }
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name2.java b/src/main/java/com/fasterxml/jackson/core/sym/Name2.java
index 42ef1b5..e60fbd4 100644
--- a/src/main/java/com/fasterxml/jackson/core/sym/Name2.java
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name2.java
@@ -25,6 +25,8 @@
     @Override
     public boolean equals(int quad1, int quad2) { return (quad1 == q1) && (quad2 == q2); }
 
+    @Override public boolean equals(int quad1, int quad2, int q3) { return false; }
+    
     @Override
     public boolean equals(int[] quads, int qlen) { return (qlen == 2 && quads[0] == q1 && quads[1] == q2); }
 }
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name3.java b/src/main/java/com/fasterxml/jackson/core/sym/Name3.java
index ed2381c..2da81e9 100644
--- a/src/main/java/com/fasterxml/jackson/core/sym/Name3.java
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name3.java
@@ -25,6 +25,11 @@
     public boolean equals(int quad1, int quad2) { return false; }
 
     @Override
+    public boolean equals(int quad1, int quad2, int quad3) {
+        return (q1 == quad1) && (q2 == quad2) && (q3 == quad3);
+    }
+
+    @Override
     public boolean equals(int[] quads, int qlen) {
         return (qlen == 3) && (quads[0] == q1) && (quads[1] == q2) && (quads[2] == q3);
     }
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/NameN.java b/src/main/java/com/fasterxml/jackson/core/sym/NameN.java
index 0695b36..2d51df7 100644
--- a/src/main/java/com/fasterxml/jackson/core/sym/NameN.java
+++ b/src/main/java/com/fasterxml/jackson/core/sym/NameN.java
@@ -57,6 +57,10 @@
     @Override
     public boolean equals(int quad1, int quad2) { return false; }
 
+    // Implies quad length == 3, never matches
+    @Override
+    public boolean equals(int quad1, int quad2, int quad3) { return false; }
+
     @Override
     public boolean equals(int[] quads, int len) {
         if (len != qlen) { return false; }
diff --git a/src/main/java/com/fasterxml/jackson/core/type/ResolvedType.java b/src/main/java/com/fasterxml/jackson/core/type/ResolvedType.java
index d5f6b5c..2c57157 100644
--- a/src/main/java/com/fasterxml/jackson/core/type/ResolvedType.java
+++ b/src/main/java/com/fasterxml/jackson/core/type/ResolvedType.java
@@ -46,6 +46,19 @@
 
     public abstract boolean isCollectionLikeType();
 
+    /**
+     * Whether this type is a referential type, meaning that values are
+     * basically pointers to "real" values (or null) and not regular
+     * values themselves. Typical examples include things like
+     * {@link java.util.concurrent.atomic.AtomicReference}, and various
+     * <code>Optional</code> types (in JDK8, Guava).
+     *
+     * @since 2.6
+     */
+    public boolean isReferenceType() {
+        return getReferencedType() != null;
+    }
+
     public abstract boolean isMapLikeType();
 
     /*
@@ -94,6 +107,16 @@
     public abstract ResolvedType getContentType();
 
     /**
+     * Method for accessing type of value that instances of this
+     * type references, if any.
+     * 
+     * @return Referenced type, if any; null if not.
+     * 
+     * @since 2.6
+     */
+    public abstract ResolvedType getReferencedType();
+    
+    /**
      * Method for checking how many contained types this type
      * has. Contained types are usually generic types, so that
      * generic Maps have 2 contained types.
diff --git a/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java b/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java
index fdc7f32..bd2ad9c 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java
@@ -113,7 +113,7 @@
     public final char[] allocCharBuffer(int ix) {
         return allocCharBuffer(ix, 0);
     }
-    
+
     public char[] allocCharBuffer(int ix, int minSize) {
         final int DEF_SIZE = charBufferLength(ix);
         if (minSize < DEF_SIZE) {
diff --git a/src/main/java/com/fasterxml/jackson/core/util/DefaultIndenter.java b/src/main/java/com/fasterxml/jackson/core/util/DefaultIndenter.java
index e655595..143a51f 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/DefaultIndenter.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/DefaultIndenter.java
@@ -37,12 +37,15 @@
     private final int charsPerLevel;
     private final String eol;
 
-    /** Indent with two spaces and the system's default line feed */
+    /**
+     * Indent with two spaces and the system's default line feed
+     */
     public DefaultIndenter() {
         this("  ", SYS_LF);
     }
     
-    /** Create an indenter which uses the <code>indent</code> string to indent one level
+    /**
+     * Create an indenter which uses the <code>indent</code> string to indent one level
      *  and the <code>eol</code> string to separate lines.
      */
     public DefaultIndenter(String indent, String eol)
diff --git a/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java b/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java
index e19bd57..0a004c5 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java
@@ -146,7 +146,7 @@
      * @since 2.6.0
      */
     public DefaultPrettyPrinter withRootSeparator(String rootSeparator) {
-        return withRootSeparator(new SerializedString(rootSeparator));
+        return withRootSeparator((rootSeparator == null) ? null : new SerializedString(rootSeparator));
     }
     
     public void indentArraysWith(Indenter i) {
@@ -349,26 +349,24 @@
      * (white-space) decoration.
      */
     @Override
-    public void writeArrayValueSeparator(JsonGenerator jg)
-        throws IOException, JsonGenerationException
+    public void writeArrayValueSeparator(JsonGenerator gen) throws IOException
     {
-        jg.writeRaw(',');
-        _arrayIndenter.writeIndentation(jg, _nesting);
+        gen.writeRaw(',');
+        _arrayIndenter.writeIndentation(gen, _nesting);
     }
 
     @Override
-    public void writeEndArray(JsonGenerator jg, int nrOfValues)
-        throws IOException, JsonGenerationException
+    public void writeEndArray(JsonGenerator gen, int nrOfValues) throws IOException
     {
         if (!_arrayIndenter.isInline()) {
             --_nesting;
         }
         if (nrOfValues > 0) {
-            _arrayIndenter.writeIndentation(jg, _nesting);
+            _arrayIndenter.writeIndentation(gen, _nesting);
         } else {
-            jg.writeRaw(' ');
+            gen.writeRaw(' ');
         }
-        jg.writeRaw(']');
+        gen.writeRaw(']');
     }
 
     /*
@@ -393,7 +391,7 @@
     }
 
     /**
-     * This is a very simple indenter that only every adds a
+     * This is a very simple indenter that only adds a
      * single space for indentation. It is used as the default
      * indenter for array values.
      */
@@ -403,8 +401,7 @@
         public static final FixedSpaceIndenter instance = new FixedSpaceIndenter();
 
         @Override
-        public void writeIndentation(JsonGenerator jg, int level)
-            throws IOException, JsonGenerationException
+        public void writeIndentation(JsonGenerator jg, int level) throws IOException
         {
             jg.writeRaw(' ');
         }
diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java
index 4f3577b..9d53afa 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java
@@ -27,7 +27,7 @@
     /* Construction, initialization
     /**********************************************************
      */
-    
+
     public JsonGeneratorDelegate(JsonGenerator d) {
         this(d, true);
     }
@@ -77,6 +77,7 @@
     @Override public FormatSchema getSchema() { return delegate.getSchema(); }
     @Override public Version version() { return delegate.version(); }
     @Override public Object getOutputTarget() { return delegate.getOutputTarget(); }
+    @Override public int getOutputBuffered() { return delegate.getOutputBuffered(); }
 
     /*
     /**********************************************************
diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java b/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java
index 13e76eb..7f4241d 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java
@@ -98,8 +98,9 @@
     @Override public int getCurrentTokenId() { return delegate.getCurrentTokenId(); }
     @Override public boolean hasCurrentToken() { return delegate.hasCurrentToken(); }
     @Override public boolean hasTokenId(int id) { return delegate.hasTokenId(id); }
-    
-    @Override public String getCurrentName() throws IOException, JsonParseException { return delegate.getCurrentName(); }
+    @Override public boolean hasToken(JsonToken t) { return delegate.hasToken(t); }
+
+    @Override public String getCurrentName() throws IOException { return delegate.getCurrentName(); }
     @Override public JsonLocation getCurrentLocation() { return delegate.getCurrentLocation(); }
     @Override public JsonStreamContext getParsingContext() { return delegate.getParsingContext(); }
     @Override public boolean isExpectedStartArrayToken() { return delegate.isExpectedStartArrayToken(); }
@@ -121,11 +122,11 @@
     /**********************************************************
      */
 
-    @Override public String getText() throws IOException, JsonParseException { return delegate.getText();  }
+    @Override public String getText() throws IOException { return delegate.getText();  }
     @Override public boolean hasTextCharacters() { return delegate.hasTextCharacters(); }
-    @Override public char[] getTextCharacters() throws IOException, JsonParseException { return delegate.getTextCharacters(); }
-    @Override public int getTextLength() throws IOException, JsonParseException { return delegate.getTextLength(); }
-    @Override public int getTextOffset() throws IOException, JsonParseException { return delegate.getTextOffset(); }
+    @Override public char[] getTextCharacters() throws IOException { return delegate.getTextCharacters(); }
+    @Override public int getTextLength() throws IOException { return delegate.getTextLength(); }
+    @Override public int getTextOffset() throws IOException { return delegate.getTextOffset(); }
 
     /*
     /**********************************************************
@@ -134,37 +135,37 @@
      */
     
     @Override
-    public BigInteger getBigIntegerValue() throws IOException,JsonParseException { return delegate.getBigIntegerValue(); }
+    public BigInteger getBigIntegerValue() throws IOException { return delegate.getBigIntegerValue(); }
 
     @Override
-    public boolean getBooleanValue() throws IOException, JsonParseException { return delegate.getBooleanValue(); }
+    public boolean getBooleanValue() throws IOException { return delegate.getBooleanValue(); }
     
     @Override
-    public byte getByteValue() throws IOException, JsonParseException { return delegate.getByteValue(); }
+    public byte getByteValue() throws IOException { return delegate.getByteValue(); }
 
     @Override
-    public short getShortValue() throws IOException, JsonParseException { return delegate.getShortValue(); }
+    public short getShortValue() throws IOException { return delegate.getShortValue(); }
 
     @Override
-    public BigDecimal getDecimalValue() throws IOException, JsonParseException { return delegate.getDecimalValue(); }
+    public BigDecimal getDecimalValue() throws IOException { return delegate.getDecimalValue(); }
 
     @Override
-    public double getDoubleValue() throws IOException, JsonParseException { return delegate.getDoubleValue(); }
+    public double getDoubleValue() throws IOException { return delegate.getDoubleValue(); }
 
     @Override
-    public float getFloatValue() throws IOException, JsonParseException { return delegate.getFloatValue(); }
+    public float getFloatValue() throws IOException { return delegate.getFloatValue(); }
 
     @Override
-    public int getIntValue() throws IOException, JsonParseException { return delegate.getIntValue(); }
+    public int getIntValue() throws IOException { return delegate.getIntValue(); }
 
     @Override
-    public long getLongValue() throws IOException, JsonParseException { return delegate.getLongValue(); }
+    public long getLongValue() throws IOException { return delegate.getLongValue(); }
 
     @Override
-    public NumberType getNumberType() throws IOException, JsonParseException { return delegate.getNumberType(); }
+    public NumberType getNumberType() throws IOException { return delegate.getNumberType(); }
 
     @Override
-    public Number getNumberValue() throws IOException, JsonParseException { return delegate.getNumberValue(); }
+    public Number getNumberValue() throws IOException { return delegate.getNumberValue(); }
 
     /*
     /**********************************************************
@@ -172,16 +173,16 @@
     /**********************************************************
      */
     
-    @Override public int getValueAsInt() throws IOException, JsonParseException { return delegate.getValueAsInt(); }
-    @Override public int getValueAsInt(int defaultValue) throws IOException, JsonParseException { return delegate.getValueAsInt(defaultValue); }
-    @Override public long getValueAsLong() throws IOException, JsonParseException { return delegate.getValueAsLong(); }
-    @Override public long getValueAsLong(long defaultValue) throws IOException, JsonParseException { return delegate.getValueAsLong(defaultValue); }
-    @Override public double getValueAsDouble() throws IOException, JsonParseException { return delegate.getValueAsDouble(); }
-    @Override public double getValueAsDouble(double defaultValue) throws IOException, JsonParseException { return delegate.getValueAsDouble(defaultValue); }
-    @Override public boolean getValueAsBoolean() throws IOException, JsonParseException { return delegate.getValueAsBoolean(); }
-    @Override public boolean getValueAsBoolean(boolean defaultValue) throws IOException, JsonParseException { return delegate.getValueAsBoolean(defaultValue); }
-    @Override public String getValueAsString() throws IOException, JsonParseException { return delegate.getValueAsString(); }
-    @Override public String getValueAsString(String defaultValue) throws IOException, JsonParseException { return delegate.getValueAsString(defaultValue); }
+    @Override public int getValueAsInt() throws IOException { return delegate.getValueAsInt(); }
+    @Override public int getValueAsInt(int defaultValue) throws IOException { return delegate.getValueAsInt(defaultValue); }
+    @Override public long getValueAsLong() throws IOException { return delegate.getValueAsLong(); }
+    @Override public long getValueAsLong(long defaultValue) throws IOException { return delegate.getValueAsLong(defaultValue); }
+    @Override public double getValueAsDouble() throws IOException { return delegate.getValueAsDouble(); }
+    @Override public double getValueAsDouble(double defaultValue) throws IOException { return delegate.getValueAsDouble(defaultValue); }
+    @Override public boolean getValueAsBoolean() throws IOException { return delegate.getValueAsBoolean(); }
+    @Override public boolean getValueAsBoolean(boolean defaultValue) throws IOException { return delegate.getValueAsBoolean(defaultValue); }
+    @Override public String getValueAsString() throws IOException { return delegate.getValueAsString(); }
+    @Override public String getValueAsString(String defaultValue) throws IOException { return delegate.getValueAsString(defaultValue); }
     
     /*
     /**********************************************************
@@ -189,15 +190,15 @@
     /**********************************************************
      */
 
-    @Override public Object getEmbeddedObject() throws IOException, JsonParseException { return delegate.getEmbeddedObject(); }
-    @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException { return delegate.getBinaryValue(b64variant); }
-    @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException, JsonParseException { return delegate.readBinaryValue(b64variant, out); }
+    @Override public Object getEmbeddedObject() throws IOException { return delegate.getEmbeddedObject(); }
+    @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException { return delegate.getBinaryValue(b64variant); }
+    @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException { return delegate.readBinaryValue(b64variant, out); }
     @Override public JsonLocation getTokenLocation() { return delegate.getTokenLocation(); }
-    @Override public JsonToken nextToken() throws IOException, JsonParseException { return delegate.nextToken(); }
-    @Override public JsonToken nextValue() throws IOException, JsonParseException { return delegate.nextValue(); }
+    @Override public JsonToken nextToken() throws IOException { return delegate.nextToken(); }
+    @Override public JsonToken nextValue() throws IOException { return delegate.nextValue(); }
     
     @Override
-    public JsonParser skipChildren() throws IOException, JsonParseException {
+    public JsonParser skipChildren() throws IOException {
         delegate.skipChildren();
         // NOTE: must NOT delegate this method to delegate, needs to be self-reference for chaining
         return this;
@@ -211,6 +212,6 @@
 
     @Override public boolean canReadObjectId() { return delegate.canReadObjectId(); }
     @Override public boolean canReadTypeId() { return delegate.canReadTypeId(); }
-    @Override public Object getObjectId() throws IOException, JsonGenerationException { return delegate.getObjectId(); }
-    @Override public Object getTypeId() throws IOException, JsonGenerationException { return delegate.getTypeId(); }
+    @Override public Object getObjectId() throws IOException { return delegate.getObjectId(); }
+    @Override public Object getTypeId() throws IOException { return delegate.getTypeId(); }
 }
diff --git a/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java b/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java
index ab65ffe..889d853 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java
@@ -26,7 +26,7 @@
 public class MinimalPrettyPrinter
     implements PrettyPrinter, java.io.Serializable
 {
-    private static final long serialVersionUID = -562765100295218442L;
+    private static final long serialVersionUID = 1L;
 
     /**
      * Default String used for separating root values is single space.
diff --git a/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java b/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
index e6f1cbc..68ba240 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
@@ -294,7 +294,12 @@
         if (_resultString != null) return false;
         return true;
     }
-    
+
+    /**
+     * Accessor that may be used to get the contents of this buffer in a single
+     * <code>char</code> array regardless of whether they were collected in a segmented
+     * fashion or not.
+     */
     public char[] getTextBuffer()
     {
         // Are we just using shared input buffer?
@@ -304,7 +309,9 @@
             return (_resultArray = _resultString.toCharArray());
         }
         // Nope; but does it fit in just one segment?
-        if (!_hasSegments)  return _currentSegment;
+        if (!_hasSegments) {
+            return (_currentSegment == null) ? NO_CHARS : _currentSegment;
+        }
         // Nope, need to have/create a non-segmented array and return it
         return contentsAsArray();
     }
diff --git a/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java b/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java
index 8b01016..4c121ad 100644
--- a/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java
+++ b/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java
@@ -19,6 +19,9 @@
  * 2.1 and 2.2; earlier code used file named "VERSION.txt"; but this has serious
  * performance issues on some platforms (Android), so a replacement system
  * was implemented to use class generation and dynamic class loading.
+ *<p>
+ * Note that functionality for reading "VERSION.txt" was removed completely
+ * from Jackson 2.6.
  */
 public class VersionUtil
 {
@@ -64,81 +67,36 @@
      * First, tries to load version info from a class named
      * "PackageVersion" in the same package as the class.
      *
-     * Next, if that fails, class loader that loaded specified class is
-     * asked to load resource with name "VERSION" from same location
-     * (package) as class itself had.
-     *
      * If no version information is found, {@link Version#unknownVersion()} is returned.
      */
-    @SuppressWarnings("resource")
     public static Version versionFor(Class<?> cls)
     {
-        Version packageVersion = packageVersionFor(cls);
-        if (packageVersion != null) {
-            return packageVersion;
-        }
-        final InputStream in = cls.getResourceAsStream("VERSION.txt");
-        if (in == null) {
-            return Version.unknownVersion();
-        }
-        try {
-            InputStreamReader reader = new InputStreamReader(in, "UTF-8");
-            return doReadVersion(reader);
-        } catch (UnsupportedEncodingException e) {
-            return Version.unknownVersion();
-        } finally {
-            _close(in);
-        }
+        return packageVersionFor(cls);
     }
 
     /**
      * Loads version information by introspecting a class named
      * "PackageVersion" in the same package as the given class.
-     *
+     *<p>
      * If the class could not be found or does not have a public
      * static Version field named "VERSION", returns null.
      */
     public static Version packageVersionFor(Class<?> cls)
     {
+        Version v = null;
         try {
             String versionInfoClassName = cls.getPackage().getName() + ".PackageVersion";
             Class<?> vClass = Class.forName(versionInfoClassName, true, cls.getClassLoader());
             // However, if class exists, it better work correctly, no swallowing exceptions
             try {
-                return ((Versioned) vClass.newInstance()).version();
+                v = ((Versioned) vClass.newInstance()).version();
             } catch (Exception e) {
                 throw new IllegalArgumentException("Failed to get Versioned out of "+vClass);
             }
-        } catch (Exception e) { // ok to be missing (not good, acceptable)
-            return null;
+        } catch (Exception e) { // ok to be missing (not good but acceptable)
+            ;
         }
-    }
-
-    private static Version doReadVersion(final Reader r)
-    {
-        String version = null, group = null, artifact = null;
-
-        final BufferedReader br = new BufferedReader(r);
-        try {
-            version = br.readLine();
-            if (version != null) {
-                group = br.readLine();
-                if (group != null) {
-                    artifact = br.readLine();
-                }
-            }
-        } catch (IOException ignored) {
-        } finally {
-            _close(br);
-        }
-        // We don't trim() version: parseVersion() takes care ot that
-        if (group != null) {
-            group = group.trim();
-        }
-        if (artifact != null) {
-            artifact = artifact.trim();
-        }
-        return parseVersion(version, group, artifact);
+        return (v == null) ? Version.unknownVersion() : v;
     }
 
     /**
@@ -151,8 +109,12 @@
      * @param groupId the groupId of the library
      * @param artifactId the artifactId of the library
      * @return The version
+     * 
+     * @deprecated Since 2.6: functionality not used by any official Jackson component, should be
+     *   moved out if anyone needs it
      */
     @SuppressWarnings("resource")
+    @Deprecated // since 2.6
     public static Version mavenVersionFor(ClassLoader cl, String groupId, String artifactId)
     {
         InputStream pomProperties = cl.getResourceAsStream("META-INF/maven/"
@@ -174,6 +136,9 @@
         return Version.unknownVersion();
     }
 
+    /**
+     * Method used by <code>PackageVersion</code> classes to decode version injected by Maven build.
+     */
     public static Version parseVersion(String s, String groupId, String artifactId)
     {
         if (s != null && (s = s.trim()).length() > 0) {
@@ -184,7 +149,7 @@
                     (parts.length > 3) ? parts[3] : null,
                     groupId, artifactId);
         }
-        return null;
+        return Version.unknownVersion();
     }
 
     protected static int parseVersionPart(String s) {
diff --git a/src/test/java/com/fasterxml/jackson/core/BaseTest.java b/src/test/java/com/fasterxml/jackson/core/BaseTest.java
index 3beed51..5184162 100644
--- a/src/test/java/com/fasterxml/jackson/core/BaseTest.java
+++ b/src/test/java/com/fasterxml/jackson/core/BaseTest.java
@@ -311,6 +311,45 @@
 
     /*
     /**********************************************************
+    /* Helper read/write methods
+    /**********************************************************
+     */
+    
+    protected void writeJsonDoc(JsonFactory f, String doc, Writer w) throws IOException
+    {
+        writeJsonDoc(f, doc, f.createGenerator(w));
+    }
+
+    protected void writeJsonDoc(JsonFactory f, String doc, JsonGenerator g) throws IOException
+    {
+        JsonParser p = f.createParser(aposToQuotes(doc));
+        
+        while (p.nextToken() != null) {
+            g.copyCurrentStructure(p);
+        }
+        p.close();
+        g.close();
+    }
+
+    protected String readAndWrite(JsonFactory f, JsonParser p) throws IOException
+    {
+        StringWriter sw = new StringWriter(100);
+        JsonGenerator g = f.createGenerator(sw);
+        try {
+            while (p.nextToken() != null) {
+                g.copyCurrentEvent(p);
+            }
+        } catch (IOException e) {
+            g.flush();
+            fail("Unexpected problem during `readAndWrite`. Output so far: '"+sw+"'; problem: "+e);
+        }
+        p.close();
+        g.close();
+        return sw.toString();
+    }
+
+    /*
+    /**********************************************************
     /* Additional assertion methods
     /**********************************************************
      */
@@ -356,8 +395,7 @@
      * available methods, and ensures results are consistent, before
      * returning them
      */
-    protected String getAndVerifyText(JsonParser jp)
-        throws IOException, JsonParseException
+    protected String getAndVerifyText(JsonParser jp) throws IOException
     {
         // Ok, let's verify other accessors
         int actLen = jp.getTextLength();
@@ -393,10 +431,14 @@
         return result;
     }
 
-    public String quote(String str) {
+    protected String quote(String str) {
         return '"'+str+'"';
     }
 
+    protected String aposToQuotes(String json) {
+        return json.replace("'", "\"");
+    }
+
     protected void fieldNameFor(StringBuilder sb, int index)
     {
         /* let's do something like "f1.1" to exercise different
@@ -425,4 +467,23 @@
         return sb.toString();
     }
 
+    protected int[] calcQuads(byte[] wordBytes) {
+        int blen = wordBytes.length;
+        int[] result = new int[(blen + 3) / 4];
+        for (int i = 0; i < blen; ++i) {
+            int x = wordBytes[i] & 0xFF;
+
+            if (++i < blen) {
+                x = (x << 8) | (wordBytes[i] & 0xFF);
+                if (++i < blen) {
+                    x = (x << 8) | (wordBytes[i] & 0xFF);
+                    if (++i < blen) {
+                        x = (x << 8) | (wordBytes[i] & 0xFF);
+                    }
+                }
+            }
+            result[i >> 2] = x;
+        }
+        return result;
+    }
 }
diff --git a/src/test/java/com/fasterxml/jackson/core/filter/BasicGeneratorFilteringTest.java b/src/test/java/com/fasterxml/jackson/core/filter/BasicGeneratorFilteringTest.java
new file mode 100644
index 0000000..8c6369c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/filter/BasicGeneratorFilteringTest.java
@@ -0,0 +1,308 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+/**
+ * Low-level tests for explicit, hand-written tests for generator-side
+ * filtering.
+ */
+@SuppressWarnings("resource")
+public class BasicGeneratorFilteringTest extends BaseTest
+{
+    static class NameMatchFilter extends TokenFilter
+    {
+        private final Set<String> _names;
+        
+        public NameMatchFilter(String... names) {
+            _names = new HashSet<String>(Arrays.asList(names));
+        }
+
+        @Override
+        public TokenFilter includeElement(int index) {
+            return this;
+        }
+
+        @Override
+        public TokenFilter includeProperty(String name) {
+            if (_names.contains(name)) {
+                return TokenFilter.INCLUDE_ALL;
+            }
+            return this;
+        }
+
+        @Override
+        protected boolean _includeScalar() { return false; }
+    }
+
+    static class IndexMatchFilter extends TokenFilter
+    {
+        private final BitSet _indices;
+        
+        public IndexMatchFilter(int... ixs) {
+            _indices = new BitSet();
+            for (int ix : ixs) {
+                _indices.set(ix);
+            }
+        }
+
+        @Override
+        public TokenFilter includeProperty(String name) {
+            return this;
+        }
+        
+        @Override
+        public TokenFilter includeElement(int index) {
+            if (_indices.get(index)) {
+                return TokenFilter.INCLUDE_ALL;
+            }
+            return null;
+        }
+
+        @Override
+        protected boolean _includeScalar() { return false; }
+    }
+
+    /*
+    /**********************************************************
+    /* Test methods
+    /**********************************************************
+     */
+
+    private final JsonFactory JSON_F = new JsonFactory();
+
+    public void testNonFiltering() throws Exception
+    {
+        // First, verify non-filtering
+        StringWriter w = new StringWriter();
+        JsonGenerator gen = JSON_F.createGenerator(w);
+        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes(
+                "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}"),
+                w.toString());
+    }
+
+    public void testSingleMatchFilteringWithoutPath() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        JsonGenerator gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new NameMatchFilter("value"),
+                false, // includePath
+                false // multipleMatches
+                );
+        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
+        writeJsonDoc(JSON_F, JSON, gen);
+
+        // 21-Apr-2015, tatu: note that there were plans to actually
+        //     allow "immediate parent inclusion" for matches on property
+        //    names. This behavior was NOT included in release however, so:
+//        assertEquals(aposToQuotes("{'value':3}"), w.toString());
+
+        assertEquals(aposToQuotes("3"), w.toString());
+    }
+
+    public void testSingleMatchFilteringWithPath() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        JsonGenerator origGen = JSON_F.createGenerator(w);
+        NameMatchFilter filter = new NameMatchFilter("value");
+        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(origGen,
+                filter,
+                true, // includePath
+                false // multipleMatches
+                );
+
+        // Hmmh. Should we get access to eventual target?
+        assertSame(w, gen.getOutputTarget());
+        assertNotNull(gen.getFilterContext());
+        assertSame(filter, gen.getFilter());
+
+        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'ob':{'value':3}}"), w.toString());
+
+        assertEquals(1, gen.getMatchCount());
+    }
+
+    public void testSingleMatchFilteringWithPathSkippedArray() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        JsonGenerator origGen = JSON_F.createGenerator(w);
+        NameMatchFilter filter = new NameMatchFilter("value");
+        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(origGen,
+                filter,
+                true, // includePath
+                false // multipleMatches
+                );
+
+        // Hmmh. Should we get access to eventual target?
+        assertSame(w, gen.getOutputTarget());
+        assertNotNull(gen.getFilterContext());
+        assertSame(filter, gen.getFilter());
+
+        final String JSON = "{'array':[1,[2,3]],'ob':[{'value':'bar'}],'b':{'foo':[1,'foo']}}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'ob':[{'value':'bar'}]}"), w.toString());
+        assertEquals(1, gen.getMatchCount());
+    }
+
+    // Alternative take, using slightly different calls for FIELD_NAME, START_ARRAY
+    public void testSingleMatchFilteringWithPathAlternate1() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new NameMatchFilter("value"),
+                true, // includePath
+                false // multipleMatches
+                );
+        //final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':[3],'value2':'foo'},'b':true}";
+
+        gen.writeStartObject();
+        gen.writeFieldName(new SerializedString("a"));
+        gen.writeNumber(123);
+
+        gen.writeFieldName("array");
+        gen.writeStartArray(2);
+        gen.writeNumber("1");
+        gen.writeNumber((short) 2);
+        gen.writeEndArray();
+
+        gen.writeFieldName(new SerializedString("ob"));
+        gen.writeStartObject();
+        gen.writeNumberField("value0", 2);
+        gen.writeFieldName(new SerializedString("value"));
+        gen.writeStartArray(1);
+        gen.writeString(new SerializedString("x")); // just to vary generation method
+        gen.writeEndArray();
+        gen.writeStringField("value2", "foo");
+
+        gen.writeEndObject();
+
+        gen.writeBooleanField("b", true);
+        
+        gen.writeEndObject();
+        gen.close();
+
+        assertEquals(aposToQuotes("{'ob':{'value':['x']}}"), w.toString());
+    }
+
+    public void testSingleMatchFilteringWithPathRawBinary() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        FilteringGeneratorDelegate gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new NameMatchFilter("array"),
+                true, // includePath
+                false // multipleMatches
+                );
+        //final String JSON = "{'header':['ENCODED',raw],'array':['base64stuff',1,2,3,4,5,6.25,7.5],'extra':[1,2,3,4,5,6.25,7.5]}";
+
+        gen.writeStartObject();
+
+        gen.writeFieldName("header");
+        gen.writeStartArray();
+        gen.writeBinary(new byte[] { 1 });
+        gen.writeRawValue(new SerializedString("1"));
+        gen.writeRawValue("2");
+        gen.writeEndArray();
+        
+        gen.writeFieldName("array");
+
+        gen.writeStartArray();
+        gen.writeBinary(new byte[] { 1 });
+        gen.writeNumber((short) 1);
+        gen.writeNumber((int) 2);
+        gen.writeNumber((long) 3);
+        gen.writeNumber(BigInteger.valueOf(4));
+        gen.writeRaw(" ");
+        gen.writeNumber(new BigDecimal("5.0"));
+        gen.writeRaw(new SerializedString(" /*x*/"));
+        gen.writeNumber(6.25f);
+        gen.writeNumber(7.5);
+        gen.writeEndArray();
+
+        gen.writeArrayFieldStart("extra");
+        gen.writeNumber((short) 1);
+        gen.writeNumber((int) 2);
+        gen.writeNumber((long) 3);
+        gen.writeNumber(BigInteger.valueOf(4));
+        gen.writeNumber(new BigDecimal("5.0"));
+        gen.writeNumber(6.25f);
+        gen.writeNumber(7.5);
+        gen.writeEndArray();
+        
+        gen.writeEndObject();
+        gen.close();
+
+        assertEquals(aposToQuotes("{'array':['AQ==',1,2,3,4 ,5.0 /*x*/,6.25,7.5]}"), w.toString());
+    }
+    
+    public void testMultipleMatchFilteringWithPath1() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        JsonGenerator gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new NameMatchFilter("value0", "value2"),
+                true, /* includePath */ true /* multipleMatches */ );
+        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'ob':{'value0':2,'value2':4}}"), w.toString());
+    }
+
+    public void testMultipleMatchFilteringWithPath2() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        
+        JsonGenerator gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new NameMatchFilter("array", "b", "value"),
+                true, true);
+        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'array':[1,2],'ob':{'value':3},'b':true}"), w.toString());
+    }
+
+    public void testMultipleMatchFilteringWithPath3() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        
+        JsonGenerator gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new NameMatchFilter("value"),
+                true, true);
+        final String JSON = "{'root':{'a0':true,'a':{'value':3},'b':{'value':4}},'b0':false}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'root':{'a':{'value':3},'b':{'value':4}}}"), w.toString());
+    }
+
+    public void testIndexMatchWithPath1() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        JsonGenerator gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new IndexMatchFilter(1),
+                true, true);
+        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'array':[2]}"), w.toString());
+
+        w = new StringWriter();
+        gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new IndexMatchFilter(0),
+                true, true);
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'array':[1]}"), w.toString());
+    }
+
+    public void testIndexMatchWithPath2() throws Exception
+    {
+        StringWriter w = new StringWriter();
+        JsonGenerator gen = new FilteringGeneratorDelegate(JSON_F.createGenerator(w),
+                new IndexMatchFilter(0,1),
+                true, true);
+        final String JSON = "{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}";
+        writeJsonDoc(JSON_F, JSON, gen);
+        assertEquals(aposToQuotes("{'array':[1,2]}"), w.toString());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/filter/BasicParserFilteringTest.java b/src/test/java/com/fasterxml/jackson/core/filter/BasicParserFilteringTest.java
new file mode 100644
index 0000000..f5abba9
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/filter/BasicParserFilteringTest.java
@@ -0,0 +1,168 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.util.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class BasicParserFilteringTest extends BaseTest
+{
+    static class NameMatchFilter extends TokenFilter
+    {
+        private final Set<String> _names;
+        
+        public NameMatchFilter(String... names) {
+            _names = new HashSet<String>(Arrays.asList(names));
+        }
+
+        @Override
+        public TokenFilter includeElement(int index) {
+            return this;
+        }
+
+        @Override
+        public TokenFilter includeProperty(String name) {
+            if (_names.contains(name)) {
+                return TokenFilter.INCLUDE_ALL;
+            }
+            return this;
+        }
+
+        @Override
+        protected boolean _includeScalar() { return false; }
+    }
+
+    static class IndexMatchFilter extends TokenFilter
+    {
+        private final BitSet _indices;
+        
+        public IndexMatchFilter(int... ixs) {
+            _indices = new BitSet();
+            for (int ix : ixs) {
+                _indices.set(ix);
+            }
+        }
+
+        @Override
+        public TokenFilter includeProperty(String name) {
+            return this;
+        }
+        
+        @Override
+        public TokenFilter includeElement(int index) {
+            if (_indices.get(index)) {
+                return TokenFilter.INCLUDE_ALL;
+            }
+            return null;
+        }
+
+        @Override
+        protected boolean _includeScalar() { return false; }
+    }
+
+    /*
+    /**********************************************************
+    /* Test methods
+    /**********************************************************
+     */
+
+    private final JsonFactory JSON_F = new JsonFactory();
+
+    private final String SIMPLE = aposToQuotes("{'a':123,'array':[1,2],'ob':{'value0':2,'value':3,'value2':4},'b':true}");
+
+    @SuppressWarnings("resource")
+    public void testNonFiltering() throws Exception
+    {
+        JsonParser p = JSON_F.createParser(SIMPLE);
+        String result = readAndWrite(JSON_F, p);
+        assertEquals(SIMPLE, result);
+    }
+
+    @SuppressWarnings("resource")
+    public void testSingleMatchFilteringWithoutPath() throws Exception
+    {
+        JsonParser p0 = JSON_F.createParser(SIMPLE);
+        JsonParser p = new FilteringParserDelegate(p0,
+               new NameMatchFilter("value"),
+                   false, // includePath
+                   false // multipleMatches
+                );
+        String result = readAndWrite(JSON_F, p);
+        assertEquals(aposToQuotes("3"), result);
+    }
+
+    @SuppressWarnings("resource")
+    public void testSingleMatchFilteringWithPath() throws Exception
+    {
+        JsonParser p0 = JSON_F.createParser(SIMPLE);
+        JsonParser p = new FilteringParserDelegate(p0,
+               new NameMatchFilter("value"),
+                   true, // includePath
+                   false // multipleMatches
+                );
+        String result = readAndWrite(JSON_F, p);
+        assertEquals(aposToQuotes("{'ob':{'value':3}}"), result);
+    }
+
+    @SuppressWarnings("resource")
+    public void testMultipleMatchFilteringWithPath1() throws Exception
+    {
+        JsonParser p0 = JSON_F.createParser(SIMPLE);
+        JsonParser p = new FilteringParserDelegate(p0,
+                new NameMatchFilter("value0", "value2"),
+                true, /* includePath */ true /* multipleMatches */ );
+        String result = readAndWrite(JSON_F, p);
+        assertEquals(aposToQuotes("{'ob':{'value0':2,'value2':4}}"), result);
+    }
+
+    @SuppressWarnings("resource")
+    public void testMultipleMatchFilteringWithPath2() throws Exception
+    {
+        String INPUT = aposToQuotes("{'a':123,'ob':{'value0':2,'value':3,'value2':4},'b':true}");
+        JsonParser p0 = JSON_F.createParser(INPUT);
+        JsonParser p = new FilteringParserDelegate(p0,
+                new NameMatchFilter("b", "value"),
+                true, true);
+
+        String result = readAndWrite(JSON_F, p);
+        assertEquals(aposToQuotes("{'ob':{'value':3},'b':true}"), result);
+    }
+
+    @SuppressWarnings("resource")
+    public void testMultipleMatchFilteringWithPath3() throws Exception
+    {
+        final String JSON = aposToQuotes("{'root':{'a0':true,'a':{'value':3},'b':{'value':4}},'b0':false}");
+        JsonParser p0 = JSON_F.createParser(JSON);
+        JsonParser p = new FilteringParserDelegate(p0,
+                new NameMatchFilter("value"),
+                true, true);
+        String result = readAndWrite(JSON_F, p);
+        assertEquals(aposToQuotes("{'root':{'a':{'value':3},'b':{'value':4}}}"), result);
+    }
+
+    @SuppressWarnings("resource")
+    public void testIndexMatchWithPath1() throws Exception
+    {
+        JsonParser p = new FilteringParserDelegate(JSON_F.createParser(SIMPLE),
+                new IndexMatchFilter(1), true, true);
+        String result = readAndWrite(JSON_F, p);
+        assertEquals(aposToQuotes("{'array':[2]}"), result);
+
+        p = new FilteringParserDelegate(JSON_F.createParser(SIMPLE),
+                new IndexMatchFilter(0), true, true);
+        result = readAndWrite(JSON_F, p);
+        assertEquals(aposToQuotes("{'array':[1]}"), result);
+    }
+
+    @SuppressWarnings("resource")
+    public void testIndexMatchWithPath2() throws Exception
+    {
+        JsonParser p = new FilteringParserDelegate(JSON_F.createParser(SIMPLE),
+                new IndexMatchFilter(0, 1), true, true);
+        assertEquals(aposToQuotes("{'array':[1,2]}"), readAndWrite(JSON_F, p));
+    
+        String JSON = aposToQuotes("{'a':123,'array':[1,2,3,4,5],'b':[1,2,3]}");
+        p = new FilteringParserDelegate(JSON_F.createParser(JSON),
+                new IndexMatchFilter(1, 3), true, true);
+        assertEquals(aposToQuotes("{'array':[2,4],'b':[2]}"), readAndWrite(JSON_F, p));
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/filter/JsonPointerGeneratorFilteringTest.java b/src/test/java/com/fasterxml/jackson/core/filter/JsonPointerGeneratorFilteringTest.java
new file mode 100644
index 0000000..0113a30
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/filter/JsonPointerGeneratorFilteringTest.java
@@ -0,0 +1,111 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+@SuppressWarnings("resource")
+public class JsonPointerGeneratorFilteringTest extends com.fasterxml.jackson.core.BaseTest
+{
+    private final JsonFactory JSON_F = new JsonFactory();
+
+    final String SIMPLE_INPUT = aposToQuotes("{'a':1,'b':[1,2,3],'c':{'d':{'a':true}},'d':null}");
+
+    public void testSimplePropertyWithPath() throws Exception
+    {
+        _assert(SIMPLE_INPUT, "/c", true, "{'c':{'d':{'a':true}}}");
+        _assert(SIMPLE_INPUT, "/c/d", true, "{'c':{'d':{'a':true}}}");
+        _assert(SIMPLE_INPUT, "/c/d/a", true, "{'c':{'d':{'a':true}}}");
+
+        _assert(SIMPLE_INPUT, "/c/d/a", true, "{'c':{'d':{'a':true}}}");
+        
+        _assert(SIMPLE_INPUT, "/a", true, "{'a':1}");
+        _assert(SIMPLE_INPUT, "/d", true, "{'d':null}");
+
+        // and then non-match
+        _assert(SIMPLE_INPUT, "/x", true, "");
+    }
+    
+    public void testSimplePropertyWithoutPath() throws Exception
+    {
+        _assert(SIMPLE_INPUT, "/c", false, "{'d':{'a':true}}");
+        _assert(SIMPLE_INPUT, "/c/d", false, "{'a':true}");
+        _assert(SIMPLE_INPUT, "/c/d/a", false, "true");
+        
+        _assert(SIMPLE_INPUT, "/a", false, "1");
+        _assert(SIMPLE_INPUT, "/d", false, "null");
+
+        // and then non-match
+        _assert(SIMPLE_INPUT, "/x", false, "");
+    }
+
+    public void testArrayElementWithPath() throws Exception
+    {
+        _assert(SIMPLE_INPUT, "/b", true, "{'b':[1,2,3]}");
+        _assert(SIMPLE_INPUT, "/b/1", true, "{'b':[2]}");
+        _assert(SIMPLE_INPUT, "/b/2", true, "{'b':[3]}");
+        
+        // and then non-match
+        _assert(SIMPLE_INPUT, "/b/8", true, "");
+    }
+
+    public void testArrayNestedWithPath() throws Exception
+    {
+        _assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", true, "{'a':[{'b':3}]}");
+        _assert("[true,[1]]", "/0", true, "[true]");
+        _assert("[true,[1]]", "/1", true, "[[1]]");
+        _assert("[true,[1,2,[true],3],0]", "/0", true, "[true]");
+        _assert("[true,[1,2,[true],3],0]", "/1", true, "[[1,2,[true],3]]");
+
+        _assert("[true,[1,2,[true],3],0]", "/1/2", true, "[[[true]]]");
+        _assert("[true,[1,2,[true],3],0]", "/1/2/0", true, "[[[true]]]");
+        _assert("[true,[1,2,[true],3],0]", "/1/3/0", true, "");
+    }
+
+    public void testArrayNestedWithoutPath() throws Exception
+    {
+        _assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", false, "3");
+        _assert("[true,[1,2,[true],3],0]", "/0", false, "true");
+        _assert("[true,[1,2,[true],3],0]", "/1", false,
+                "[1,2,[true],3]");
+
+        _assert("[true,[1,2,[true],3],0]", "/1/2", false, "[true]");
+        _assert("[true,[1,2,[true],3],0]", "/1/2/0", false, "true");
+        _assert("[true,[1,2,[true],3],0]", "/1/3/0", false, "");
+    }
+    
+//    final String SIMPLE_INPUT = aposToQuotes("{'a':1,'b':[1,2,3],'c':{'d':{'a':true}},'d':null}");
+    
+    public void testArrayElementWithoutPath() throws Exception
+    {
+        _assert(SIMPLE_INPUT, "/b", false, "[1,2,3]");
+        _assert(SIMPLE_INPUT, "/b/1", false, "2");
+        _assert(SIMPLE_INPUT, "/b/2", false, "3");
+
+        _assert(SIMPLE_INPUT, "/b/8", false, "");
+
+        // and then non-match
+        _assert(SIMPLE_INPUT, "/x", false, "");
+    }
+
+    private void _assert(String input, String pathExpr, boolean includeParent, String exp)
+        throws Exception
+    {
+        StringWriter w = new StringWriter();
+
+        JsonGenerator g0 = JSON_F.createGenerator(w);
+        FilteringGeneratorDelegate g = new FilteringGeneratorDelegate(g0,
+                new JsonPointerBasedFilter(pathExpr),
+                includeParent, false);
+
+        try {
+            writeJsonDoc(JSON_F, input, g);
+        } catch (Exception e) {
+            g0.flush();
+            System.err.println("With input '"+input+"', output at point of failure: <"+w+">");
+            throw e;
+        }
+
+        assertEquals(aposToQuotes(exp), w.toString());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/filter/JsonPointerParserFilteringTest.java b/src/test/java/com/fasterxml/jackson/core/filter/JsonPointerParserFilteringTest.java
new file mode 100644
index 0000000..58ecca7
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/filter/JsonPointerParserFilteringTest.java
@@ -0,0 +1,83 @@
+package com.fasterxml.jackson.core.filter;
+
+import java.io.StringWriter;
+
+import com.fasterxml.jackson.core.*;
+
+public class JsonPointerParserFilteringTest extends com.fasterxml.jackson.core.BaseTest
+{
+    private final JsonFactory JSON_F = new JsonFactory();
+
+    final String SIMPLEST_INPUT = aposToQuotes("{'a':1,'b':2,'c':3}");
+
+    final String SIMPLE_INPUT = aposToQuotes("{'a':1,'b':[1,2,3],'c':{'d':{'a':true}},'d':null}");
+
+    public void testSimplestWithPath() throws Exception
+    {
+        _assert(SIMPLEST_INPUT, "/a", true, "{'a':1}");
+        _assert(SIMPLEST_INPUT, "/b", true, "{'b':2}");
+        _assert(SIMPLEST_INPUT, "/c", true, "{'c':3}");
+        _assert(SIMPLEST_INPUT, "/c/0", true, "");
+        _assert(SIMPLEST_INPUT, "/d", true, "");
+    }
+
+    public void testSimplestNoPath() throws Exception
+    {
+        _assert(SIMPLEST_INPUT, "/a", false, "1");
+        _assert(SIMPLEST_INPUT, "/b", false, "2");
+        _assert(SIMPLEST_INPUT, "/b/2", false, "");
+        _assert(SIMPLEST_INPUT, "/c", false, "3");
+        _assert(SIMPLEST_INPUT, "/d", false, "");
+    }
+
+    public void testSimpleWithPath() throws Exception
+    {
+        _assert(SIMPLE_INPUT, "/c", true, "{'c':{'d':{'a':true}}}");
+        _assert(SIMPLE_INPUT, "/c/d", true, "{'c':{'d':{'a':true}}}");
+        _assert(SIMPLE_INPUT, "/a", true, "{'a':1}");
+        _assert(SIMPLE_INPUT, "/b", true, "{'b':[1,2,3]}");
+        _assert(SIMPLE_INPUT, "/b/0", true, "{'b':[1]}");
+        _assert(SIMPLE_INPUT, "/b/1", true, "{'b':[2]}");
+        _assert(SIMPLE_INPUT, "/b/2", true, "{'b':[3]}");
+        _assert(SIMPLE_INPUT, "/b/3", true, "");
+    }
+
+    public void testSimpleNoPath() throws Exception
+    {
+        _assert(SIMPLE_INPUT, "/c", false, "{'d':{'a':true}}");
+
+        _assert(SIMPLE_INPUT, "/c/d", false, "{'a':true}");
+        _assert(SIMPLE_INPUT, "/a", false, "1");
+        _assert(SIMPLE_INPUT, "/b", false, "[1,2,3]");
+        _assert(SIMPLE_INPUT, "/b/0", false, "1");
+        _assert(SIMPLE_INPUT, "/b/1", false, "2");
+        _assert(SIMPLE_INPUT, "/b/2", false, "3");
+        _assert(SIMPLE_INPUT, "/b/3", false, "");
+    }
+
+    @SuppressWarnings("resource")
+    void _assert(String input, String pathExpr, boolean includeParent, String exp)
+        throws Exception
+    {
+        JsonParser p0 = JSON_F.createParser(input);
+        FilteringParserDelegate p = new FilteringParserDelegate(p0,
+                new JsonPointerBasedFilter(pathExpr),
+                includeParent, false);
+        StringWriter w = new StringWriter();
+        JsonGenerator g = JSON_F.createGenerator(w);
+
+        try {
+            while (p.nextToken() != null) {
+                g.copyCurrentEvent(p);
+            }
+            p.close();
+            g.close();
+        } catch (Exception e) {
+            g.flush();
+            System.err.println("With input '"+input+"', output at point of failure: <"+w+">");
+            throw e;
+        }
+
+        assertEquals(aposToQuotes(exp), w.toString());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/io/SegmentedStringWriterTest.java b/src/test/java/com/fasterxml/jackson/core/io/SegmentedStringWriterTest.java
new file mode 100644
index 0000000..08dfddd
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/io/SegmentedStringWriterTest.java
@@ -0,0 +1,48 @@
+package com.fasterxml.jackson.core.io;
+
+import com.fasterxml.jackson.core.io.SegmentedStringWriter;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+
+public class SegmentedStringWriterTest
+    extends com.fasterxml.jackson.core.BaseTest
+{
+    public void testSimple() throws Exception
+    {
+        BufferRecycler br = new BufferRecycler();
+        SegmentedStringWriter w = new SegmentedStringWriter(br);
+
+        StringBuilder exp = new StringBuilder();
+
+        for (int i = 0; exp.length() < 100; ++i) {
+            String nr = String.valueOf(i);
+            exp.append(' ').append(nr);
+            w.append(' ');
+            switch (i % 4) {
+            case 0:
+                w.append(nr);
+                break;
+            case 1:
+                {
+                    String str = "  "+nr;
+                    w.append(str, 2, str.length());
+                }
+                break;
+            case 2:
+                w.write(nr.toCharArray());
+                break;
+            default:
+                {
+                    char[] ch = (" "+nr+" ").toCharArray();
+                    w.write(ch, 1, nr.length());
+                }
+                break;
+            }
+        }
+        // flush, close are nops but trigger just for fun
+        w.flush();
+        w.close();
+
+        String act = w.getAndClear();
+        assertEquals(exp.toString(), act);
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/io/TestUTF8Writer.java b/src/test/java/com/fasterxml/jackson/core/io/UTF8WriterTest.java
similarity index 70%
rename from src/test/java/com/fasterxml/jackson/core/io/TestUTF8Writer.java
rename to src/test/java/com/fasterxml/jackson/core/io/UTF8WriterTest.java
index a08a1ea..32d1c39 100644
--- a/src/test/java/com/fasterxml/jackson/core/io/TestUTF8Writer.java
+++ b/src/test/java/com/fasterxml/jackson/core/io/UTF8WriterTest.java
@@ -6,7 +6,7 @@
 import com.fasterxml.jackson.core.io.UTF8Writer;
 import com.fasterxml.jackson.core.util.BufferRecycler;
 
-public class TestUTF8Writer
+public class UTF8WriterTest
     extends com.fasterxml.jackson.core.BaseTest
 {
     public void testSimple() throws Exception
@@ -25,6 +25,7 @@
         w.append(ch[0]);
         w.write(ch[1]);
         w.write(ch, 2, 3);
+        w.flush();
 
         w.write(str, 0, str.length());
         w.close();
@@ -39,6 +40,26 @@
         assertEquals(str+str+str, act);
     }
 
+    public void testSimpleAscii() throws Exception
+    {
+        BufferRecycler rec = new BufferRecycler();
+        IOContext ctxt = new IOContext(rec, null, false);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        UTF8Writer w = new UTF8Writer(ctxt, out);
+
+        String str = "abcdefghijklmnopqrst\u00A0";
+        char[] ch = str.toCharArray();
+
+        w.write(ch, 0, ch.length);
+        w.close();
+
+        byte[] data = out.toByteArray();
+        // one 2-byte encoded char
+        assertEquals(ch.length+1, data.length);
+        String act = out.toString("UTF-8");
+        assertEquals(str, act);
+    }
+    
     public void testFlushAfterClose() throws Exception
     {
         BufferRecycler rec = new BufferRecycler();
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestJsonGenerator.java b/src/test/java/com/fasterxml/jackson/core/json/GeneratorBasicTest.java
similarity index 79%
rename from src/test/java/com/fasterxml/jackson/core/json/TestJsonGenerator.java
rename to src/test/java/com/fasterxml/jackson/core/json/GeneratorBasicTest.java
index 27312e2..7b159a9 100644
--- a/src/test/java/com/fasterxml/jackson/core/json/TestJsonGenerator.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/GeneratorBasicTest.java
@@ -8,14 +8,15 @@
  * Set of basic unit tests for verifying that the basic generator
  * functionality works as expected.
  */
-public class TestJsonGenerator
+public class GeneratorBasicTest
     extends com.fasterxml.jackson.core.BaseTest
 {
+    private final JsonFactory JSON_F = new JsonFactory();
+
     // // // First, tests for primitive (non-structured) values
 
     public void testStringWrite() throws Exception
     {
-        JsonFactory jf = new JsonFactory();
         String[] inputStrings = new String[] { "", "X", "1234567890" };
         for (int useReader = 0; useReader < 2; ++useReader) {
             for (int writeString = 0; writeString < 2; ++writeString) {
@@ -24,9 +25,9 @@
                     JsonGenerator gen;
                     ByteArrayOutputStream bout = new ByteArrayOutputStream();
                     if (useReader != 0) {
-                        gen = jf.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
+                        gen = JSON_F.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
                     } else {
-                        gen = jf.createGenerator(bout, JsonEncoding.UTF8);
+                        gen = JSON_F.createGenerator(bout, JsonEncoding.UTF8);
                     }
                     if (writeString > 0) {
                         gen.writeString(input);
@@ -39,7 +40,7 @@
                     }
                     gen.flush();
                     gen.close();
-                    JsonParser jp = jf.createParser(new ByteArrayInputStream(bout.toByteArray()));
+                    JsonParser jp = JSON_F.createParser(new ByteArrayInputStream(bout.toByteArray()));
                 
                     JsonToken t = jp.nextToken();
                     assertNotNull("Document \""+bout.toString("UTF-8")+"\" yielded no tokens", t);
@@ -52,16 +53,16 @@
         }
     }
 
-    public void testIntWrite() throws Exception
+    public void testIntValueWrite() throws Exception
     {
-        doTestIntWrite(false);
-        doTestIntWrite(true);
+        doTestIntValueWrite(false);
+        doTestIntValueWrite(true);
     }
 
-    public void testLongWrite() throws Exception
+    public void testLongValueWrite() throws Exception
     {
-        doTestLongWrite(false);
-        doTestLongWrite(true);
+        doTestLongValueWrite(false);
+        doTestLongValueWrite(true);
     }
 
     public void testBooleanWrite() throws Exception
@@ -70,7 +71,7 @@
             boolean state = (i & 1) == 0;
             boolean pad = (i & 2) == 0;
             StringWriter sw = new StringWriter();
-            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            JsonGenerator gen = JSON_F.createGenerator(sw);
             gen.writeBoolean(state);
             if (pad) {
                 gen.writeRaw(" ");
@@ -95,7 +96,7 @@
         for (int i = 0; i < 2; ++i) {
             boolean pad = (i & 1) == 0;
             StringWriter sw = new StringWriter();
-            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            JsonGenerator gen = JSON_F.createGenerator(sw);
             gen.writeNull();
             if (pad) {
                 gen.writeRaw(" ");
@@ -120,7 +121,7 @@
          throws Exception
      {
          StringWriter sw = new StringWriter();
-         JsonGenerator gen = new JsonFactory().createGenerator(sw);
+         JsonGenerator gen = JSON_F.createGenerator(sw);
          gen.writeNumber(1);
          gen.writeNumber(2);
          gen.writeNumber(-13);
@@ -144,7 +145,7 @@
          throws Exception
      {
          StringWriter sw = new StringWriter();
-         JsonGenerator gen = new JsonFactory().createGenerator(sw);
+         JsonGenerator gen = JSON_F.createGenerator(sw);
          gen.writeStartObject();
          gen.writeNumberField("long", 3L);
          gen.writeNumberField("double", 0.25);
@@ -161,7 +162,7 @@
     public void testOutputContext() throws Exception
     {
         StringWriter sw = new StringWriter();
-        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        JsonGenerator gen = JSON_F.createGenerator(sw);
         JsonStreamContext ctxt = gen.getOutputContext();
         assertTrue(ctxt.inRoot());
 
@@ -226,35 +227,44 @@
         gen.close();
     }
 
-    // [core#167]: no error for writing field name twice
-    public void testDupFieldNameWrites() throws Exception
+    public void testGetOutputTarget() throws Exception
     {
-        JsonFactory f = new JsonFactory();
-        _testDupFieldNameWrites(f, false);
-        _testDupFieldNameWrites(f, true);        
+        OutputStream out = new ByteArrayOutputStream();
+        JsonGenerator gen = JSON_F.createGenerator(out);
+        assertSame(out, gen.getOutputTarget());
+        gen.close();
+
+        StringWriter sw = new StringWriter();
+        gen = JSON_F.createGenerator(sw);
+        assertSame(sw, gen.getOutputTarget());
+        gen.close();
     }
 
-    private void _testDupFieldNameWrites(JsonFactory f, boolean useReader) throws Exception
+    // for [core#195]
+    public void testGetOutputBufferd() throws Exception
     {
-        JsonGenerator gen;
-        ByteArrayOutputStream bout = new ByteArrayOutputStream();
-        if (useReader) {
-            gen = f.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
-        } else {
-            gen = f.createGenerator(bout, JsonEncoding.UTF8);
-        }
-        gen.writeStartObject();
-        gen.writeFieldName("a");
-        
-        try {
-            gen.writeFieldName("b");
-            gen.flush();
-            String json = bout.toString("UTF-8");
-            fail("Should not have let two consequtive field name writes succeed: output = "+json);
-        } catch (JsonProcessingException e) {
-            verifyException(e, "can not write a field name, expecting a value");
-        }
+        OutputStream out = new ByteArrayOutputStream();
+        JsonGenerator gen = JSON_F.createGenerator(out);
+        _testOutputBuffered(gen);
         gen.close();
+
+        StringWriter sw = new StringWriter();
+        gen = JSON_F.createGenerator(sw);
+        _testOutputBuffered(gen);
+        gen.close();
+    }
+
+    private void _testOutputBuffered(JsonGenerator gen) throws IOException
+    {
+        gen.writeStartArray(); // 1 byte
+        gen.writeNumber(1234); // 4 bytes
+        assertEquals(5, gen.getOutputBuffered());
+        gen.flush();
+        assertEquals(0, gen.getOutputBuffered());
+        gen.writeEndArray();
+        assertEquals(1, gen.getOutputBuffered());
+        gen.close();
+        assertEquals(0, gen.getOutputBuffered());
     }
 
     /*
@@ -262,9 +272,8 @@
     /* Internal methods
     /**********************************************************
      */
-    
-    private void doTestIntWrite(boolean pad)
-        throws Exception
+
+    private void doTestIntValueWrite(boolean pad) throws Exception
     {
         int[] VALUES = new int[] {
             0, 1, -9, 32, -32, 57, 189, 2017, -9999, 13240, 123456,
@@ -273,7 +282,7 @@
         for (int i = 0; i < VALUES.length; ++i) {
             int VALUE = VALUES[i];
             StringWriter sw = new StringWriter();
-            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            JsonGenerator gen = JSON_F.createGenerator(sw);
             gen.writeNumber(VALUE);
             if (pad) {
                 gen.writeRaw(" ");
@@ -295,8 +304,7 @@
         }
     }
 
-    private void doTestLongWrite(boolean pad)
-        throws Exception
+    private void doTestLongValueWrite(boolean pad) throws Exception
     {
         long[] VALUES = new long[] {
             0L, 1L, -1L, -12005002294L, Long.MIN_VALUE, Long.MAX_VALUE
@@ -304,7 +312,7 @@
         for (int i = 0; i < VALUES.length; ++i) {
             long VALUE = VALUES[i];
             StringWriter sw = new StringWriter();
-            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            JsonGenerator gen = JSON_F.createGenerator(sw);
             gen.writeNumber(VALUE);
             if (pad) {
                 gen.writeRaw(" ");
@@ -325,3 +333,4 @@
         }
     }
 }
+
diff --git a/src/test/java/com/fasterxml/jackson/core/json/GeneratorFailTest.java b/src/test/java/com/fasterxml/jackson/core/json/GeneratorFailTest.java
new file mode 100644
index 0000000..7b4368b
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/GeneratorFailTest.java
@@ -0,0 +1,87 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+public class GeneratorFailTest
+    extends com.fasterxml.jackson.core.BaseTest
+{
+    private final JsonFactory F = new JsonFactory();
+
+    // [core#167]: no error for writing field name twice
+    public void testDupFieldNameWrites() throws Exception
+    {
+        _testDupFieldNameWrites(F, false);
+        _testDupFieldNameWrites(F, true);        
+    }
+
+    // [core#177]
+    // Also: should not try writing JSON String if field name expected
+    // (in future maybe take one as alias... but not yet)
+    public void testFailOnWritingStringNotFieldNameBytes() throws Exception {
+        _testFailOnWritingStringNotFieldName(F, false);
+    }
+
+    // [core#177]
+    public void testFailOnWritingStringNotFieldNameChars() throws Exception {
+        _testFailOnWritingStringNotFieldName(F, true);        
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    private void _testDupFieldNameWrites(JsonFactory f, boolean useReader) throws Exception
+    {
+        JsonGenerator gen;
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        if (useReader) {
+            gen = f.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
+        } else {
+            gen = f.createGenerator(bout, JsonEncoding.UTF8);
+        }
+        gen.writeStartObject();
+        gen.writeFieldName("a");
+        
+        try {
+            gen.writeFieldName("b");
+            gen.flush();
+            String json = bout.toString("UTF-8");
+            fail("Should not have let two consecutive field name writes succeed: output = "+json);
+        } catch (JsonProcessingException e) {
+            verifyException(e, "can not write a field name, expecting a value");
+        }
+        gen.close();
+    }
+
+    private void _testFailOnWritingStringNotFieldName(JsonFactory f, boolean useReader) throws Exception
+    {
+        JsonGenerator gen;
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        if (useReader) {
+            gen = f.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
+        } else {
+            gen = f.createGenerator(bout, JsonEncoding.UTF8);
+        }
+        gen.writeStartObject();
+        
+        try {
+            gen.writeString("a");
+            gen.flush();
+            String json = bout.toString("UTF-8");
+            fail("Should not have let "+gen.getClass().getName()+".writeString() be used in place of 'writeFieldName()': output = "+json);
+        } catch (JsonProcessingException e) {
+            verifyException(e, "can not write a String");
+        }
+        gen.close();
+    }
+
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/JsonFactoryTest.java b/src/test/java/com/fasterxml/jackson/core/json/JsonFactoryTest.java
new file mode 100644
index 0000000..ede5ec1
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/JsonFactoryTest.java
@@ -0,0 +1,135 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class JsonFactoryTest
+    extends com.fasterxml.jackson.core.BaseTest
+{
+    public void testGeneratorFeatures() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        assertNull(f.getCodec());
+
+        f.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, true);
+        assertTrue(f.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
+        f.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
+        assertFalse(f.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
+    }
+
+    public void testFactoryFeatures() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+
+        f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, true);
+        assertTrue(f.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+        f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false);
+        assertFalse(f.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+
+        // by default, should be enabled
+        assertTrue(f.isEnabled(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING));
+        f.configure(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING, false);
+        assertFalse(f.isEnabled(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING));
+    }
+
+    // for [core#189]: verify that it's ok to disable recycling
+    // Basically simply exercises basic functionality, to ensure
+    // there are no obvious problems; needed since testing never
+    // disables this handling otherwise
+    public void testDisablingBufferRecycling() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+
+        f.disable(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING);
+
+        // First, generation
+        for (int i = 0; i < 3; ++i) {
+            StringWriter w = new StringWriter();
+            JsonGenerator gen = f.createGenerator(w);
+            gen.writeStartObject();
+            gen.writeEndObject();
+            gen.close();
+            assertEquals("{}", w.toString());
+        }
+    
+        for (int i = 0; i < 3; ++i) {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            JsonGenerator gen = f.createGenerator(bytes);
+            gen.writeStartArray();
+            gen.writeEndArray();
+            gen.close();
+            assertEquals("[]", bytes.toString("UTF-8"));
+        }
+
+        // Then parsing:
+        for (int i = 0; i < 3; ++i) {
+            JsonParser p = f.createParser("{}");
+            assertToken(JsonToken.START_OBJECT, p.nextToken());
+            assertToken(JsonToken.END_OBJECT, p.nextToken());
+            assertNull(p.nextToken());
+            p.close();
+
+            p = f.createParser("{}".getBytes("UTF-8"));
+            assertToken(JsonToken.START_OBJECT, p.nextToken());
+            assertToken(JsonToken.END_OBJECT, p.nextToken());
+            assertNull(p.nextToken());
+            p.close();
+        }
+    }
+    
+    public void testJsonWithFiles() throws Exception
+    {
+        File file = File.createTempFile("jackson-test", null);
+        file.deleteOnExit();
+        
+        JsonFactory f = new JsonFactory();
+
+        // First: create file via generator.. and use an odd encoding
+        JsonGenerator jg = f.createGenerator(file, JsonEncoding.UTF16_LE);
+        jg.writeStartObject();
+        jg.writeRaw("   ");
+        jg.writeEndObject();
+        jg.close();
+
+        // Ok: first read file directly
+        JsonParser jp = f.createParser(file);
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        assertNull(jp.nextToken());
+        jp.close();
+
+        // Then via URL:
+        jp = f.createParser(file.toURI().toURL());
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        assertNull(jp.nextToken());
+        jp.close();
+
+        // ok, delete once we are done
+        file.delete();
+    }
+
+    // #72
+    public void testCopy() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        // first, verify defaults
+        assertTrue(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+        assertFalse(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+        assertFalse(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+        jf.disable(JsonFactory.Feature.INTERN_FIELD_NAMES);
+        jf.enable(JsonParser.Feature.ALLOW_COMMENTS);
+        jf.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
+        // then change, verify that changes "stick"
+        assertFalse(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+        assertTrue(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+        assertTrue(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+
+        JsonFactory jf2 = jf.copy();
+        assertFalse(jf2.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+        assertTrue(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+        assertTrue(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+    }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/core/json/ParserSequenceTest.java b/src/test/java/com/fasterxml/jackson/core/json/ParserSequenceTest.java
new file mode 100644
index 0000000..6b3f4c9
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/ParserSequenceTest.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.core.json;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.JsonParserSequence;
+
+public class ParserSequenceTest
+    extends com.fasterxml.jackson.core.BaseTest
+{
+    private final JsonFactory JSON_FACTORY = new JsonFactory();
+
+    public void testSimple() throws Exception
+    {
+        JsonParser p1 = JSON_FACTORY.createParser("[ 1 ]");
+        JsonParser p2 = JSON_FACTORY.createParser("[ 2 ]");
+        JsonParserSequence seq = JsonParserSequence.createFlattened(p1, p2);
+        assertEquals(2, seq.containedParsersCount());
+
+        assertFalse(p1.isClosed());
+        assertFalse(p2.isClosed());
+        assertFalse(seq.isClosed());
+        assertToken(JsonToken.START_ARRAY, seq.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
+        assertEquals(1, seq.getIntValue());
+        assertToken(JsonToken.END_ARRAY, seq.nextToken());
+        assertFalse(p1.isClosed());
+        assertFalse(p2.isClosed());
+        assertFalse(seq.isClosed());
+        assertToken(JsonToken.START_ARRAY, seq.nextToken());
+
+        // first parser ought to be closed now
+        assertTrue(p1.isClosed());
+        assertFalse(p2.isClosed());
+        assertFalse(seq.isClosed());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
+        assertEquals(2, seq.getIntValue());
+        assertToken(JsonToken.END_ARRAY, seq.nextToken());
+        assertTrue(p1.isClosed());
+        assertFalse(p2.isClosed());
+        assertFalse(seq.isClosed());
+
+        assertNull(seq.nextToken());
+        assertTrue(p1.isClosed());
+        assertTrue(p2.isClosed());
+        assertTrue(seq.isClosed());
+
+        seq.close();
+        // redundant, but call to remove IDE warnings
+        p1.close();
+        p2.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestStringGeneration.java b/src/test/java/com/fasterxml/jackson/core/json/StringGenerationTest.java
similarity index 76%
rename from src/test/java/com/fasterxml/jackson/core/main/TestStringGeneration.java
rename to src/test/java/com/fasterxml/jackson/core/json/StringGenerationTest.java
index ed167e6..b36cbe6 100644
--- a/src/test/java/com/fasterxml/jackson/core/main/TestStringGeneration.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/StringGenerationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.core.main;
+package com.fasterxml.jackson.core.json;
 
 import java.io.*;
 
@@ -10,7 +10,7 @@
  * Set of basic unit tests for verifying that the string
  * generation, including character escaping, works as expected.
  */
-public class TestStringGeneration
+public class StringGenerationTest
     extends BaseTest
 {
     final static String[] SAMPLES = new String[] {
@@ -23,15 +23,33 @@
  
     private final JsonFactory FACTORY = new JsonFactory();
     
-    public void testBasicEscaping()
-        throws Exception
+    public void testBasicEscaping() throws Exception
     {
         doTestBasicEscaping(false);
         doTestBasicEscaping(true);
     }
 
-    public void testLongerRandomSingleChunk()
-        throws Exception
+    // for [core#194]
+    public void testMediumStringsBytes() throws Exception
+    {
+        _testMediumStrings(true, 1100);
+        _testMediumStrings(true, 2300);
+        _testMediumStrings(true, 3800);
+        _testMediumStrings(true, 7500);
+        _testMediumStrings(true, 19000);
+    }
+
+    // for [core#194]
+    public void testMediumStringsChars() throws Exception
+    {
+        _testMediumStrings(false, 1100);
+        _testMediumStrings(false, 2300);
+        _testMediumStrings(false, 3800);
+        _testMediumStrings(false, 7500);
+        _testMediumStrings(false, 19000);
+    }
+
+    public void testLongerRandomSingleChunk() throws Exception
     {
         /* Let's first generate 100k of pseudo-random characters, favoring
          * 7-bit ascii range
@@ -43,8 +61,7 @@
         }
     }
 
-    public void testLongerRandomMultiChunk()
-        throws Exception
+    public void testLongerRandomMultiChunk() throws Exception
     {
         /* Let's first generate 100k of pseudo-random characters, favoring
          * 7-bit ascii range
@@ -62,6 +79,29 @@
     /**********************************************************
      */
 
+    private String _generareMediumText(int minLen)
+    {
+        StringBuilder sb = new StringBuilder(minLen + 1000);
+        Random rnd = new Random(minLen);
+        do {
+            switch (rnd.nextInt() % 4) {
+            case 0:
+                sb.append(" foo");
+                break;
+            case 1:
+                sb.append(" bar");
+                break;
+            case 2:
+                sb.append(String.valueOf(sb.length()));
+                break;
+            default:
+                sb.append(" \"stuff\"");
+                break;
+            }
+        } while (sb.length() < minLen);
+        return sb.toString();
+    }
+    
     private String generateRandom(int len)
     {
         StringBuilder sb = new StringBuilder(len+1000); // pad for surrogates
@@ -84,8 +124,36 @@
             }
         }
         return sb.toString();
-    }   
+    }
 
+    private void _testMediumStrings(boolean useBinary, int length) throws Exception
+    {
+        String text = _generareMediumText(length);
+        StringWriter sw = new StringWriter();
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+        JsonGenerator gen = useBinary ? FACTORY.createGenerator(bytes)
+                : FACTORY.createGenerator(sw);
+        gen.writeStartArray();
+        gen.writeString(text);
+        gen.writeEndArray();
+        gen.close();
+
+        String json;
+        if (useBinary) {
+            json = bytes.toString("UTF-8");
+        } else {
+            json = sw.toString();
+        }
+
+        JsonParser p = FACTORY.createParser(json);
+        assertToken(JsonToken.START_ARRAY, p.nextToken());
+        assertToken(JsonToken.VALUE_STRING, p.nextToken());
+        assertEquals(text, p.getText());
+        assertToken(JsonToken.END_ARRAY, p.nextToken());
+        p.close();
+    }
+    
     private void doTestBasicEscaping(boolean charArray)
         throws Exception
     {
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestJsonFactory.java b/src/test/java/com/fasterxml/jackson/core/json/TestJsonFactory.java
deleted file mode 100644
index d29651f..0000000
--- a/src/test/java/com/fasterxml/jackson/core/json/TestJsonFactory.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.fasterxml.jackson.core.json;
-
-import java.io.*;
-
-import com.fasterxml.jackson.core.*;
-
-public class TestJsonFactory
-    extends com.fasterxml.jackson.core.BaseTest
-{
-    public void testGeneratorFeatures() throws Exception
-    {
-        JsonFactory f = new JsonFactory();
-        assertNull(f.getCodec());
-
-        f.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, true);
-        assertTrue(f.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
-        f.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
-        assertFalse(f.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
-    }
-
-    public void testParserFeatures() throws Exception
-    {
-        JsonFactory f = new JsonFactory();
-        assertNull(f.getCodec());
-
-        f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, true);
-        assertTrue(f.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
-        f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false);
-        assertFalse(f.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
-    }
-    
-    public void testJsonWithFiles() throws Exception
-    {
-        File file = File.createTempFile("jackson-test", null);
-        file.deleteOnExit();
-        
-        JsonFactory f = new JsonFactory();
-
-        // First: create file via generator.. and use an odd encoding
-        JsonGenerator jg = f.createGenerator(file, JsonEncoding.UTF16_LE);
-        jg.writeStartObject();
-        jg.writeRaw("   ");
-        jg.writeEndObject();
-        jg.close();
-
-        // Ok: first read file directly
-        JsonParser jp = f.createParser(file);
-        assertToken(JsonToken.START_OBJECT, jp.nextToken());
-        assertToken(JsonToken.END_OBJECT, jp.nextToken());
-        assertNull(jp.nextToken());
-        jp.close();
-
-        // Then via URL:
-        jp = f.createParser(file.toURI().toURL());
-        assertToken(JsonToken.START_OBJECT, jp.nextToken());
-        assertToken(JsonToken.END_OBJECT, jp.nextToken());
-        assertNull(jp.nextToken());
-        jp.close();
-
-        // ok, delete once we are done
-        file.delete();
-    }
-
-    // #72
-    public void testCopy() throws Exception
-    {
-        JsonFactory jf = new JsonFactory();
-        // first, verify defaults
-        assertTrue(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
-        assertFalse(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
-        assertFalse(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
-        jf.disable(JsonFactory.Feature.INTERN_FIELD_NAMES);
-        jf.enable(JsonParser.Feature.ALLOW_COMMENTS);
-        jf.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
-        // then change, verify that changes "stick"
-        assertFalse(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
-        assertTrue(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
-        assertTrue(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
-
-        JsonFactory jf2 = jf.copy();
-        assertFalse(jf2.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
-        assertTrue(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
-        assertTrue(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
-    }
-}
-
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestJsonParser.java b/src/test/java/com/fasterxml/jackson/core/json/TestJsonParser.java
index ea9410d..d4dead5 100644
--- a/src/test/java/com/fasterxml/jackson/core/json/TestJsonParser.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestJsonParser.java
@@ -1,6 +1,7 @@
 package com.fasterxml.jackson.core.json;
 
 import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.JsonParserDelegate;
 
 import java.io.*;
 import java.net.URL;
@@ -510,7 +511,65 @@
         }
         jp.close();
     }
+
+    public void testGetValueAsTextBytes() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        _testGetValueAsText(f, true, false);
+        _testGetValueAsText(f, true, true);
+    }
+
+    public void testGetValueAsTextChars() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        _testGetValueAsText(f, false, false);
+        _testGetValueAsText(f, false, true);
+    }
     
+    @SuppressWarnings("resource")
+    private void _testGetValueAsText(JsonFactory f,
+            boolean useBytes, boolean delegate) throws Exception
+    {
+        String JSON = "{\"a\":1,\"b\":true,\"c\":null,\"d\":\"foo\"}";
+        JsonParser p = useBytes ? f.createParser(JSON.getBytes("UTF-8"))
+                : f.createParser(JSON);
+
+        if (delegate) {
+            p = new JsonParserDelegate(p);
+        }
+        
+        assertToken(JsonToken.START_OBJECT, p.nextToken());
+        assertNull(p.getValueAsString());
+
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("a", p.getText());
+        assertEquals("a", p.getValueAsString());
+        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+        assertEquals("1", p.getValueAsString());
+
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("b", p.getValueAsString());
+        assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+        assertEquals("true", p.getValueAsString());
+
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("c", p.getValueAsString());
+        assertToken(JsonToken.VALUE_NULL, p.nextToken());
+        // null token returned as Java null, as per javadoc
+        assertNull(p.getValueAsString());
+
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("d", p.getValueAsString());
+        assertToken(JsonToken.VALUE_STRING, p.nextToken());
+        assertEquals("foo", p.getValueAsString());
+
+        assertToken(JsonToken.END_OBJECT, p.nextToken());
+        assertNull(p.getValueAsString());
+
+        assertNull(p.nextToken());
+        p.close();
+    }
+
     /*
     /**********************************************************
     /* Helper methods
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestNextXxx.java b/src/test/java/com/fasterxml/jackson/core/json/TestNextXxx.java
index 8a95c42..feb8560 100644
--- a/src/test/java/com/fasterxml/jackson/core/json/TestNextXxx.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestNextXxx.java
@@ -19,6 +19,8 @@
     /********************************************************
      */
 
+    private final JsonFactory JSON_F = new JsonFactory();
+
     public void testIsNextTokenName() throws Exception
     {
         _testIsNextTokenName1(false);
@@ -29,21 +31,14 @@
         _testIsNextTokenName3(true);
     }
 
-    // for [core#220]: problem with `nextFieldName(str)`, indented content
-    public void testNextNameWithIndentation() throws Exception
-    {
-        _testNextFieldNameIndent(false);
-        _testNextFieldNameIndent(true);
-    }
-
-    // [core#34]
+    // [jackson-core#34]
     public void testIssue34() throws Exception
     {
         _testIssue34(false);
         _testIssue34(true);
     }
 
-    // [core#38] with nextFieldName
+    // [jackson-core#38] with nextFieldName
     public void testIssue38() throws Exception
     {
         _testIssue38(false);
@@ -52,12 +47,41 @@
 
     public void testNextNameWithLongContent() throws Exception
     {
-        final JsonFactory jf = new JsonFactory();
-
-        _testLong(jf, false);
-        _testLong(jf, true);
+        _testNextNameWithLong(false);
+        _testNextNameWithLong(true);
     }
 
+    // for [core#220]: problem with `nextFieldName(str)`, indented content
+    public void testNextNameWithIndentation() throws Exception
+    {
+        _testNextFieldNameIndent(false);
+        _testNextFieldNameIndent(true);
+    }
+    
+    public void testNextTextValue() throws Exception
+    {
+        _textNextText(false);
+        _textNextText(true);
+    }
+
+    public void testNextIntValue() throws Exception
+    {
+        _textNextInt(false);
+        _textNextInt(true);
+    }
+
+    public void testNextLongValue() throws Exception
+    {
+        _textNextLong(false);
+        _textNextLong(true);
+    }
+
+    public void testNextBooleanValue() throws Exception
+    {
+        _textNextBoolean(false);
+        _textNextBoolean(true);
+    }
+    
     /*
     /********************************************************
     /* Actual test code
@@ -138,10 +162,9 @@
     private void _testIsNextTokenName2(boolean useStream) throws Exception
     {
         final String DOC = "{\"name\":123,\"name2\":14,\"x\":\"name\"}";
-        JsonFactory jf = new JsonFactory();
         JsonParser jp = useStream ?
-            jf.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
-            : jf.createParser(new StringReader(DOC));
+                JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : JSON_F.createParser(new StringReader(DOC));
         SerializableString NAME = new SerializedString("name");
         assertFalse(jp.nextFieldName(NAME));
         assertToken(JsonToken.START_OBJECT, jp.getCurrentToken());
@@ -177,48 +200,46 @@
     private void _testIsNextTokenName3(boolean useStream) throws Exception
     {
         final String DOC = "{\"name\":123,\"name2\":14,\"x\":\"name\"}";
-        JsonFactory jf = new JsonFactory();
-        JsonParser jp = useStream ?
-            jf.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
-            : jf.createParser(new StringReader(DOC));
-        assertNull(jp.nextFieldName());
-        assertToken(JsonToken.START_OBJECT, jp.getCurrentToken());
-        assertEquals("name", jp.nextFieldName());
-        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
-        assertEquals("name", jp.getCurrentName());
-        assertEquals("name", jp.getText());
-        assertNull(jp.nextFieldName());
-        assertToken(JsonToken.VALUE_NUMBER_INT, jp.getCurrentToken());
-        assertEquals(123, jp.getIntValue());
+        JsonParser p = useStream ?
+                JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : JSON_F.createParser(new StringReader(DOC));
+        assertNull(p.nextFieldName());
+        assertToken(JsonToken.START_OBJECT, p.getCurrentToken());
+        assertEquals("name", p.nextFieldName());
+        assertToken(JsonToken.FIELD_NAME, p.getCurrentToken());
+        assertEquals("name", p.getCurrentName());
+        assertEquals("name", p.getText());
+        assertNull(p.nextFieldName());
+        assertToken(JsonToken.VALUE_NUMBER_INT, p.getCurrentToken());
+        assertEquals(123, p.getIntValue());
 
-        assertEquals("name2", jp.nextFieldName());
-        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
-        assertEquals("name2", jp.getCurrentName());
-        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals("name2", p.nextFieldName());
+        assertToken(JsonToken.FIELD_NAME, p.getCurrentToken());
+        assertEquals("name2", p.getCurrentName());
+        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
 
-        assertEquals("x", jp.nextFieldName());
-        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
-        assertEquals("x", jp.getCurrentName());
+        assertEquals("x", p.nextFieldName());
+        assertToken(JsonToken.FIELD_NAME, p.getCurrentToken());
+        assertEquals("x", p.getCurrentName());
 
-        assertNull(jp.nextFieldName());
-        assertToken(JsonToken.VALUE_STRING, jp.getCurrentToken());
+        assertNull(p.nextFieldName());
+        assertToken(JsonToken.VALUE_STRING, p.getCurrentToken());
 
-        assertNull(jp.nextFieldName());
-        assertToken(JsonToken.END_OBJECT, jp.getCurrentToken());
+        assertNull(p.nextFieldName());
+        assertToken(JsonToken.END_OBJECT, p.getCurrentToken());
 
-        assertNull(jp.nextFieldName());
-        assertNull(jp.getCurrentToken());
+        assertNull(p.nextFieldName());
+        assertNull(p.getCurrentToken());
 
-        jp.close();
+        p.close();
     }
 
     private void _testNextFieldNameIndent(boolean useStream) throws Exception
     {
         final String DOC = "{\n  \"name\" : \n  [\n  ]\n   }";
-        JsonFactory f = new JsonFactory();
         JsonParser p = useStream ?
-                f.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
-            : f.createParser(new StringReader(DOC));
+                JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : JSON_F.createParser(new StringReader(DOC));
         assertToken(JsonToken.START_OBJECT, p.nextToken());
         assertTrue(p.nextFieldName(new SerializedString("name")));
 
@@ -230,7 +251,155 @@
 
         p.close();
     }
+    
+    private void _textNextText(boolean useStream) throws Exception
+    {
+        final String DOC = aposToQuotes("{'a':'123','b':5,'c':[false,'foo']}");
+        JsonParser p = useStream ?
+            JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : JSON_F.createParser(new StringReader(DOC));
+        assertNull(p.nextTextValue());
+        assertToken(JsonToken.START_OBJECT, p.getCurrentToken());
+        assertNull(p.nextTextValue());
+        assertToken(JsonToken.FIELD_NAME, p.getCurrentToken());
+        assertEquals("a", p.getCurrentName());
 
+        assertEquals("123", p.nextTextValue());
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("b", p.getCurrentName());
+        assertNull(p.nextFieldName());
+        assertToken(JsonToken.VALUE_NUMBER_INT, p.getCurrentToken());
+
+        assertEquals("c", p.nextFieldName());
+        
+        assertNull(p.nextTextValue());
+        assertToken(JsonToken.START_ARRAY, p.getCurrentToken());
+        assertNull(p.nextTextValue());
+        assertToken(JsonToken.VALUE_FALSE, p.getCurrentToken());
+        assertEquals("foo", p.nextTextValue());
+        
+        assertNull(p.nextTextValue());
+        assertToken(JsonToken.END_ARRAY, p.getCurrentToken());
+        assertNull(p.nextTextValue());
+        assertToken(JsonToken.END_OBJECT, p.getCurrentToken());
+        assertNull(p.nextTextValue());
+        assertNull(p.getCurrentToken());
+
+        p.close();
+    }
+
+    private void _textNextInt(boolean useStream) throws Exception
+    {
+        final String DOC = aposToQuotes("{'a':'123','b':5,'c':[false,456]}");
+        JsonParser p = useStream ?
+            JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : JSON_F.createParser(new StringReader(DOC));
+        assertEquals(0, p.nextIntValue(0));
+        assertToken(JsonToken.START_OBJECT, p.getCurrentToken());
+        assertEquals(0, p.nextIntValue(0));
+        assertToken(JsonToken.FIELD_NAME, p.getCurrentToken());
+        assertEquals("a", p.getCurrentName());
+
+        assertEquals(0, p.nextIntValue(0));
+        assertToken(JsonToken.VALUE_STRING, p.getCurrentToken());
+        assertEquals("123", p.getText());
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("b", p.getCurrentName());
+        assertEquals(5, p.nextIntValue(0));
+
+        assertEquals("c", p.nextFieldName());
+        
+        assertEquals(0, p.nextIntValue(0));
+        assertToken(JsonToken.START_ARRAY, p.getCurrentToken());
+        assertEquals(0, p.nextIntValue(0));
+        assertToken(JsonToken.VALUE_FALSE, p.getCurrentToken());
+        assertEquals(456, p.nextIntValue(0));
+        
+        assertEquals(0, p.nextIntValue(0));
+        assertToken(JsonToken.END_ARRAY, p.getCurrentToken());
+        assertEquals(0, p.nextIntValue(0));
+        assertToken(JsonToken.END_OBJECT, p.getCurrentToken());
+        assertEquals(0, p.nextIntValue(0));
+        assertNull(p.getCurrentToken());
+
+        p.close();
+    }
+
+    private void _textNextLong(boolean useStream) throws Exception
+    {
+        final String DOC = aposToQuotes("{'a':'xyz','b':-59,'c':[false,-1]}");
+        JsonParser p = useStream ?
+            JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : JSON_F.createParser(new StringReader(DOC));
+        assertEquals(0L, p.nextLongValue(0L));
+        assertToken(JsonToken.START_OBJECT, p.getCurrentToken());
+        assertEquals(0L, p.nextLongValue(0L));
+        assertToken(JsonToken.FIELD_NAME, p.getCurrentToken());
+        assertEquals("a", p.getCurrentName());
+
+        assertEquals(0L, p.nextLongValue(0L));
+        assertToken(JsonToken.VALUE_STRING, p.getCurrentToken());
+        assertEquals("xyz", p.getText());
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("b", p.getCurrentName());
+        assertEquals(-59L, p.nextLongValue(0L));
+
+        assertEquals("c", p.nextFieldName());
+        
+        assertEquals(0L, p.nextLongValue(0L));
+        assertToken(JsonToken.START_ARRAY, p.getCurrentToken());
+        assertEquals(0L, p.nextLongValue(0L));
+        assertToken(JsonToken.VALUE_FALSE, p.getCurrentToken());
+        assertEquals(-1L, p.nextLongValue(0L));
+        
+        assertEquals(0L, p.nextLongValue(0L));
+        assertToken(JsonToken.END_ARRAY, p.getCurrentToken());
+        assertEquals(0L, p.nextLongValue(0L));
+        assertToken(JsonToken.END_OBJECT, p.getCurrentToken());
+        assertEquals(0L, p.nextLongValue(0L));
+        assertNull(p.getCurrentToken());
+
+        p.close();
+    }
+
+    private void _textNextBoolean(boolean useStream) throws Exception
+    {
+        final String DOC = aposToQuotes("{'a':'xyz','b':true,'c':[false,0]}");
+        JsonParser p = useStream ?
+            JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : JSON_F.createParser(new StringReader(DOC));
+        assertNull(p.nextBooleanValue());
+        assertToken(JsonToken.START_OBJECT, p.getCurrentToken());
+        assertNull(p.nextBooleanValue());
+        assertToken(JsonToken.FIELD_NAME, p.getCurrentToken());
+        assertEquals("a", p.getCurrentName());
+
+        assertNull(p.nextBooleanValue());
+        assertToken(JsonToken.VALUE_STRING, p.getCurrentToken());
+        assertEquals("xyz", p.getText());
+        assertToken(JsonToken.FIELD_NAME, p.nextToken());
+        assertEquals("b", p.getCurrentName());
+        assertEquals(Boolean.TRUE, p.nextBooleanValue());
+
+        assertEquals("c", p.nextFieldName());
+        
+        assertNull(p.nextBooleanValue());
+        assertToken(JsonToken.START_ARRAY, p.getCurrentToken());
+        assertEquals(Boolean.FALSE, p.nextBooleanValue());
+        assertNull(p.nextBooleanValue());
+        assertToken(JsonToken.VALUE_NUMBER_INT, p.getCurrentToken());
+        assertEquals(0, p.getIntValue());
+        
+        assertNull(p.nextBooleanValue());
+        assertToken(JsonToken.END_ARRAY, p.getCurrentToken());
+        assertNull(p.nextBooleanValue());
+        assertToken(JsonToken.END_OBJECT, p.getCurrentToken());
+        assertNull(p.nextBooleanValue());
+        assertNull(p.getCurrentToken());
+
+        p.close();
+    }
+    
     private void _testIssue34(boolean useStream) throws Exception
     {
         final int TESTROUNDS = 223;
@@ -283,7 +452,7 @@
         parser.close();
     }
 
-    private void _testLong(JsonFactory f, boolean useStream) throws Exception
+    private void _testNextNameWithLong(boolean useStream) throws Exception
     {
         // do 5 meg thingy
         final int SIZE = 5 * 1024 * 1024;
@@ -307,8 +476,8 @@
         final String DOC = sb.toString();
     
         JsonParser parser = useStream ?
-                f.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
-                : f.createParser(new StringReader(DOC));
+                JSON_F.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+                : JSON_F.createParser(new StringReader(DOC));
         assertToken(JsonToken.START_OBJECT, parser.nextToken());
         rnd = new Random(1);
         for (int i = 0; i < count; ++i) {
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestParserSymbols.java b/src/test/java/com/fasterxml/jackson/core/json/TestParserSymbols.java
index 7aaf43e..8d87beb 100644
--- a/src/test/java/com/fasterxml/jackson/core/json/TestParserSymbols.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestParserSymbols.java
@@ -7,17 +7,21 @@
 {
     // For [Issue#148]
     public void testSymbolsWithNullBytes() throws Exception {
-        _testSymbolsWithNull(true);
+        JsonFactory f = new JsonFactory();
+        _testSymbolsWithNull(f, true);
+        // and repeat with same factory, just for fun, and to ensure symbol table is fine
+        _testSymbolsWithNull(f, true);
     }
 
     // For [Issue#148]
     public void testSymbolsWithNullChars() throws Exception {
-        _testSymbolsWithNull(false);
+        JsonFactory f = new JsonFactory();
+        _testSymbolsWithNull(f, false);
+        _testSymbolsWithNull(f, false);
     }
-    
-    private void _testSymbolsWithNull(boolean useBytes) throws Exception
+
+    private void _testSymbolsWithNull(JsonFactory f, boolean useBytes) throws Exception
     {
-        final JsonFactory f = new JsonFactory();
         final String INPUT = "{\"\\u0000abc\" : 1, \"abc\":2}";
         JsonParser parser = useBytes ? f.createParser(INPUT.getBytes("UTF-8"))
                 : f.createParser(INPUT);
@@ -25,12 +29,23 @@
         assertToken(JsonToken.START_OBJECT, parser.nextToken());
 
         assertToken(JsonToken.FIELD_NAME, parser.nextToken());
-        assertEquals("\u0000abc", parser.getCurrentName());
+        String currName = parser.getCurrentName();
+        if (!"\u0000abc".equals(currName)) {
+            fail("Expected \\0abc (4 bytes), '"+currName+"' ("+currName.length()+")");
+        }
         assertToken(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
         assertEquals(1, parser.getIntValue());
 
         assertToken(JsonToken.FIELD_NAME, parser.nextToken());
-        assertEquals("abc", parser.getCurrentName());
+        currName = parser.getCurrentName();
+        if (!"abc".equals(currName)) {
+            /*
+            for (int i = 0; i < currName.length(); ++i) {
+                System.out.println("#"+i+" -> 0x"+Integer.toHexString(currName.charAt(i)));
+            }
+            */
+            fail("Expected 'abc' (3 bytes), '"+currName+"' ("+currName.length()+")");
+        }
         assertToken(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
         assertEquals(2, parser.getIntValue());
         
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestRootValues.java b/src/test/java/com/fasterxml/jackson/core/json/TestRootValues.java
index 3e51d8c..585dbbe 100644
--- a/src/test/java/com/fasterxml/jackson/core/json/TestRootValues.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestRootValues.java
@@ -29,6 +29,29 @@
         jp.close();
     }
 
+    public void testBrokeanNumber() throws Exception
+    {
+    	_testBrokeanNumber(false);
+    	_testBrokeanNumber(true);
+    }
+
+    private void _testBrokeanNumber(boolean useStream) throws Exception
+    {
+    	JsonFactory f = new JsonFactory();
+        final String DOC = "14:89:FD:D3:E7:8C";
+        JsonParser p = useStream ?
+                createParserUsingStream(f, DOC, "UTF-8")
+                : createParserUsingReader(f, DOC);
+        // Should fail, right away
+        try {
+        	p.nextToken();
+        	fail("Ought to fail! Instead, got token: "+p.getCurrentToken());
+        } catch (JsonParseException e) {
+        	verifyException(e, "unexpected character");
+        }
+        p.close();
+    }
+    
     public void testSimpleBooleans() throws Exception
     {
         _testSimpleBooleans(false);
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Generator.java b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Generator.java
index 553693b..3fed2d5 100644
--- a/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Generator.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Generator.java
@@ -1,14 +1,15 @@
 package com.fasterxml.jackson.core.json;
 
-import java.io.ByteArrayOutputStream;
+import java.io.*;
 
 import com.fasterxml.jackson.core.*;
 import com.fasterxml.jackson.core.io.IOContext;
 import com.fasterxml.jackson.core.util.BufferRecycler;
 
-public class TestUtf8Generator
-    extends BaseTest
+public class TestUtf8Generator extends BaseTest
 {
+    private final JsonFactory JSON_F = new JsonFactory();
+
     public void testUtf8Issue462() throws Exception
     {
         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@@ -25,8 +26,7 @@
         gen.close();
         
         // Also verify it's parsable?
-        JsonFactory f = new JsonFactory();
-        JsonParser p = f.createParser(bytes.toByteArray());
+        JsonParser p = JSON_F.createParser(bytes.toByteArray());
         for (int i = 1; i <= length; ++i) {
             assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
             assertEquals(1, p.getIntValue());
@@ -41,9 +41,8 @@
     public void testSurrogatesWithRaw() throws Exception
     {
         final String VALUE = quote("\ud83d\ude0c");
-        JsonFactory f = new JsonFactory();
         ByteArrayOutputStream out = new ByteArrayOutputStream();
-        JsonGenerator jgen = f.createGenerator(out);
+        JsonGenerator jgen = JSON_F.createGenerator(out);
         jgen.writeStartArray();
         jgen.writeRaw(VALUE);
         jgen.writeEndArray();
@@ -51,7 +50,7 @@
 
         final byte[] JSON = out.toByteArray();
 
-        JsonParser jp = f.createParser(JSON);
+        JsonParser jp = JSON_F.createParser(JSON);
         assertToken(JsonToken.START_ARRAY, jp.nextToken());
         assertToken(JsonToken.VALUE_STRING, jp.nextToken());
         String str = jp.getText();
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Parser.java b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Parser.java
index d542559..d7f2f7c 100644
--- a/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Parser.java
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Parser.java
@@ -46,8 +46,7 @@
         jp.close();
     }
 
-    public void testUtf8Name2Bytes()
-        throws Exception
+    public void testUtf8Name2Bytes() throws Exception
     {
         final String[] NAMES = UTF8_2BYTE_STRINGS;
 
@@ -56,10 +55,17 @@
             String DOC = "{ \""+NAME+"\" : 0 }";
             JsonParser jp = createParserUsingStream(DOC, "UTF-8");
             assertToken(JsonToken.START_OBJECT, jp.nextToken());
-            
+
             assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+
+            assertTrue(jp.hasToken(JsonToken.FIELD_NAME));
+            assertTrue(jp.hasTokenId(JsonTokenId.ID_FIELD_NAME));
+            
             assertEquals(NAME, jp.getCurrentName());
             assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertTrue(jp.hasToken(JsonToken.VALUE_NUMBER_INT));
+            assertTrue(jp.hasTokenId(JsonTokenId.ID_NUMBER_INT));
+
             // should retain name during value entry, too
             assertEquals(NAME, jp.getCurrentName());
             
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestJsonParserSymbols.java b/src/test/java/com/fasterxml/jackson/core/sym/SymbolTableMergingTest.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/core/sym/TestJsonParserSymbols.java
rename to src/test/java/com/fasterxml/jackson/core/sym/SymbolTableMergingTest.java
index 20c9e7f..766fada 100644
--- a/src/test/java/com/fasterxml/jackson/core/sym/TestJsonParserSymbols.java
+++ b/src/test/java/com/fasterxml/jackson/core/sym/SymbolTableMergingTest.java
@@ -11,7 +11,7 @@
  * merge back symbols to the root symbol table
  */
 @SuppressWarnings("serial")
-public class TestJsonParserSymbols
+public class SymbolTableMergingTest
     extends com.fasterxml.jackson.core.BaseTest
 {
     /**
@@ -21,7 +21,7 @@
      */
     final static class MyJsonFactory extends JsonFactory
     {
-        public int byteSymbolCount() { return _rootByteSymbols.size(); }
+        public int byteSymbolCount() { return _byteSymbolCanonicalizer.size(); }
         public int charSymbolCount() { return _rootCharSymbols.size(); }
     }
 
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/SymbolsViaParserTest.java b/src/test/java/com/fasterxml/jackson/core/sym/SymbolsViaParserTest.java
new file mode 100644
index 0000000..8692735
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/sym/SymbolsViaParserTest.java
@@ -0,0 +1,94 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.io.IOException;
+import java.util.HashSet;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Tests that use symbol table functionality through parser.
+ */
+public class SymbolsViaParserTest
+    extends com.fasterxml.jackson.core.BaseTest
+{
+    // for [jackson-core#213]
+    public void test17CharSymbols() throws Exception {
+        _test17Chars(false);
+    }
+
+    // for [jackson-core#213]
+    public void test17ByteSymbols() throws Exception {
+        _test17Chars(true);
+    }
+
+    // for [jackson-core#216]
+    public void testSymbolTableExpansionChars() throws Exception {
+        _testSymbolTableExpansion(false);
+    }
+
+    // for [jackson-core#216]
+    public void testSymbolTableExpansionBytes() throws Exception {
+        _testSymbolTableExpansion(true);
+    }
+    
+    /*
+    /**********************************************************
+    /* Secondary test methods
+    /**********************************************************
+     */
+
+    private void _test17Chars(boolean useBytes) throws IOException
+    {
+        String doc = _createDoc17();
+        JsonFactory f = new JsonFactory();
+        
+        JsonParser p = useBytes
+                ? f.createParser(doc.getBytes("UTF-8"))
+                : f.createParser(doc);
+        HashSet<String> syms = new HashSet<String>();
+        assertToken(JsonToken.START_OBJECT, p.nextToken());
+        for (int i = 0; i < 50; ++i) {
+            assertToken(JsonToken.FIELD_NAME, p.nextToken());
+            syms.add(p.getCurrentName());
+            assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+        }
+        assertToken(JsonToken.END_OBJECT, p.nextToken());
+        assertEquals(50, syms.size());
+        p.close();
+    }
+
+    private String _createDoc17() {
+        StringBuilder sb = new StringBuilder(1000);
+        sb.append("{\n");
+        for (int i = 1; i <= 50; ++i) {
+            if (i > 1) {
+                sb.append(",\n");
+            }
+            sb.append("\"lengthmatters")
+                .append(1000 + i)
+                .append("\": true");
+        }
+        sb.append("\n}");
+        return sb.toString();
+    }
+
+    public void _testSymbolTableExpansion(boolean useBytes) throws Exception
+    {
+        JsonFactory jsonFactory = new JsonFactory();
+        // Important: must create separate documents to gradually build up symbol table
+        for (int i = 0; i < 200; i++) {
+            String field = Integer.toString(i);
+            final String doc = "{ \"" + field + "\" : \"test\" }";
+            JsonParser parser = useBytes
+                    ? jsonFactory.createParser(doc.getBytes("UTF-8"))
+                    : jsonFactory.createParser(doc);
+            assertToken(JsonToken.START_OBJECT, parser.nextToken());
+            assertToken(JsonToken.FIELD_NAME, parser.nextToken());
+            assertEquals(field, parser.getCurrentName());
+            assertToken(JsonToken.VALUE_STRING, parser.nextToken());
+            assertToken(JsonToken.END_OBJECT, parser.nextToken());
+            assertNull(parser.nextToken());
+            parser.close();
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestByteBasedSymbols.java b/src/test/java/com/fasterxml/jackson/core/sym/TestByteBasedSymbols.java
index 3360d48..a0ff70d 100644
--- a/src/test/java/com/fasterxml/jackson/core/sym/TestByteBasedSymbols.java
+++ b/src/test/java/com/fasterxml/jackson/core/sym/TestByteBasedSymbols.java
@@ -1,15 +1,13 @@
 package com.fasterxml.jackson.core.sym;
 
 import java.io.*;
+import java.lang.reflect.Field;
 
 import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer;
-import com.fasterxml.jackson.core.sym.Name;
 
 /**
  * Unit test(s) to verify that handling of (byte-based) symbol tables
- * is working. Created to verify fix to [JACKSON-5] (although not very
- * good at catching it...).
+ * is working.
  */
 public class TestByteBasedSymbols
     extends com.fasterxml.jackson.core.BaseTest
@@ -76,24 +74,22 @@
         jp0.close();
     }
 
-    public void testAuxMethods()
-        throws Exception
+    public void testAuxMethodsWithNewSymboTable() throws Exception
     {
         final int A_BYTES = 0x41414141; // "AAAA"
         final int B_BYTES = 0x42424242; // "BBBB"
 
-        BytesToNameCanonicalizer nc = BytesToNameCanonicalizer.createRoot()
+        ByteQuadsCanonicalizer nc = ByteQuadsCanonicalizer.createRoot()
                 .makeChild(JsonFactory.Feature.collectDefaults());
         assertNull(nc.findName(A_BYTES));
         assertNull(nc.findName(A_BYTES, B_BYTES));
 
         nc.addName("AAAA", new int[] { A_BYTES }, 1);
-        Name n1 = nc.findName(A_BYTES);
-        assertNotNull(n1);
-        assertEquals("AAAA", n1.getName());
+        String n1 = nc.findName(A_BYTES);
+        assertEquals("AAAA", n1);
         nc.addName("AAAABBBB", new int[] { A_BYTES, B_BYTES }, 2);
-        Name n2 = nc.findName(A_BYTES, B_BYTES);
-        assertEquals("AAAABBBB", n2.getName());
+        String n2 = nc.findName(A_BYTES, B_BYTES);
+        assertEquals("AAAABBBB", n2);
         assertNotNull(n2);
 
         /* and let's then just exercise this method so it gets covered;
@@ -102,14 +98,35 @@
         assertNotNull(nc.toString());
     }
 
+    // as per name, for [core#207]
+    public void testIssue207() throws Exception
+    {
+        ByteQuadsCanonicalizer nc = ByteQuadsCanonicalizer.createRoot(-523743345);
+        Field byteSymbolCanonicalizerField = JsonFactory.class.getDeclaredField("_byteSymbolCanonicalizer");
+        byteSymbolCanonicalizerField.setAccessible(true);
+        JsonFactory jsonF = new JsonFactory();
+        byteSymbolCanonicalizerField.set(jsonF, nc);
+
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("{\n");
+        stringBuilder.append("    \"expectedGCperPosition\": null");
+        for (int i = 0; i < 60; ++i) {
+            stringBuilder.append(",\n    \"").append(i + 1).append("\": null");
+        }
+        stringBuilder.append("\n}");
+
+        JsonParser p = jsonF.createParser(stringBuilder.toString().getBytes("UTF-8"));
+        while (p.nextToken() != null) { }
+        p.close();
+    }
+
     /*
     /**********************************************************
     /* Helper methods
     /**********************************************************
      */
 
-    protected JsonParser createParser(JsonFactory jf, String input)
-        throws IOException, JsonParseException
+    protected JsonParser createParser(JsonFactory jf, String input) throws IOException
     {
         byte[] data = input.getBytes("UTF-8");
         InputStream is = new ByteArrayInputStream(data);
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestHashCollisionChars.java b/src/test/java/com/fasterxml/jackson/core/sym/TestHashCollisionChars.java
index 40dac62..fd3bb1e 100644
--- a/src/test/java/com/fasterxml/jackson/core/sym/TestHashCollisionChars.java
+++ b/src/test/java/com/fasterxml/jackson/core/sym/TestHashCollisionChars.java
@@ -8,6 +8,11 @@
 /**
  * Some unit tests to try to exercise part of parser code that
  * deals with symbol (table) management.
+ *<p>
+ * Note that the problem does not necessarily affect code at or
+ * after Jackson 2.6, since hash calculation algorithm has been
+ * completely changed. It may still be relevant for character-backed
+ * sources, however.
  */
 public class TestHashCollisionChars
     extends BaseTest
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolTables.java b/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolTables.java
index 8f8156c..045a943 100644
--- a/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolTables.java
+++ b/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolTables.java
@@ -1,10 +1,15 @@
 package com.fasterxml.jackson.core.sym;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.nio.charset.Charset;
 
 import com.fasterxml.jackson.core.*;
 
+/**
+ * Tests that directly modify/access underlying low-level symbol tables
+ * (instead of indirectly using them via JsonParser).
+ */
 public class TestSymbolTables extends com.fasterxml.jackson.core.BaseTest
 {
     // Test for verifying stability of hashCode, wrt collisions, using
@@ -13,67 +18,117 @@
     {
         // pass seed, to keep results consistent:
         CharsToNameCanonicalizer symbols = CharsToNameCanonicalizer.createRoot(1);
-        final int COUNT = 6000;
+        final int COUNT = 12000;
         for (int i = 0; i < COUNT; ++i) {
             String id = fieldNameFor(i);
             char[] ch = id.toCharArray();
             symbols.findSymbol(ch, 0, ch.length, symbols.calcHash(id));
         }
 
-        assertEquals(8192, symbols.bucketCount());
+        assertEquals(16384, symbols.bucketCount());
         assertEquals(COUNT, symbols.size());
         
 //System.out.printf("Char stuff: collisions %d, max-coll %d\n", symbols.collisionCount(), symbols.maxCollisionLength());
         
         // holy guacamoley... there are way too many. 31 gives 3567 (!), 33 gives 2747
         // ... at least before shuffling. Shuffling helps quite a lot, so:
+        
+        assertEquals(3431, symbols.collisionCount());
 
-        assertEquals(1401, symbols.collisionCount()); // with 33
-//        assertEquals(1858, symbols.collisionCount()); // with 31
-
-        // esp. with collisions; first got about 30;
-        // with fixes 4 (for 33), 5 (for 31)
-
-        assertEquals(4, symbols.maxCollisionLength()); // 33
-//        assertEquals(5, symbols.maxCollisionLength()); // 31
+        assertEquals(6, symbols.maxCollisionLength());
     }
 
     // Test for verifying stability of hashCode, wrt collisions, using
     // synthetic field name generation and byte-based input (UTF-8)
-    public void testSyntheticWithBytes() throws IOException
+    @SuppressWarnings("deprecation")
+    public void testSyntheticWithBytesOld() throws IOException
     {
         // pass seed, to keep results consistent:
         final int SEED = 33333;
         BytesToNameCanonicalizer symbols =
                 BytesToNameCanonicalizer.createRoot(SEED).makeChild(JsonFactory.Feature.collectDefaults());
-        final int COUNT = 6000;
+
+        final int COUNT = 12000;
         for (int i = 0; i < COUNT; ++i) {
             String id = fieldNameFor(i);
-            int[] quads = BytesToNameCanonicalizer.calcQuads(id.getBytes("UTF-8"));
+            int[] quads = calcQuads(id.getBytes("UTF-8"));
             symbols.addName(id, quads, quads.length);
         }
         assertEquals(COUNT, symbols.size());
-        assertEquals(8192, symbols.bucketCount());
+        assertEquals(16384, symbols.bucketCount());
 
 //System.out.printf("Byte stuff: collisions %d, max-coll %d\n", symbols.collisionCount(), symbols.maxCollisionLength());
-    
-        assertEquals(1733, symbols.collisionCount());
-        // but not super long collision chains:
-        assertEquals(9, symbols.maxCollisionLength());
+        assertEquals(3476, symbols.collisionCount());
+        // longest collision chain not optimal but ok:
+        assertEquals(15, symbols.maxCollisionLength());
+
+        // But also verify entries are actually found?
+    }
+
+    public void testSyntheticWithBytesNew() throws IOException
+    {
+        // pass seed, to keep results consistent:
+        final int SEED = 33333;
+        ByteQuadsCanonicalizer symbols =
+                ByteQuadsCanonicalizer.createRoot(SEED).makeChild(JsonFactory.Feature.collectDefaults());
+
+        final int COUNT = 12000;
+        for (int i = 0; i < COUNT; ++i) {
+            String id = fieldNameFor(i);
+            int[] quads = calcQuads(id.getBytes("UTF-8"));
+            symbols.addName(id, quads, quads.length);
+        }
+        assertEquals(COUNT, symbols.size());
+        assertEquals(16384, symbols.bucketCount());
+        
+        // fragile, but essential to verify low collision counts;
+        // anywhere between 70-80% primary matches
+        assertEquals(8534, symbols.primaryCount());
+        // secondary between 10-20%
+        assertEquals(2534, symbols.secondaryCount());
+        // and most of remaining in tertiary
+        assertEquals(932, symbols.tertiaryCount());
+        // so that spill-over is empty or close to
+        assertEquals(0, symbols.spilloverCount());
     }
 
     // [Issue#145]
-    public void testThousandsOfSymbols() throws IOException
+    public void testThousandsOfSymbolsWithChars() throws IOException
     {
         final int SEED = 33333;
 
-        BytesToNameCanonicalizer symbolsBRoot = BytesToNameCanonicalizer.createRoot(SEED);
         CharsToNameCanonicalizer symbolsCRoot = CharsToNameCanonicalizer.createRoot(SEED);
-        final Charset utf8 = Charset.forName("UTF-8");
+        int exp = 0;
         
         for (int doc = 0; doc < 100; ++doc) {
             CharsToNameCanonicalizer symbolsC =
                     symbolsCRoot.makeChild(JsonFactory.Feature.collectDefaults());
+            for (int i = 0; i < 250; ++i) {
+                String name = "f_"+doc+"_"+i;
+                char[] ch = name.toCharArray();
+                String str = symbolsC.findSymbol(ch, 0, ch.length,
+                        symbolsC.calcHash(name));
+                assertNotNull(str);
+            }
+            symbolsC.release();
+            exp += 250;
+            if (exp > CharsToNameCanonicalizer.MAX_ENTRIES_FOR_REUSE) {
+                exp = 0;
+            }
+            assertEquals(exp, symbolsCRoot.size());
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    public void testThousandsOfSymbolsWithOldBytes() throws IOException
+    {
+        final int SEED = 33333;
+
+        BytesToNameCanonicalizer symbolsBRoot = BytesToNameCanonicalizer.createRoot(SEED);
+        final Charset utf8 = Charset.forName("UTF-8");
+        int exp = 0;
+        
+        for (int doc = 0; doc < 100; ++doc) {
             BytesToNameCanonicalizer symbolsB =
                     symbolsBRoot.makeChild(JsonFactory.Feature.collectDefaults());
             for (int i = 0; i < 250; ++i) {
@@ -81,18 +136,105 @@
 
                 int[] quads = BytesToNameCanonicalizer.calcQuads(name.getBytes(utf8));
                 symbolsB.addName(name, quads, quads.length);
-
-                char[] ch = name.toCharArray();
-                String str = symbolsC.findSymbol(ch, 0, ch.length,
-                        symbolsC.calcHash(name));
-                assertNotNull(str);
+                Name n = symbolsB.findName(quads, quads.length);
+                assertEquals(name, n.getName());
             }
             symbolsB.release();
-            symbolsC.release();
+            exp += 250;
+            if (exp > BytesToNameCanonicalizer.MAX_ENTRIES_FOR_REUSE) {
+                exp = 0;
+            }
+            assertEquals(exp, symbolsBRoot.size());
         }
     }
 
+    // Since 2.6
+    public void testThousandsOfSymbolsWithNew() throws IOException
+    {
+        final int SEED = 33333;
+
+        ByteQuadsCanonicalizer symbolsBRoot = ByteQuadsCanonicalizer.createRoot(SEED);
+        final Charset utf8 = Charset.forName("UTF-8");
+        int exp = 0;
+        ByteQuadsCanonicalizer symbolsB = null;
+
+        // loop to get 
+        for (int doc = 0; doc < 100; ++doc) {
+            symbolsB = symbolsBRoot.makeChild(JsonFactory.Feature.collectDefaults());
+            for (int i = 0; i < 250; ++i) {
+                String name = "f_"+doc+"_"+i;
+
+                int[] quads = calcQuads(name.getBytes(utf8));
+                
+                symbolsB.addName(name, quads, quads.length);
+                String n = symbolsB.findName(quads, quads.length);
+                assertEquals(name, n);
+            }
+            symbolsB.release();
+            
+            exp += 250;
+            if (exp > ByteQuadsCanonicalizer.MAX_ENTRIES_FOR_REUSE) {
+                exp = 0;
+            }
+            assertEquals(exp, symbolsBRoot.size());
+        }
+        /* 05-Feb-2015, tatu: Fragile, but it is important to ensure that collision
+         *   rates are not accidentally increased...
+         */
+        assertEquals(6250, symbolsB.size());
+        assertEquals(4761, symbolsB.primaryCount()); // 80% primary hit rate
+        assertEquals(1190, symbolsB.secondaryCount()); // 13% secondary
+        assertEquals(299, symbolsB.tertiaryCount()); // 7% tertiary
+        assertEquals(0, symbolsB.spilloverCount()); // and couple of leftovers
+    }
+    
+    // And then one more test just for Bytes-based symbol table
+    public void testByteBasedSymbolTable() throws Exception
+    {
+        // combination of short, medium1/2, long names...
+        final String JSON = aposToQuotes("{'abc':1, 'abc\\u0000':2, '\\u0000abc':3, "
+                // then some medium
+                +"'abc123':4,'abcd1234':5,"
+                +"'abcd1234a':6,'abcd1234abcd':7,"
+                +"'abcd1234abcd1':8"
+                +"}");
+
+        JsonFactory f = new JsonFactory();
+        JsonParser p = f.createParser(JSON.getBytes("UTF-8"));
+        ByteQuadsCanonicalizer symbols = _findSymbols(p);
+        assertEquals(0, symbols.size());
+        _streamThrough(p);
+        assertEquals(8, symbols.size());
+        p.close();
+
+        // and, for fun, try again
+        p = f.createParser(JSON.getBytes("UTF-8"));
+        _streamThrough(p);
+        symbols = _findSymbols(p);
+        assertEquals(8, symbols.size());
+        p.close();
+
+        p = f.createParser(JSON.getBytes("UTF-8"));
+        _streamThrough(p);
+        symbols = _findSymbols(p);
+        assertEquals(8, symbols.size());
+        p.close();
+    }
+
+    private void _streamThrough(JsonParser p) throws IOException
+    {
+        while (p.nextToken() != null) { }
+    }
+    
+    private ByteQuadsCanonicalizer _findSymbols(JsonParser p) throws Exception
+    {
+        Field syms = p.getClass().getDeclaredField("_symbols");
+        syms.setAccessible(true);
+        return ((ByteQuadsCanonicalizer) syms.get(p));
+    }
+
     // [core#187]: unexpectedly high number of collisions for straight numbers
+    @SuppressWarnings("deprecation")
     public void testCollisionsWithBytes187() throws IOException
     {
         BytesToNameCanonicalizer symbols =
@@ -129,9 +271,63 @@
         assertEquals(65536, symbols.bucketCount());
 
         // collision count rather high, but has to do
-        assertEquals(14408, symbols.collisionCount());
+        assertEquals(7127, symbols.collisionCount());
         // as well as collision counts
-        assertEquals(10, symbols.maxCollisionLength());
+        assertEquals(4, symbols.maxCollisionLength());
+    }
+
+    // [core#187]: unexpectedly high number of collisions for straight numbers
+    public void testCollisionsWithBytesNew187a() throws IOException
+    {
+        ByteQuadsCanonicalizer symbols =
+                ByteQuadsCanonicalizer.createRoot(1).makeChild(JsonFactory.Feature.collectDefaults());
+
+        final int COUNT = 43000;
+        for (int i = 0; i < COUNT; ++i) {
+            String id = String.valueOf(10000 + i);
+            int[] quads = calcQuads(id.getBytes("UTF-8"));
+            symbols.addName(id, quads, quads.length);
+        }
+
+        assertEquals(COUNT, symbols.size());
+        assertEquals(65536, symbols.bucketCount());
+
+        /* 29-Mar-2015, tatu: To get collision counts down for this
+         *    test took quite a bit of tweaking...
+         */
+        assertEquals(32342, symbols.primaryCount());
+        assertEquals(8863, symbols.secondaryCount());
+        assertEquals(1795, symbols.tertiaryCount());
+
+        // finally managed to get this to 0; other variants produced thousands
+        assertEquals(0, symbols.spilloverCount());
+    }
+
+    // Another variant, but with 1-quad names
+    public void testCollisionsWithBytesNew187b() throws IOException
+    {
+        ByteQuadsCanonicalizer symbols =
+                ByteQuadsCanonicalizer.createRoot(1).makeChild(JsonFactory.Feature.collectDefaults());
+
+        final int COUNT = 10000;
+        for (int i = 0; i < COUNT; ++i) {
+            String id = String.valueOf(i);
+            int[] quads = calcQuads(id.getBytes("UTF-8"));
+            symbols.addName(id, quads, quads.length);
+        }
+        assertEquals(COUNT, symbols.size());
+        
+        assertEquals(16384, symbols.bucketCount());
+
+        // fragile, but essential to verify low collision counts;
+        // here bit low primary, 55% 
+        assertEquals(5402, symbols.primaryCount());
+        // secondary higher than usual, above 25%
+        assertEquals(2744, symbols.secondaryCount());
+        // and most of remaining in tertiary
+        assertEquals(1834, symbols.tertiaryCount());
+        // with a bit of spillover
+        assertEquals(20, symbols.spilloverCount());
     }
 
     // [core#191]: similarly, but for "short" symbols:
@@ -152,6 +348,26 @@
         p.close();
     }
 
+    private String _shortDoc191() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{\n");
+        for (int i = 0; i < 400; ++i) {
+            if (i > 0) {
+                sb.append(",\n");
+            }
+            sb.append('"');
+            char c = (char) i;
+            if (Character.isLetterOrDigit(c)) {
+                sb.append((char) i);
+            } else {
+                sb.append(String.format("\\u%04x", i));
+            }
+            sb.append("\" : "+i);
+        }
+        sb.append("}\n");
+        return sb.toString();
+    }
+    
     // [core#191]
     public void testShortQuotedDirectChars() throws IOException
     {
@@ -166,11 +382,12 @@
         assertEquals(COUNT, symbols.size());
         assertEquals(1024, symbols.bucketCount());
 
-        assertEquals(112, symbols.collisionCount());
+        assertEquals(50, symbols.collisionCount());
         assertEquals(2, symbols.maxCollisionLength());
     }
 
-    public void testShortQuotedDirectBytes() throws IOException
+    @SuppressWarnings("deprecation")
+    public void testShortQuotedDirectBytesOld() throws IOException
     {
         final int COUNT = 400;
         BytesToNameCanonicalizer symbols =
@@ -186,11 +403,31 @@
         assertEquals(44, symbols.collisionCount());
         assertEquals(2, symbols.maxCollisionLength());
     }
-    
-    // [core#191]
-    public void testShortNameCollisionsDirect() throws IOException
+
+    public void testShortQuotedDirectBytes() throws IOException
     {
         final int COUNT = 400;
+        ByteQuadsCanonicalizer symbols =
+                ByteQuadsCanonicalizer.createRoot(123).makeChild(JsonFactory.Feature.collectDefaults());
+        for (int i = 0; i < COUNT; ++i) {
+            String id = String.format("\\u%04x", i);
+            int[] quads = calcQuads(id.getBytes("UTF-8"));
+            symbols.addName(id, quads, quads.length);
+        }
+        assertEquals(COUNT, symbols.size());
+        assertEquals(512, symbols.bucketCount());
+
+        assertEquals(285, symbols.primaryCount());
+        assertEquals(90, symbols.secondaryCount());
+        assertEquals(25, symbols.tertiaryCount());
+        assertEquals(0, symbols.spilloverCount());
+    }
+    
+    // [core#191]
+    @SuppressWarnings("deprecation")
+    public void testShortNameCollisionsDirect() throws IOException
+    {
+        final int COUNT = 600;
 
         // First, char-based
         {
@@ -203,37 +440,69 @@
             assertEquals(COUNT, symbols.size());
             assertEquals(1024, symbols.bucketCount());
     
-            assertEquals(0, symbols.collisionCount());
-            assertEquals(0, symbols.maxCollisionLength());
+            assertEquals(16, symbols.collisionCount());
+            assertEquals(1, symbols.maxCollisionLength());
         }
-
+        
         // then byte-based
         {
             BytesToNameCanonicalizer symbols =
                     BytesToNameCanonicalizer.createRoot(1).makeChild(JsonFactory.Feature.collectDefaults());
             for (int i = 0; i < COUNT; ++i) {
                 String id = String.valueOf((char) i);
-                int[] quads = BytesToNameCanonicalizer.calcQuads(id.getBytes("UTF-8"));
+                int[] quads = calcQuads(id.getBytes("UTF-8"));
                 symbols.addName(id, quads, quads.length);
             }
             assertEquals(COUNT, symbols.size());
             assertEquals(1024, symbols.bucketCount());
     
-            assertEquals(15, symbols.collisionCount());
+            assertEquals(209, symbols.collisionCount());
             assertEquals(1, symbols.maxCollisionLength());
         }
     }
-    
-    private String _shortDoc191() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("{\n");
-        for (int i = 0; i < 400; ++i) {
-            if (i > 0) {
-                sb.append(",\n");
+
+    public void testShortNameCollisionsDirectNew() throws IOException
+    {
+        final int COUNT = 700;
+        {
+            ByteQuadsCanonicalizer symbols =
+                    ByteQuadsCanonicalizer.createRoot(333).makeChild(JsonFactory.Feature.collectDefaults());
+            for (int i = 0; i < COUNT; ++i) {
+                String id = String.valueOf((char) i);
+                int[] quads = calcQuads(id.getBytes("UTF-8"));
+                symbols.addName(id, quads, quads.length);
             }
-            sb.append(String.format("\"\\u%04x\" : %d", i, i));
+            assertEquals(COUNT, symbols.size());
+
+            assertEquals(1024, symbols.bucketCount());
+
+            // Primary is good, but secondary spills cluster in nasty way...
+            assertEquals(564, symbols.primaryCount());
+            assertEquals(122, symbols.secondaryCount());
+            assertEquals(14, symbols.tertiaryCount());
+            assertEquals(0, symbols.spilloverCount());
+
+            assertEquals(COUNT,
+                    symbols.primaryCount() + symbols.secondaryCount() + symbols.tertiaryCount() + symbols.spilloverCount());
         }
-        sb.append("}\n");
-        return sb.toString();
+    }
+
+    // to verify [jackson-core#213] -- did not fail, but ruled out low-level bug
+
+    public void testLongSymbols17Bytes() throws Exception
+    {
+        ByteQuadsCanonicalizer symbolsB =
+                ByteQuadsCanonicalizer.createRoot(3).makeChild(JsonFactory.Feature.collectDefaults());
+        CharsToNameCanonicalizer symbolsC = CharsToNameCanonicalizer.createRoot(3);
+
+        for (int i = 1001; i <= 1050; ++i) {
+            String id = "lengthmatters"+i;
+            int[] quads = calcQuads(id.getBytes("UTF-8"));
+            symbolsB.addName(id, quads, quads.length);
+            char[] idChars = id.toCharArray();
+            symbolsC.findSymbol(idChars, 0, idChars.length, symbolsC.calcHash(id));
+        }
+        assertEquals(50, symbolsB.size());
+        assertEquals(50, symbolsC.size());
     }
 }
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolsWithMediaItem.java b/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolsWithMediaItem.java
new file mode 100644
index 0000000..f0b5239
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolsWithMediaItem.java
@@ -0,0 +1,94 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestSymbolsWithMediaItem extends com.fasterxml.jackson.core.BaseTest
+{
+    private final String JSON = aposToQuotes(
+            "{'media' : {\n"
+            +"      'uri' : 'http://foo.com',"
+            +"      'title' : 'Test title 1',"
+            +"      'width' : 640, 'height' : 480,"
+            +"      'format' : 'video/mpeg4',"
+            +"      'duration' : 18000000,"
+            +"      'size' : 58982400,"
+            +"      'bitrate' : 262144,"
+            +"      'persons' : [ ],"
+            +"      'player' : 'native',"
+            +"      'copyright' : 'None'"
+            +"   },\n"
+            +"   'images' : [ {\n"
+            +"      'uri' : 'http://bar.com',\n"
+            +"      'title' : 'Test title 1',\n"
+            +"      'width' : 1024,'height' : 768,\n"
+            +"      'size' : 'LARGE'\n"
+            +"    }, {\n"
+            +"      'uri' : 'http://foobar.org',\n"
+            +"      'title' : 'Javaone Keynote',\n"
+            +"      'width' : 320, 'height' : 240,\n"
+            +"      'size' : 'SMALL'\n"
+            +"    } ]\n"
+            +"}\n");
+
+    public void testSmallSymbolSetWithBytes() throws IOException
+    {
+        final int SEED = 33333;
+
+        ByteQuadsCanonicalizer symbolsRoot = ByteQuadsCanonicalizer.createRoot(SEED);
+        ByteQuadsCanonicalizer symbols = symbolsRoot.makeChild(JsonFactory.Feature.collectDefaults());
+        JsonFactory f = new JsonFactory();
+        JsonParser p = f.createParser(JSON.getBytes("UTF-8"));
+
+        JsonToken t;
+        while ((t = p.nextToken()) != null) {
+            if (t != JsonToken.FIELD_NAME) {
+                continue;
+            }
+            String name = p.getCurrentName();
+            int[] quads = calcQuads(name.getBytes("UTF-8"));
+
+            if (symbols.findName(quads, quads.length) != null) {
+                continue;
+            }
+            symbols.addName(name, quads, quads.length);
+        }
+        p.close();
+        
+        assertEquals(13, symbols.size());
+        assertEquals(12, symbols.primaryCount()); // 80% primary hit rate
+        assertEquals(1, symbols.secondaryCount()); // 13% secondary
+        assertEquals(0, symbols.tertiaryCount()); // 7% tertiary
+        assertEquals(0, symbols.spilloverCount()); // and couple of leftovers
+    }
+
+    public void testSmallSymbolSetWithChars() throws IOException
+    {
+        final int SEED = 33333;
+
+        CharsToNameCanonicalizer symbols = CharsToNameCanonicalizer.createRoot(SEED);
+        JsonFactory f = new JsonFactory();
+        JsonParser p = f.createParser(JSON);
+
+        JsonToken t;
+        while ((t = p.nextToken()) != null) {
+            if (t != JsonToken.FIELD_NAME) {
+                continue;
+            }
+            String name = p.getCurrentName();
+            char[] ch = name.toCharArray();
+            symbols.findSymbol(ch, 0, ch.length, symbols.calcHash(name));
+        }
+        p.close();
+        
+        assertEquals(13, symbols.size());
+        assertEquals(13, symbols.size());
+        assertEquals(64, symbols.bucketCount());
+
+        // usually get 1 collision, but sometimes get lucky with 0; other times less so with 2
+        // (with differing shifting for hash etc)
+        assertEquals(0, symbols.collisionCount());
+        assertEquals(0, symbols.maxCollisionLength());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/type/TypeReferenceTest.java b/src/test/java/com/fasterxml/jackson/core/type/TypeReferenceTest.java
new file mode 100644
index 0000000..1bf326b
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/type/TypeReferenceTest.java
@@ -0,0 +1,27 @@
+package com.fasterxml.jackson.core.type;
+
+import java.util.List;
+
+import com.fasterxml.jackson.core.BaseTest;
+
+// Not much to test, but exercise to prevent code coverage tool from showing all red for package
+public class TypeReferenceTest extends BaseTest
+{
+    public void testSimple()
+    {
+        TypeReference<?> ref = new TypeReference<List<String>>() { };
+        assertNotNull(ref);
+        ref.equals(null);
+    }
+
+    @SuppressWarnings("rawtypes")
+    public void testInvalid()
+    {
+        try { 
+            Object ob = new TypeReference() { };
+            fail("Should not pass, got: "+ob);
+        } catch (IllegalArgumentException e) {
+            verifyException(e, "without actual type information");
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/util/ByteArrayBuilderTest.java b/src/test/java/com/fasterxml/jackson/core/util/ByteArrayBuilderTest.java
new file mode 100644
index 0000000..59ec2ae
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/util/ByteArrayBuilderTest.java
@@ -0,0 +1,30 @@
+package com.fasterxml.jackson.core.util;
+
+import org.junit.Assert;
+
+public class ByteArrayBuilderTest extends com.fasterxml.jackson.core.BaseTest
+{
+    public void testSimple() throws Exception
+    {
+        ByteArrayBuilder b = new ByteArrayBuilder(null, 20);
+        Assert.assertArrayEquals(new byte[0], b.toByteArray());
+
+        b.write((byte) 0);
+        b.append(1);
+
+        byte[] foo = new byte[98];
+        for (int i = 0; i < foo.length; ++i) {
+            foo[i] = (byte) (2 + i);
+        }
+        b.write(foo);
+
+        byte[] result = b.toByteArray();
+        assertEquals(100, result.length);
+        for (int i = 0; i < 100; ++i) {
+            assertEquals(i, (int) result[i]);
+        }
+        
+        b.release();
+        b.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestDefaultPrettyPrinter.java b/src/test/java/com/fasterxml/jackson/core/util/TestDefaultPrettyPrinter.java
index 0d581a8..ad1a7e7 100644
--- a/src/test/java/com/fasterxml/jackson/core/util/TestDefaultPrettyPrinter.java
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestDefaultPrettyPrinter.java
@@ -37,7 +37,7 @@
     public void testWithIndent() throws IOException
     {
         PrettyPrinter pp = new DefaultPrettyPrinter()
-        .withObjectIndenter(new DefaultIndenter().withIndent(" "));
+        .withObjectIndenter(new DefaultIndenter().withLinefeed("\n").withIndent(" "));
         String EXP = "{\n" +
             " \"name\" : \"John Doe\",\n" +
             " \"age\" : 3.14\n" +
@@ -84,7 +84,7 @@
 
     public void testRootSeparator() throws IOException
     {
-        PrettyPrinter pp = new DefaultPrettyPrinter()
+        DefaultPrettyPrinter pp = new DefaultPrettyPrinter()
             .withRootSeparator("|");
         final String EXP = "1|2|3";
 
@@ -107,6 +107,27 @@
         gen.writeNumber(3);
         gen.close();
         assertEquals(EXP, bytes.toString("UTF-8"));
+
+        // Also: let's try removing separator altogether
+        pp = pp.withRootSeparator((String) null)
+                .withArrayIndenter(null)
+                .withObjectIndenter(null)
+                .withoutSpacesInObjectEntries();
+        sw = new StringWriter();
+        gen = JSON_F.createGenerator(sw);
+        gen.setPrettyPrinter(pp);
+
+        gen.writeNumber(1);
+        gen.writeStartArray();
+        gen.writeNumber(2);
+        gen.writeEndArray();
+        gen.writeStartObject();
+        gen.writeFieldName("a");
+        gen.writeNumber(3);
+        gen.writeEndObject();
+        gen.close();
+        // no root separator, nor array, object
+        assertEquals("1[2]{\"a\":3}", sw.toString());
     }
     
     private String _printTestData(PrettyPrinter pp, boolean useBytes) throws IOException
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java b/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java
index 468d3b4..9cce04a 100644
--- a/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java
@@ -6,22 +6,44 @@
 
 public class TestDelegates extends com.fasterxml.jackson.core.BaseTest
 {
+    private final JsonFactory JSON_F = new JsonFactory();
+    
     /**
      * Test default, non-overridden parser delegate.
      */
     public void testParserDelegate() throws IOException
     {
-        JsonParser jp = new JsonFactory().createParser("[ 1, true ]");
-        assertNull(jp.getCurrentToken());
-        assertToken(JsonToken.START_ARRAY, jp.nextToken());
-        assertEquals("[", jp.getText());
-        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
-        assertEquals(1, jp.getIntValue());
-        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
-        assertTrue(jp.getBooleanValue());
-        assertToken(JsonToken.END_ARRAY, jp.nextToken());
-        jp.close();
-        assertTrue(jp.isClosed());
+        final String TOKEN ="foo";
+
+        JsonParser parser = JSON_F.createParser("[ 1, true, null, { } ]");
+        JsonParserDelegate del = new JsonParserDelegate(parser);
+        
+        assertNull(del.getCurrentToken());
+        assertToken(JsonToken.START_ARRAY, del.nextToken());
+        assertEquals("[", del.getText());
+        assertToken(JsonToken.VALUE_NUMBER_INT, del.nextToken());
+        assertEquals(1, del.getIntValue());
+
+        assertToken(JsonToken.VALUE_TRUE, del.nextToken());
+        assertTrue(del.getBooleanValue());
+
+        assertToken(JsonToken.VALUE_NULL, del.nextToken());
+        assertNull(del.getCurrentValue());
+        del.setCurrentValue(TOKEN);
+
+        assertToken(JsonToken.START_OBJECT, del.nextToken());
+        assertNull(del.getCurrentValue());
+
+        assertToken(JsonToken.END_OBJECT, del.nextToken());
+        assertEquals(TOKEN, del.getCurrentValue());
+
+        assertToken(JsonToken.END_ARRAY, del.nextToken());
+
+        del.close();
+        assertTrue(del.isClosed());
+        assertTrue(parser.isClosed());
+
+        parser.close();
     }
 
     /**
@@ -29,23 +51,48 @@
      */
     public void testGeneratorDelegate() throws IOException
     {
+        final String TOKEN ="foo";
+
         StringWriter sw = new StringWriter();
-        JsonGenerator jg = new JsonFactory().createGenerator(sw);
-        jg.writeStartArray();
-        jg.writeNumber(13);
-        jg.writeNull();
-        jg.writeBoolean(false);
-        jg.writeEndArray();
-        jg.close();
-        assertTrue(jg.isClosed());        
-        assertEquals("[13,null,false]", sw.toString());
+        JsonGenerator g0 = JSON_F.createGenerator(sw);
+        JsonGeneratorDelegate del = new JsonGeneratorDelegate(g0);
+        del.writeStartArray();
+
+        assertEquals(1, del.getOutputBuffered());
+        
+        del.writeNumber(13);
+        del.writeNull();
+        del.writeBoolean(false);
+        del.writeString("foo");
+
+        // verify that we can actually set/get "current value" as expected, even with delegates
+        assertNull(del.getCurrentValue());
+        del.setCurrentValue(TOKEN);
+
+        del.writeStartObject();
+        assertNull(del.getCurrentValue());
+        del.writeEndObject();
+        assertEquals(TOKEN, del.getCurrentValue());
+
+        del.writeStartArray(0);
+        del.writeEndArray();
+
+        del.writeEndArray();
+        
+        del.flush();
+        del.close();
+        assertTrue(del.isClosed());        
+        assertTrue(g0.isClosed());        
+        assertEquals("[13,null,false,\"foo\",{},[]]", sw.toString());
+
+        g0.close();
     }
 
     public void testNotDelegateCopyMethods() throws IOException
     {
-        JsonParser jp = new JsonFactory().createParser("[{\"a\":[1,2,{\"b\":3}],\"c\":\"d\"},{\"e\":false},null]");
+        JsonParser jp = JSON_F.createParser("[{\"a\":[1,2,{\"b\":3}],\"c\":\"d\"},{\"e\":false},null]");
         StringWriter sw = new StringWriter();
-        JsonGenerator jg = new JsonGeneratorDelegate(new JsonFactory().createGenerator(sw), false) {
+        JsonGenerator jg = new JsonGeneratorDelegate(JSON_F.createGenerator(sw), false) {
             @Override
             public void writeFieldName(String name) throws IOException, JsonGenerationException {
                 super.writeFieldName(name+"-test");
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java b/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java
index 878224e..ab1d23a 100644
--- a/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java
@@ -1,8 +1,5 @@
 package com.fasterxml.jackson.core.util;
 
-import com.fasterxml.jackson.core.util.BufferRecycler;
-import com.fasterxml.jackson.core.util.TextBuffer;
-
 public class TestTextBuffer
     extends com.fasterxml.jackson.core.BaseTest
 {
@@ -77,4 +74,14 @@
               }
           }
       }
+
+    // [Core#182]
+    public void testEmpty() {
+        TextBuffer tb = new TextBuffer(new BufferRecycler());
+        tb.resetWithEmpty();
+
+        assertTrue(tb.getTextBuffer().length == 0);
+        tb.contentsAsString();
+        assertTrue(tb.getTextBuffer().length == 0);
+    }
 }
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestVersionUtil.java b/src/test/java/com/fasterxml/jackson/core/util/TestVersionUtil.java
index ecd0f43..eb8cebd 100644
--- a/src/test/java/com/fasterxml/jackson/core/util/TestVersionUtil.java
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestVersionUtil.java
@@ -19,6 +19,7 @@
                 VersionUtil.parseVersion("1.2.15-foo", "group", "artifact"));
     }
 
+    @SuppressWarnings("deprecation")
     public void testMavenVersionParsing() {
         assertEquals(new Version(1, 2, 3, "SNAPSHOT", "foo.bar", "foo-bar"),
                 VersionUtil.mavenVersionFor(TestVersionUtil.class.getClassLoader(), "foo.bar", "foo-bar"));
diff --git a/src/test/java/perf/EnumByBytesLookup.java b/src/test/java/perf/EnumByBytesLookup.java
new file mode 100644
index 0000000..1f757ac
--- /dev/null
+++ b/src/test/java/perf/EnumByBytesLookup.java
@@ -0,0 +1,173 @@
+package perf;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+/**
+ * Trie container/wrapper, in this case implements Enum-value lookup.
+ * Sample code to possibly use for streamlined-lookup by dictionary, using
+ * UTF-8 bytes of {@link Enum#name()} as the key.
+ */
+public class EnumByBytesLookup<E extends Enum<E>>
+{
+    private final static Charset UTF8 = Charset.forName("UTF-8");
+
+    private final Trie<E> _root;
+    private final int _size;
+
+    private EnumByBytesLookup(Trie<E> root, int size) {
+        _root = root;
+        _size = size;
+    }
+
+    public static <EIN extends Enum<EIN>> EnumByBytesLookup<EIN> buildFor(Class<EIN> enumClass)
+    {
+        Trie<EIN> root = new Trie<EIN>(null);
+        int size = 0;
+        for (EIN en : enumClass.getEnumConstants()) {
+            byte[] key = en.name().getBytes(UTF8);
+            root = root.with(en, key);
+            ++size;
+        }
+        return new EnumByBytesLookup<EIN>(root, size);
+    }
+
+    public E find(byte[] rawId) {
+      return _root.find(rawId);
+    }
+
+    public int size() { return _size; }
+}
+
+/**
+ * Trie nodes
+ */
+class Trie<T> {
+    private final static byte[] NO_BYTES = new byte[0];
+
+    private final static Trie<?>[] NO_NODES = new Trie<?>[0];
+
+    /**
+     * For leaves, value matched by sequence
+     */
+    private final T _match;
+
+    private final byte[] _nextBytes;
+    private final Trie<T>[] nextNodes;
+
+    private final int nextCount;
+
+    @SuppressWarnings("unchecked")
+    Trie(T match) {
+      this(match, NO_BYTES, (Trie<T>[]) NO_NODES);
+    }
+
+    private Trie(T match, byte[] nextBytes, Trie<T>[] nextNodes) {
+      this._match = match;
+      this._nextBytes = nextBytes;
+      this.nextNodes = nextNodes;
+      nextCount = nextBytes.length;
+    }
+
+    private Trie(Trie<T> base, T match) {
+      // should we allow duplicate calls with same match? For now, let's not
+      if (base._match != null) {
+        throw new IllegalArgumentException("Trying to add same match multiple times");
+      }
+      this._match = match;
+      _nextBytes = base._nextBytes;
+      nextNodes = base.nextNodes;
+      nextCount = base.nextCount;
+    }
+
+    private Trie(Trie<T> base, byte nextByte, Trie<T> nextNode) {
+      // should we allow duplicate calls with same match? For now, let's not
+      if (base._match != null) {
+        throw new IllegalArgumentException("Trying to add same match multiple times");
+      }
+      _match = base._match;
+      int size = base._nextBytes.length + 1;
+      _nextBytes = Arrays.copyOf(base._nextBytes, size);
+      _nextBytes[size-1] = nextByte;
+      nextNodes = Arrays.copyOf(base.nextNodes, size);
+      nextNodes[size-1] = nextNode;
+      nextCount = size;
+    }
+
+    /**
+     * Constructor used when an existing branch needs to be replaced due to addition
+     */
+    private Trie(Trie<T> base, int offset, Trie<T> newNode) {
+      _match = base._match;
+      // can keep nextBytes, as they don't change
+      _nextBytes = base._nextBytes;
+      // but must create a copy of next nodes, to modify one entry
+      nextNodes = Arrays.copyOf(base.nextNodes, base.nextNodes.length);
+      nextNodes[offset] = newNode;
+      nextCount = base.nextCount;
+    }
+
+    /**
+     * "Mutant factory" method: constructs a modified Trie, with specified raw id
+     * added.
+     */
+    public Trie<T> with(T match, byte[] rawId) {
+      return with(match, rawId, 0, rawId.length);
+    }
+
+    private Trie<T> with(T match, byte[] rawId, int start, int end) {
+      if (start == end) {
+        return new Trie<T>(this, match);
+      }
+      // Ok: two choices; either we follow existing branch; or need to create new one
+      final byte b = rawId[start++];
+      for (int i = 0; i < nextCount; ++i) {
+        if (_nextBytes[i] == b) {
+          // existing branch: good day for delegation...
+          Trie<T> old = nextNodes[i];
+          // to keep things truly immutable, copy underlying arrays, then
+          return new Trie<T>(this, i, old.with(match, rawId, start, end));
+        }
+      }
+      // simplest recursively, but for fun let's convert to iteration. Start with tail
+      Trie<T> curr = new Trie<T>(match);
+
+      for (int i = end-1; i >= start; --i) {
+        curr = new Trie<T>(this, rawId[i], curr);
+      }
+      return new Trie<T>(this, b, curr);
+    }
+
+    public T find(byte[] id) {
+      return find(id, 0, id.length);
+    }
+
+    public T find(byte[] id, int offset, int length) {
+      Trie<T> t = this;
+      final int end = offset+length;
+
+      for (; offset < end; ++offset) {
+        byte b = id[offset];
+        t = t.next(b);
+        if (t == null) {
+            // NOTE: if using null-padding, would trim here
+            /*
+          if (b == (byte) 0) {
+            break;
+          }
+          */
+          return null;
+        }
+      }
+      return t._match;
+    }
+
+    private Trie<T> next(int b) {
+      for (int i = 0; i < nextCount; ++i) {
+        if (_nextBytes[i] == b) {
+          return nextNodes[i];
+        }
+      }
+      return null;
+    }
+}