[jackson-core] 01/02: Imported Upstream version 2.2.2

Wolodja Wentland babilen-guest at alioth.debian.org
Thu Aug 22 17:45:14 UTC 2013


This is an automated email from the git hooks/post-receive script.

babilen-guest pushed a commit to branch master
in repository jackson-core.

commit 8854a1641342da62cdc4900716080d7d3adea6c6
Author: Wolodja Wentland <babilen at gmail.com>
Date:   Wed Jul 31 14:34:31 2013 +0100

    Imported Upstream version 2.2.2
---
 .gitignore                                         |   21 +
 README.md                                          |  102 +
 create-test-report.sh                              |    3 +
 pom.xml                                            |  164 +
 release-notes/CREDITS                              |   21 +
 release-notes/VERSION                              |  123 +
 .../com/fasterxml/jackson/core/Base64Variant.java  |  417 +++
 .../com/fasterxml/jackson/core/Base64Variants.java |  111 +
 .../com/fasterxml/jackson/core/FormatSchema.java   |   32 +
 .../com/fasterxml/jackson/core/JsonEncoding.java   |   52 +
 .../com/fasterxml/jackson/core/JsonFactory.java    | 1422 +++++++++
 .../jackson/core/JsonGenerationException.java      |   32 +
 .../com/fasterxml/jackson/core/JsonGenerator.java  | 1221 ++++++++
 .../com/fasterxml/jackson/core/JsonLocation.java   |  139 +
 .../fasterxml/jackson/core/JsonParseException.java |   27 +
 .../com/fasterxml/jackson/core/JsonParser.java     | 1381 +++++++++
 .../jackson/core/JsonProcessingException.java      |  130 +
 .../fasterxml/jackson/core/JsonStreamContext.java  |  112 +
 .../java/com/fasterxml/jackson/core/JsonToken.java |  162 +
 .../com/fasterxml/jackson/core/ObjectCodec.java    |  163 +
 .../com/fasterxml/jackson/core/PrettyPrinter.java  |  177 ++
 .../fasterxml/jackson/core/SerializableString.java |  154 +
 .../java/com/fasterxml/jackson/core/TreeNode.java  |  241 ++
 .../java/com/fasterxml/jackson/core/Version.java   |  141 +
 .../java/com/fasterxml/jackson/core/Versioned.java |   23 +
 .../fasterxml/jackson/core/base/GeneratorBase.java |  526 ++++
 .../fasterxml/jackson/core/base/ParserBase.java    | 1094 +++++++
 .../jackson/core/base/ParserMinimalBase.java       |  616 ++++
 .../fasterxml/jackson/core/base/package-info.java  |    9 +
 .../jackson/core/format/DataFormatDetector.java    |  211 ++
 .../jackson/core/format/DataFormatMatcher.java     |  124 +
 .../jackson/core/format/InputAccessor.java         |  151 +
 .../jackson/core/format/MatchStrength.java         |   62 +
 .../jackson/core/format/package-info.java          |    6 +
 .../com/fasterxml/jackson/core/io/BaseReader.java  |  116 +
 .../com/fasterxml/jackson/core/io/CharTypes.java   |  228 ++
 .../jackson/core/io/CharacterEscapes.java          |   73 +
 .../com/fasterxml/jackson/core/io/IOContext.java   |  257 ++
 .../fasterxml/jackson/core/io/InputDecorator.java  |   68 +
 .../jackson/core/io/JsonStringEncoder.java         |  395 +++
 .../fasterxml/jackson/core/io/MergedStream.java    |  145 +
 .../com/fasterxml/jackson/core/io/NumberInput.java |  290 ++
 .../fasterxml/jackson/core/io/NumberOutput.java    |  398 +++
 .../fasterxml/jackson/core/io/OutputDecorator.java |   41 +
 .../jackson/core/io/SegmentedStringWriter.java     |  102 +
 .../jackson/core/io/SerializedString.java          |  272 ++
 .../com/fasterxml/jackson/core/io/UTF32Reader.java |  218 ++
 .../com/fasterxml/jackson/core/io/UTF8Writer.java  |  387 +++
 .../core/json/ByteSourceJsonBootstrapper.java      |  516 ++++
 .../jackson/core/json/JsonGeneratorImpl.java       |  172 ++
 .../jackson/core/json/JsonReadContext.java         |  185 ++
 .../jackson/core/json/JsonWriteContext.java        |  178 ++
 .../jackson/core/json/PackageVersion.java.in       |   20 +
 .../jackson/core/json/ReaderBasedJsonParser.java   | 1992 +++++++++++++
 .../jackson/core/json/UTF8JsonGenerator.java       | 1851 ++++++++++++
 .../jackson/core/json/UTF8StreamJsonParser.java    | 3143 ++++++++++++++++++++
 .../core/json/WriterBasedJsonGenerator.java        | 1911 ++++++++++++
 .../fasterxml/jackson/core/json/package-info.java  |    7 +
 .../com/fasterxml/jackson/core/package-info.java   |   28 +
 .../jackson/core/sym/BytesToNameCanonicalizer.java | 1208 ++++++++
 .../jackson/core/sym/CharsToNameCanonicalizer.java |  730 +++++
 .../java/com/fasterxml/jackson/core/sym/Name.java  |   50 +
 .../java/com/fasterxml/jackson/core/sym/Name1.java |   44 +
 .../java/com/fasterxml/jackson/core/sym/Name2.java |   40 +
 .../java/com/fasterxml/jackson/core/sym/Name3.java |   39 +
 .../java/com/fasterxml/jackson/core/sym/NameN.java |   68 +
 .../fasterxml/jackson/core/sym/package-info.java   |    5 +
 .../fasterxml/jackson/core/type/ResolvedType.java  |  122 +
 .../fasterxml/jackson/core/type/TypeReference.java |   61 +
 .../fasterxml/jackson/core/type/package-info.java  |    8 +
 .../jackson/core/util/BufferRecycler.java          |  117 +
 .../jackson/core/util/ByteArrayBuilder.java        |  278 ++
 .../jackson/core/util/DefaultPrettyPrinter.java    |  389 +++
 .../jackson/core/util/Instantiatable.java          |   24 +
 .../fasterxml/jackson/core/util/InternCache.java   |   53 +
 .../jackson/core/util/JsonGeneratorDelegate.java   |  414 +++
 .../jackson/core/util/JsonParserDelegate.java      |  350 +++
 .../jackson/core/util/JsonParserSequence.java      |  147 +
 .../jackson/core/util/MinimalPrettyPrinter.java    |  153 +
 .../fasterxml/jackson/core/util/TextBuffer.java    |  718 +++++
 .../fasterxml/jackson/core/util/VersionUtil.java   |  270 ++
 .../fasterxml/jackson/core/util/package-info.java  |    4 +
 src/main/resources/META-INF/LICENSE                |    8 +
 src/main/resources/META-INF/NOTICE                 |   20 +
 .../com.fasterxml.jackson.core.JsonFactory         |    1 +
 src/site/site.xml                                  |   34 +
 .../jackson/core/TestJDKSerializability.java       |   98 +
 .../fasterxml/jackson/core/TestJsonFactory.java    |   28 +
 .../com/fasterxml/jackson/core/TestVersions.java   |   40 +
 .../core/format/TestJsonFormatDetection.java       |   92 +
 .../fasterxml/jackson/core/io/TestIOContext.java   |   93 +
 .../jackson/core/io/TestJDKSerializable.java       |   30 +
 .../jackson/core/io/TestJsonStringEncoder.java     |  105 +
 .../jackson/core/io/TestMergedStream.java          |   52 +
 .../fasterxml/jackson/core/io/TestUTF8Writer.java  |   60 +
 .../jackson/core/json/TestBase64Codec.java         |   59 +
 .../jackson/core/json/TestBase64Generation.java    |  124 +
 .../jackson/core/json/TestBase64Parsing.java       |  150 +
 .../jackson/core/json/TestCustomEscaping.java      |  176 ++
 .../jackson/core/json/TestDecorators.java          |  115 +
 .../jackson/core/json/TestJsonParser.java          |  543 ++++
 .../jackson/core/json/TestJsonParserBinary.java    |  153 +
 .../fasterxml/jackson/core/json/TestNextXxx.java   |  179 ++
 .../jackson/core/json/TestParserNonStandard.java   |  466 +++
 .../jackson/core/json/TestParserOverrides.java     |  102 +
 .../jackson/core/json/TestUtf8Generator.java       |   29 +
 .../jackson/core/json/TestUtf8Parser.java          |  194 ++
 .../jackson/core/main/TestArrayParsing.java        |   73 +
 .../jackson/core/main/TestCharEscaping.java        |  146 +
 .../fasterxml/jackson/core/main/TestComments.java  |  115 +
 .../jackson/core/main/TestExceptions.java          |   17 +
 .../jackson/core/main/TestGeneratorArray.java      |  109 +
 .../jackson/core/main/TestGeneratorClosing.java    |  235 ++
 .../jackson/core/main/TestGeneratorCopy.java       |   82 +
 .../jackson/core/main/TestGeneratorMisc.java       |  300 ++
 .../jackson/core/main/TestGeneratorObject.java     |  208 ++
 .../main/TestGeneratorWithSerializedString.java    |   95 +
 .../jackson/core/main/TestJsonFactory.java         |   63 +
 .../jackson/core/main/TestJsonGenerator.java       |  228 ++
 .../core/main/TestJsonGeneratorFeatures.java       |  131 +
 .../jackson/core/main/TestNumberParsing.java       |   83 +
 .../jackson/core/main/TestNumericValues.java       |  375 +++
 .../jackson/core/main/TestParserClosing.java       |  167 ++
 .../jackson/core/main/TestParserFeatures.java      |  107 +
 .../jackson/core/main/TestParserLinefeeds.java     |   70 +
 .../jackson/core/main/TestParserWithObjects.java   |  170 ++
 .../jackson/core/main/TestPrettyPrinter.java       |  224 ++
 .../jackson/core/main/TestRawStringWriting.java    |  132 +
 .../jackson/core/main/TestScopeMatching.java       |  140 +
 .../jackson/core/main/TestStringGeneration.java    |  225 ++
 .../fasterxml/jackson/core/main/TestUnicode.java   |   38 +
 .../jackson/core/main/TestValueConversions.java    |  189 ++
 .../jackson/core/main/TestWithTonsaSymbols.java    |   82 +
 .../jackson/core/sym/TestByteBasedSymbols.java     |  139 +
 .../jackson/core/sym/TestJsonParserSymbols.java    |  105 +
 .../jackson/core/sym/TestSymbolTables.java         |  109 +
 .../jackson/core/test/PackageVersion.java          |   17 +
 .../jackson/core/test/TestPackageVersion.java      |   14 +
 .../fasterxml/jackson/core/util/TestCharTypes.java |   17 +
 .../fasterxml/jackson/core/util/TestDelegates.java |   43 +
 .../jackson/core/util/TestNumberPrinting.java      |  102 +
 .../jackson/core/util/TestSerializedString.java    |   60 +
 .../jackson/core/util/TestTextBuffer.java          |   65 +
 .../jackson/core/util/TestVersionUtil.java         |   30 +
 .../java/com/fasterxml/jackson/test/BaseTest.java  |  432 +++
 src/test/java/perf/ConcurrencyReadTest.java        |   80 +
 .../META-INF/maven/foo/bar/foo-bar/pom.properties  |    3 +
 147 files changed, 35626 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..84914ec
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# use glob syntax.
+syntax: glob
+*.class
+*~
+*.bak
+*.off
+*.old
+.DS_Store
+
+# building
+target
+
+# Eclipse
+.classpath
+.project
+.settings
+
+# IDEA
+*.iml
+*.ipr
+*.iws
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5d27ed1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,102 @@
+# Overview
+
+This project contains core low-level incremental ("streaming") parser and generator abstractions used by
+[Jackson Data Processor](http://wiki.fasterxml.com/JacksonHome).
+It also includes the default implementation of handler types (parser, generator) that handle JSON format.
+The core abstractions are not JSON specific, although naming does contain 'JSON' in many places, due to historical reasons. Only packages that specifically contain word 'json' are JSON-specific.
+
+This package is the base on which [Jackson data-binding](https://github.com/FasterXML/jackson-databind) package builds on.
+
+Alternate data format implementations (like
+[Smile (binary JSON)](https://github.com/FasterXML/jackson-dataformat-smile),
+[XML](https://github.com/FasterXML/jackson-dataformat-xml)
+and [CSV](https://github.com/FasterXML/jackson-dataformat-csv))
+also build on this base package, implementing the core interfaces,
+making it possible to use standard [data-binding package](https://github.com/FasterXML/jackson-databind) regardless of underlying data format.
+
+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://fasterxml.ci.cloudbees.com/job/jackson-core-master/badge/icon)](https://fasterxml.ci.cloudbees.com/job/jackson-core-master/)
+
+### Differences from Jackson 1.x
+
+Project contains versions 2.0 and above: source code for earlier (1.x) versions is available from [Codehaus](http://jackson.codehaus.org) SVN repository
+
+Note that the main differences compared to 1.0 core jar are:
+
+* Maven build instead of Ant
+* Annotations carved out to a separate package (that this package depends on)
+* Java package is now `com.fasterxml.jackson.core` (instead of `org.codehaus.jackson`)
+
+----
+
+# Get it!
+
+## Maven
+
+Functionality of this package is contained in 
+Java package `com.fasterxml.jackson.core`.
+
+To use the package, you need to use following Maven dependency:
+
+```xml
+<dependency>
+  <groupId>com.fasterxml.jackson.core</groupId>
+  <artifactId>jackson-core</artifactId>
+  <version>2.2.0</version>
+</dependency>
+```
+
+or download jars from Maven repository or [Download page](http://wiki.fasterxml.com/JacksonDownload).
+Core jar is a functional OSGi bundle, with proper import/export declarations.
+
+Package has no external dependencies, except for testing (which uses `JUnit`).
+
+## Non-Maven
+
+For non-Maven use cases, you download jars from [Central Maven repository](http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/) or [Download page](https://github.com/FasterXML/jackson-core/wiki/Downloads).
+
+Core jar is also a functional OSGi bundle, with proper import/export declarations, so it can be use on OSGi container as is.
+
+-----
+# Use it!
+
+## General
+
+Usage typically starts with creation of a reusable (and thread-safe, once configured) `JsonFactory` instance:
+
+```java
+JsonFactory factory = new JsonFactory();
+// configure, if necessary:
+factory.enable(JsonParser.Feature.ALLOW_COMMENTS);
+```
+
+Alternatively, you have a `ObjectMapper` (from [Jackson Databind package](https://github.com/FasterXML/jackson-databind)) handy; if so, you can do:
+
+```java
+JsonFactory factory = objectMapper.getJsonFactory();
+```
+
+More information can be found from [Streaming API](http://wiki.fasterxml.com/JacksonStreamingApi
+) at Jackson Wiki.
+
+## Usage, simple reading
+
+All reading is by using `JsonParser` (or its sub-classes, in case of data formats other than JSON),
+instance of which is constructed by `JsonFactory`.
+
+An example can be found from [Reading and Writing Event Streams](http://www.cowtowncoder.com/blog/archives/2009/01/entry_132.html)
+
+## Usage, simple writing
+
+All writing is by using `JsonGenerator` (or its sub-classes, in case of data formats other than JSON),
+instance of which is constructed by `JsonFactory`:
+
+An example can be found from [Reading and Writing Event Streams](http://www.cowtowncoder.com/blog/archives/2009/01/entry_132.html)
+
+-----
+
+# Further reading
+
+* [Documentation](https://github.com/FasterXML/jackson-core/wiki/Documentation) has other project documentation
+* [Jackson Project Home](http://wiki.fasterxml.com/JacksonHome)
diff --git a/create-test-report.sh b/create-test-report.sh
new file mode 100755
index 0000000..5fef678
--- /dev/null
+++ b/create-test-report.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+mvn surefire-report:report  
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c34fc1d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,164 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion> 
+  <parent>
+    <groupId>com.fasterxml</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>10</version>
+  </parent>
+
+  <groupId>com.fasterxml.jackson.core</groupId>
+  <artifactId>jackson-core</artifactId>
+  <name>Jackson-core</name>
+  <version>2.2.2</version>
+  <description>Core Jackson abstractions, basic JSON streaming API implementation
+  </description>
+
+  <url>http://wiki.fasterxml.com/JacksonHome</url>
+  <scm>
+    <connection>scm:git:git at github.com:FasterXML/jackson-core.git</connection>
+    <developerConnection>scm:git:git at github.com:FasterXML/jackson-core.git</developerConnection>
+    <url>http://github.com/FasterXML/jackson-core</url>    
+    <tag>jackson-core-2.2.2</tag>
+  </scm>
+
+  <properties>
+    <!--
+     | Configuration properties for the OSGi maven-bundle-plugin
+    -->
+    <osgi.export>com.fasterxml.jackson.core;version=${project.version},
+com.fasterxml.jackson.core.*;version=${project.version}
+    </osgi.export>
+
+    <!-- Generate PackageVersion.java into this directory. -->
+    <packageVersion.dir>com/fasterxml/jackson/core/json</packageVersion.dir>
+    <packageVersion.package>com.fasterxml.jackson.core.json</packageVersion.package>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <version>2.8.1</version>
+          <configuration>
+            <source>1.6</source>
+            <target>1.6</target>
+            <encoding>UTF-8</encoding>
+            <maxmemory>512m</maxmemory>
+            <links>
+              <link>http://docs.oracle.com/javase/6/docs/api/</link>
+            </links>
+          </configuration>
+          <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+          </executions>
+        </plugin>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-site-plugin</artifactId>
+            <version>3.1</version>
+        </plugin>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <version>${surefire.version}</version>
+            <configuration>
+                <redirectTestOutputToFile>${surefire.redirectTestOutputToFile}</redirectTestOutputToFile>
+            </configuration>
+        </plugin>
+      <plugin>
+        <!-- Inherited from oss-base. Generate PackageVersion.java.-->
+        <groupId>com.google.code.maven-replacer-plugin</groupId>
+        <artifactId>replacer</artifactId>
+        <executions>
+          <execution>
+            <id>process-packageVersion</id>
+            <phase>generate-sources</phase>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    <extensions>
+        <!-- Enabling the use of SSH -->
+        <extension>
+            <groupId>org.apache.maven.wagon</groupId>
+            <artifactId>wagon-ssh-external</artifactId>
+            <version>1.0-beta-6</version>
+        </extension>
+        <extension>
+            <groupId>org.apache.maven.scm</groupId>
+            <artifactId>maven-scm-provider-gitexe</artifactId>
+            <version>1.6</version>
+        </extension>
+        <extension>
+            <groupId>org.apache.maven.scm</groupId>
+            <artifactId>maven-scm-manager-plexus</artifactId>
+            <version>1.6</version>
+        </extension>
+        <extension>
+            <groupId>org.kathrynhuxtable.maven.wagon</groupId>
+            <artifactId>wagon-gitsite</artifactId>
+            <version>0.3.1</version>
+        </extension>
+    </extensions>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <version>2.8.1</version>
+          <configuration>
+              <aggregate>true</aggregate>
+              <source>1.6</source>
+              <encoding>UTF-8</encoding>
+              <maxmemory>1g</maxmemory>
+              <links>
+                  <!-- JDK, other Jackson pkgs -->
+                  <link>http://docs.oracle.com/javase/6/docs/api/</link>
+                  <link>http://fasterxml.github.com/jackson-core/javadoc/2.2.0/</link>
+              </links>
+              <excludePackageNames>${javadoc.package.exclude}</excludePackageNames>
+              <bootclasspath>${sun.boot.class.path}</bootclasspath>
+              <doclet>com.google.doclava.Doclava</doclet>
+              <useStandardDocletOptions>false</useStandardDocletOptions>
+              <additionalJOption>-J-Xmx1024m</additionalJOption>
+              <docletArtifact>
+                  <groupId>com.google.doclava</groupId>
+                  <artifactId>doclava</artifactId>
+                  <version>1.0.3</version>
+              </docletArtifact>
+              <additionalparam>
+                  -hdf project.name "${project.name} ${project.version}"
+                  -d ${project.reporting.outputDirectory}/apidocs
+              </additionalparam>
+          </configuration>
+          <reportSets>
+              <reportSet>
+                  <id>default</id>
+                  <reports>
+                      <report>javadoc</report>
+                  </reports>
+              </reportSet>
+          </reportSets>
+      </plugin>
+    </plugins>
+  </reporting>
+
+</project>
diff --git a/release-notes/CREDITS b/release-notes/CREDITS
new file mode 100644
index 0000000..03891e0
--- /dev/null
+++ b/release-notes/CREDITS
@@ -0,0 +1,21 @@
+Here are people who have contributed to development Jackson JSON process 
+core component, version 2.x
+(version numbers in brackets indicate release in which the problem was fixed)
+
+(note: for older credits, check out release notes for 1.x versions)
+
+Tatu Saloranta, tatu.saloranta at iki.fi: author
+
+Pascal G�linas:
+  * Reported [JACKSON-827]: 2.0.0 was accidentally requiring JDK 1.6
+    (should still be 1.5)
+   [2.0.1]
+
+Ben Gertzfield (bgertzfield at github):
+  * Contributed [Issue#49]: Improvements to VersionUtil to more efficiently
+    read dynamically generated/embedded version information, to improve
+    Android startup time
+   [2.2.0]
+
+Klaus Brunner (KlausBrunner at github)
+  * Reported [Issue#48]: Problem with URLs, spaces
diff --git a/release-notes/VERSION b/release-notes/VERSION
new file mode 100644
index 0000000..6058b8c
--- /dev/null
+++ b/release-notes/VERSION
@@ -0,0 +1,123 @@
+Project: jackson-core
+Version: 2.2.2 (26-May-2013)
+
+No changes since previous version.
+
+------------------------------------------------------------------------
+=== History: ===
+------------------------------------------------------------------------
+
+2.2.1 (03-May-2013)
+
+#72: JsonFactory.copy() was not copying settings properly
+ (reported by Christian S (squiddle at github))
+- Moved VERSION/LICENSE contained in jars under META-INF/, to resolve
+  Android packaging (APK) issues
+
+2.2.0 (22-Apr-2013)
+
+Fixes:
+
+#51: JsonLocation had non-serializable field, mark as transient
+
+Improvements
+
+#46, #49: Improve VersionUtil to generate PackageVersion, instead of
+  reading VERSION.txt from jar -- improves startup perf on Android significantly
+ (contributed by Ben G)
+#59: Add more functionality in `TreeNode` interface, to allow some
+ level of traversal over any and all Tree Model implementations
+#69: Add support for writing `short` values in JsonGenerator
+
+2.1.3 (19-Jan-2013)
+
+* [JACKSON-884]: JsonStringEncoder.quoteAsStringValue() fails to encode 
+  ctrl chars correctly.
+* [Issue#48] Problems with spaces in URLs
+ (reported by KlausBrunner)
+
+2.1.2 (04-Dec-2012)
+
+* [Issue#42] Problems with UTF32Reader
+ (reported by James R [jroper at github])
+* Added missing methods (like 'setPrettyPrinter()' in JsonGeneratorDelegate
+
+2.1.1 (11-Nov-2012)
+
+* [Issue#34] `JsonParser.nextFieldName()` fails on buffer boundary
+ (reported by gsson at github)
+* [Issue#38] `JsonParser.nextFieldName()` problems when handling
+ names with trailing spaces
+ (reported by matjazs at github)
+
+2.1.0 (08-Oct-2012)
+
+A new minor version for 2.x.
+
+New features:
+
+* [Issue#14]: add 'readBinaryValue(...)' method in JsonParser
+* [Issue#16]: add 'writeBinary(InputStream, int)' method in JsonGenerator
+  (and implement for JSON backend)
+* [Issue#26]: Allow overriding "root value separator"
+ (suggested by Henning S)
+
+Improvements:
+
+* [JACKSON-837]: Made JsonGenerator implement Flushable.
+ (suggested by Matt G)
+* [Issue#10]: add 'JsonProcessingException.getOriginalMessage()' for accessing
+  message without location info
+* [Issue#31]: make `JsonFactory` java.io.Serializable (via JDK)
+
+Other:
+
+* [Issue-25]: Add 'createParser' and 'createGenerator' (as eventual replacements
+  for 'createJsonParser'/'createJsonGenerator') in 'JsonFactory'
+* Try to improve locking aspects of symbol tables, by reducing scope of
+  synchronized sections when creating, merging table contents.
+* Added 'JsonFactory.copy()' method to support databinding's 'ObjectMapper.copy()'
+* Added method 'requiresCustomCodec()' for JsonFactory and JsonParser
+* Added 'JsonParser.getValueAsString()' method (to support flexible conversions)
+* Added META-INF/services/com.fasterxml.jackson.core.JsonFactory SPI to register
+  `JsonFactory` for even more automatic format discovery in future.
+
+2.0.4 (26-Jun-2012)
+
+Fixes:
+
+* [Issue-6] PrettyPrinter, count wrong for end-object case
+* 1.9.x fixes up to 1.9.8
+
+2.0.3: skipped;	 only some modules use this version
+
+2.0.2 (14-May-2012)
+
+* 1.9.x fixes up to 1.9.7
+
+2.0.1 (22-Apr-2012)
+
+Fixes:
+
+* [JACKSON-827] Fix incompatibilities with JDK 1.5 (2.0.0 accidentally
+  required 1.6)
+ (reported Pascal G)
+
+2.0.0 (25-Mar-2012)
+
+Fixes:
+
+(all fixes up until 1.9.6)
+
+Improvements
+
+* [JACKSON-730]: Add checks to ensure that Features are applicable for
+  instances (parsers, generators), or if not, throw IllegalArgumentException
+* [JACKSON-742]: Add append-methods in SerializableString
+
+New features:
+
+* [JACKSON-782]: Add 'JsonParser.overrideCurrentName()', needed as a workaround
+  for some exotic data binding cases (and/or formats)
+
+[entries for versions 1.x and earlier not retained; refer to earlier releases)
diff --git a/src/main/java/com/fasterxml/jackson/core/Base64Variant.java b/src/main/java/com/fasterxml/jackson/core/Base64Variant.java
new file mode 100644
index 0000000..728065a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Base64Variant.java
@@ -0,0 +1,417 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+package com.fasterxml.jackson.core;
+
+import java.util.Arrays;
+
+/**
+ * Abstract base class used to define specific details of which
+ * variant of Base64 encoding/decoding is to be used. Although there is
+ * somewhat standard basic version (so-called "MIME Base64"), other variants
+ * exists, see <a href="http://en.wikipedia.org/wiki/Base64">Base64 Wikipedia entry</a> for details.
+ * 
+ * @author Tatu Saloranta
+ */
+public final class Base64Variant
+    implements java.io.Serializable
+{
+    // We'll only serialize name
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Placeholder used by "no padding" variant, to be used when a character
+     * value is needed.
+     */
+    final static char PADDING_CHAR_NONE = '\0';
+
+    /**
+     * Marker used to denote ascii characters that do not correspond
+     * to a 6-bit value (in this variant), and is not used as a padding
+     * character.
+     */
+    public final static int BASE64_VALUE_INVALID = -1;
+
+    /**
+     * Marker used to denote ascii character (in decoding table) that
+     * is the padding character using this variant (if any).
+     */
+    public final static int BASE64_VALUE_PADDING = -2;
+
+    /*
+    /**********************************************************
+    /* Encoding/decoding tables
+    /**********************************************************
+     */
+
+    /**
+     * Decoding table used for base 64 decoding.
+     */
+    private final transient int[] _asciiToBase64 = new int[128];
+
+    /**
+     * Encoding table used for base 64 decoding when output is done
+     * as characters.
+     */
+    private final transient char[] _base64ToAsciiC = new char[64];
+
+    /**
+     * Alternative encoding table used for base 64 decoding when output is done
+     * as ascii bytes.
+     */
+    private final transient byte[] _base64ToAsciiB = new byte[64];
+
+    /*
+    /**********************************************************
+    /* Other configuration
+    /**********************************************************
+     */
+
+    /**
+     * Symbolic name of variant; used for diagnostics/debugging.
+     *<p>
+     * Note that this is the only non-transient field; used when reading
+     * back from serialized state
+     */
+    protected final String _name;
+
+    /**
+     * Whether this variant uses padding or not.
+     */
+    protected final transient boolean _usesPadding;
+
+    /**
+     * Characted used for padding, if any ({@link #PADDING_CHAR_NONE} if not).
+     */
+    protected final transient char _paddingChar;
+    
+    /**
+     * Maximum number of encoded base64 characters to output during encoding
+     * before adding a linefeed, if line length is to be limited
+     * ({@link java.lang.Integer#MAX_VALUE} if not limited).
+     *<p>
+     * Note: for some output modes (when writing attributes) linefeeds may
+     * need to be avoided, and this value ignored.
+     */
+    protected final transient int _maxLineLength;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public Base64Variant(String name, String base64Alphabet, boolean usesPadding, char paddingChar, int maxLineLength)
+    {
+        _name = name;
+        _usesPadding = usesPadding;
+        _paddingChar = paddingChar;
+        _maxLineLength = maxLineLength;
+
+        // Ok and then we need to create codec tables.
+
+        // First the main encoding table:
+        int alphaLen = base64Alphabet.length();
+        if (alphaLen != 64) {
+            throw new IllegalArgumentException("Base64Alphabet length must be exactly 64 (was "+alphaLen+")");
+        }
+
+        // And then secondary encoding table and decoding table:
+        base64Alphabet.getChars(0, alphaLen, _base64ToAsciiC, 0);
+        Arrays.fill(_asciiToBase64, BASE64_VALUE_INVALID);
+        for (int i = 0; i < alphaLen; ++i) {
+            char alpha = _base64ToAsciiC[i];
+            _base64ToAsciiB[i] = (byte) alpha;
+            _asciiToBase64[alpha] = i;
+        }
+
+        // Plus if we use padding, add that in too
+        if (usesPadding) {
+            _asciiToBase64[(int) paddingChar] = BASE64_VALUE_PADDING;
+        }
+    }
+
+    /**
+     * "Copy constructor" that can be used when the base alphabet is identical
+     * to one used by another variant except for the maximum line length
+     * (and obviously, name).
+     */
+    public Base64Variant(Base64Variant base, String name, int maxLineLength)
+    {
+        this(base, name, base._usesPadding, base._paddingChar, maxLineLength);
+    }
+
+    /**
+     * "Copy constructor" that can be used when the base alphabet is identical
+     * to one used by another variant, but other details (padding, maximum
+     * line length) differ
+     */
+    public Base64Variant(Base64Variant base, String name, boolean usesPadding, char paddingChar, int maxLineLength)
+    {
+        _name = name;
+        byte[] srcB = base._base64ToAsciiB;
+        System.arraycopy(srcB, 0, this._base64ToAsciiB, 0, srcB.length);
+        char[] srcC = base._base64ToAsciiC;
+        System.arraycopy(srcC, 0, this._base64ToAsciiC, 0, srcC.length);
+        int[] srcV = base._asciiToBase64;
+        System.arraycopy(srcV, 0, this._asciiToBase64, 0, srcV.length);
+
+        _usesPadding = usesPadding;
+        _paddingChar = paddingChar;
+        _maxLineLength = maxLineLength;
+    }
+
+    /*
+    /**********************************************************
+    /* Serializable overrides
+    /**********************************************************
+     */
+
+    /**
+     * Method used to "demote" deserialized instances back to 
+     * canonical ones
+     */
+    protected Object readResolve() {
+        return Base64Variants.valueOf(_name);
+    }
+    
+    /*
+    /**********************************************************
+    /* Public accessors
+    /**********************************************************
+     */
+
+    public String getName() { return _name; }
+
+    public boolean usesPadding() { return _usesPadding; }
+    public boolean usesPaddingChar(char c) { return c == _paddingChar; }
+    public boolean usesPaddingChar(int ch) { return ch == (int) _paddingChar; }
+    public char getPaddingChar() { return _paddingChar; }
+    public byte getPaddingByte() { return (byte)_paddingChar; }
+
+    public int getMaxLineLength() { return _maxLineLength; }
+
+    /*
+    /**********************************************************
+    /* Decoding support
+    /**********************************************************
+     */
+
+    /**
+     * @return 6-bit decoded value, if valid character; 
+     */
+    public int decodeBase64Char(char c)
+    {
+        int ch = (int) c;
+        return (ch <= 127) ? _asciiToBase64[ch] : BASE64_VALUE_INVALID;
+    }
+
+    public int decodeBase64Char(int ch)
+    {
+        return (ch <= 127) ? _asciiToBase64[ch] : BASE64_VALUE_INVALID;
+    }
+
+    public int decodeBase64Byte(byte b)
+    {
+        int ch = (int) b;
+        return (ch <= 127) ? _asciiToBase64[ch] : BASE64_VALUE_INVALID;
+    }
+
+    /*
+    /**********************************************************
+    /* Encoding support
+    /**********************************************************
+     */
+
+    public char encodeBase64BitsAsChar(int value)
+    {
+        /* Let's assume caller has done necessary checks; this
+         * method must be fast and inlinable
+         */
+        return _base64ToAsciiC[value];
+    }
+
+    /**
+     * Method that encodes given right-aligned (LSB) 24-bit value
+     * into 4 base64 characters, stored in given result buffer.
+     */
+    public int encodeBase64Chunk(int b24, char[] buffer, int ptr)
+    {
+        buffer[ptr++] = _base64ToAsciiC[(b24 >> 18) & 0x3F];
+        buffer[ptr++] = _base64ToAsciiC[(b24 >> 12) & 0x3F];
+        buffer[ptr++] = _base64ToAsciiC[(b24 >> 6) & 0x3F];
+        buffer[ptr++] = _base64ToAsciiC[b24 & 0x3F];
+        return ptr;
+    }
+
+    public void encodeBase64Chunk(StringBuilder sb, int b24)
+    {
+        sb.append(_base64ToAsciiC[(b24 >> 18) & 0x3F]);
+        sb.append(_base64ToAsciiC[(b24 >> 12) & 0x3F]);
+        sb.append(_base64ToAsciiC[(b24 >> 6) & 0x3F]);
+        sb.append(_base64ToAsciiC[b24 & 0x3F]);
+    }
+
+    /**
+     * Method that outputs partial chunk (which only encodes one
+     * or two bytes of data). Data given is still aligned same as if
+     * it as full data; that is, missing data is at the "right end"
+     * (LSB) of int.
+     *
+     * @param outputBytes Number of encoded bytes included (either 1 or 2)
+     */
+    public int encodeBase64Partial(int bits, int outputBytes, char[] buffer, int outPtr)
+    {
+        buffer[outPtr++] = _base64ToAsciiC[(bits >> 18) & 0x3F];
+        buffer[outPtr++] = _base64ToAsciiC[(bits >> 12) & 0x3F];
+        if (_usesPadding) {
+            buffer[outPtr++] = (outputBytes == 2) ?
+                _base64ToAsciiC[(bits >> 6) & 0x3F] : _paddingChar;
+            buffer[outPtr++] = _paddingChar;
+        } else {
+            if (outputBytes == 2) {
+                buffer[outPtr++] = _base64ToAsciiC[(bits >> 6) & 0x3F];
+            }
+        }
+        return outPtr;
+    }
+
+    public void encodeBase64Partial(StringBuilder sb, int bits, int outputBytes)
+    {
+        sb.append(_base64ToAsciiC[(bits >> 18) & 0x3F]);
+        sb.append(_base64ToAsciiC[(bits >> 12) & 0x3F]);
+        if (_usesPadding) {
+            sb.append((outputBytes == 2) ?
+                      _base64ToAsciiC[(bits >> 6) & 0x3F] : _paddingChar);
+            sb.append(_paddingChar);
+        } else {
+            if (outputBytes == 2) {
+                sb.append(_base64ToAsciiC[(bits >> 6) & 0x3F]);
+            }
+        }
+    }
+
+    public byte encodeBase64BitsAsByte(int value)
+    {
+        // As with above, assuming it is 6-bit value
+        return _base64ToAsciiB[value];
+    }
+
+    /**
+     * Method that encodes given right-aligned (LSB) 24-bit value
+     * into 4 base64 bytes (ascii), stored in given result buffer.
+     */
+    public int encodeBase64Chunk(int b24, byte[] buffer, int ptr)
+    {
+        buffer[ptr++] = _base64ToAsciiB[(b24 >> 18) & 0x3F];
+        buffer[ptr++] = _base64ToAsciiB[(b24 >> 12) & 0x3F];
+        buffer[ptr++] = _base64ToAsciiB[(b24 >> 6) & 0x3F];
+        buffer[ptr++] = _base64ToAsciiB[b24 & 0x3F];
+        return ptr;
+    }
+
+    /**
+     * Method that outputs partial chunk (which only encodes one
+     * or two bytes of data). Data given is still aligned same as if
+     * it as full data; that is, missing data is at the "right end"
+     * (LSB) of int.
+     *
+     * @param outputBytes Number of encoded bytes included (either 1 or 2)
+     */
+    public int encodeBase64Partial(int bits, int outputBytes, byte[] buffer, int outPtr)
+    {
+        buffer[outPtr++] = _base64ToAsciiB[(bits >> 18) & 0x3F];
+        buffer[outPtr++] = _base64ToAsciiB[(bits >> 12) & 0x3F];
+        if (_usesPadding) {
+            byte pb = (byte) _paddingChar;
+            buffer[outPtr++] = (outputBytes == 2) ?
+                _base64ToAsciiB[(bits >> 6) & 0x3F] : pb;
+            buffer[outPtr++] = pb;
+        } else {
+            if (outputBytes == 2) {
+                buffer[outPtr++] = _base64ToAsciiB[(bits >> 6) & 0x3F];
+            }
+        }
+        return outPtr;
+    }
+
+    /**
+     * Convenience method for converting given byte array as base64 encoded
+     * String using this variant's settings.
+     * Resulting value is "raw", that is, not enclosed in double-quotes.
+     * 
+     * @param input Byte array to encode
+     */
+    public String encode(byte[] input)
+    {
+        return encode(input, false);
+    }
+
+    /**
+     * Convenience method for converting given byte array as base64 encoded
+     * String using this variant's settings, optionally enclosed in
+     * double-quotes.
+     * 
+     * @param input Byte array to encode
+     * @param addQuotes Whether to surround resulting value in double quotes or not
+     */
+    public String encode(byte[] input, boolean addQuotes)
+    {
+        int inputEnd = input.length;
+        StringBuilder sb;
+        {
+            // let's approximate... 33% overhead, ~= 3/8 (0.375)
+            int outputLen = inputEnd + (inputEnd >> 2) + (inputEnd >> 3);
+            sb = new StringBuilder(outputLen);
+        }
+        if (addQuotes) {
+            sb.append('"');
+        }
+
+        int chunksBeforeLF = getMaxLineLength() >> 2;
+
+        // Ok, first we loop through all full triplets of data:
+        int inputPtr = 0;
+        int safeInputEnd = inputEnd-3; // to get only full triplets
+
+        while (inputPtr <= safeInputEnd) {
+            // First, mash 3 bytes into lsb of 32-bit int
+            int b24 = ((int) input[inputPtr++]) << 8;
+            b24 |= ((int) input[inputPtr++]) & 0xFF;
+            b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF);
+            encodeBase64Chunk(sb, b24);
+            if (--chunksBeforeLF <= 0) {
+                // note: must quote in JSON value, so not really useful...
+                sb.append('\\');
+                sb.append('n');
+                chunksBeforeLF = getMaxLineLength() >> 2;
+            }
+        }
+
+        // And then we may have 1 or 2 leftover bytes to encode
+        int inputLeft = inputEnd - inputPtr; // 0, 1 or 2
+        if (inputLeft > 0) { // yes, but do we have room for output?
+            int b24 = ((int) input[inputPtr++]) << 16;
+            if (inputLeft == 2) {
+                b24 |= (((int) input[inputPtr++]) & 0xFF) << 8;
+            }
+            encodeBase64Partial(sb, b24, inputLeft);
+        }
+
+        if (addQuotes) {
+            sb.append('"');
+        }
+        return sb.toString();
+    }
+    
+    /*
+    /**********************************************************
+    /* other methods
+    /**********************************************************
+     */
+
+    @Override
+    public String toString() { return _name; }
+}
+
diff --git a/src/main/java/com/fasterxml/jackson/core/Base64Variants.java b/src/main/java/com/fasterxml/jackson/core/Base64Variants.java
new file mode 100644
index 0000000..63f434a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Base64Variants.java
@@ -0,0 +1,111 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+package com.fasterxml.jackson.core;
+
+/**
+ * Container for commonly used Base64 variants:
+ *<ul>
+ * <li> {@link #MIME}
+ * <li> {@link #MIME_NO_LINEFEEDS}
+ * <li> {@link #PEM}
+ * <li> {@link #MODIFIED_FOR_URL}
+ * </ul>
+ * 
+ * @author Tatu Saloranta
+ */
+public final class Base64Variants
+{
+    final static String STD_BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    /**
+     * This variant is what most people would think of "the standard"
+     * Base64 encoding.
+     *<p>
+     * See <a href="">wikipedia Base64 entry</a> for details.
+     *<p>
+     * Note that although this can be thought of as the standard variant,
+     * it is <b>not</b> the default for Jackson: no-linefeeds alternative
+     * is because of JSON requirement of escaping all linefeeds.
+     */
+    public final static Base64Variant MIME;
+    static {
+        MIME = new Base64Variant("MIME", STD_BASE64_ALPHABET, true, '=', 76);
+    }
+
+    /**
+     * Slightly non-standard modification of {@link #MIME} which does not
+     * use linefeeds (max line length set to infinite). Useful when linefeeds
+     * wouldn't work well (possibly in attributes), or for minor space savings
+     * (save 1 linefeed per 76 data chars, ie. ~1.4% savings).
+     */
+    public final static Base64Variant MIME_NO_LINEFEEDS;
+    static {
+        MIME_NO_LINEFEEDS = new Base64Variant(MIME, "MIME-NO-LINEFEEDS", Integer.MAX_VALUE);
+    }
+
+    /**
+     * This variant is the one that predates {@link #MIME}: it is otherwise
+     * identical, except that it mandates shorter line length.
+     */
+    public final static Base64Variant PEM = new Base64Variant(MIME, "PEM", true, '=', 64);
+
+    /**
+     * This non-standard variant is usually used when encoded data needs to be
+     * passed via URLs (such as part of GET request). It differs from the
+     * base {@link #MIME} variant in multiple ways.
+     * First, no padding is used: this also means that it generally can not
+     * be written in multiple separate but adjacent chunks (which would not
+     * be the usual use case in any case). Also, no linefeeds are used (max
+     * line length set to infinite). And finally, two characters (plus and
+     * slash) that would need quoting in URLs are replaced with more
+     * optimal alternatives (hyphen and underscore, respectively).
+     */
+    public final static Base64Variant MODIFIED_FOR_URL;
+    static {
+        StringBuffer sb = new StringBuffer(STD_BASE64_ALPHABET);
+        // Replace plus with hyphen, slash with underscore (and no padding)
+        sb.setCharAt(sb.indexOf("+"), '-');
+        sb.setCharAt(sb.indexOf("/"), '_');
+        /* And finally, let's not split lines either, wouldn't work too
+         * well with URLs
+         */
+        MODIFIED_FOR_URL = new Base64Variant("MODIFIED-FOR-URL", sb.toString(), false, Base64Variant.PADDING_CHAR_NONE, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Method used to get the default variant ("MIME_NO_LINEFEEDS") for cases
+     * where caller does not explicitly specify the variant.
+     * We will prefer no-linefeed version because linefeeds in JSON values
+     * must be escaped, making linefeed-containing variants sub-optimal.
+     */
+    public static Base64Variant getDefaultVariant() {
+        return MIME_NO_LINEFEEDS;
+    }
+
+    /**
+     * @since 2.1
+     */
+    public static Base64Variant valueOf(String name) throws IllegalArgumentException
+    {
+        if (MIME._name.equals(name)) {
+            return MIME;
+        }
+        if (MIME_NO_LINEFEEDS._name.equals(name)) {
+            return MIME_NO_LINEFEEDS;
+        }
+        if (PEM._name.equals(name)) {
+            return PEM;
+        }
+        if (MODIFIED_FOR_URL._name.equals(name)) {
+            return MODIFIED_FOR_URL;
+        }
+        if (name == null) {
+            name = "<null>";
+        } else {
+            name = "'"+name+"'";
+        }
+        throw new IllegalArgumentException("No Base64Variant with name "+name);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/FormatSchema.java b/src/main/java/com/fasterxml/jackson/core/FormatSchema.java
new file mode 100644
index 0000000..e096733
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/FormatSchema.java
@@ -0,0 +1,32 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Simple tag interface used to mark schema objects that are used by some
+ * {@link JsonParser} and {@link JsonGenerator} implementations to further
+ * specify structure of expected format.
+ * Basic JSON-based parsers and generators do not use schemas, but some data
+ * formats (like many binary data formats like Thrift, protobuf) mandate
+ * use of schemas.
+ *<p>
+ * Since there is little commonality between schemas for different data formats,
+ * this interface does not define much meaningful functionality for accessing
+ * schema details; rather, specific parser and generator implementations need
+ * to cast to schema implementations they use. This marker interface is mostly
+ * used for tagging "some kind of schema" -- instead of passing opaque
+ * {@link java.lang.Object} -- for documentation purposes.
+ */
+public interface FormatSchema
+{
+    /**
+     * Method that can be used to get an identifier that can be used for diagnostics
+     * purposes, to indicate what kind of data format this schema is used for: typically
+     * it is a short name of format itself, but it can also contain additional information
+     * in cases where data format supports multiple types of schemas.
+     */
+    String getSchemaType();
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonEncoding.java b/src/main/java/com/fasterxml/jackson/core/JsonEncoding.java
new file mode 100644
index 0000000..75912e5
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonEncoding.java
@@ -0,0 +1,52 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Enumeration that defines legal encodings that can be used
+ * for JSON content, based on list of allowed encodings from
+ * <a href="http://www.ietf.org/rfc/rfc4627.txt">JSON specification</a>.
+ *<p>
+ * Note: if application want to explicitly disregard Encoding
+ * limitations (to read in JSON encoded using an encoding not
+ * listed as allowed), they can use {@link java.io.Reader} /
+ * {@link java.io.Writer} instances as input
+ */
+public enum JsonEncoding {
+    UTF8("UTF-8", false), // N/A for big-endian, really
+        UTF16_BE("UTF-16BE", true),
+        UTF16_LE("UTF-16LE", false),
+        UTF32_BE("UTF-32BE", true),
+        UTF32_LE("UTF-32LE", false)
+        ;
+    
+    protected final String _javaName;
+
+    protected final boolean _bigEndian;
+    
+    JsonEncoding(String javaName, boolean bigEndian)
+    {
+        _javaName = javaName;
+        _bigEndian = bigEndian;
+    }
+
+    /**
+     * Method for accessing encoding name that JDK will support.
+     *
+     * @return Matching encoding name that JDK will support.
+     */
+    public String getJavaName() { return _javaName; }
+
+    /**
+     * Whether encoding is big-endian (if encoding supports such
+     * notion). If no such distinction is made (as is the case for
+     * {@link #UTF8}), return value is undefined.
+     *
+     * @return True for big-endian encodings; false for little-endian
+     *   (or if not applicable)
+     */
+    public boolean isBigEndian() { return _bigEndian; }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
new file mode 100644
index 0000000..cc5ee74
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
@@ -0,0 +1,1422 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+package com.fasterxml.jackson.core;
+
+import java.io.*;
+import java.lang.ref.SoftReference;
+import java.net.URL;
+
+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.json.*;
+import com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer;
+import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+
+/**
+ * The main factory class of Jackson package, used to configure and
+ * construct reader (aka parser, {@link JsonParser})
+ * and writer (aka generator, {@link JsonGenerator})
+ * instances.
+ *<p>
+ * Factory instances are thread-safe and reusable after configuration
+ * (if any). Typically applications and services use only a single
+ * globally shared factory instance, unless they need differently
+ * configured factories. Factory reuse is important if efficiency matters;
+ * most recycling of expensive construct is done on per-factory basis.
+ *<p>
+ * Creation of a factory instance is a light-weight operation,
+ * and since there is no need for pluggable alternative implementations
+ * (as there is no "standard" JSON processor API to implement),
+ * the default constructor is used for constructing factory
+ * instances.
+ *
+ * @author Tatu Saloranta
+ */
+public class JsonFactory
+    implements Versioned,
+        java.io.Serializable // since 2.1 (for Android, mostly)
+{
+    /**
+     * Computed for Jackson 2.2.0 release
+     */
+    private static final long serialVersionUID = 8726401676402117450L;
+
+    /*
+    /**********************************************************
+    /* Helper types
+    /**********************************************************
+     */
+    
+    /**
+     * Enumeration that defines all on/off features that can only be
+     * changed for {@link JsonFactory}.
+     */
+    public enum Feature {
+        
+        // // // Symbol handling (interning etc)
+        
+        /**
+         * Feature that determines whether JSON object field names are
+         * to be canonicalized using {@link String#intern} or not:
+         * if enabled, all field names will be intern()ed (and caller
+         * can count on this being true for all such names); if disabled,
+         * no intern()ing is done. There may still be basic
+         * canonicalization (that is, same String will be used to represent
+         * all identical object property names for a single document).
+         *<p>
+         * Note: this setting only has effect if
+         * {@link #CANONICALIZE_FIELD_NAMES} is true -- otherwise no
+         * canonicalization of any sort is done.
+         *<p>
+         * This setting is enabled by default.
+         */
+        INTERN_FIELD_NAMES(true),
+
+        /**
+         * Feature that determines whether JSON object field names are
+         * to be canonicalized (details of how canonicalization is done
+         * then further specified by
+         * {@link #INTERN_FIELD_NAMES}).
+         *<p>
+         * This setting is enabled by default.
+         */
+        CANONICALIZE_FIELD_NAMES(true)
+
+        ;
+
+        /**
+         * Whether feature is enabled or disabled by default.
+         */
+        private final boolean _defaultState;
+        
+        /**
+         * Method that calculates bit set (flags) of all features that
+         * are enabled by default.
+         */
+        public static int collectDefaults()
+        {
+            int flags = 0;
+            for (Feature f : values()) {
+                if (f.enabledByDefault()) {
+                    flags |= f.getMask();
+                }
+            }
+            return flags;
+        }
+        
+        private Feature(boolean defaultState)
+        {
+            _defaultState = defaultState;
+        }
+        
+        public boolean enabledByDefault() { return _defaultState; }
+
+        public boolean enabledIn(int flags) { return (flags & getMask()) != 0; }
+        
+        public int getMask() { return (1 << ordinal()); }
+    }
+
+    /*
+    /**********************************************************
+    /* Constants
+    /**********************************************************
+     */
+    
+    /**
+     * Name used to identify JSON format
+     * (and returned by {@link #getFormatName()}
+     */
+    public final static String FORMAT_NAME_JSON = "JSON";
+    
+    /**
+     * Bitfield (set of flags) of all factory features that are enabled by default.
+     */
+    protected final static int DEFAULT_FACTORY_FEATURE_FLAGS = JsonFactory.Feature.collectDefaults();
+
+    /**
+     * Bitfield (set of flags) of all parser features that are enabled
+     * by default.
+     */
+    protected final static int DEFAULT_PARSER_FEATURE_FLAGS = JsonParser.Feature.collectDefaults();
+    
+    /**
+     * Bitfield (set of flags) of all generator features that are enabled
+     * by default.
+     */
+    protected final static int DEFAULT_GENERATOR_FEATURE_FLAGS = JsonGenerator.Feature.collectDefaults();
+
+    private final static SerializableString DEFAULT_ROOT_VALUE_SEPARATOR = DefaultPrettyPrinter.DEFAULT_ROOT_VALUE_SEPARATOR;
+    
+    /*
+    /**********************************************************
+    /* Buffer, symbol table management
+    /**********************************************************
+     */
+
+    /**
+     * This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftReference}
+     * to a {@link BufferRecycler} used to provide a low-cost
+     * buffer recycling between reader and writer instances.
+     */
+    final protected static ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef
+        = new ThreadLocal<SoftReference<BufferRecycler>>();
+
+    /**
+     * Each factory comes equipped with a shared root symbol table.
+     * It should not be linked back to the original blueprint, to
+     * avoid contents from leaking between factories.
+     */
+    protected final transient CharsToNameCanonicalizer _rootCharSymbols = CharsToNameCanonicalizer.createRoot();
+
+    /**
+     * Alternative to the basic symbol table, some stream-based
+     * parsers use different name canonicalization method.
+     *<p>
+     * TODO: should clean up this; looks messy having 2 alternatives
+     * with not very clear differences.
+     */
+    protected final transient BytesToNameCanonicalizer _rootByteSymbols = BytesToNameCanonicalizer.createRoot();
+
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    /**
+     * Object that implements conversion functionality between
+     * Java objects and JSON content. For base JsonFactory implementation
+     * usually not set by default, but can be explicitly set.
+     * Sub-classes (like @link org.codehaus.jackson.map.MappingJsonFactory}
+     * usually provide an implementation.
+     */
+    protected ObjectCodec _objectCodec;
+
+    /**
+     * Currently enabled factory features.
+     */
+    protected int _factoryFeatures = DEFAULT_FACTORY_FEATURE_FLAGS;
+    
+    /**
+     * Currently enabled parser features.
+     */
+    protected int _parserFeatures = DEFAULT_PARSER_FEATURE_FLAGS;
+
+    /**
+     * Currently enabled generator features.
+     */
+    protected int _generatorFeatures = DEFAULT_GENERATOR_FEATURE_FLAGS;
+
+    /**
+     * Definition of custom character escapes to use for generators created
+     * by this factory, if any. If null, standard data format specific
+     * escapes are used.
+     */
+    protected CharacterEscapes _characterEscapes;
+
+    /**
+     * Optional helper object that may decorate input sources, to do
+     * additional processing on input during parsing.
+     */
+    protected InputDecorator _inputDecorator;
+
+    /**
+     * Optional helper object that may decorate output object, to do
+     * additional processing on output during content generation.
+     */
+    protected OutputDecorator _outputDecorator;
+
+    /**
+     * Separator used between root-level values, if any; null indicates
+     * "do not add separator".
+     * Default separator is a single space character.
+     * 
+     * @since 2.1
+     */
+    protected SerializableString _rootValueSeparator = DEFAULT_ROOT_VALUE_SEPARATOR;
+    
+    /*
+    /**********************************************************
+    /* Construction
+    /**********************************************************
+     */
+    
+    /**
+     * Default constructor used to create factory instances.
+     * Creation of a factory instance is a light-weight operation,
+     * but it is still a good idea to reuse limited number of
+     * factory instances (and quite often just a single instance):
+     * factories are used as context for storing some reused
+     * processing objects (such as symbol tables parsers use)
+     * and this reuse only works within context of a single
+     * factory instance.
+     */
+    public JsonFactory() { this((ObjectCodec) null); }
+
+    public JsonFactory(ObjectCodec oc) { _objectCodec = oc; }
+
+    /**
+     * Constructor used when copy()ing a factory instance.
+     * 
+     * @since 2.2.1
+     */
+    protected JsonFactory(JsonFactory src, ObjectCodec codec)
+    {
+        _objectCodec = null;
+        _factoryFeatures = src._factoryFeatures;
+        _parserFeatures = src._parserFeatures;
+        _generatorFeatures = src._generatorFeatures;
+        _characterEscapes = src._characterEscapes;
+        _inputDecorator = src._inputDecorator;
+        _outputDecorator = src._outputDecorator;
+        _rootValueSeparator = src._rootValueSeparator;
+        
+        /* 27-Apr-2013, tatu: How about symbol table; should we try to
+         *   reuse shared symbol tables? Could be more efficient that way;
+         *   although can slightly add to concurrency overhead.
+         */
+    }
+    
+    /**
+     * Method for constructing a new {@link JsonFactory} that has
+     * the same settings as this instance, but is otherwise
+     * independent (i.e. nothing is actually shared, symbol tables
+     * are separate).
+     * Note that {@link ObjectCodec} reference is not copied but is
+     * set to null; caller typically needs to set it after calling
+     * this method. Reason for this is that the codec is used for
+     * callbacks, and assumption is that there is strict 1-to-1
+     * mapping between codec, factory. Caller has to, then, explicitly
+     * set codec after making the copy.
+     * 
+     * @since 2.1
+     */
+    public JsonFactory copy()
+    {
+        _checkInvalidCopy(JsonFactory.class);
+        // as per above, do clear ObjectCodec
+        return new JsonFactory(this, null);
+    }
+    
+    /**
+     * @since 2.1
+     * @param exp
+     */
+    protected void _checkInvalidCopy(Class<?> exp)
+    {
+        if (getClass() != exp) {
+            throw new IllegalStateException("Failed copy(): "+getClass().getName()
+                    +" (version: "+version()+") does not override copy(); it has to");
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Serializable overrides
+    /**********************************************************
+     */
+
+    /**
+     * Method that we need to override to actually make restoration go
+     * through constructors etc.
+     * Also: must be overridden by sub-classes as well.
+     */
+    protected Object readResolve() {
+        return new JsonFactory(this, _objectCodec);
+    }
+    
+    /*
+    /**********************************************************
+    /* Format detection functionality (since 1.8)
+    /**********************************************************
+     */
+
+    /**
+     * Method that can be used to quickly check whether given schema
+     * is something that parsers and/or generators constructed by this
+     * factory could use. Note that this means possible use, at the level
+     * of data format (i.e. schema is for same data format as parsers and
+     * generators this factory constructs); individual schema instances
+     * may have further usage restrictions.
+     * 
+     * @since 2.1
+     */
+    public boolean canUseSchema(FormatSchema schema) {
+        String ourFormat = getFormatName();
+        return (ourFormat != null) && ourFormat.equals(schema.getSchemaType());
+    }
+
+    /**
+     * Method that returns short textual id identifying format
+     * this factory supports.
+     *<p>
+     * Note: sub-classes should override this method; default
+     * implementation will return null for all sub-classes
+     */
+    public String getFormatName()
+    {
+        /* Somewhat nasty check: since we can't make this abstract
+         * (due to backwards compatibility concerns), need to prevent
+         * format name "leakage"
+         */
+        if (getClass() == JsonFactory.class) {
+            return FORMAT_NAME_JSON;
+        }
+        return null;
+    }
+
+    public MatchStrength hasFormat(InputAccessor acc) throws IOException
+    {
+        // since we can't keep this abstract, only implement for "vanilla" instance
+        if (getClass() == JsonFactory.class) {
+            return hasJSONFormat(acc);
+        }
+        return null;
+    }
+
+    /**
+     * Method that can be called to determine if a custom
+     * {@link ObjectCodec} is needed for binding data parsed
+     * using {@link JsonParser} constructed by this factory
+     * (which typically also implies the same for serialization
+     * with {@link JsonGenerator}).
+     * 
+     * @return True if custom codec is needed with parsers and
+     *   generators created by this factory; false if a general
+     *   {@link ObjectCodec} is enough
+     * 
+     * @since 2.1
+     */
+    public boolean requiresCustomCodec() {
+        return false;
+    }
+    
+    /**
+     * Helper method that can be called to determine if content accessed
+     * using given accessor seems to be JSON content.
+     */
+    protected MatchStrength hasJSONFormat(InputAccessor acc) throws IOException
+    {
+        return ByteSourceJsonBootstrapper.hasJSONFormat(acc);
+    }
+
+    /*
+    /**********************************************************
+    /* Versioned
+    /**********************************************************
+     */
+
+    @Override
+    public Version version() {
+        return PackageVersion.VERSION;
+    }
+
+    /*
+    /**********************************************************
+    /* Configuration, factory features
+    /**********************************************************
+     */
+
+    /**
+     * Method for enabling or disabling specified parser feature
+     * (check {@link JsonParser.Feature} for list of features)
+     */
+    public final JsonFactory configure(JsonFactory.Feature f, boolean state) {
+        return state ? enable(f) : disable(f);
+    }
+
+    /**
+     * Method for enabling specified parser feature
+     * (check {@link JsonFactory.Feature} for list of features)
+     */
+    public JsonFactory enable(JsonFactory.Feature f) {
+        _factoryFeatures |= f.getMask();
+        return this;
+    }
+
+    /**
+     * Method for disabling specified parser features
+     * (check {@link JsonFactory.Feature} for list of features)
+     */
+    public JsonFactory disable(JsonFactory.Feature f) {
+        _factoryFeatures &= ~f.getMask();
+        return this;
+    }
+
+    /**
+     * Checked whether specified parser feature is enabled.
+     */
+    public final boolean isEnabled(JsonFactory.Feature f) {
+        return (_factoryFeatures & f.getMask()) != 0;
+    }
+    
+    /*
+    /**********************************************************
+    /* Configuration, parser configuration
+    /**********************************************************
+     */
+    
+    /**
+     * Method for enabling or disabling specified parser feature
+     * (check {@link JsonParser.Feature} for list of features)
+     */
+    public final JsonFactory configure(JsonParser.Feature f, boolean state) {
+        return state ? enable(f) : disable(f);
+    }
+
+    /**
+     * Method for enabling specified parser feature
+     * (check {@link JsonParser.Feature} for list of features)
+     */
+    public JsonFactory enable(JsonParser.Feature f) {
+        _parserFeatures |= f.getMask();
+        return this;
+    }
+
+    /**
+     * Method for disabling specified parser features
+     * (check {@link JsonParser.Feature} for list of features)
+     */
+    public JsonFactory disable(JsonParser.Feature f) {
+        _parserFeatures &= ~f.getMask();
+        return this;
+    }
+
+    /**
+     * Checked whether specified parser feature is enabled.
+     */
+    public final boolean isEnabled(JsonParser.Feature f) {
+        return (_parserFeatures & f.getMask()) != 0;
+    }
+
+    /**
+     * Method for getting currently configured input decorator (if any;
+     * there is no default decorator).
+     */
+    public InputDecorator getInputDecorator() {
+        return _inputDecorator;
+    }
+
+    /**
+     * Method for overriding currently configured input decorator
+     */
+    public JsonFactory setInputDecorator(InputDecorator d) {
+        _inputDecorator = d;
+        return this;
+    }
+    
+    /*
+    /**********************************************************
+    /* Configuration, generator settings
+    /**********************************************************
+     */
+
+    /**
+     * Method for enabling or disabling specified generator feature
+     * (check {@link JsonGenerator.Feature} for list of features)
+     */
+    public final JsonFactory configure(JsonGenerator.Feature f, boolean state) {
+        return state ? enable(f) : disable(f);
+    }
+
+
+    /**
+     * Method for enabling specified generator features
+     * (check {@link JsonGenerator.Feature} for list of features)
+     */
+    public JsonFactory enable(JsonGenerator.Feature f) {
+        _generatorFeatures |= f.getMask();
+        return this;
+    }
+
+    /**
+     * Method for disabling specified generator feature
+     * (check {@link JsonGenerator.Feature} for list of features)
+     */
+    public JsonFactory disable(JsonGenerator.Feature f) {
+        _generatorFeatures &= ~f.getMask();
+        return this;
+    }
+
+    /**
+     * Check whether specified generator feature is enabled.
+     */
+    public final boolean isEnabled(JsonGenerator.Feature f) {
+        return (_generatorFeatures & f.getMask()) != 0;
+    }
+
+    /**
+     * Method for accessing custom escapes factory uses for {@link JsonGenerator}s
+     * it creates.
+     */
+    public CharacterEscapes getCharacterEscapes() {
+        return _characterEscapes;
+    }
+
+    /**
+     * Method for defining custom escapes factory uses for {@link JsonGenerator}s
+     * it creates.
+     */
+    public JsonFactory setCharacterEscapes(CharacterEscapes esc) {
+        _characterEscapes = esc;
+        return this;
+    }
+
+    /**
+     * Method for getting currently configured output decorator (if any;
+     * there is no default decorator).
+     */
+    public OutputDecorator getOutputDecorator() {
+        return _outputDecorator;
+    }
+
+    /**
+     * Method for overriding currently configured output decorator
+     */
+    public JsonFactory setOutputDecorator(OutputDecorator d) {
+        _outputDecorator = d;
+        return this;
+    }
+
+    /**
+     * Method that allows overriding String used for separating root-level
+     * JSON values (default is single space character)
+     * 
+     * @param sep Separator to use, if any; null means that no separator is
+     *   automatically added
+     * 
+     * @since 2.1
+     */
+    public JsonFactory setRootValueSeparator(String sep) {
+        _rootValueSeparator = (sep == null) ? null : new SerializedString(sep);
+        return this;
+    }
+
+    /**
+     * @since 2.1
+     */
+    public String getRootValueSeparator() {
+        return (_rootValueSeparator == null) ? null : _rootValueSeparator.getValue();
+    }
+    
+    /*
+    /**********************************************************
+    /* Configuration, other
+    /**********************************************************
+     */
+
+    /**
+     * Method for associating a {@link ObjectCodec} (typically
+     * a <code>com.fasterxml.jackson.databind.ObjectMapper</code>)
+     * with this factory (and more importantly, parsers and generators
+     * it constructs). This is needed to use data-binding methods
+     * of {@link JsonParser} and {@link JsonGenerator} instances.
+     */
+    public JsonFactory setCodec(ObjectCodec oc) {
+        _objectCodec = oc;
+        return this;
+    }
+
+    public ObjectCodec getCodec() { return _objectCodec; }
+
+    /*
+    /**********************************************************
+    /* Parser factories (new ones, as per [Issue-25])
+    /**********************************************************
+     */
+
+    /**
+     * Method for constructing JSON parser instance to parse
+     * contents of specified file. Encoding is auto-detected
+     * from contents according to JSON specification recommended
+     * mechanism.
+     *<p>
+     * Underlying input stream (needed for reading contents)
+     * will be <b>owned</b> (and managed, i.e. closed as need be) by
+     * the parser, since caller has no access to it.
+     *
+     * @param f File that contains JSON content to parse
+     * 
+     * @since 2.1
+     */
+    @SuppressWarnings("resource")
+    public JsonParser createParser(File f)
+        throws IOException, JsonParseException
+    {
+        // true, since we create InputStream from File
+        IOContext ctxt = _createContext(f, true);
+        InputStream in = new FileInputStream(f);
+        // [JACKSON-512]: allow wrapping with InputDecorator
+        if (_inputDecorator != null) {
+            in = _inputDecorator.decorate(ctxt, in);
+        }
+        return _createParser(in, ctxt);
+    }
+
+    /**
+     * Method for constructing JSON parser instance to parse
+     * contents of resource reference by given URL.
+     * Encoding is auto-detected
+     * from contents according to JSON specification recommended
+     * mechanism.
+     *<p>
+     * Underlying input stream (needed for reading contents)
+     * will be <b>owned</b> (and managed, i.e. closed as need be) by
+     * the parser, since caller has no access to it.
+     *
+     * @param url URL pointing to resource that contains JSON content to parse
+     * 
+     * @since 2.1
+     */
+    public JsonParser createParser(URL url)
+        throws IOException, JsonParseException
+    {
+        // true, since we create InputStream from URL
+        IOContext ctxt = _createContext(url, true);
+        InputStream in = _optimizedStreamFromURL(url);
+        // [JACKSON-512]: allow wrapping with InputDecorator
+        if (_inputDecorator != null) {
+            in = _inputDecorator.decorate(ctxt, in);
+        }
+        return _createParser(in, ctxt);
+    }
+
+    /**
+     * Method for constructing JSON parser instance to parse
+     * the contents accessed via specified input stream.
+     *<p>
+     * The input stream will <b>not be owned</b> by
+     * the parser, it will still be managed (i.e. closed if
+     * end-of-stream is reacher, or parser close method called)
+     * if (and only if) {@link com.fasterxml.jackson.core.JsonParser.Feature#AUTO_CLOSE_SOURCE}
+     * is enabled.
+     *<p>
+     * Note: no encoding argument is taken since it can always be
+     * auto-detected as suggested by JSON RFC.
+     *
+     * @param in InputStream to use for reading JSON content to parse
+     * 
+     * @since 2.1
+     */
+    public JsonParser createParser(InputStream in)
+        throws IOException, JsonParseException
+    {
+        IOContext ctxt = _createContext(in, false);
+        // [JACKSON-512]: allow wrapping with InputDecorator
+        if (_inputDecorator != null) {
+            in = _inputDecorator.decorate(ctxt, in);
+        }
+        return _createParser(in, ctxt);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * the contents accessed via specified Reader.
+     <p>
+     * The read stream will <b>not be owned</b> by
+     * the parser, it will still be managed (i.e. closed if
+     * end-of-stream is reacher, or parser close method called)
+     * if (and only if) {@link com.fasterxml.jackson.core.JsonParser.Feature#AUTO_CLOSE_SOURCE}
+     * is enabled.
+     *
+     * @param r Reader to use for reading JSON content to parse
+     * 
+     * @since 2.1
+     */
+    public JsonParser createParser(Reader r)
+        throws IOException, JsonParseException
+    {
+        // false -> we do NOT own Reader (did not create it)
+        IOContext ctxt = _createContext(r, false);
+        // [JACKSON-512]: allow wrapping with InputDecorator
+        if (_inputDecorator != null) {
+            r = _inputDecorator.decorate(ctxt, r);
+        }
+        return _createParser(r, ctxt);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * the contents of given byte array.
+     * 
+     * @since 2.1
+     */
+    public JsonParser createParser(byte[] data)
+        throws IOException, JsonParseException
+    {
+        IOContext ctxt = _createContext(data, true);
+        // [JACKSON-512]: allow wrapping with InputDecorator
+        if (_inputDecorator != null) {
+            InputStream in = _inputDecorator.decorate(ctxt, data, 0, data.length);
+            if (in != null) {
+                return _createParser(in, ctxt);
+            }
+        }
+        return _createParser(data, 0, data.length, ctxt);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * the contents of given byte array.
+     * 
+     * @param data Buffer that contains data to parse
+     * @param offset Offset of the first data byte within buffer
+     * @param len Length of contents to parse within buffer
+     * 
+     * @since 2.1
+     */
+    public JsonParser createParser(byte[] data, int offset, int len)
+        throws IOException, JsonParseException
+    {
+        IOContext ctxt = _createContext(data, true);
+        // [JACKSON-512]: allow wrapping with InputDecorator
+        if (_inputDecorator != null) {
+            InputStream in = _inputDecorator.decorate(ctxt, data, offset, len);
+            if (in != null) {
+                return _createParser(in, ctxt);
+            }
+        }
+        return _createParser(data, offset, len, ctxt);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * contents of given String.
+     * 
+     * @since 2.1
+     */
+    public JsonParser createParser(String content)
+        throws IOException, JsonParseException
+    {
+        Reader r = new StringReader(content);
+        // true -> we own the Reader (and must close); not a big deal
+        IOContext ctxt = _createContext(r, true);
+        // [JACKSON-512]: allow wrapping with InputDecorator
+        if (_inputDecorator != null) {
+            r = _inputDecorator.decorate(ctxt, r);
+        }
+        return _createParser(r, ctxt);
+    }
+
+    /*
+    /**********************************************************
+    /* Parser factories (old ones, as per [Issue-25])
+    /**********************************************************
+     */
+
+    /**
+     * Method for constructing JSON parser instance to parse
+     * contents of specified file. Encoding is auto-detected
+     * from contents according to JSON specification recommended
+     * mechanism.
+     *<p>
+     * Underlying input stream (needed for reading contents)
+     * will be <b>owned</b> (and managed, i.e. closed as need be) by
+     * the parser, since caller has no access to it.
+     *<p>
+     * NOTE: as of 2.1, should not be used (will be deprecated in 2.2);
+     * instead, should call <code>createParser</code>.
+     *
+     * @param f File that contains JSON content to parse
+     * 
+     * @deprecated Since 2.2, use {@link #createParser(File)} instead.
+     */
+    @Deprecated
+    public JsonParser createJsonParser(File f)
+        throws IOException, JsonParseException
+    {
+        return createParser(f);
+    }
+
+    /**
+     * Method for constructing JSON parser instance to parse
+     * contents of resource reference by given URL.
+     * Encoding is auto-detected
+     * from contents according to JSON specification recommended
+     * mechanism.
+     *<p>
+     * Underlying input stream (needed for reading contents)
+     * will be <b>owned</b> (and managed, i.e. closed as need be) by
+     * the parser, since caller has no access to it.
+     *<p>
+     * NOTE: as of 2.1, should not be used (will be deprecated in 2.2);
+     * instead, should call <code>createParser</code>.
+     *
+     * @param url URL pointing to resource that contains JSON content to parse
+     * 
+     * @deprecated Since 2.2, use {@link #createParser(URL)} instead.
+     */
+    @Deprecated
+    public JsonParser createJsonParser(URL url)
+        throws IOException, JsonParseException
+    {
+        return createParser(url);
+    }
+
+    /**
+     * Method for constructing JSON parser instance to parse
+     * the contents accessed via specified input stream.
+     *<p>
+     * The input stream will <b>not be owned</b> by
+     * the parser, it will still be managed (i.e. closed if
+     * end-of-stream is reacher, or parser close method called)
+     * if (and only if) {@link com.fasterxml.jackson.core.JsonParser.Feature#AUTO_CLOSE_SOURCE}
+     * is enabled.
+     *<p>
+     * Note: no encoding argument is taken since it can always be
+     * auto-detected as suggested by JSON RFC.
+     *<p>
+     * NOTE: as of 2.1, should not be used (will be deprecated in 2.2);
+     * instead, should call <code>createParser</code>.
+     *
+     * @param in InputStream to use for reading JSON content to parse
+     * 
+     * @deprecated Since 2.2, use {@link #createParser(InputStream)} instead.
+     */
+    @Deprecated
+    public JsonParser createJsonParser(InputStream in)
+        throws IOException, JsonParseException
+    {
+        return createParser(in);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * the contents accessed via specified Reader.
+     <p>
+     * The read stream will <b>not be owned</b> by
+     * the parser, it will still be managed (i.e. closed if
+     * end-of-stream is reacher, or parser close method called)
+     * if (and only if) {@link com.fasterxml.jackson.core.JsonParser.Feature#AUTO_CLOSE_SOURCE}
+     * is enabled.
+     *<p>
+     * NOTE: as of 2.1, should not be used (will be deprecated in 2.2);
+     * instead, should call <code>createParser</code>.
+     *
+     * @param r Reader to use for reading JSON content to parse
+     * 
+     * @deprecated Since 2.2, use {@link #createParser(Reader)} instead.
+     */
+    @Deprecated
+    public JsonParser createJsonParser(Reader r)
+        throws IOException, JsonParseException
+    {
+        return createParser(r);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * the contents of given byte array.
+     *<p>
+     * NOTE: as of 2.1, should not be used (will be deprecated in 2.2);
+     * instead, should call <code>createParser</code>.
+     * 
+     * @deprecated Since 2.2, use {@link #createParser(byte[])} instead.
+     */
+    @Deprecated
+    public JsonParser createJsonParser(byte[] data)
+        throws IOException, JsonParseException
+    {
+        return createParser(data);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * the contents of given byte array.
+     *<p>
+     * NOTE: as of 2.1, should not be used (will be deprecated in 2.2);
+     * instead, should call <code>createParser</code>.
+     * 
+     * @param data Buffer that contains data to parse
+     * @param offset Offset of the first data byte within buffer
+     * @param len Length of contents to parse within buffer
+     * 
+     * @deprecated Since 2.2, use {@link #createParser(byte[],int,int)} instead.
+     */
+    @Deprecated
+    public JsonParser createJsonParser(byte[] data, int offset, int len)
+        throws IOException, JsonParseException
+    {
+        return createParser(data, offset, len);
+    }
+
+    /**
+     * Method for constructing parser for parsing
+     * contents of given String.
+     * 
+     * @deprecated Since 2.2, use {@link #createParser(String)} instead.
+     */
+    @Deprecated
+    public JsonParser createJsonParser(String content)
+        throws IOException, JsonParseException
+    {
+        return createParser(content);
+    }
+
+    /*
+    /**********************************************************
+    /* Generator factories, new (as per [Issue-25]
+    /**********************************************************
+     */
+
+    /**
+     * Method for constructing JSON generator for writing JSON content
+     * using specified output stream.
+     * Encoding to use must be specified, and needs to be one of available
+     * types (as per JSON specification).
+     *<p>
+     * Underlying stream <b>is NOT owned</b> by the generator constructed,
+     * so that generator will NOT close the output stream when
+     * {@link JsonGenerator#close} is called (unless auto-closing
+     * feature,
+     * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#AUTO_CLOSE_TARGET}
+     * is enabled).
+     * Using application needs to close it explicitly if this is the case.
+     *<p>
+     * Note: there are formats that use fixed encoding (like most binary data formats)
+     * and that ignore passed in encoding.
+     *
+     * @param out OutputStream to use for writing JSON content 
+     * @param enc Character encoding to use
+     * 
+     * @since 2.1
+     */
+    public JsonGenerator createGenerator(OutputStream out, JsonEncoding enc)
+        throws IOException
+    {
+        // false -> we won't manage the stream unless explicitly directed to
+        IOContext ctxt = _createContext(out, false);
+        ctxt.setEncoding(enc);
+        if (enc == JsonEncoding.UTF8) {
+            // [JACKSON-512]: allow wrapping with _outputDecorator
+            if (_outputDecorator != null) {
+                out = _outputDecorator.decorate(ctxt, out);
+            }
+            return _createUTF8Generator(out, ctxt);
+        }
+        Writer w = _createWriter(out, enc, ctxt);
+        // [JACKSON-512]: allow wrapping with _outputDecorator
+        if (_outputDecorator != null) {
+            w = _outputDecorator.decorate(ctxt, w);
+        }
+        return _createGenerator(w, ctxt);
+    }
+
+    /**
+     * Convenience method for constructing generator that uses default
+     * encoding of the format (UTF-8 for JSON and most other data formats).
+     *<p>
+     * Note: there are formats that use fixed encoding (like most binary data formats).
+     * 
+     * @since 2.1
+     */
+    public JsonGenerator createGenerator(OutputStream out) throws IOException {
+        return createGenerator(out, JsonEncoding.UTF8);
+    }
+    
+    /**
+     * Method for constructing JSON generator for writing JSON content
+     * using specified Writer.
+     *<p>
+     * Underlying stream <b>is NOT owned</b> by the generator constructed,
+     * so that generator will NOT close the Reader when
+     * {@link JsonGenerator#close} is called (unless auto-closing
+     * feature,
+     * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#AUTO_CLOSE_TARGET} is enabled).
+     * Using application needs to close it explicitly.
+     * 
+     * @since 2.1
+     *
+     * @param out Writer to use for writing JSON content 
+     */
+    public JsonGenerator createGenerator(Writer out)
+        throws IOException
+    {
+        IOContext ctxt = _createContext(out, false);
+        // [JACKSON-512]: allow wrapping with _outputDecorator
+        if (_outputDecorator != null) {
+            out = _outputDecorator.decorate(ctxt, out);
+        }
+        return _createGenerator(out, ctxt);
+    }
+    
+    /**
+     * Method for constructing JSON generator for writing JSON content
+     * to specified file, overwriting contents it might have (or creating
+     * it if such file does not yet exist).
+     * Encoding to use must be specified, and needs to be one of available
+     * types (as per JSON specification).
+     *<p>
+     * Underlying stream <b>is owned</b> by the generator constructed,
+     * i.e. generator will handle closing of file when
+     * {@link JsonGenerator#close} is called.
+     *
+     * @param f File to write contents to
+     * @param enc Character encoding to use
+     * 
+     * @since 2.1
+     */
+    public JsonGenerator createGenerator(File f, JsonEncoding enc)
+        throws IOException
+    {
+        OutputStream out = new FileOutputStream(f);
+        // true -> yes, we have to manage the stream since we created it
+        IOContext ctxt = _createContext(out, true);
+        ctxt.setEncoding(enc);
+        if (enc == JsonEncoding.UTF8) {
+            // [JACKSON-512]: allow wrapping with _outputDecorator
+            if (_outputDecorator != null) {
+                out = _outputDecorator.decorate(ctxt, out);
+            }
+            return _createUTF8Generator(out, ctxt);
+        }
+        Writer w = _createWriter(out, enc, ctxt);
+        // [JACKSON-512]: allow wrapping with _outputDecorator
+        if (_outputDecorator != null) {
+            w = _outputDecorator.decorate(ctxt, w);
+        }
+        return _createGenerator(w, ctxt);
+    }    
+
+    /*
+    /**********************************************************
+    /* Generator factories, old (as per [Issue-25]
+    /**********************************************************
+     */
+
+    /**
+     * Method for constructing JSON generator for writing JSON content
+     * using specified output stream.
+     * Encoding to use must be specified, and needs to be one of available
+     * types (as per JSON specification).
+     *<p>
+     * Underlying stream <b>is NOT owned</b> by the generator constructed,
+     * so that generator will NOT close the output stream when
+     * {@link JsonGenerator#close} is called (unless auto-closing
+     * feature,
+     * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#AUTO_CLOSE_TARGET}
+     * is enabled).
+     * Using application needs to close it explicitly if this is the case.
+     *<p>
+     * Note: there are formats that use fixed encoding (like most binary data formats)
+     * and that ignore passed in encoding.
+     *
+     * @param out OutputStream to use for writing JSON content 
+     * @param enc Character encoding to use
+     *
+     * @deprecated Since 2.2, use {@link #createGenerator(OutputStream, JsonEncoding)} instead.
+     */
+    @Deprecated
+    public JsonGenerator createJsonGenerator(OutputStream out, JsonEncoding enc)
+        throws IOException
+    {
+        return createGenerator(out, enc);
+    }
+
+    /**
+     * Method for constructing JSON generator for writing JSON content
+     * using specified Writer.
+     *<p>
+     * Underlying stream <b>is NOT owned</b> by the generator constructed,
+     * so that generator will NOT close the Reader when
+     * {@link JsonGenerator#close} is called (unless auto-closing
+     * feature,
+     * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#AUTO_CLOSE_TARGET} is enabled).
+     * Using application needs to close it explicitly.
+     *
+     * @param out Writer to use for writing JSON content 
+     * 
+     * @deprecated Since 2.2, use {@link #createGenerator(Writer)} instead.
+     */
+    @Deprecated
+    public JsonGenerator createJsonGenerator(Writer out)
+        throws IOException
+    {
+        return createGenerator(out);
+    }
+
+    /**
+     * Convenience method for constructing generator that uses default
+     * encoding of the format (UTF-8 for JSON and most other data formats).
+     *<p>
+     * Note: there are formats that use fixed encoding (like most binary data formats).
+     * 
+     * @deprecated Since 2.2, use {@link #createGenerator(OutputStream)} instead.
+     */
+    @Deprecated
+    public JsonGenerator createJsonGenerator(OutputStream out) throws IOException {
+        return createGenerator(out, JsonEncoding.UTF8);
+    }
+    
+    /**
+     * Method for constructing JSON generator for writing JSON content
+     * to specified file, overwriting contents it might have (or creating
+     * it if such file does not yet exist).
+     * Encoding to use must be specified, and needs to be one of available
+     * types (as per JSON specification).
+     *<p>
+     * Underlying stream <b>is owned</b> by the generator constructed,
+     * i.e. generator will handle closing of file when
+     * {@link JsonGenerator#close} is called.
+     *
+     * @param f File to write contents to
+     * @param enc Character encoding to use
+     * 
+     * 
+     * @deprecated Since 2.2, use {@link #createGenerator(File,JsonEncoding)} instead.
+     */
+    @Deprecated
+    public JsonGenerator createJsonGenerator(File f, JsonEncoding enc)
+        throws IOException
+    {
+        return createGenerator(f, enc);
+    }
+
+    /*
+    /**********************************************************
+    /* Factory methods used by factory for creating parser instances,
+    /* overridable by sub-classes
+    /**********************************************************
+     */
+
+    /**
+     * Overridable factory method that actually instantiates desired parser
+     * given {@link InputStream} and context object.
+     *<p>
+     * This method is specifically designed to remain
+     * compatible between minor versions so that sub-classes can count
+     * on it being called as expected. That is, it is part of official
+     * interface from sub-class perspective, although not a public
+     * method available to users of factory implementations.
+     * 
+     * @since 2.1
+     */
+    protected JsonParser _createParser(InputStream in, IOContext ctxt)
+        throws IOException, JsonParseException
+    {
+        // As per [JACKSON-259], may want to fully disable canonicalization:
+        return new ByteSourceJsonBootstrapper(ctxt, in).constructParser(_parserFeatures,
+                _objectCodec, _rootByteSymbols, _rootCharSymbols,
+                isEnabled(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES),
+                isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+    }
+
+    /**
+     * @deprecated since 2.1 -- use {@link #_createParser(InputStream, IOContext)} instead
+     */
+    @Deprecated
+    protected JsonParser _createJsonParser(InputStream in, IOContext ctxt) throws IOException, JsonParseException {
+        return _createParser(in, ctxt);
+    }
+    
+    /**
+     * Overridable factory method that actually instantiates parser
+     * using given {@link Reader} object for reading content.
+     *<p>
+     * This method is specifically designed to remain
+     * compatible between minor versions so that sub-classes can count
+     * on it being called as expected. That is, it is part of official
+     * interface from sub-class perspective, although not a public
+     * method available to users of factory implementations.
+     * 
+     * @since 2.1
+     */
+    protected JsonParser _createParser(Reader r, IOContext ctxt)
+        throws IOException, JsonParseException
+    {
+        return new ReaderBasedJsonParser(ctxt, _parserFeatures, r, _objectCodec,
+                _rootCharSymbols.makeChild(isEnabled(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES),
+                        isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES)));
+    }
+
+    /**
+     * @deprecated since 2.1 -- use {@link #_createParser(Reader, IOContext)} instead
+     */
+    @Deprecated
+    protected JsonParser _createJsonParser(Reader r, IOContext ctxt) throws IOException, JsonParseException {
+        return _createParser(r, ctxt);
+    }
+
+    /**
+     * Overridable factory method that actually instantiates parser
+     * using given {@link Reader} object for reading content
+     * passed as raw byte array.
+     *<p>
+     * This method is specifically designed to remain
+     * compatible between minor versions so that sub-classes can count
+     * on it being called as expected. That is, it is part of official
+     * interface from sub-class perspective, although not a public
+     * method available to users of factory implementations.
+     */
+    protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt)
+        throws IOException, JsonParseException
+    {
+        return new ByteSourceJsonBootstrapper(ctxt, data, offset, len).constructParser(_parserFeatures,
+                _objectCodec, _rootByteSymbols, _rootCharSymbols,
+                isEnabled(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES),
+                isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+    }
+
+    /**
+     * @deprecated since 2.1 -- use {@link #_createParser(byte[], int, int, IOContext)} instead
+     */
+    @Deprecated
+    protected JsonParser _createJsonParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException, JsonParseException {
+        return _createParser(data, offset, len, ctxt);
+    }
+    
+    /*
+    /**********************************************************
+    /* Factory methods used by factory for creating generator instances,
+    /* overridable by sub-classes
+    /**********************************************************
+     */
+    
+    /**
+     * Overridable factory method that actually instantiates generator for
+     * given {@link Writer} and context object.
+     *<p>
+     * This method is specifically designed to remain
+     * compatible between minor versions so that sub-classes can count
+     * on it being called as expected. That is, it is part of official
+     * interface from sub-class perspective, although not a public
+     * method available to users of factory implementations.
+     */
+    protected JsonGenerator _createGenerator(Writer out, IOContext ctxt)
+        throws IOException
+    {
+        WriterBasedJsonGenerator gen = new WriterBasedJsonGenerator(ctxt,
+                _generatorFeatures, _objectCodec, out);
+        if (_characterEscapes != null) {
+            gen.setCharacterEscapes(_characterEscapes);
+        }
+        SerializableString rootSep = _rootValueSeparator;
+        if (rootSep != DEFAULT_ROOT_VALUE_SEPARATOR) {
+            gen.setRootValueSeparator(rootSep);
+        }
+        return gen;
+    }
+
+    /**
+     * @deprecated since 2.1 -- use {@link #_createGenerator(Writer, IOContext)} instead
+     */
+    @Deprecated
+    protected JsonGenerator _createJsonGenerator(Writer out, IOContext ctxt)
+        throws IOException
+    {
+        /* NOTE: MUST call the deprecated method until it is deleted, just so
+         * that override still works as expected, for now.
+         */
+        return _createGenerator(out, ctxt);
+    }
+
+    /**
+     * Overridable factory method that actually instantiates generator for
+     * given {@link OutputStream} and context object, using UTF-8 encoding.
+     *<p>
+     * This method is specifically designed to remain
+     * compatible between minor versions so that sub-classes can count
+     * on it being called as expected. That is, it is part of official
+     * interface from sub-class perspective, although not a public
+     * method available to users of factory implementations.
+     */
+    protected JsonGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException {
+        UTF8JsonGenerator gen = new UTF8JsonGenerator(ctxt,
+                _generatorFeatures, _objectCodec, out);
+        if (_characterEscapes != null) {
+            gen.setCharacterEscapes(_characterEscapes);
+        }
+        SerializableString rootSep = _rootValueSeparator;
+        if (rootSep != DEFAULT_ROOT_VALUE_SEPARATOR) {
+            gen.setRootValueSeparator(rootSep);
+        }
+        return gen;
+    }
+
+    /**
+     * @deprecated since 2.1
+     */
+    @Deprecated
+    protected JsonGenerator _createUTF8JsonGenerator(OutputStream out, IOContext ctxt)
+        throws IOException
+    {
+        return _createUTF8Generator(out, ctxt);
+    }
+
+    protected Writer _createWriter(OutputStream out, JsonEncoding enc, IOContext ctxt) throws IOException
+    {
+        // note: this should not get called any more (caller checks, dispatches)
+        if (enc == JsonEncoding.UTF8) { // We have optimized writer for UTF-8
+            return new UTF8Writer(ctxt, out);
+        }
+        // not optimal, but should do unless we really care about UTF-16/32 encoding speed
+        return new OutputStreamWriter(out, enc.getJavaName());
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal factory methods, other
+    /**********************************************************
+     */
+
+    /**
+     * Overridable factory method that actually instantiates desired
+     * context object.
+     */
+    protected IOContext _createContext(Object srcRef, boolean resourceManaged)
+    {
+        return new IOContext(_getBufferRecycler(), srcRef, resourceManaged);
+    }
+
+    /**
+     * Method used by factory to create buffer recycler instances
+     * for parsers and generators.
+     *<p>
+     * Note: only public to give access for <code>ObjectMapper</code>
+     */
+    public BufferRecycler _getBufferRecycler()
+    {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            _recyclerRef.set(new SoftReference<BufferRecycler>(br));
+        }
+        return br;
+    }
+    
+    /**
+     * Helper methods used for constructing an optimal stream for
+     * parsers to use, when input is to be read from an URL.
+     * This helps when reading file content via URL.
+     */
+    protected InputStream _optimizedStreamFromURL(URL url)
+        throws IOException
+    {
+        if ("file".equals(url.getProtocol())) {
+            /* Can not do this if the path refers
+             * to a network drive on windows. This fixes the problem;
+             * might not be needed on all platforms (NFS?), but should not
+             * matter a lot: performance penalty of extra wrapping is more
+             * relevant when accessing local file system.
+             */
+            String host = url.getHost();
+            if (host == null || host.length() == 0) {
+                // [Issue#48]: Let's try to avoid probs with URL encoded stuff
+                String path = url.getPath();
+                if (path.indexOf('%') < 0) {
+                    return new FileInputStream(url.getPath());
+
+                }
+                // otherwise, let's fall through and let URL decoder do its magic
+            }
+        }
+        return url.openStream();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerationException.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerationException.java
new file mode 100644
index 0000000..58a8b77
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerationException.java
@@ -0,0 +1,32 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Exception type for exceptions during JSON writing, such as trying
+ * to output  content in wrong context (non-matching end-array or end-object,
+ * for example).
+ */
+public class JsonGenerationException
+    extends JsonProcessingException
+{
+    private final static long serialVersionUID = 123; // Stupid eclipse...
+    
+    public JsonGenerationException(Throwable rootCause)
+    {
+        super(rootCause);
+    }
+
+    public JsonGenerationException(String msg)
+    {
+        super(msg, (JsonLocation)null);
+    }
+
+    public JsonGenerationException(String msg, Throwable rootCause)
+    {
+        super(msg, (JsonLocation)null, rootCause);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
new file mode 100644
index 0000000..5af4e64
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
@@ -0,0 +1,1221 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+package com.fasterxml.jackson.core;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.io.CharacterEscapes;
+
+/**
+ * Base class that defines public API for writing JSON content.
+ * Instances are created using factory methods of
+ * a {@link JsonFactory} instance.
+ *
+ * @author Tatu Saloranta
+ */
+public abstract class JsonGenerator
+    implements Closeable, Flushable, // as of 2.1
+        Versioned
+{
+    /**
+     * Enumeration that defines all togglable features for generators.
+     */
+    public enum Feature {
+        /**
+         * Feature that determines whether generator will automatically
+         * close underlying output target that is NOT owned by the
+         * generator.
+         * If disabled, calling application has to separately
+         * close the underlying {@link OutputStream} and {@link Writer}
+         * instances used to create the generator. If enabled, generator
+         * will handle closing, as long as generator itself gets closed:
+         * this happens when end-of-input is encountered, or generator
+         * is closed by a call to {@link JsonGenerator#close}.
+         *<p>
+         * Feature is enabled by default.
+         */
+        AUTO_CLOSE_TARGET(true),
+
+        /**
+         * Feature that determines what happens when the generator is
+         * closed while there are still unmatched
+         * {@link JsonToken#START_ARRAY} or {@link JsonToken#START_OBJECT}
+         * entries in output content. If enabled, such Array(s) and/or
+         * Object(s) are automatically closed; if disabled, nothing
+         * specific is done.
+         *<p>
+         * Feature is enabled by default.
+         */
+        AUTO_CLOSE_JSON_CONTENT(true),
+
+        /**
+         * 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
+         * cases where they are not usually expected, which most commonly
+         * occurs when used straight from Javascript.
+         *<p>
+         * Feature is enabled by default (since it is required by JSON specification).
+         */
+        QUOTE_FIELD_NAMES(true),
+
+        /**
+         * Feature that determines whether "exceptional" (not real number)
+         * float/double values are output as quoted strings.
+         * The values checked are Double.Nan,
+         * Double.POSITIVE_INFINITY and Double.NEGATIVE_INIFINTY (and 
+         * associated Float values).
+         * If feature is disabled, these numbers are still output using
+         * associated literal values, resulting in non-conformant
+         * output.
+         *<p>
+         * Feature is enabled by default.
+         */
+        QUOTE_NON_NUMERIC_NUMBERS(true),
+
+        /**
+         * Feature that forces all Java numbers to be written as JSON strings.
+         * Default state is 'false', meaning that Java numbers are to
+         * be serialized using basic numeric serialization (as JSON
+         * numbers, integral or floating point). If enabled, all such
+         * numeric values are instead written out as JSON Strings.
+         *<p>
+         * One use case is to avoid problems with Javascript limitations:
+         * since Javascript standard specifies that all number handling
+         * should be done using 64-bit IEEE 754 floating point values,
+         * result being that some 64-bit integer values can not be
+         * accurately represent (as mantissa is only 51 bit wide).
+         *<p>
+         * Feature is disabled by default.
+         */
+        WRITE_NUMBERS_AS_STRINGS(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),
+         * if format uses escaping mechanisms (which is generally true
+         * for textual formats but not for binary formats).
+         *<p>
+         * Feature is disabled by default.
+         */
+        ESCAPE_NON_ASCII(false),
+        
+            ;
+
+        private final boolean _defaultState;
+        
+        private final int _mask;
+        
+        /**
+         * Method that calculates bit set (flags) of all features that
+         * are enabled by default.
+         */
+        public static int collectDefaults()
+        {
+            int flags = 0;
+            for (Feature f : values()) {
+                if (f.enabledByDefault()) {
+                    flags |= f.getMask();
+                }
+            }
+            return flags;
+        }
+        
+        private Feature(boolean defaultState) {
+            _mask = (1 << ordinal());
+            _defaultState = defaultState;
+        }
+        
+        public boolean enabledByDefault() { return _defaultState; }
+        public int getMask() { return _mask; }
+    }
+
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    /**
+     * Object that handles pretty-printing (usually additional
+     * white space to make results more human-readable) during
+     * output. If null, no pretty-printing is done.
+     */
+    protected PrettyPrinter _cfgPrettyPrinter;
+
+    /*
+    /**********************************************************
+    /* Construction, initialization
+    /**********************************************************
+     */
+    
+    protected JsonGenerator() { }
+
+    /**
+     * Method that can be called to set or reset the object to
+     * use for writing Java objects as JsonContent
+     * (using method {@link #writeObject}).
+     *
+     * @return Generator itself (this), to allow chaining
+     */
+    public abstract JsonGenerator setCodec(ObjectCodec oc);
+
+    /**
+     * Method for accessing the object used for writing Java
+     * object as Json content
+     * (using method {@link #writeObject}).
+     */
+    public abstract ObjectCodec getCodec();
+    
+    /**
+     * Method to call to make this generator use specified schema.
+     * Method must be called before generating any content, right after instance
+     * has been created.
+     * Note that not all generators support schemas; and those that do usually only
+     * accept specific types of schemas: ones defined for data format this generator
+     * produces.
+     *<p>
+     * If generator does not support specified schema, {@link UnsupportedOperationException}
+     * is thrown.
+     * 
+     * @param schema Schema to use
+     * 
+     * @throws UnsupportedOperationException if generator does not support schema
+     */
+    public void setSchema(FormatSchema schema)
+    {
+        throw new UnsupportedOperationException("Generator of type "+getClass().getName()+" does not support schema of type '"
+                +schema.getSchemaType()+"'");
+    }
+
+    /**
+     * Method for accessing Schema that this parser uses, if any.
+     * Default implementation returns null.
+     *
+     * @since 2.1
+     */
+    public FormatSchema getSchema() {
+        return null;
+    }
+    
+    /**
+     * Method that can be used to verify that given schema can be used with
+     * this generator (using {@link #setSchema}).
+     * 
+     * @param schema Schema to check
+     * 
+     * @return True if this generator can use given schema; false if not
+     */
+    public boolean canUseSchema(FormatSchema schema) {
+        return false;
+    }
+    
+    /**
+     * Accessor for finding out version of the bundle that provided this generator instance.
+     */
+    @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;
+    }
+
+    /**
+     * Method that allows overriding String used for separating root-level
+     * JSON values (default is single space character)
+     * 
+     * @param sep Separator to use, if any; null means that no separator is
+     *   automatically added
+     * 
+     * @since 2.1
+     */
+    public JsonGenerator setRootValueSeparator(SerializableString sep) {
+        throw new UnsupportedOperationException();
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, configuration
+    /**********************************************************
+     */
+
+    /**
+     * Method for enabling specified parser features:
+     * check {@link Feature} for list of available features.
+     *
+     * @return Generator itself (this), to allow chaining
+     */
+    public abstract JsonGenerator enable(Feature f);
+
+    /**
+     * Method for disabling specified  features
+     * (check {@link Feature} for list of features)
+     *
+     * @return Generator itself (this), to allow chaining
+     */
+    public abstract JsonGenerator disable(Feature f);
+
+    /**
+     * Method for enabling or disabling specified feature:
+     * check {@link Feature} for list of available features.
+     *
+     * @return Generator itself (this), to allow chaining
+     */
+    public final JsonGenerator configure(Feature f, boolean state)
+    {
+        if (state) {
+            enable(f);
+        } else {
+            disable(f);
+        }
+        return this;
+    }
+
+    /**
+     * Method for checking whether given feature is enabled.
+     * Check {@link Feature} for list of available features.
+     */
+    public abstract boolean isEnabled(Feature f);
+
+    /*
+    /**********************************************************
+    /* Configuring generator
+    /**********************************************************
+      */
+
+    /**
+     * Method for setting a custom pretty printer, which is usually
+     * used to add indentation for improved human readability.
+     * By default, generator does not do pretty printing.
+     *<p>
+     * To use the default pretty printer that comes with core
+     * Jackson distribution, call {@link #useDefaultPrettyPrinter}
+     * instead.
+     *
+     * @return Generator itself (this), to allow chaining
+     */
+    public JsonGenerator setPrettyPrinter(PrettyPrinter pp) {
+        _cfgPrettyPrinter = pp;
+        return this;
+    }
+
+    /**
+     * Accessor for checking whether this generator has a configured
+     * {@link PrettyPrinter}; returns it if so, null if none configured.
+     * 
+     * @since 2.1
+     */
+    public PrettyPrinter getPrettyPrinter() {
+        return _cfgPrettyPrinter;
+    }
+    
+    /**
+     * Convenience method for enabling pretty-printing using
+     * the default pretty printer
+     * ({@link com.fasterxml.jackson.core.util.DefaultPrettyPrinter}).
+     *
+     * @return Generator itself (this), to allow chaining
+     */
+    public abstract JsonGenerator useDefaultPrettyPrinter();
+
+    /**
+     * Method that can be called to request that generator escapes
+     * all character codes above specified code point (if positive value);
+     * or, to not escape any characters except for ones that must be
+     * escaped for the data format (if -1).
+     * To force escaping of all non-ASCII characters, for example,
+     * this method would be called with value of 127.
+     *<p>
+     * Note that generators are NOT required to support setting of value
+     * higher than 127, because there are other ways to affect quoting
+     * (or lack thereof) of character codes between 0 and 127.
+     * Not all generators support concept of escaping, either; if so,
+     * calling this method will have no effect.
+     *<p>
+     * Default implementation does nothing; sub-classes need to redefine
+     * it according to rules of supported data format.
+     * 
+     * @param charCode Either -1 to indicate that no additional escaping
+     *   is to be done; or highest code point not to escape (meaning higher
+     *   ones will be), if positive value.
+     */
+    public JsonGenerator setHighestNonEscapedChar(int charCode) {
+        return this;
+    }
+
+    /**
+     * Accessor method for testing what is the highest unescaped character
+     * configured for this generator. This may be either positive value
+     * (when escaping configuration has been set and is in effect), or
+     * 0 to indicate that no additional escaping is in effect.
+     * Some generators may not support additional escaping: for example,
+     * generators for binary formats that do not use escaping should
+     * simply return 0.
+     * 
+     * @return Currently active limitation for highest non-escaped character,
+     *   if defined; or -1 to indicate no additional escaping is performed.
+     */
+    public int getHighestEscapedChar() {
+        return 0;
+    }
+
+    /**
+     * Method for accessing custom escapes factory uses for {@link JsonGenerator}s
+     * it creates.
+     */
+    public CharacterEscapes getCharacterEscapes() {
+        return null;
+    }
+
+    /**
+     * Method for defining custom escapes factory uses for {@link JsonGenerator}s
+     * it creates.
+     */
+    public JsonGenerator setCharacterEscapes(CharacterEscapes esc) {
+        return this;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, structural
+    /**********************************************************
+     */
+
+    /**
+     * Method for writing starting marker of a JSON Array value
+     * (character '['; plus possible white space decoration
+     * if pretty-printing is enabled).
+     *<p>
+     * Array values can be written in any context where values
+     * are allowed: meaning everywhere except for when
+     * a field name is expected.
+     */
+    public abstract void writeStartArray()
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for writing closing marker of a JSON Array value
+     * (character ']'; plus possible white space decoration
+     * if pretty-printing is enabled).
+     *<p>
+     * Marker can be written if the innermost structured type
+     * is Array.
+     */
+    public abstract void writeEndArray()
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for writing starting marker of a JSON Object value
+     * (character '{'; plus possible white space decoration
+     * if pretty-printing is enabled).
+     *<p>
+     * Object values can be written in any context where values
+     * are allowed: meaning everywhere except for when
+     * a field name is expected.
+     */
+    public abstract void writeStartObject()
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for writing closing marker of a JSON Object value
+     * (character '}'; plus possible white space decoration
+     * if pretty-printing is enabled).
+     *<p>
+     * Marker can be written if the innermost structured type
+     * is Object, and the last written event was either a
+     * complete value, or START-OBJECT marker (see JSON specification
+     * for more details).
+     */
+    public abstract void writeEndObject()
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for writing a field name (JSON String surrounded by
+     * double quotes: syntactically identical to a JSON String value),
+     * possibly decorated by white space if pretty-printing is enabled.
+     *<p>
+     * Field names can only be written in Object context (check out
+     * JSON specification for details), when field name is expected
+     * (field names alternate with values).
+     */
+    public abstract void writeFieldName(String name)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method similar to {@link #writeFieldName(String)}, main difference
+     * being that it may perform better as some of processing (such as
+     * quoting of certain characters, or encoding into external encoding
+     * if supported by generator) can be done just once and reused for
+     * later calls.
+     *<p>
+     * Default implementation simple uses unprocessed name container in
+     * serialized String; implementations are strongly encouraged to make
+     * use of more efficient methods argument object has.
+     */
+    public abstract void writeFieldName(SerializableString name)
+        throws IOException, JsonGenerationException;
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, text/String values
+    /**********************************************************
+     */
+
+    /**
+     * Method for outputting a String value. Depending on context
+     * this means either array element, (object) field value or
+     * a stand alone String; but in all cases, String will be
+     * surrounded in double quotes, and contents will be properly
+     * escaped as required by JSON specification.
+     */
+    public abstract void writeString(String text)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for outputting a String value. Depending on context
+     * this means either array element, (object) field value or
+     * a stand alone String; but in all cases, String will be
+     * 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, JsonGenerationException;
+
+    /**
+     * Method similar to {@link #writeString(String)}, but that takes
+     * {@link SerializableString} which can make this potentially
+     * more efficient to call as generator may be able to reuse
+     * quoted and/or encoded representation.
+     *<p>
+     * Default implementation just calls {@link #writeString(String)};
+     * sub-classes should override it with more efficient implementation
+     * if possible.
+     */
+    public abstract void writeString(SerializableString text)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method similar to {@link #writeString(String)} but that takes as
+     * its input a UTF-8 encoded String that is to be output as-is, without additional
+     * escaping (type of which depends on data format; backslashes for JSON).
+     * However, quoting that data format requires (like double-quotes for JSON) will be added
+     * around the value if and as necessary.
+     *<p>
+     * Note that some backends may choose not to support this method: for
+     * example, if underlying destination is a {@link java.io.Writer}
+     * using this method would require UTF-8 decoding.
+     * If so, implementation may instead choose to throw a
+     * {@link UnsupportedOperationException} due to ineffectiveness
+     * of having to decode input.
+     */
+    public abstract void writeRawUTF8String(byte[] text, int offset, int length)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method similar to {@link #writeString(String)} but that takes as its input
+     * a UTF-8 encoded String which has <b>not</b> been escaped using whatever
+     * escaping scheme data format requires (for JSON that is backslash-escaping
+     * for control characters and double-quotes; for other formats something else).
+     * This means that textual JSON backends need to check if value needs
+     * JSON escaping, but otherwise can just be copied as is to output.
+     * Also, quoting that data format requires (like double-quotes for JSON) will be added
+     * around the value if and as necessary.
+     *<p>
+     * Note that some backends may choose not to support this method: for
+     * example, if underlying destination is a {@link java.io.Writer}
+     * using this method would require UTF-8 decoding.
+     * In this case
+     * generator implementation may instead choose to throw a
+     * {@link UnsupportedOperationException} due to ineffectiveness
+     * of having to decode input.
+     */
+    public abstract void writeUTF8String(byte[] text, int offset, int length)
+        throws IOException, JsonGenerationException;
+    
+    /*
+    /**********************************************************
+    /* Public API, write methods, binary/raw content
+    /**********************************************************
+     */
+    
+    /**
+     * Method that will force generator to copy
+     * input text verbatim with <b>no</b> modifications (including
+     * that no escaping is done and no separators are added even
+     * if context [array, object] would otherwise require such).
+     * If such separators are desired, use
+     * {@link #writeRawValue(String)} instead.
+     *<p>
+     * Note that not all generator implementations necessarily support
+     * such by-pass methods: those that do not will throw
+     * {@link UnsupportedOperationException}.
+     */
+    public abstract void writeRaw(String text)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method that will force generator to copy
+     * input text verbatim with <b>no</b> modifications (including
+     * that no escaping is done and no separators are added even
+     * if context [array, object] would otherwise require such).
+     * If such separators are desired, use
+     * {@link #writeRawValue(String)} instead.
+     *<p>
+     * Note that not all generator implementations necessarily support
+     * such by-pass methods: those that do not will throw
+     * {@link UnsupportedOperationException}.
+     */
+    public abstract void writeRaw(String text, int offset, int len)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method that will force generator to copy
+     * input text verbatim with <b>no</b> modifications (including
+     * that no escaping is done and no separators are added even
+     * if context [array, object] would otherwise require such).
+     * If such separators are desired, use
+     * {@link #writeRawValue(String)} instead.
+     *<p>
+     * Note that not all generator implementations necessarily support
+     * such by-pass methods: those that do not will throw
+     * {@link UnsupportedOperationException}.
+     */
+    public abstract void writeRaw(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method that will force generator to copy
+     * input text verbatim with <b>no</b> modifications (including
+     * that no escaping is done and no separators are added even
+     * if context [array, object] would otherwise require such).
+     * If such separators are desired, use
+     * {@link #writeRawValue(String)} instead.
+     *<p>
+     * Note that not all generator implementations necessarily support
+     * such by-pass methods: those that do not will throw
+     * {@link UnsupportedOperationException}.
+     */
+    public abstract void writeRaw(char c)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method that will force generator to copy
+     * input text verbatim with <b>no</b> modifications (including
+     * that no escaping is done and no separators are added even
+     * if context [array, object] would otherwise require such).
+     * If such separators are desired, use
+     * {@link #writeRawValue(String)} instead.
+     *<p>
+     * Note that not all generator implementations necessarily support
+     * such by-pass methods: those that do not will throw
+     * {@link UnsupportedOperationException}.
+     *<p>
+     * The default implementation delegates to {@link #writeRaw(String)};
+     * other backends that support raw inclusion of text are encouraged
+     * to implement it in more efficient manner (especially if they
+     * use UTF-8 encoding).
+     * 
+     * @since 2.1
+     */
+    public void writeRaw(SerializableString raw)
+        throws IOException, JsonGenerationException {
+        writeRaw(raw.getValue());
+    }
+    
+    /**
+     * Method that will force generator to copy
+     * input text verbatim without any modifications, but assuming
+     * it must constitute a single legal JSON value (number, string,
+     * boolean, null, Array or List). Assuming this, proper separators
+     * are added if and as needed (comma or colon), and generator
+     * state updated to reflect this.
+     */
+    public abstract void writeRawValue(String text)
+        throws IOException, JsonGenerationException;
+
+    public abstract void writeRawValue(String text, int offset, int len)
+        throws IOException, JsonGenerationException;
+
+    public abstract void writeRawValue(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method that will output given chunk of binary data as base64
+     * encoded, as a complete String value (surrounded by double quotes).
+     * This method defaults
+     *<p>
+     * Note: because Json Strings can not contain unescaped linefeeds,
+     * if linefeeds are included (as per last argument), they must be
+     * escaped. This adds overhead for decoding without improving
+     * readability.
+     * Alternatively if linefeeds are not included,
+     * resulting String value may violate the requirement of base64
+     * RFC which mandates line-length of 76 characters and use of
+     * linefeeds. However, all {@link JsonParser} implementations
+     * are required to accept such "long line base64"; as do
+     * typical production-level base64 decoders.
+     *
+     * @param b64variant Base64 variant to use: defines details such as
+     *   whether padding is used (and if so, using which character);
+     *   what is the maximum line length before adding linefeed,
+     *   and also the underlying alphabet to use.
+     */
+    public abstract void writeBinary(Base64Variant b64variant,
+            byte[] data, int offset, int len)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Similar to {@link #writeBinary(Base64Variant,byte[],int,int)},
+     * but default to using the Jackson default Base64 variant 
+     * (which is {@link Base64Variants#MIME_NO_LINEFEEDS}).
+     */
+    public void writeBinary(byte[] data, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        writeBinary(Base64Variants.getDefaultVariant(), data, offset, len);
+    }
+
+    /**
+     * Similar to {@link #writeBinary(Base64Variant,byte[],int,int)},
+     * but assumes default to using the Jackson default Base64 variant 
+     * (which is {@link Base64Variants#MIME_NO_LINEFEEDS}). Also
+     * assumes that whole byte array is to be output.
+     */
+    public void writeBinary(byte[] data)
+        throws IOException, JsonGenerationException
+    {
+        writeBinary(Base64Variants.getDefaultVariant(), data, 0, data.length);
+    }
+
+    /**
+     * Similar to {@link #writeBinary(Base64Variant,InputStream,int)},
+     * but assumes default to using the Jackson default Base64 variant 
+     * (which is {@link Base64Variants#MIME_NO_LINEFEEDS}).
+     * 
+     * @param data InputStream to use for reading binary data to write.
+     *    Will not be closed after successful write operation
+     * @param dataLength (optional) number of bytes that will be available;
+     *    or -1 to be indicate it is not known. Note that implementations
+     *    need not support cases where length is not known in advance; this
+     *    depends on underlying data format: JSON output does NOT require length,
+     *    other formats may
+     */
+    public int writeBinary(InputStream data, int dataLength)
+        throws IOException, JsonGenerationException {
+        return writeBinary(Base64Variants.getDefaultVariant(), data, dataLength);
+    }
+    
+    /**
+     * Method similar to {@link #writeBinary(Base64Variant,byte[],int,int)},
+     * but where input is provided through a stream, allowing for incremental
+     * writes without holding the whole input in memory.
+     * 
+     * @param b64variant Base64 variant to use
+     * @param data InputStream to use for reading binary data to write.
+     *    Will not be closed after successful write operation
+     * @param dataLength (optional) number of bytes that will be available;
+     *    or -1 to be indicate it is not known.
+     *    If a positive length is given, <code>data</code> MUST provide at least
+     *    that many bytes: if not, an exception will be thrown.
+     *    Note that implementations
+     *    need not support cases where length is not known in advance; this
+     *    depends on underlying data format: JSON output does NOT require length,
+     *    other formats may.
+     * 
+     * @return Number of bytes read from <code>data</code> and written as binary payload
+     * 
+     * @since 2.1
+     */
+    public abstract int writeBinary(Base64Variant b64variant,
+            InputStream data, int dataLength)
+        throws IOException, JsonGenerationException;
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, other value types
+    /**********************************************************
+     */
+
+    /**
+     * Method for outputting given value as Json number.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     *
+     * @since 2.2
+     */
+    public void writeNumber(short v) throws IOException, JsonGenerationException {
+        writeNumber((int) v);
+    }
+
+    /**
+     * Method for outputting given value as Json number.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeNumber(int v)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for outputting given value as Json number.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeNumber(long v)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for outputting given value as Json number.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeNumber(BigInteger v)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for outputting indicate Json numeric value.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeNumber(double d)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for outputting indicate Json numeric value.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeNumber(float f)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for outputting indicate Json numeric value.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeNumber(BigDecimal dec)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Write method that can be used for custom numeric types that can
+     * not be (easily?) converted to "standard" Java number types.
+     * Because numbers are not surrounded by double quotes, regular
+     * {@link #writeString} method can not be used; nor
+     * {@link #writeRaw} because that does not properly handle
+     * value separators needed in Array or Object contexts.
+     *<p>
+     * Note: because of lack of type safety, some generator
+     * implementations may not be able to implement this
+     * method. For example, if a binary json format is used,
+     * it may require type information for encoding; similarly
+     * for generator-wrappers around Java objects or Json nodes.
+     * If implementation does not implement this method,
+     * it needs to throw {@link UnsupportedOperationException}.
+     */
+    public abstract void writeNumber(String encodedValue)
+        throws IOException, JsonGenerationException,
+               UnsupportedOperationException;
+
+    /**
+     * Method for outputting literal Json boolean value (one of
+     * Strings 'true' and 'false').
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeBoolean(boolean state)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method for outputting literal Json null value.
+     * Can be called in any context where a value is expected
+     * (Array value, Object field value, root-level value).
+     * Additional white space may be added around the value
+     * if pretty-printing is enabled.
+     */
+    public abstract void writeNull()
+        throws IOException, JsonGenerationException;
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, serializing Java objects
+    /**********************************************************
+     */
+
+    /**
+     * Method for writing given Java object (POJO) as Json.
+     * Exactly how the object gets written depends on object
+     * in question (ad on codec, its configuration); for most
+     * beans it will result in Json object, but for others Json
+     * array, or String or numeric value (and for nulls, Json
+     * null literal.
+     * <b>NOTE</b>: generator must have its <b>object codec</b>
+     * set to non-null value; for generators created by a mapping
+     * factory this is the case, for others not.
+     */
+    public abstract void writeObject(Object pojo)
+        throws IOException, JsonProcessingException;
+
+    /**
+     * Method for writing given JSON tree (expressed as a tree
+     * where given JsonNode is the root) using this generator.
+     * This will generally just call
+     * {@link #writeObject} with given node, but is added
+     * for convenience and to make code more explicit in cases
+     * where it deals specifically with trees.
+     */
+    public abstract void writeTree(TreeNode rootNode)
+        throws IOException, JsonProcessingException;
+
+    /*
+    /**********************************************************
+    /* Public API, convenience field write methods
+    /**********************************************************
+     */
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has a String value. Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeString(value);
+     *</pre>
+     *<p>
+     * Note: many performance-sensitive implementations override this method
+     */
+    public void writeStringField(String fieldName, String value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeString(value);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has a boolean value. Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeBoolean(value);
+     *</pre>
+     */
+    public final void writeBooleanField(String fieldName, boolean value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeBoolean(value);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has JSON literal value null. Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeNull();
+     *</pre>
+     */
+    public final void writeNullField(String fieldName)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeNull();
+    }
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has the specified numeric value. Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeNumber(value);
+     *</pre>
+     */
+    public final void writeNumberField(String fieldName, int value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeNumber(value);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has the specified numeric value. Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeNumber(value);
+     *</pre>
+     */
+    public final void writeNumberField(String fieldName, long value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeNumber(value);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has the specified numeric value. Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeNumber(value);
+     *</pre>
+     */
+    public final void writeNumberField(String fieldName, double value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeNumber(value);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has the specified numeric value. Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeNumber(value);
+     *</pre>
+     */
+    public final void writeNumberField(String fieldName, float value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeNumber(value);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has the specified numeric value.
+     * Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeNumber(value);
+     *</pre>
+     */
+    public final void writeNumberField(String fieldName, BigDecimal value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeNumber(value);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that contains specified data in base64-encoded form.
+     * Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeBinary(value);
+     *</pre>
+     */
+    public final void writeBinaryField(String fieldName, byte[] data)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeBinary(data);
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * (that will contain a JSON Array value), and the START_ARRAY marker.
+     * Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeStartArray();
+     *</pre>
+     *<p>
+     * Note: caller still has to take care to close the array
+     * (by calling {#link #writeEndArray}) after writing all values
+     * of the value Array.
+     */
+    public final void writeArrayFieldStart(String fieldName)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeStartArray();
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * (that will contain a JSON Object value), and the START_OBJECT marker.
+     * Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeStartObject();
+     *</pre>
+     *<p>
+     * Note: caller still has to take care to close the Object
+     * (by calling {#link #writeEndObject}) after writing all
+     * entries of the value Object.
+     */
+    public final void writeObjectFieldStart(String fieldName)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeStartObject();
+    }
+
+    /**
+     * Convenience method for outputting a field entry ("member")
+     * that has contents of specific Java object as its value.
+     * Equivalent to:
+     *<pre>
+     *  writeFieldName(fieldName);
+     *  writeObject(pojo);
+     *</pre>
+     */
+    public final void writeObjectField(String fieldName, Object pojo)
+        throws IOException, JsonProcessingException
+    {
+        writeFieldName(fieldName);
+        writeObject(pojo);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, copy-through methods
+    /**********************************************************
+     */
+
+    /**
+     * Method for copying contents of the current event that
+     * the given parser instance points to.
+     * Note that the method <b>will not</b> copy any other events,
+     * such as events contained within Json Array or Object structures.
+     *<p>
+     * Calling this method will not advance the given
+     * parser, although it may cause parser to internally process
+     * more data (if it lazy loads contents of value events, for example)
+     */
+    public abstract void copyCurrentEvent(JsonParser jp)
+        throws IOException, JsonProcessingException;
+
+    /**
+     * Method for copying contents of the current event
+     * <b>and following events that it encloses</b>
+     * the given parser instance points to.
+     *<p>
+     * So what constitutes enclosing? Here is the list of
+     * events that have associated enclosed events that will
+     * get copied:
+     *<ul>
+     * <li>{@link JsonToken#START_OBJECT}:
+     *   all events up to and including matching (closing)
+     *   {@link JsonToken#END_OBJECT} will be copied
+     *  </li>
+     * <li>{@link JsonToken#START_ARRAY}
+     *   all events up to and including matching (closing)
+     *   {@link JsonToken#END_ARRAY} will be copied
+     *  </li>
+     * <li>{@link JsonToken#FIELD_NAME} the logical value (which
+     *   can consist of a single scalar value; or a sequence of related
+     *   events for structured types (Json Arrays, Objects)) will
+     *   be copied along with the name itself. So essentially the
+     *   whole <b>field entry</b> (name and value) will be copied.
+     *  </li>
+     *</ul>
+     *<p>
+     * After calling this method, parser will point to the
+     * <b>last event</b> that was copied. This will either be
+     * the event parser already pointed to (if there were no
+     * enclosed events), or the last enclosed event copied.
+     */
+    public abstract void copyCurrentStructure(JsonParser jp)
+        throws IOException, JsonProcessingException;
+
+    /*
+    /**********************************************************
+    /* Public API, context access
+    /**********************************************************
+     */
+
+    /**
+     * @return Context object that can give information about logical
+     *   position within generated json content.
+     */
+    public abstract JsonStreamContext getOutputContext();
+
+    /*
+    /**********************************************************
+    /* Public API, buffer handling
+    /**********************************************************
+     */
+
+    /**
+     * Method called to flush any buffered content to the underlying
+     * target (output stream, writer), and to flush the target itself
+     * as well.
+     */
+    @Override
+    public abstract void flush() throws IOException;
+
+    /**
+     * Method that can be called to determine whether this generator
+     * is closed or not. If it is closed, no more output can be done.
+     */
+    public abstract boolean isClosed();
+
+    /*
+    /**********************************************************
+    /* Closeable implementation
+    /**********************************************************
+     */
+
+    /**
+     * Method called to close this generator, so that no more content
+     * can be written.
+     *<p>
+     * Whether the underlying target (stream, writer) gets closed depends
+     * on whether this generator either manages the target (i.e. is the
+     * only one with access to the target -- case if caller passes a
+     * reference to the resource such as File, but not stream); or
+     * has feature {@link Feature#AUTO_CLOSE_TARGET} enabled.
+     * If either of above is true, the target is also closed. Otherwise
+     * (not managing, feature not enabled), target is not closed.
+     */
+    @Override
+    public abstract void close() throws IOException;
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonLocation.java b/src/main/java/com/fasterxml/jackson/core/JsonLocation.java
new file mode 100644
index 0000000..40e3f96
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonLocation.java
@@ -0,0 +1,139 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Object that encapsulates Location information used for reporting
+ * parsing (or potentially generation) errors, as well as current location
+ * within input streams.
+ */
+public class JsonLocation
+    implements java.io.Serializable // as per [JACKSON-168]
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Shared immutable "N/A location" that can be returned to indicate
+     * that no location information is available
+     */
+    public final static JsonLocation NA = new JsonLocation("N/A", -1L, -1L, -1, -1);
+
+    final long _totalBytes;
+    final long _totalChars;
+
+    final int _lineNr;
+    final int _columnNr;
+
+    /**
+     * Displayable description for input source: file path, URL.
+     *<p>
+     * NOTE: <code>transient</code> since 2.2 so that Location itself is Serializable.
+     */
+    final transient Object _sourceRef;
+
+    public JsonLocation(Object srcRef, long totalChars, int lineNr, int colNr)
+    {
+        /* Unfortunately, none of legal encodings are straight single-byte
+         * encodings. Could determine offset for UTF-16/UTF-32, but the
+         * most important one is UTF-8...
+         * so for now, we'll just not report any real byte count
+         */
+        this(srcRef, -1L, totalChars, lineNr, colNr);
+    }
+
+    public JsonLocation(Object sourceRef, long totalBytes, long totalChars,
+            int lineNr, int columnNr)
+    {
+        _sourceRef = sourceRef;
+        _totalBytes = totalBytes;
+        _totalChars = totalChars;
+        _lineNr = lineNr;
+        _columnNr = columnNr;
+    }
+
+    /**
+     * Reference to the original resource being read, if one available.
+     * For example, when a parser has been constructed by passing
+     * a {@link java.io.File} instance, this method would return
+     * that File. Will return null if no such reference is available,
+     * for example when {@link java.io.InputStream} was used to
+     * construct the parser instance.
+     */
+    public Object getSourceRef() { return _sourceRef; }
+
+    /**
+     * @return Line number of the location (1-based)
+     */
+    public int getLineNr() { return _lineNr; }
+
+    /**
+     * @return Column number of the location (1-based)
+     */
+    public int getColumnNr() { return _columnNr; }
+
+    /**
+     * @return Character offset within underlying stream, reader or writer,
+     *   if available; -1 if not.
+     */
+    public long getCharOffset() { return _totalChars; }
+
+    /**
+     * @return Byte offset within underlying stream, reader or writer,
+     *   if available; -1 if not.
+     */
+    public long getByteOffset()
+    {
+        return _totalBytes;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder(80);
+        sb.append("[Source: ");
+        if (_sourceRef == null) {
+            sb.append("UNKNOWN");
+        } else {
+            sb.append(_sourceRef.toString());
+        }
+        sb.append("; line: ");
+        sb.append(_lineNr);
+        sb.append(", column: ");
+        sb.append(_columnNr);
+        sb.append(']');
+        return sb.toString();
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int hash = (_sourceRef == null) ? 1 : _sourceRef.hashCode();
+        hash ^= _lineNr;
+        hash += _columnNr;
+        hash ^= (int) _totalChars;
+        hash += (int) _totalBytes;
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object other)
+    {
+        if (other == this) return true;
+        if (other == null) return false;
+        if (!(other instanceof JsonLocation)) return false;
+        JsonLocation otherLoc = (JsonLocation) other;
+
+        if (_sourceRef == null) {
+            if (otherLoc._sourceRef != null) return false;
+        } else if (!_sourceRef.equals(otherLoc._sourceRef)) return false;
+
+        return (_lineNr == otherLoc._lineNr)
+            && (_columnNr == otherLoc._columnNr)
+            && (_totalChars == otherLoc._totalChars)
+            && (getByteOffset() == otherLoc.getByteOffset())
+            ;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonParseException.java b/src/main/java/com/fasterxml/jackson/core/JsonParseException.java
new file mode 100644
index 0000000..1c019e8
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonParseException.java
@@ -0,0 +1,27 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Exception type for parsing problems, used when non-well-formed content
+ * (content that does not conform to JSON syntax as per specification)
+ * is encountered.
+ */
+public class JsonParseException
+    extends JsonProcessingException
+{
+    final static long serialVersionUID = 123; // Stupid eclipse...
+
+    public JsonParseException(String msg, JsonLocation loc)
+    {
+        super(msg, loc);
+    }
+
+    public JsonParseException(String msg, JsonLocation loc, Throwable root)
+    {
+        super(msg, loc, root);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonParser.java b/src/main/java/com/fasterxml/jackson/core/JsonParser.java
new file mode 100644
index 0000000..31f5b58
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonParser.java
@@ -0,0 +1,1381 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Iterator;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+/**
+ * Base class that defines public API for reading JSON content.
+ * Instances are created using factory methods of
+ * a {@link JsonFactory} instance.
+ *
+ * @author Tatu Saloranta
+ */
+public abstract class JsonParser
+    implements Closeable, Versioned
+{
+    private final static int MIN_BYTE_I = (int) Byte.MIN_VALUE;
+    // as per [JACKSON-804], allow range up to and including 255
+    private final static int MAX_BYTE_I = (int) 255;
+
+    private final static int MIN_SHORT_I = (int) Short.MIN_VALUE;
+    private final static int MAX_SHORT_I = (int) Short.MAX_VALUE;
+
+    /**
+     * Enumeration of possible "native" (optimal) types that can be
+     * used for numbers.
+     */
+    public enum NumberType {
+        INT, LONG, BIG_INTEGER, FLOAT, DOUBLE, BIG_DECIMAL
+    };
+
+    /**
+     * Enumeration that defines all on/off features for parsers.
+     */
+    public enum Feature {
+        
+        // // // Low-level I/O handling features:
+        
+        /**
+         * Feature that determines whether parser will automatically
+         * close underlying input source that is NOT owned by the
+         * parser. If disabled, calling application has to separately
+         * close the underlying {@link InputStream} and {@link Reader}
+         * instances used to create the parser. If enabled, parser
+         * will handle closing, as long as parser itself gets closed:
+         * this happens when end-of-input is encountered, or parser
+         * is closed by a call to {@link JsonParser#close}.
+         *<p>
+         * Feature is enabled by default.
+         */
+        AUTO_CLOSE_SOURCE(true),
+            
+        // // // Support for non-standard data format constructs
+
+        /**
+         * Feature that determines whether parser will allow use
+         * of Java/C++ style comments (both '/'+'*' and
+         * '//' varieties) within parsed content or not.
+         *<p>
+         * Since JSON specification does not mention comments as legal
+         * construct,
+         * this is a non-standard feature; however, in the wild
+         * this is extensively used. As such, feature is
+         * <b>disabled by default</b> for parsers and must be
+         * explicitly enabled.
+         */
+        ALLOW_COMMENTS(false),
+
+        /**
+         * Feature that determines whether parser will allow use
+         * of unquoted field names (which is allowed by Javascript,
+         * but not by JSON specification).
+         *<p>
+         * Since JSON specification requires use of double quotes for
+         * field names,
+         * this is a non-standard feature, and as such disabled by default.
+         */
+        ALLOW_UNQUOTED_FIELD_NAMES(false),
+
+        /**
+         * Feature that determines whether parser will allow use
+         * of single quotes (apostrophe, character '\'') for
+         * quoting Strings (names and String values). If so,
+         * this is in addition to other acceptabl markers.
+         * but not by JSON specification).
+         *<p>
+         * Since JSON specification requires use of double quotes for
+         * field names,
+         * this is a non-standard feature, and as such disabled by default.
+         */
+        ALLOW_SINGLE_QUOTES(false),
+
+        /**
+         * Feature that determines whether parser will allow
+         * JSON Strings to contain unquoted control characters
+         * (ASCII characters with value less than 32, including
+         * tab and line feed characters) or not.
+         * If feature is set false, an exception is thrown if such a
+         * character is encountered.
+         *<p>
+         * Since JSON specification requires quoting for all control characters,
+         * this is a non-standard feature, and as such disabled by default.
+         */
+        ALLOW_UNQUOTED_CONTROL_CHARS(false),
+
+        /**
+         * Feature that can be enabled to accept quoting of all character
+         * using backslash qooting mechanism: if not enabled, only characters
+         * that are explicitly listed by JSON specification can be thus
+         * escaped (see JSON spec for small list of these characters)
+         *<p>
+         * Since JSON specification requires quoting for all control characters,
+         * this is a non-standard feature, and as such disabled by default.
+         */
+        ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER(false),
+
+        /**
+         * Feature that determines whether parser will allow
+         * JSON integral numbers to start with additional (ignorable) 
+         * zeroes (like: 000001). If enabled, no exception is thrown, and extra
+         * nulls are silently ignored (and not included in textual representation
+         * exposed via {@link JsonParser#getText}).
+         *<p>
+         * Since JSON specification does not allow leading zeroes,
+         * this is a non-standard feature, and as such disabled by default.
+         */
+        ALLOW_NUMERIC_LEADING_ZEROS(false),
+        
+        /**
+         * Feature that allows parser to recognize set of
+         * "Not-a-Number" (NaN) tokens as legal floating number
+         * values (similar to how many other data formats and
+         * programming language source code allows it).
+         * Specific subset contains values that
+         * <a href="http://www.w3.org/TR/xmlschema-2/">XML Schema</a>
+         * (see section 3.2.4.1, Lexical Representation)
+         * allows (tokens are quoted contents, not including quotes):
+         *<ul>
+         *  <li>"INF" (for positive infinity), as well as alias of "Infinity"
+         *  <li>"-INF" (for negative infinity), alias "-Infinity"
+         *  <li>"NaN" (for other not-a-numbers, like result of division by zero)
+         *</ul>
+         *<p>
+         * Since JSON specification does not allow use of such values,
+         * this is a non-standard feature, and as such disabled by default.
+         */
+         ALLOW_NON_NUMERIC_NUMBERS(false),
+        
+            ;
+
+        /**
+         * Whether feature is enabled or disabled by default.
+         */
+        private final boolean _defaultState;
+        
+        /**
+         * Method that calculates bit set (flags) of all features that
+         * are enabled by default.
+         */
+        public static int collectDefaults()
+        {
+            int flags = 0;
+            for (Feature f : values()) {
+                if (f.enabledByDefault()) {
+                    flags |= f.getMask();
+                }
+            }
+            return flags;
+        }
+        
+        private Feature(boolean defaultState) {
+            _defaultState = defaultState;
+        }
+        
+        public boolean enabledByDefault() { return _defaultState; }
+//        public boolean enabledIn(int flags) { return (flags & getMask()) != 0; }
+        public int getMask() { return (1 << ordinal()); }
+    }
+
+    /*
+    /**********************************************************
+    /* Minimal configuration state
+    /**********************************************************
+     */
+
+    /**
+     * Bit flag composed of bits that indicate which
+     * {@link com.fasterxml.jackson.core.JsonParser.Feature}s
+     * are enabled.
+     */
+    protected int _features;
+
+    /*
+    /**********************************************************
+    /* Construction, configuration, initialization
+    /**********************************************************
+     */
+
+    protected JsonParser() { }
+    protected JsonParser(int features) {
+        _features = features;
+    }
+
+    /**
+     * Accessor for {@link ObjectCodec} associated with this
+     * parser, if any. Codec is used by {@link #readValueAs(Class)}
+     * method (and its variants).
+     */
+    public abstract ObjectCodec getCodec();
+
+    /**
+     * Setter that allows defining {@link ObjectCodec} associated with this
+     * parser, if any. Codec is used by {@link #readValueAs(Class)}
+     * method (and its variants).
+     */
+    public abstract void setCodec(ObjectCodec c);
+
+    /**
+     * Method that can be used to get access to object that is used
+     * to access input being parsed; this is usually either
+     * {@link InputStream} or {@link Reader}, depending on what
+     * parser was constructed with.
+     * Note that returned value may be null in some cases; including
+     * case where parser implementation does not want to exposed raw
+     * source to caller.
+     * In cases where input has been decorated, object returned here
+     * is the decorated version; this allows some level of interaction
+     * between users of parser 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 getInputSource() {
+        return null;
+    }
+
+    /*
+    /**********************************************************
+    /* Format support
+    /**********************************************************
+     */
+    
+    /**
+     * Method to call to make this parser use specified schema. Method must
+     * be called before trying to parse any content, right after parser instance
+     * has been created.
+     * Note that not all parsers support schemas; and those that do usually only
+     * accept specific types of schemas: ones defined for data format parser can read.
+     *<p>
+     * If parser does not support specified schema, {@link UnsupportedOperationException}
+     * is thrown.
+     * 
+     * @param schema Schema to use
+     * 
+     * @throws UnsupportedOperationException if parser does not support schema
+     */
+    public void setSchema(FormatSchema schema)
+    {
+        throw new UnsupportedOperationException("Parser of type "+getClass().getName()+" does not support schema of type '"
+                +schema.getSchemaType()+"'");
+    }
+
+    /**
+     * Method for accessing Schema that this parser uses, if any.
+     * Default implementation returns null.
+     *
+     * @since 2.1
+     */
+    public FormatSchema getSchema() {
+        return null;
+    }
+    
+    /**
+     * Method that can be used to verify that given schema can be used with
+     * this parser (using {@link #setSchema}).
+     * 
+     * @param schema Schema to check
+     * 
+     * @return True if this parser can use given schema; false if not
+     */
+    public boolean canUseSchema(FormatSchema schema) {
+        return false;
+    }
+
+    /**
+     * Method that can be called to determine if a custom
+     * {@link ObjectCodec} is needed for binding data parsed
+     * using {@link JsonParser} constructed by this factory
+     * (which typically also implies the same for serialization
+     * with {@link JsonGenerator}).
+     * 
+     * @return True if custom codec is needed with parsers and
+     *   generators created by this factory; false if a general
+     *   {@link ObjectCodec} is enough
+     * 
+     * @since 2.1
+     */
+    public boolean requiresCustomCodec() {
+        return false;
+    }
+
+    /*
+    /**********************************************************
+    /* Versioned
+    /**********************************************************
+     */
+    
+    /**
+     * Accessor for getting version of the core package, given a parser instance.
+     * Left for sub-classes to implement.
+     */
+    @Override
+    public abstract Version version();
+    
+    /*
+    /**********************************************************
+    /* Closeable implementation
+    /**********************************************************
+     */
+
+    /**
+     * Closes the parser so that no further iteration or data access
+     * can be made; will also close the underlying input source
+     * if parser either <b>owns</b> the input source, or feature
+     * {@link Feature#AUTO_CLOSE_SOURCE} is enabled.
+     * Whether parser owns the input source depends on factory
+     * method that was used to construct instance (so check
+     * {@link com.fasterxml.jackson.core.JsonFactory} for details,
+     * but the general
+     * idea is that if caller passes in closable resource (such
+     * as {@link InputStream} or {@link Reader}) parser does NOT
+     * own the source; but if it passes a reference (such as
+     * {@link java.io.File} or {@link java.net.URL} and creates
+     * stream or reader it does own them.
+     */
+    @Override
+    public abstract void close() throws IOException;
+
+    /*
+    /**********************************************************
+    /* Buffer handling
+    /**********************************************************
+     */
+
+    /**
+     * Method that can be called to push back any content that
+     * has been read but not consumed by the parser. This is usually
+     * done after reading all content of interest using parser.
+     * Content is released by writing it to given stream if possible;
+     * if underlying input is byte-based it can released, if not (char-based)
+     * it can not.
+     * 
+     * @return -1 if the underlying content source is not byte based
+     *    (that is, input can not be sent to {@link OutputStream};
+     *    otherwise number of bytes released (0 if there was nothing to release)
+     *    
+     * @throws IOException if write to stream threw exception
+     */    
+    public int releaseBuffered(OutputStream out) throws IOException
+    {
+        return -1;
+    }
+
+    /**
+     * Method that can be called to push back any content that
+     * has been read but not consumed by the parser.
+     * This is usually
+     * done after reading all content of interest using parser.
+     * Content is released by writing it to given writer if possible;
+     * if underlying input is char-based it can released, if not (byte-based)
+     * it can not.
+     * 
+     * @return -1 if the underlying content source is not char-based
+     *    (that is, input can not be sent to {@link Writer};
+     *    otherwise number of chars released (0 if there was nothing to release)
+     *    
+     * @throws IOException if write using Writer threw exception
+     */    
+    public int releaseBuffered(Writer w) throws IOException
+    {
+        return -1;
+    }
+    
+    /*
+    /***************************************************
+    /* Public API, configuration
+    /***************************************************
+     */
+
+    /**
+     * Method for enabling specified parser feature
+     * (check {@link Feature} for list of features)
+     */
+    public JsonParser enable(Feature f)
+    {
+        _features |= f.getMask();
+        return this;
+    }
+
+    /**
+     * Method for disabling specified  feature
+     * (check {@link Feature} for list of features)
+     */
+    public JsonParser disable(Feature f)
+    {
+        _features &= ~f.getMask();
+        return this;
+    }
+
+    /**
+     * Method for enabling or disabling specified feature
+     * (check {@link Feature} for list of features)
+     */
+    public JsonParser configure(Feature f, boolean state)
+    {
+        if (state) {
+            enable(f);
+        } else {
+            disable(f);
+        }
+        return this;
+    }
+    
+    /**
+     * Method for checking whether specified {@link Feature} is enabled.
+     */
+    public boolean isEnabled(Feature f) {
+        return (_features & f.getMask()) != 0;
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, traversal
+    /**********************************************************
+     */
+
+    /**
+     * Main iteration method, which will advance stream enough
+     * to determine type of the next token, if any. If none
+     * remaining (stream has no content other than possible
+     * white space before ending), null will be returned.
+     *
+     * @return Next token from the stream, if any found, or null
+     *   to indicate end-of-input
+     */
+    public abstract JsonToken nextToken()
+        throws IOException, JsonParseException;
+
+    /**
+     * Iteration method that will advance stream enough
+     * to determine type of the next token that is a value type
+     * (including JSON Array and Object start/end markers).
+     * Or put another way, nextToken() will be called once,
+     * and if {@link JsonToken#FIELD_NAME} is returned, another
+     * time to get the value for the field.
+     * Method is most useful for iterating over value entries
+     * of JSON objects; field name will still be available
+     * by calling {@link #getCurrentName} when parser points to
+     * the value.
+     *
+     * @return Next non-field-name token from the stream, if any found,
+     *   or null to indicate end-of-input (or, for non-blocking
+     *   parsers, {@link JsonToken#NOT_AVAILABLE} if no tokens were
+     *   available yet)
+     */
+    public abstract JsonToken nextValue()
+        throws IOException, JsonParseException;
+
+    /**
+     * Method that fetches next token (as if calling {@link #nextToken}) and
+     * verifies whether it is {@link JsonToken#FIELD_NAME} with specified name
+     * and returns result of that comparison.
+     * It is functionally equivalent to:
+     *<pre>
+     *  return (nextToken() == JsonToken.FIELD_NAME) && str.getValue().equals(getCurrentName());
+     *</pre>
+     * but may be faster for parser to verify, and can therefore be used if caller
+     * expects to get such a property name from input next.
+     * 
+     * @param str Property name to compare next token to (if next token is <code>JsonToken.FIELD_NAME<code>)
+     */
+    public boolean nextFieldName(SerializableString str)
+        throws IOException, JsonParseException
+    {
+        return (nextToken() == JsonToken.FIELD_NAME) && str.getValue().equals(getCurrentName());
+    }
+
+    /**
+     * Method that fetches next token (as if calling {@link #nextToken}) and
+     * if it is {@link JsonToken#VALUE_STRING} returns contained String value;
+     * otherwise returns null.
+     * It is functionally equivalent to:
+     *<pre>
+     *  return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
+     *</pre>
+     * but may be faster for parser to process, and can therefore be used if caller
+     * expects to get a String value next from input.
+     */
+    public String nextTextValue()
+        throws IOException, JsonParseException
+    {
+        return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
+    }
+
+    /**
+     * Method that fetches next token (as if calling {@link #nextToken}) and
+     * if it is {@link JsonToken#VALUE_NUMBER_INT} returns 32-bit int value;
+     * otherwise returns specified default value
+     * It is functionally equivalent to:
+     *<pre>
+     *  return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
+     *</pre>
+     * but may be faster for parser to process, and can therefore be used if caller
+     * expects to get a String value next from input.
+     */
+    public int nextIntValue(int defaultValue)
+        throws IOException, JsonParseException
+    {
+        return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
+    }
+
+    /**
+     * Method that fetches next token (as if calling {@link #nextToken}) and
+     * if it is {@link JsonToken#VALUE_NUMBER_INT} returns 64-bit long value;
+     * otherwise returns specified default value
+     * It is functionally equivalent to:
+     *<pre>
+     *  return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getLongValue() : defaultValue;
+     *</pre>
+     * but may be faster for parser to process, and can therefore be used if caller
+     * expects to get a String value next from input.
+     */
+    public long nextLongValue(long defaultValue)
+        throws IOException, JsonParseException
+    {
+        return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getLongValue() : defaultValue;
+    }
+
+    /**
+     * Method that fetches next token (as if calling {@link #nextToken}) and
+     * if it is {@link JsonToken#VALUE_TRUE} or {@link JsonToken#VALUE_FALSE}
+     * returns matching Boolean value; otherwise return null.
+     * It is functionally equivalent to:
+     *<pre>
+     *  JsonToken t = nextToken();
+     *  if (t == JsonToken.VALUE_TRUE) return Boolean.TRUE;
+     *  if (t == JsonToken.VALUE_FALSE) return Boolean.FALSE;
+     *  return null;
+     *</pre>
+     * but may be faster for parser to process, and can therefore be used if caller
+     * expects to get a String value next from input.
+     */
+    public Boolean nextBooleanValue()
+        throws IOException, JsonParseException
+    {
+        switch (nextToken()) {
+        case VALUE_TRUE:
+            return Boolean.TRUE;
+        case VALUE_FALSE:
+            return Boolean.FALSE;
+        default:
+            return null;
+        }
+    }
+    
+    /**
+     * Method that will skip all child tokens of an array or
+     * object token that the parser currently points to,
+     * iff stream points to 
+     * {@link JsonToken#START_OBJECT} or {@link JsonToken#START_ARRAY}.
+     * If not, it will do nothing.
+     * After skipping, stream will point to <b>matching</b>
+     * {@link JsonToken#END_OBJECT} or {@link JsonToken#END_ARRAY}
+     * (possibly skipping nested pairs of START/END OBJECT/ARRAY tokens
+     * as well as value tokens).
+     * The idea is that after calling this method, application
+     * will call {@link #nextToken} to point to the next
+     * available token, if any.
+     */
+    public abstract JsonParser skipChildren()
+        throws IOException, JsonParseException;
+    
+    /**
+     * Method that can be called to determine whether this parser
+     * is closed or not. If it is closed, no new tokens can be
+     * retrieved by calling {@link #nextToken} (and the underlying
+     * stream may be closed). Closing may be due to an explicit
+     * call to {@link #close} or because parser has encountered
+     * end of input.
+     */
+    public abstract boolean isClosed();
+    
+    /*
+    /**********************************************************
+    /* Public API, token accessors
+    /**********************************************************
+     */
+
+    /**
+     * Accessor to find which token parser currently points to, if any;
+     * null will be returned if none.
+     * If return value is non-null, data associated with the token
+     * is available via other accessor methods.
+     *
+     * @return Type of the token this parser currently points to,
+     *   if any: null before any tokens have been read, and
+     *   after end-of-input has been encountered, as well as
+     *   if the current token has been explicitly cleared.
+     */
+    public abstract JsonToken getCurrentToken();
+
+    /**
+     * Method for checking whether parser currently points to
+     * a token (and data for that token is available).
+     * Equivalent to check for <code>parser.getCurrentToken() != null</code>.
+     *
+     * @return True if the parser just returned a valid
+     *   token via {@link #nextToken}; false otherwise (parser
+     *   was just constructed, encountered end-of-input
+     *   and returned null from {@link #nextToken}, or the token
+     *   has been consumed)
+     */
+    public abstract boolean hasCurrentToken();
+
+    /**
+     * 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;
+     * for field values it will be preceding field name;
+     * and for others (array values, root-level values) null.
+     */
+    public abstract String getCurrentName()
+        throws IOException, JsonParseException;
+
+    /**
+     * Method that can be used to access current parsing context reader
+     * is in. There are 3 different types: root, array and object contexts,
+     * with slightly different available information. Contexts are
+     * hierarchically nested, and can be used for example for figuring
+     * out part of the input document that correspond to specific
+     * array or object (for highlighting purposes, or error reporting).
+     * Contexts can also be used for simple xpath-like matching of
+     * input, if so desired.
+     */
+    public abstract JsonStreamContext getParsingContext();
+
+    /**
+     * Method that return the <b>starting</b> location of the current
+     * token; that is, position of the first character from input
+     * that starts the current token.
+     */
+    public abstract JsonLocation getTokenLocation();
+
+    /**
+     * Method that returns location of the last processed character;
+     * usually for error reporting purposes.
+     */
+    public abstract JsonLocation getCurrentLocation();
+
+    /**
+     * Specialized accessor that can be used to verify that the current
+     * token indicates start array (usually meaning that current token
+     * is {@link JsonToken#START_ARRAY}) when start array is expected.
+     * For some specialized parsers this can return true for other cases
+     * as well; this is usually done to emulate arrays.
+     *<p>
+     * Default implementation is equivalent to:
+     *<pre>
+     *   getCurrentToken() == JsonToken.START_ARRAY
+     *</pre>
+     * but may be overridden by custom parser implementations.
+     *
+     * @return True if the current token can be considered as a
+     *   start-array marker (such {@link JsonToken#START_ARRAY});
+     *   false if not.
+     */
+    public boolean isExpectedStartArrayToken() {
+        return getCurrentToken() == JsonToken.START_ARRAY;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, token state overrides
+    /**********************************************************
+     */
+
+    /**
+     * Method called to "consume" the current token by effectively
+     * removing it so that {@link #hasCurrentToken} returns false, and
+     * {@link #getCurrentToken} null).
+     * Cleared token value can still be accessed by calling
+     * {@link #getLastClearedToken} (if absolutely needed), but
+     * usually isn't.
+     *<p>
+     * Method was added to be used by the optional data binder, since
+     * it has to be able to consume last token used for binding (so that
+     * it will not be used again).
+     */
+    public abstract void clearCurrentToken();
+
+    /**
+     * Method that can be called to get the last token that was
+     * cleared using {@link #clearCurrentToken}. This is not necessarily
+     * the latest token read.
+     * Will return null if no tokens have been cleared,
+     * or if parser has been closed.
+     */
+    public abstract JsonToken getLastClearedToken();
+    
+    /**
+     * Method that can be used to change what is considered to be
+     * the current (field) name.
+     * May be needed to support non-JSON data formats or unusual binding
+     * conventions; not needed for typical processing.
+     *<p>
+     * Note that use of this method should only be done as sort of last
+     * resort, as it is a work-around for regular operation.
+     * 
+     * @param name Name to use as the current name; may be null.
+     * 
+     * @since 2.0
+     */
+    public abstract void overrideCurrentName(String name);
+    
+    /*
+    /**********************************************************
+    /* Public API, access to token information, text
+    /**********************************************************
+     */
+
+    /**
+     * Method for accessing textual representation of the current token;
+     * if no current token (before first call to {@link #nextToken}, or
+     * after encountering end-of-input), returns null.
+     * Method can be called for any token type.
+     */
+    public abstract String getText()
+        throws IOException, JsonParseException;
+
+    /**
+     * Method similar to {@link #getText}, but that will return
+     * underlying (unmodifiable) character array that contains
+     * textual value, instead of constructing a String object
+     * to contain this information.
+     * Note, however, that:
+     *<ul>
+     * <li>Textual contents are not guaranteed to start at
+     *   index 0 (rather, call {@link #getTextOffset}) to
+     *   know the actual offset
+     *  </li>
+     * <li>Length of textual contents may be less than the
+     *  length of returned buffer: call {@link #getTextLength}
+     *  for actual length of returned content.
+     *  </li>
+     * </ul>
+     *<p>
+     * Note that caller <b>MUST NOT</b> modify the returned
+     * character array in any way -- doing so may corrupt
+     * current parser state and render parser instance useless.
+     *<p>
+     * The only reason to call this method (over {@link #getText})
+     * is to avoid construction of a String object (which
+     * will make a copy of contents).
+     */
+    public abstract char[] getTextCharacters()
+        throws IOException, JsonParseException;
+
+    /**
+     * Accessor used with {@link #getTextCharacters}, to know length
+     * of String stored in returned buffer.
+     *
+     * @return Number of characters within buffer returned
+     *   by {@link #getTextCharacters} that are part of
+     *   textual content of the current token.
+     */
+    public abstract int getTextLength()
+        throws IOException, JsonParseException;
+
+    /**
+     * Accessor used with {@link #getTextCharacters}, to know offset
+     * of the first text content character within buffer.
+     *
+     * @return Offset of the first character within buffer returned
+     *   by {@link #getTextCharacters} that is part of
+     *   textual content of the current token.
+     */
+    public abstract int getTextOffset()
+        throws IOException, JsonParseException;
+
+    /**
+     * Method that can be used to determine whether calling of
+     * {@link #getTextCharacters} would be the most efficient
+     * way to access textual content for the event parser currently
+     * points to.
+     *<p> 
+     * Default implementation simply returns false since only actual
+     * implementation class has knowledge of its internal buffering
+     * state.
+     * Implementations are strongly encouraged to properly override
+     * this method, to allow efficient copying of content by other
+     * code.
+     * 
+     * @return True if parser currently has character array that can
+     *   be efficiently returned via {@link #getTextCharacters}; false
+     *   means that it may or may not exist
+     */
+    public abstract boolean hasTextCharacters();
+    
+    /*
+    /**********************************************************
+    /* Public API, access to token information, numeric
+    /**********************************************************
+     */
+
+    /**
+     * Generic number value accessor method that will work for
+     * all kinds of numeric values. It will return the optimal
+     * (simplest/smallest possible) wrapper object that can
+     * express the numeric value just parsed.
+     */
+    public abstract Number getNumberValue()
+        throws IOException, JsonParseException;
+
+    /**
+     * If current token is of type 
+     * {@link JsonToken#VALUE_NUMBER_INT} or
+     * {@link JsonToken#VALUE_NUMBER_FLOAT}, returns
+     * one of {@link NumberType} constants; otherwise returns null.
+     */
+    public abstract NumberType getNumberType()
+        throws IOException, JsonParseException;
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_INT} and
+     * it can be expressed as a value of Java byte primitive type.
+     * It can also be called for {@link JsonToken#VALUE_NUMBER_FLOAT};
+     * if so, it is equivalent to calling {@link #getDoubleValue}
+     * and then casting; except for possible overflow/underflow
+     * exception.
+     *<p>
+     * Note: if the resulting integer value falls outside range of
+     * Java byte, a {@link JsonParseException}
+     * will be thrown to indicate numeric overflow/underflow.
+     */
+    public byte getByteValue()
+        throws IOException, JsonParseException
+    {
+        int value = getIntValue();
+        // So far so good: but does it fit?
+        // [JACKSON-804]: Let's actually allow range of [-128, 255], as those are uniquely mapped
+        //  (instead of just signed range of [-128, 127])
+        if (value < MIN_BYTE_I || value > MAX_BYTE_I) {
+            throw _constructError("Numeric value ("+getText()+") out of range of Java byte");
+        }
+        return (byte) value;
+    }
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_INT} and
+     * it can be expressed as a value of Java short primitive type.
+     * It can also be called for {@link JsonToken#VALUE_NUMBER_FLOAT};
+     * if so, it is equivalent to calling {@link #getDoubleValue}
+     * and then casting; except for possible overflow/underflow
+     * exception.
+     *<p>
+     * Note: if the resulting integer value falls outside range of
+     * Java short, a {@link JsonParseException}
+     * will be thrown to indicate numeric overflow/underflow.
+     */
+    public short getShortValue()
+        throws IOException, JsonParseException
+    {
+        int value = getIntValue();
+        if (value < MIN_SHORT_I || value > MAX_SHORT_I) {
+            throw _constructError("Numeric value ("+getText()+") out of range of Java short");
+        }
+        return (short) value;
+    }
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_INT} and
+     * it can be expressed as a value of Java int primitive type.
+     * It can also be called for {@link JsonToken#VALUE_NUMBER_FLOAT};
+     * if so, it is equivalent to calling {@link #getDoubleValue}
+     * and then casting; except for possible overflow/underflow
+     * exception.
+     *<p>
+     * Note: if the resulting integer value falls outside range of
+     * Java int, a {@link JsonParseException}
+     * may be thrown to indicate numeric overflow/underflow.
+     */
+    public abstract int getIntValue()
+        throws IOException, JsonParseException;
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_INT} and
+     * it can be expressed as a Java long primitive type.
+     * It can also be called for {@link JsonToken#VALUE_NUMBER_FLOAT};
+     * if so, it is equivalent to calling {@link #getDoubleValue}
+     * and then casting to int; except for possible overflow/underflow
+     * exception.
+     *<p>
+     * Note: if the token is an integer, but its value falls
+     * outside of range of Java long, a {@link JsonParseException}
+     * may be thrown to indicate numeric overflow/underflow.
+     */
+    public abstract long getLongValue()
+        throws IOException, JsonParseException;
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_INT} and
+     * it can not be used as a Java long primitive type due to its
+     * magnitude.
+     * It can also be called for {@link JsonToken#VALUE_NUMBER_FLOAT};
+     * if so, it is equivalent to calling {@link #getDecimalValue}
+     * and then constructing a {@link BigInteger} from that value.
+     */
+    public abstract BigInteger getBigIntegerValue()
+        throws IOException, JsonParseException;
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_FLOAT} and
+     * it can be expressed as a Java float primitive type.
+     * It can also be called for {@link JsonToken#VALUE_NUMBER_INT};
+     * if so, it is equivalent to calling {@link #getLongValue}
+     * and then casting; except for possible overflow/underflow
+     * exception.
+     *<p>
+     * Note: if the value falls
+     * outside of range of Java float, a {@link JsonParseException}
+     * will be thrown to indicate numeric overflow/underflow.
+     */
+    public abstract float getFloatValue()
+        throws IOException, JsonParseException;
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_FLOAT} and
+     * it can be expressed as a Java double primitive type.
+     * It can also be called for {@link JsonToken#VALUE_NUMBER_INT};
+     * if so, it is equivalent to calling {@link #getLongValue}
+     * and then casting; except for possible overflow/underflow
+     * exception.
+     *<p>
+     * Note: if the value falls
+     * outside of range of Java double, a {@link JsonParseException}
+     * will be thrown to indicate numeric overflow/underflow.
+     */
+    public abstract double getDoubleValue()
+        throws IOException, JsonParseException;
+
+    /**
+     * Numeric accessor that can be called when the current
+     * token is of type {@link JsonToken#VALUE_NUMBER_FLOAT} or
+     * {@link JsonToken#VALUE_NUMBER_INT}. No under/overflow exceptions
+     * are ever thrown.
+     */
+    public abstract BigDecimal getDecimalValue()
+        throws IOException, JsonParseException;
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, other
+    /**********************************************************
+     */
+    
+    /**
+     * Convenience accessor that can be called when the current
+     * token is {@link JsonToken#VALUE_TRUE} or
+     * {@link JsonToken#VALUE_FALSE}.
+     *<p>
+     * Note: if the token is not of above-mentioned boolean types,
+ an integer, but its value falls
+     * outside of range of Java long, a {@link JsonParseException}
+     * may be thrown to indicate numeric overflow/underflow.
+     */
+    public boolean getBooleanValue()
+        throws IOException, JsonParseException
+    {
+        JsonToken t = getCurrentToken();
+        if (t == JsonToken.VALUE_TRUE) return true;
+        if (t == JsonToken.VALUE_FALSE) return false;
+        throw new JsonParseException("Current token ("+t+") not of boolean type", getCurrentLocation());
+    }
+
+    /**
+     * Accessor that can be called if (and only if) the current token
+     * is {@link JsonToken#VALUE_EMBEDDED_OBJECT}. For other token types,
+     * null is returned.
+     *<p>
+     * Note: only some specialized parser implementations support
+     * embedding of objects (usually ones that are facades on top
+     * of non-streaming sources, such as object trees).
+     */
+    public abstract Object getEmbeddedObject()
+        throws IOException, JsonParseException;
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, binary
+    /**********************************************************
+     */
+
+    /**
+     * Method that can be used to read (and consume -- results
+     * may not be accessible using other methods after the call)
+     * base64-encoded binary data
+     * included in the current textual JSON value.
+     * It works similar to getting String value via {@link #getText}
+     * and decoding result (except for decoding part),
+     * but should be significantly more performant.
+     *<p>
+     * Note that non-decoded textual contents of the current token
+     * are not guaranteed to be accessible after this method
+     * is called. Current implementation, for example, clears up
+     * textual content during decoding.
+     * Decoded binary content, however, will be retained until
+     * parser is advanced to the next event.
+     *
+     * @param b64variant Expected variant of base64 encoded
+     *   content (see {@link Base64Variants} for definitions
+     *   of "standard" variants).
+     *
+     * @return Decoded binary data
+     */
+    public abstract byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException;
+
+    /**
+     * Convenience alternative to {@link #getBinaryValue(Base64Variant)}
+     * that defaults to using
+     * {@link Base64Variants#getDefaultVariant} as the default encoding.
+     */
+    public byte[] getBinaryValue() throws IOException, JsonParseException {
+        return getBinaryValue(Base64Variants.getDefaultVariant());
+    }
+
+    /**
+     * Method that can be used as an alternative to {@link #getBigIntegerValue()},
+     * especially when value can be large. The main difference (beyond method
+     * of returning content using {@link OutputStream} instead of as byte array)
+     * is that content will NOT remain accessible after method returns: any content
+     * processed will be consumed and is not buffered in any way. If caller needs
+     * buffering, it has to implement it.
+     * 
+     * @param out Output stream to use for passing decoded binary data
+     * 
+     * @return Number of bytes that were decoded and written via {@link OutputStream}
+     * 
+     * @since 2.1
+     */
+    public int readBinaryValue(OutputStream out) throws IOException, JsonParseException {
+        return readBinaryValue(Base64Variants.getDefaultVariant(), out);
+    }
+
+    /**
+     * Similar to {@link #readBinaryValue(OutputStream)} but allows explicitly
+     * specifying base64 variant to use.
+     * 
+     * @param b64variant base64 variant to use
+     * @param out Output stream to use for passing decoded binary data
+     * 
+     * @return Number of bytes that were decoded and written via {@link OutputStream}
+     * 
+     * @since 2.1
+     */
+    public int readBinaryValue(Base64Variant b64variant, OutputStream out)
+            throws IOException, JsonParseException
+    {
+        _reportUnsupportedOperation();
+        return 0; // never gets here
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, access to token information, coercion/conversion
+    /**********************************************************
+     */
+    
+    /**
+     * Method that will try to convert value of current token to a
+     * <b>int</b>.
+     * Numbers are coerced using default Java rules; booleans convert to 0 (false)
+     * and 1 (true), and Strings are parsed using default Java language integer
+     * parsing rules.
+     *<p>
+     * If representation can not be converted to an int (including structured type
+     * markers like start/end Object/Array)
+     * default value of <b>0</b> will be returned; no exceptions are thrown.
+     */
+    public int getValueAsInt() throws IOException, JsonParseException {
+        return getValueAsInt(0);
+    }
+    
+    /**
+     * Method that will try to convert value of current token to a
+     * <b>int</b>.
+     * Numbers are coerced using default Java rules; booleans convert to 0 (false)
+     * and 1 (true), and Strings are parsed using default Java language integer
+     * parsing rules.
+     *<p>
+     * If representation can not be converted to an int (including structured type
+     * markers like start/end Object/Array)
+     * specified <b>defaultValue</b> will be returned; no exceptions are thrown.
+     */
+    public int getValueAsInt(int defaultValue) throws IOException, JsonParseException {
+        return defaultValue;
+    }
+
+    /**
+     * Method that will try to convert value of current token to a
+     * <b>long</b>.
+     * Numbers are coerced using default Java rules; booleans convert to 0 (false)
+     * and 1 (true), and Strings are parsed using default Java language integer
+     * parsing rules.
+     *<p>
+     * If representation can not be converted to an int (including structured type
+     * markers like start/end Object/Array)
+     * default value of <b>0</b> will be returned; no exceptions are thrown.
+     */
+    public long getValueAsLong() throws IOException, JsonParseException {
+        return getValueAsLong(0);
+    }
+    
+    /**
+     * Method that will try to convert value of current token to a
+     * <b>long</b>.
+     * Numbers are coerced using default Java rules; booleans convert to 0 (false)
+     * and 1 (true), and Strings are parsed using default Java language integer
+     * parsing rules.
+     *<p>
+     * If representation can not be converted to an int (including structured type
+     * markers like start/end Object/Array)
+     * specified <b>defaultValue</b> will be returned; no exceptions are thrown.
+     */
+    public long getValueAsLong(long defaultValue) throws IOException, JsonParseException {
+        return defaultValue;
+    }
+    
+    /**
+     * Method that will try to convert value of current token to a Java
+     * <b>double</b>.
+     * Numbers are coerced using default Java rules; booleans convert to 0.0 (false)
+     * and 1.0 (true), and Strings are parsed using default Java language integer
+     * parsing rules.
+     *<p>
+     * If representation can not be converted to an int (including structured types
+     * like Objects and Arrays),
+     * default value of <b>0.0</b> will be returned; no exceptions are thrown.
+     */
+    public double getValueAsDouble() throws IOException, JsonParseException {
+        return getValueAsDouble(0.0);
+    }
+    
+    /**
+     * Method that will try to convert value of current token to a
+     * Java <b>double</b>.
+     * Numbers are coerced using default Java rules; booleans convert to 0.0 (false)
+     * and 1.0 (true), and Strings are parsed using default Java language integer
+     * parsing rules.
+     *<p>
+     * If representation can not be converted to an int (including structured types
+     * like Objects and Arrays),
+     * specified <b>defaultValue</b> will be returned; no exceptions are thrown.
+     */
+    public double getValueAsDouble(double defaultValue) throws IOException, JsonParseException {
+        return defaultValue;
+    }
+
+    /**
+     * Method that will try to convert value of current token to a
+     * <b>boolean</b>.
+     * JSON booleans map naturally; integer numbers other than 0 map to true, and
+     * 0 maps to false
+     * and Strings 'true' and 'false' map to corresponding values.
+     *<p>
+     * If representation can not be converted to a boolean value (including structured types
+     * like Objects and Arrays),
+     * default value of <b>false</b> will be returned; no exceptions are thrown.
+     */
+    public boolean getValueAsBoolean() throws IOException, JsonParseException {
+        return getValueAsBoolean(false);
+    }
+
+    /**
+     * Method that will try to convert value of current token to a
+     * <b>boolean</b>.
+     * JSON booleans map naturally; integer numbers other than 0 map to true, and
+     * 0 maps to false
+     * and Strings 'true' and 'false' map to corresponding values.
+     *<p>
+     * If representation can not be converted to a boolean value (including structured types
+     * like Objects and Arrays),
+     * specified <b>defaultValue</b> will be returned; no exceptions are thrown.
+     */
+    public boolean getValueAsBoolean(boolean defaultValue) throws IOException, JsonParseException {
+        return defaultValue;
+    }
+
+    /**
+     * Method that will try to convert value of current token to a
+     * {@link java.lang.String}.
+     * JSON Strings map naturally; scalar values get converted to
+     * their textual representation.
+     * If representation can not be converted to a String value (including structured types
+     * like Objects and Arrays and null token), default value of
+     * <b>null</b> will be returned; no exceptions are thrown.
+     * 
+     * @since 2.1
+     */
+    public String getValueAsString() throws IOException, JsonParseException {
+        return getValueAsString(null);
+    }
+    
+    /**
+     * Method that will try to convert value of current token to a
+     * {@link java.lang.String}.
+     * JSON Strings map naturally; scalar values get converted to
+     * their textual representation.
+     * If representation can not be converted to a String value (including structured types
+     * like Objects and Arrays and null token), specified default value
+     * will be returned; no exceptions are thrown.
+     * 
+     * @since 2.1
+     */
+    public abstract String getValueAsString(String defaultValue)
+        throws IOException, JsonParseException;
+    
+    /*
+    /**********************************************************
+    /* Public API, optional data binding functionality
+    /**********************************************************
+     */
+
+    /**
+     * Method to deserialize JSON content into a non-container
+     * type (it can be an array type, however): typically a bean, array
+     * or a wrapper type (like {@link java.lang.Boolean}).
+     * <b>Note</b>: method can only be called if the parser has
+     * an object codec assigned; this is true for parsers constructed
+     * by <code>MappingJsonFactory</code> (from "jackson-databind" jar)
+     * but not for {@link JsonFactory} (unless its <code>setCodec</code>
+     * method has been explicitly called).
+     *<p>
+     * This method may advance the event stream, for structured types
+     * the current token will be the closing end marker (END_ARRAY,
+     * END_OBJECT) of the bound structure. For non-structured Json types
+     * (and for {@link JsonToken#VALUE_EMBEDDED_OBJECT})
+     * stream is not advanced.
+     *<p>
+     * Note: this method should NOT be used if the result type is a
+     * container ({@link java.util.Collection} or {@link java.util.Map}.
+     * The reason is that due to type erasure, key and value types
+     * can not be introspected when using this method.
+     */
+    public <T> T readValueAs(Class<T> valueType)
+        throws IOException, JsonProcessingException
+    {
+        ObjectCodec codec = getCodec();
+        if (codec == null) {
+            throw new IllegalStateException("No ObjectCodec defined for the parser, can not deserialize JSON into Java objects");
+        }
+        return codec.readValue(this, valueType);
+    }
+
+    /**
+     * Method to deserialize JSON content into a Java type, reference
+     * to which is passed as argument. Type is passed using so-called
+     * "super type token"
+     * and specifically needs to be used if the root type is a 
+     * parameterized (generic) container type.
+     * <b>Note</b>: method can only be called if the parser has
+     * an object codec assigned; this is true for parsers constructed
+     * by <code>MappingJsonFactory</code> (defined in 'jackson-databind' bundle)
+     * but not for {@link JsonFactory} (unless its <code>setCodec</code>
+     * method has been explicitly called).
+     *<p>
+     * This method may advance the event stream, for structured types
+     * the current token will be the closing end marker (END_ARRAY,
+     * END_OBJECT) of the bound structure. For non-structured Json types
+     * (and for {@link JsonToken#VALUE_EMBEDDED_OBJECT})
+     * stream is not advanced.
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T readValueAs(TypeReference<?> valueTypeRef)
+        throws IOException, JsonProcessingException
+    {
+        ObjectCodec codec = getCodec();
+        if (codec == null) {
+            throw new IllegalStateException("No ObjectCodec defined for the parser, can not deserialize JSON into Java objects");
+        }
+        /* Ugh. Stupid Java type erasure... can't just chain call,s
+         * must cast here also.
+         */
+        return (T) codec.readValue(this, valueTypeRef);
+    }
+
+    /**
+     * Method for reading sequence of Objects from parser stream,
+     * all with same specified value type.
+     */
+    public <T> Iterator<T> readValuesAs(Class<T> valueType)
+        throws IOException, JsonProcessingException
+    {
+        ObjectCodec codec = getCodec();
+        if (codec == null) {
+            throw new IllegalStateException("No ObjectCodec defined for the parser, can not deserialize JSON into Java objects");
+        }
+        return codec.readValues(this, valueType);
+    }
+
+    /**
+     * Method for reading sequence of Objects from parser stream,
+     * all with same specified value type.
+     */
+    public <T> Iterator<T> readValuesAs(TypeReference<?> valueTypeRef)
+        throws IOException, JsonProcessingException
+    {
+        ObjectCodec codec = getCodec();
+        if (codec == null) {
+            throw new IllegalStateException("No ObjectCodec defined for the parser, can not deserialize JSON into Java objects");
+        }
+        return codec.readValues(this, valueTypeRef);
+    }
+    
+    /**
+     * Method to deserialize JSON content into equivalent "tree model",
+     * represented by root {@link TreeNode} of resulting model.
+     * For JSON Arrays it will an array node (with child nodes),
+     * for objects object node (with child nodes), and for other types
+     * matching leaf node type
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends TreeNode> T readValueAsTree()
+        throws IOException, JsonProcessingException
+    {
+        ObjectCodec codec = getCodec();
+        if (codec == null) {
+            throw new IllegalStateException("No ObjectCodec defined for the parser, can not deserialize JSON into JsonNode tree");
+        }
+        return (T) codec.readTree(this);
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    /**
+     * Helper method for constructing {@link JsonParseException}s
+     * based on current state of the parser
+     */
+    protected JsonParseException _constructError(String msg)
+    {
+        return new JsonParseException(msg, getCurrentLocation());
+    }
+
+    /**
+     * Helper method to call for operations that are not supported by
+     * parser implementation.
+     *
+     * @since 2.1
+     */
+    protected void _reportUnsupportedOperation() {
+        throw new UnsupportedOperationException("Operation not supported by parser of type "+getClass().getName());
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java b/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java
new file mode 100644
index 0000000..71682dd
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java
@@ -0,0 +1,130 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Intermediate base class for all problems encountered when
+ * processing (parsing, generating) JSON content
+ * that are not pure I/O problems.
+ * Regular {@link java.io.IOException}s will be passed through as is.
+ * Sub-class of {@link java.io.IOException} for convenience.
+ */
+public class JsonProcessingException
+    extends java.io.IOException
+{
+    final static long serialVersionUID = 123; // Stupid eclipse...
+	
+    protected JsonLocation _location;
+
+    protected JsonProcessingException(String msg, JsonLocation loc, Throwable rootCause)
+    {
+        /* Argh. IOException(Throwable,String) is only available starting
+         * with JDK 1.6...
+         */
+        super(msg);
+        if (rootCause != null) {
+            initCause(rootCause);
+        }
+        _location = loc;
+    }
+
+    protected JsonProcessingException(String msg)
+    {
+        super(msg);
+    }
+
+    protected JsonProcessingException(String msg, JsonLocation loc)
+    {
+        this(msg, loc, null);
+    }
+
+    protected JsonProcessingException(String msg, Throwable rootCause)
+    {
+        this(msg, null, rootCause);
+    }
+
+    protected JsonProcessingException(Throwable rootCause)
+    {
+        this(null, null, rootCause);
+    }
+
+    public JsonLocation getLocation() {
+        return _location;
+    }
+
+    /*
+    /**********************************************************
+    /* Extended API
+    /**********************************************************
+     */
+
+    /**
+     * Method that allows accessing the original "message" argument,
+     * without additional decorations (like location information)
+     * that overridden {@link #getMessage} adds.
+     * 
+     * @since 2.1
+     */
+    public String getOriginalMessage()
+    {
+        return super.getMessage();
+    }
+
+    /*
+    /**********************************************************
+    /* Methods for sub-classes to use, override
+    /**********************************************************
+     */
+    
+    /**
+     * Accessor that sub-classes can override to append additional
+     * information right after the main message, but before
+     * source location information.
+     */
+    protected String getMessageSuffix() {
+        return null;
+    }
+
+    /*
+    /**********************************************************
+    /* Overrides of standard methods
+    /**********************************************************
+     */
+    
+    /**
+     * Default method overridden so that we can add location information
+     */
+    @Override
+    public String getMessage()
+    {
+        String msg = super.getMessage();
+        if (msg == null) {
+            msg = "N/A";
+        }
+        JsonLocation loc = getLocation();
+        String suffix = getMessageSuffix();
+        // mild optimization, if nothing extra is needed:
+        if (loc != null || suffix != null) {
+            StringBuilder sb = new StringBuilder(100);
+            sb.append(msg);
+            if (suffix != null) {
+                sb.append(suffix);
+            }
+            if (loc != null) {
+                sb.append('\n');
+                sb.append(" at ");
+                sb.append(loc.toString());
+            }
+            msg = sb.toString();
+        }
+        return msg;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getName()+": "+getMessage();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java
new file mode 100644
index 0000000..ea81b72
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java
@@ -0,0 +1,112 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Shared base class for streaming processing contexts used during
+ * reading and writing of Json content using Streaming API.
+ * This context is also exposed to applications:
+ * context object can be used by applications to get an idea of
+ * relative position of the parser/generator within json content
+ * being processed. This allows for some contextual processing: for
+ * example, output within Array context can differ from that of
+ * Object context.
+ */
+public abstract class JsonStreamContext
+{
+    // // // Type constants used internally
+
+    protected final static int TYPE_ROOT = 0;
+    protected final static int TYPE_ARRAY = 1;
+    protected final static int TYPE_OBJECT = 2;
+
+    protected int _type;
+
+    /**
+     * Index of the currently processed entry. Starts with -1 to signal
+     * that no entries have been started, and gets advanced each
+     * time a new entry is started, either by encountering an expected
+     * separator, or with new values if no separators are expected
+     * (the case for root context).
+     */
+    protected int _index;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    protected JsonStreamContext() { }
+
+    /*
+    /**********************************************************
+    /* Public API, accessors
+    /**********************************************************
+     */
+
+    /**
+     * Accessor for finding parent context of this context; will
+     * return null for root context.
+     */
+    public abstract JsonStreamContext getParent();
+
+    /**
+     * Method that returns true if this context is an Array context;
+     * that is, content is being read from or written to a Json Array.
+     */
+    public final boolean inArray() { return _type == TYPE_ARRAY; }
+
+    /**
+     * Method that returns true if this context is a Root context;
+     * that is, content is being read from or written to without
+     * enclosing array or object structure.
+     */
+    public final boolean inRoot() { return _type == TYPE_ROOT; }
+
+    /**
+     * Method that returns true if this context is an Object context;
+     * that is, content is being read from or written to a Json Object.
+     */
+    public final boolean inObject() { return _type == TYPE_OBJECT; }
+
+    /**
+     * Method for accessing simple type description of current context;
+     * either ROOT (for root-level values), OBJECT (for field names and
+     * values of JSON Objects) or ARRAY (for values of JSON Arrays)
+     */
+    public final String getTypeDesc() {
+        switch (_type) {
+        case TYPE_ROOT: return "ROOT";
+        case TYPE_ARRAY: return "ARRAY";
+        case TYPE_OBJECT: return "OBJECT";
+        }
+        return "?";
+    }
+
+    /**
+     * @return Number of entries that are complete and started.
+     */
+    public final int getEntryCount()
+    {
+        return _index + 1;
+    }
+
+    /**
+     * @return Index of the currently processed entry, if any
+     */
+    public final int getCurrentIndex()
+    {
+        return (_index < 0) ? 0 : _index;
+    }
+
+    /**
+     * Method for accessing name associated with the current location.
+     * Non-null for <code>FIELD_NAME</code> and value events that directly
+     * follow field names; null for root level and array values.
+     */
+    public abstract String getCurrentName();
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonToken.java b/src/main/java/com/fasterxml/jackson/core/JsonToken.java
new file mode 100644
index 0000000..917ca5c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonToken.java
@@ -0,0 +1,162 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Enumeration for basic token types used for returning results
+ * of parsing JSON content.
+ */
+public enum JsonToken
+{
+    /* Some notes on implementation:
+     *
+     * - Entries are to be ordered such that start/end array/object
+     *   markers come first, then field name marker (if any), and
+     *   finally scalar value tokens. This is assumed by some
+     *   typing checks.
+     */
+
+    /**
+     * NOT_AVAILABLE can be returned if {@link JsonParser}
+     * implementation can not currently return the requested
+     * token (usually next one), or even if any will be
+     * available, but that may be able to determine this in
+     * future. This is the case with non-blocking parsers --
+     * they can not block to wait for more data to parse and
+     * must return something.
+     */
+    NOT_AVAILABLE(null),
+
+    /**
+     * START_OBJECT is returned when encountering '{'
+     * which signals starting of an Object value.
+     */
+    START_OBJECT("{"),
+        
+    /**
+     * END_OBJECT is returned when encountering '}'
+     * which signals ending of an Object value
+     */
+    END_OBJECT("}"),
+        
+    /**
+     * START_ARRAY is returned when encountering '['
+     * which signals starting of an Array value
+     */
+    START_ARRAY("["),
+
+    /**
+     * END_ARRAY is returned when encountering ']'
+     * which signals ending of an Array value
+     */
+    END_ARRAY("]"),
+        
+    /**
+     * FIELD_NAME is returned when a String token is encountered
+     * as a field name (same lexical value, different function)
+     */
+    FIELD_NAME(null),
+        
+    /**
+     * Placeholder token returned when the input source has a concept
+     * of embedded Object that are not accessible as usual structure
+     * (of starting with {@link #START_OBJECT}, having values, ending with
+     * {@link #END_OBJECT}), but as "raw" objects.
+     *<p>
+     * Note: this token is never returned by regular JSON readers, but
+     * only by readers that expose other kinds of source (like
+     * <code>JsonNode</code>-based JSON trees, Maps, Lists and such).
+     */
+    VALUE_EMBEDDED_OBJECT(null),
+
+    /**
+     * VALUE_STRING is returned when a String token is encountered
+     * in value context (array element, field value, or root-level
+     * stand-alone value)
+     */
+    VALUE_STRING(null),
+
+    /**
+     * VALUE_NUMBER_INT is returned when an integer numeric token is
+     * encountered in value context: that is, a number that does
+     * not have floating point or exponent marker in it (consists
+     * only of an optional sign, followed by one or more digits)
+     */
+    VALUE_NUMBER_INT(null),
+
+    /**
+     * VALUE_NUMBER_INT is returned when a numeric token other
+     * that is not an integer is encountered: that is, a number that does
+     * have floating point or exponent marker in it, in addition
+     * to one or more digits.
+     */
+    VALUE_NUMBER_FLOAT(null),
+
+    /**
+     * VALUE_TRUE is returned when encountering literal "true" in
+     * value context
+     */
+    VALUE_TRUE("true"),
+
+    /**
+     * VALUE_FALSE is returned when encountering literal "false" in
+     * value context
+     */
+    VALUE_FALSE("false"),
+
+    /**
+     * VALUE_NULL is returned when encountering literal "null" in
+     * value context
+     */
+    VALUE_NULL("null")
+        ;
+
+    final String _serialized;
+
+    final char[] _serializedChars;
+
+    final byte[] _serializedBytes;
+
+    /**
+     * @param token representation for this token, if there is a
+     *   single static representation; null otherwise
+     */
+    JsonToken(String token)
+    {
+        if (token == null) {
+            _serialized = null;
+            _serializedChars = null;
+            _serializedBytes = null;
+        } else {
+            _serialized = token;
+            _serializedChars = token.toCharArray();
+            // It's all in ascii, can just case...
+            int len = _serializedChars.length;
+            _serializedBytes = new byte[len];
+            for (int i = 0; i < len; ++i) {
+                _serializedBytes[i] = (byte) _serializedChars[i];
+            }
+        }
+    }
+
+    public String asString() { return _serialized; }
+    public char[] asCharArray() { return _serializedChars; }
+    public byte[] asByteArray() { return _serializedBytes; }
+
+    public boolean isNumeric() {
+        return (this == VALUE_NUMBER_INT) || (this == VALUE_NUMBER_FLOAT);
+    }
+
+    /**
+     * Method that can be used to check whether this token represents
+     * a valid non-structured value. This means all tokens other than
+     * Object/Array start/end markers all field names.
+     */
+    public boolean isScalarValue() {
+        // note: up to 1.5, VALUE_EMBEDDED_OBJECT was incorrectly considered non-scalar!
+        return ordinal() >= VALUE_EMBEDDED_OBJECT.ordinal();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/ObjectCodec.java b/src/main/java/com/fasterxml/jackson/core/ObjectCodec.java
new file mode 100644
index 0000000..9f7f24b
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/ObjectCodec.java
@@ -0,0 +1,163 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import com.fasterxml.jackson.core.type.ResolvedType;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+/**
+ * Abstract class that defines the interface that {@link JsonParser} and
+ * {@link JsonGenerator} use to serialize and deserialize regular
+ * Java objects (POJOs aka Beans).
+ *<p>
+ * The standard implementation of this class is
+ * <code>com.fasterxml.jackson.databind.ObjectMapper</code>,
+ * defined in the "jackson-databind".
+ */
+public abstract class ObjectCodec
+{
+    protected ObjectCodec() { }
+
+    /*
+    /**********************************************************
+    /* API for de-serialization (JSON-to-Object)
+    /**********************************************************
+     */
+
+    /**
+     * Method to deserialize JSON content into a non-container
+     * type (it can be an array type, however): typically a bean, array
+     * or a wrapper type (like {@link java.lang.Boolean}).
+     *<p>
+     * Note: this method should NOT be used if the result type is a
+     * container ({@link java.util.Collection} or {@link java.util.Map}.
+     * The reason is that due to type erasure, key and value types
+     * can not be introspected when using this method.
+     */
+    public abstract <T> T readValue(JsonParser jp, Class<T> valueType)
+        throws IOException, JsonProcessingException;
+
+    /**
+     * Method to deserialize JSON content into a Java type, reference
+     * to which is passed as argument. Type is passed using so-called
+     * "super type token" 
+     * and specifically needs to be used if the root type is a 
+     * parameterized (generic) container type.
+     */
+    public abstract <T> T readValue(JsonParser jp, TypeReference<?> valueTypeRef)
+        throws IOException, JsonProcessingException;
+
+    /**
+     * Method to deserialize JSON content into a POJO, type specified
+     * with fully resolved type object (so it can be a generic type,
+     * including containers like {@link java.util.Collection} and
+     * {@link java.util.Map}).
+     */
+    public abstract <T> T readValue(JsonParser jp, ResolvedType valueType)
+        throws IOException, JsonProcessingException;
+
+    /**
+     * Method to deserialize JSON content as tree expressed
+     * using set of {@link TreeNode} instances. Returns
+     * root of the resulting tree (where root can consist
+     * of just a single node if the current event is a
+     * value event, not container).
+     */
+    public abstract <T extends TreeNode> T readTree(JsonParser jp)
+        throws IOException, JsonProcessingException;
+
+    /**
+     * Method for reading sequence of Objects from parser stream,
+     * all with same specified value type.
+     */
+    public abstract <T> Iterator<T> readValues(JsonParser jp, Class<T> valueType)
+        throws IOException, JsonProcessingException;
+
+    /**
+     * Method for reading sequence of Objects from parser stream,
+     * all with same specified value type.
+     */
+    public abstract <T> Iterator<T> readValues(JsonParser jp, TypeReference<?> valueTypeRef)
+        throws IOException, JsonProcessingException;
+    
+    /**
+     * Method for reading sequence of Objects from parser stream,
+     * all with same specified value type.
+     */
+    public abstract <T> Iterator<T> readValues(JsonParser jp, ResolvedType valueType)
+        throws IOException, JsonProcessingException;
+    
+    /*
+    /**********************************************************
+    /* API for serialization (Object-to-JSON)
+    /**********************************************************
+     */
+
+    /**
+     * Method to serialize given Java Object, using generator
+     * provided.
+     */
+    public abstract void writeValue(JsonGenerator jgen, Object value)
+        throws IOException, JsonProcessingException;
+
+    /*
+    /**********************************************************
+    /* API for Tree Model handling
+    /**********************************************************
+     */
+
+    /**
+     * Method for construct root level Object nodes
+     * for Tree Model instances.
+     */
+    public abstract TreeNode createObjectNode();
+
+    /**
+     * Method for construct root level Array nodes
+     * for Tree Model instances.
+     */
+    public abstract TreeNode createArrayNode();
+
+    /**
+     * Method for constructing a {@link JsonParser} for reading
+     * contents of a JSON tree, as if it was external serialized
+     * JSON content.
+     */
+    public abstract JsonParser treeAsTokens(TreeNode n);
+
+    /**
+     * Convenience method for converting given JSON tree into instance of specified
+     * value type. This is equivalent to first constructing a {@link JsonParser} to
+     * iterate over contents of the tree, and using that parser for data binding.
+     */
+    public abstract <T> T treeToValue(TreeNode n, Class<T> valueType)
+        throws JsonProcessingException;
+
+    /*
+    /**********************************************************
+    /* Basic accessors
+    /**********************************************************
+     */
+
+    /**
+     * @deprecated Since 2.1: Use {@link #getFactory} instead.
+     */
+    @Deprecated
+    public abstract JsonFactory getJsonFactory();
+
+    /**
+     * Accessor for finding underlying data format factory
+     * ({@link JsonFactory}) codec will use for data binding.
+     * 
+     * @since 2.1
+     */
+    public JsonFactory getFactory() {
+        return getJsonFactory();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java b/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java
new file mode 100644
index 0000000..17716ae
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java
@@ -0,0 +1,177 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+import java.io.IOException;
+
+/**
+ * Interface for objects that implement pretty printer functionality, such
+ * as indentation.
+ * Pretty printers are used to add white space in output JSON content,
+ * to make results more human readable. Usually this means things like adding
+ * linefeeds and indentation.
+ *<p>
+ * Note: since Jackson 2.1, stateful implementations MUST implement
+ * {@link com.fasterxml.jackson.core.util.Instantiatable} interface,
+ * to allow for constructing  per-generation instances and avoid
+ * state corruption (see [JACKSON-851] for details).
+ * Stateless implementations need not do this; but those are less common.
+ */
+public interface PrettyPrinter
+{
+    /*
+    /**********************************************************
+    /* First methods that act both as events, and expect
+    /* output for correct functioning (i.e something gets
+    /* output even when not pretty-printing)
+    /**********************************************************
+     */
+
+    // // // Root-level handling:
+
+    /**
+     * Method called after a root-level value has been completely
+     * output, and before another value is to be output.
+     *<p>
+     * Default
+     * handling (without pretty-printing) will output a space, to
+     * allow values to be parsed correctly. Pretty-printer is
+     * to output some other suitable and nice-looking separator
+     * (tab(s), space(s), linefeed(s) or any combination thereof).
+     */
+    void writeRootValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+
+    // // Object handling
+
+    /**
+     * Method called when an Object value is to be output, before
+     * any fields are output.
+     *<p>
+     * Default handling (without pretty-printing) will output
+     * the opening curly bracket.
+     * Pretty-printer is
+     * to output a curly bracket as well, but can surround that
+     * with other (white-space) decoration.
+     */
+    void writeStartObject(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method called after an Object value has been completely output
+     * (minus closing curly bracket).
+     *<p>
+     * Default handling (without pretty-printing) will output
+     * the closing curly bracket.
+     * Pretty-printer is
+     * to output a curly bracket as well, but can surround that
+     * with other (white-space) decoration.
+     *
+     * @param nrOfEntries Number of direct members of the array that
+     *   have been output
+     */
+    void writeEndObject(JsonGenerator jg, int nrOfEntries)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method called after an object entry (field:value) has been completely
+     * output, and before another value is to be output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * comma to separate the two. Pretty-printer is
+     * to output a comma as well, but can surround that with other
+     * (white-space) decoration.
+     */
+    void writeObjectEntrySeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method called after an object field has been output, but
+     * before the value is output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * colon to separate the two. Pretty-printer is
+     * to output a colon as well, but can surround that with other
+     * (white-space) decoration.
+     */
+    void writeObjectFieldValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+
+    // // // Array handling
+
+    /**
+     * Method called when an Array value is to be output, before
+     * any member/child values are output.
+     *<p>
+     * Default handling (without pretty-printing) will output
+     * the opening bracket.
+     * Pretty-printer is
+     * to output a bracket as well, but can surround that
+     * with other (white-space) decoration.
+     */
+    void writeStartArray(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method called after an Array value has been completely output
+     * (minus closing bracket).
+     *<p>
+     * Default handling (without pretty-printing) will output
+     * the closing bracket.
+     * Pretty-printer is
+     * to output a bracket as well, but can surround that
+     * with other (white-space) decoration.
+     *
+     * @param nrOfValues Number of direct members of the array that
+     *   have been output
+     */
+    void writeEndArray(JsonGenerator jg, int nrOfValues)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method called after an array value has been completely
+     * output, and before another value is to be output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * comma to separate the two. Pretty-printer is
+     * to output a comma as well, but can surround that with other
+     * (white-space) decoration.
+     */
+    void writeArrayValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+
+    /*
+    /**********************************************************
+    /* Then events that by default do not produce any output
+    /* but that are often overridden to add white space
+    /* in pretty-printing mode
+    /**********************************************************
+     */
+
+    /**
+     * Method called after array start marker has been output,
+     * and right before the first value is to be output.
+     * It is <b>not</b> called for arrays with no values.
+     *<p>
+     * Default handling does not output anything, but pretty-printer
+     * is free to add any white space decoration.
+     */
+    void beforeArrayValues(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Method called after object start marker has been output,
+     * and right before the field name of the first entry is
+     * to be output.
+     * It is <b>not</b> called for objects without entries.
+     *<p>
+     * Default handling does not output anything, but pretty-printer
+     * is free to add any white space decoration.
+     */
+    void beforeObjectEntries(JsonGenerator jg)
+        throws IOException, JsonGenerationException;
+}
+
diff --git a/src/main/java/com/fasterxml/jackson/core/SerializableString.java b/src/main/java/com/fasterxml/jackson/core/SerializableString.java
new file mode 100644
index 0000000..2a19b4f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/SerializableString.java
@@ -0,0 +1,154 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Interface that defines how Jackson package can interact with efficient
+ * pre-serialized or lazily-serialized and reused String representations.
+ * Typically implementations store possible serialized version(s) so that
+ * serialization of String can be done more efficiently, especially when
+ * used multiple times.
+ * 
+ * @see com.fasterxml.jackson.core.io.SerializedString
+ */
+public interface SerializableString
+{
+    /**
+     * Returns unquoted String that this object represents (and offers
+     * serialized forms for)
+     */
+    String getValue();
+    
+    /**
+     * Returns length of the (unquoted) String as characters.
+     * Functionally equvalent to:
+     *<pre>
+     *   getValue().length();
+     *</pre>
+     */
+    int charLength();
+
+    
+    /*
+    /**********************************************************
+    /* Accessors for byte sequences
+    /**********************************************************
+     */
+    
+    /**
+     * Returns JSON quoted form of the String, as character array. Result
+     * can be embedded as-is in textual JSON as property name or JSON String.
+     */
+    char[] asQuotedChars();
+
+    /**
+     * Returns UTF-8 encoded version of unquoted String.
+     * Functionally equivalent to (but more efficient than):
+     *<pre>
+     * getValue().getBytes("UTF-8");
+     *</pre>
+     */
+    byte[] asUnquotedUTF8();
+
+    /**
+     * Returns UTF-8 encoded version of JSON-quoted String.
+     * Functionally equivalent to (but more efficient than):
+     *<pre>
+     * new String(asQuotedChars()).getBytes("UTF-8");
+     *</pre>
+     */
+    byte[] asQuotedUTF8();
+
+    /*
+    /**********************************************************
+    /* Helper methods for appending byte/char sequences
+    /**********************************************************
+     */
+
+    /**
+     * Method that will append quoted UTF-8 bytes of this String into given
+     * buffer, if there is enough room; if not, returns -1.
+     * Functionally equivalent to:
+     *<pre>
+     *  byte[] bytes = str.asQuotedUTF8();
+     *  System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+     *  return bytes.length;
+     *</pre>
+     * 
+     * @return Number of bytes appended, if successful, otherwise -1
+     */
+    int appendQuotedUTF8(byte[] buffer, int offset);
+
+    /**
+     * Method that will append quoted characters of this String into given
+     * buffer. Functionally equivalent to:
+     *<pre>
+     *  char[] ch = str.asQuotedChars();
+     *  System.arraycopy(ch, 0, buffer, offset, ch.length);
+     *  return ch.length;
+     *</pre>
+     * 
+     * @return Number of characters appended, if successful, otherwise -1
+     */
+    int appendQuoted(char[] buffer, int offset);
+    
+    /**
+     * Method that will append unquoted ('raw') UTF-8 bytes of this String into given
+     * buffer. Functionally equivalent to:
+     *<pre>
+     *  byte[] bytes = str.asUnquotedUTF8();
+     *  System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+     *  return bytes.length;
+     *</pre>
+     * 
+     * @return Number of bytes appended, if successful, otherwise -1
+     */
+    int appendUnquotedUTF8(byte[] buffer, int offset);
+
+    
+    /**
+     * Method that will append unquoted characters of this String into given
+     * buffer. Functionally equivalent to:
+     *<pre>
+     *  char[] ch = str.getValue().toCharArray();
+     *  System.arraycopy(bytes, 0, buffer, offset, ch.length);
+     *  return ch.length;
+     *</pre>
+     * 
+     * @return Number of characters appended, if successful, otherwise -1
+     */
+    int appendUnquoted(char[] buffer, int offset);
+
+    /*
+    /**********************************************************
+    /* Helper methods for writing out byte sequences
+    /**********************************************************
+     */
+
+    /**
+     * @return Number of bytes written
+     */
+    int writeQuotedUTF8(OutputStream out) throws IOException;
+
+    /**
+     * @return Number of bytes written
+     */
+    int writeUnquotedUTF8(OutputStream out) throws IOException;
+
+    /**
+     * @return Number of bytes put, if successful, otherwise -1
+     */
+    int putQuotedUTF8(ByteBuffer buffer) throws IOException;
+
+    /**
+     * @return Number of bytes put, if successful, otherwise -1
+     */
+    int putUnquotedUTF8(ByteBuffer out) throws IOException;
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/TreeNode.java b/src/main/java/com/fasterxml/jackson/core/TreeNode.java
new file mode 100644
index 0000000..aab0646
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/TreeNode.java
@@ -0,0 +1,241 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+import java.util.Iterator;
+
+/**
+ * Marker interface used to denote JSON Tree nodes, as far as
+ * the core package knows them (which is very little): mostly
+ * needed to allow {@link ObjectCodec} to have some level
+ * of interoperability.
+ * Most functionality is within <code>JsonNode</code>
+ * base class in <code>mapper</code> package.
+ *<p>
+ * Note that in Jackson 1.x <code>JsonNode</code> itself
+ * was part of core package: Jackson 2.x refactored this
+ * since conceptually Tree Model is part of mapper package,
+ * and so part visible to <code>core</code> package should
+ * be minimized.
+ *<p>
+ * NOTE: starting with Jackson 2.2, there is more functionality
+ * available via this class, and the intent is that this should
+ * form actual base for multiple alternative tree representations;
+ * for example, immutable trees could use different implementation
+ * than mutable trees. It should also be possible to move actual
+ * Tree Model implementation out of databind package eventually
+ * (Jackson 3?).
+ */
+public interface TreeNode
+{
+    /*
+    /**********************************************************
+    /* Minimal introspection methods
+    /**********************************************************
+     */
+    
+    /**
+     * Method that can be used for efficient type detection
+     * when using stream abstraction for traversing nodes.
+     * Will return the first {@link JsonToken} that equivalent
+     * stream event would produce (for most nodes there is just
+     * one token but for structured/container types multiple)
+     */
+    JsonToken asToken();
+
+    /**
+     * If this node is a numeric type (as per {@link JsonToken#isNumeric}),
+     * returns native type that node uses to store the numeric value;
+     * otherwise returns null.
+     * 
+     * @return Type of number contained, if any; or null if node does not
+     *  contain numeric value.
+     */
+    JsonParser.NumberType numberType();
+
+    /**
+     * Method that returns number of child nodes this node contains:
+     * for Array nodes, number of child elements, for Object nodes,
+     * number of fields, and for all other nodes 0.
+     *
+     * @return For non-container nodes returns 0; for arrays number of
+     *   contained elements, and for objects number of fields.
+     * 
+     * @since 2.2
+     */
+    int size();
+
+    /**
+     * Method that returns true for all value nodes: ones that 
+     * are not containers, and that do not represent "missing" nodes
+     * in the path. Such value nodes represent String, Number, Boolean
+     * and null values from JSON.
+     *<p>
+     * Note: one and only one of methods {@link #isValueNode},
+     * {@link #isContainerNode} and {@link #isMissingNode} ever
+     * returns true for any given node.
+     * 
+     * @since 2.2
+     */
+    boolean isValueNode();
+
+    /**
+     * Method that returns true for container nodes: Arrays and Objects.
+     *<p>
+     * Note: one and only one of methods {@link #isValueNode},
+     * {@link #isContainerNode} and {@link #isMissingNode} ever
+     * returns true for any given node.
+     * 
+     * @since 2.2
+     */
+    boolean isContainerNode();
+    
+    /**
+     * Method that returns true for "virtual" nodes which represent
+     * missing entries constructed by path accessor methods when
+     * there is no actual node matching given criteria.
+     *<p>
+     * Note: one and only one of methods {@link #isValueNode},
+     * {@link #isContainerNode} and {@link #isMissingNode} ever
+     * returns true for any given node.
+     * 
+     * @since 2.2
+     */
+    boolean isMissingNode();
+    
+    /**
+     * Method that returns true if this node is an Array node, false
+     * otherwise.
+     * Note that if true is returned, {@link #isContainerNode}
+     * must also return true.
+     * 
+     * @since 2.2
+     */
+    boolean isArray();
+
+    /**
+     * Method that returns true if this node is an Object node, false
+     * otherwise.
+     * Note that if true is returned, {@link #isContainerNode}
+     * must also return true.
+     * 
+     * @since 2.2
+     */
+    boolean isObject();
+    
+    /*
+    /**********************************************************
+    /* Basic traversal through structured entries (Arrays, Objects)
+    /**********************************************************
+     */
+
+    /**
+     * Method for accessing value of the specified field of
+     * an object node. If this node is not an object (or it
+     * does not have a value for specified field name), or
+     * if there is no field with such name, null is returned.
+     *<p>
+     * NOTE: handling of explicit null values may vary between
+     * implementations; some trees may retain explicit nulls, others
+     * not.
+     * 
+     * @return Node that represent value of the specified field,
+     *   if this node is an object and has value for the specified
+     *   field. Null otherwise.
+     * 
+     * @since 2.2
+     */
+    TreeNode get(String fieldName);
+
+    /**
+     * Method for accessing value of the specified element of
+     * an array node. For other nodes, null is returned.
+     *<p>
+     * For array nodes, index specifies
+     * exact location within array and allows for efficient iteration
+     * over child elements (underlying storage is guaranteed to
+     * be efficiently indexable, i.e. has random-access to elements).
+     * If index is less than 0, or equal-or-greater than
+     * <code>node.size()</code>, null is returned; no exception is
+     * thrown for any index.
+     *
+     * @return Node that represent value of the specified element,
+     *   if this node is an array and has specified element.
+     *   Null otherwise.
+     * 
+     * @since 2.2
+     */
+    TreeNode get(int index);
+
+    /**
+     * Method for accessing value of the specified field of
+     * an object node.
+     * For other nodes, a "missing node" (virtual node
+     * for which {@link #isMissingNode} returns true) is returned.
+     * 
+     * @return Node that represent value of the specified field,
+     *   if this node is an object and has value for the specified field;
+     *   otherwise "missing node" is returned.
+     * 
+     * @since 2.2
+     */
+    TreeNode path(String fieldName);
+
+    /**
+     * Method for accessing value of the specified element of
+     * an array node.
+     * For other nodes, a "missing node" (virtual node
+     * for which {@link #isMissingNode} returns true) is returned.
+     *<p>
+     * For array nodes, index specifies
+     * exact location within array and allows for efficient iteration
+     * over child elements (underlying storage is guaranteed to
+     * be efficiently indexable, i.e. has random-access to elements).
+     * If index is less than 0, or equal-or-greater than
+     * <code>node.size()</code>, "missing node" is returned; no exception is
+     * thrown for any index.
+     *
+     * @return Node that represent value of the specified element,
+     *   if this node is an array and has specified element;
+     *   otherwise "missing node" is returned.
+     * 
+     * @since 2.2
+     */
+    TreeNode path(int index);
+    
+    /**
+     * Method for accessing names of all fields for this node, iff
+     * this node is an Object node. Number of field names accessible
+     * will be {@link #size}.
+     * 
+     * @since 2.2
+     */
+    Iterator<String> fieldNames();
+    
+    /*
+    /**********************************************************
+    /* Converting to/from Streaming API
+    /**********************************************************
+     */
+
+    /**
+     * Method for constructing a {@link JsonParser} instance for
+     * iterating over contents of the tree that this node is root of.
+     * Functionally equivalent to first serializing tree using
+     * {@link ObjectCodec} and then re-parsing but
+     * more efficient.
+     */
+    JsonParser traverse();
+
+    /**
+     * Same as {@link #traverse()}, but additionally passes {@link com.fasterxml.jackson.core.ObjectCodec}
+     * to use if {@link JsonParser#readValueAs(Class)} is used (otherwise caller must call
+     * {@link JsonParser#setCodec} on response explicitly).
+     * 
+     * @since 2.1
+     */
+    JsonParser traverse(ObjectCodec codec);
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/Version.java b/src/main/java/com/fasterxml/jackson/core/Version.java
new file mode 100644
index 0000000..cc2603c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Version.java
@@ -0,0 +1,141 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Object that encapsulates versioning information of a component.
+ * Version information includes not just version number but also
+ * optionally group and artifact ids of the component being versioned.
+ *<p>
+ * Note that optional group and artifact id properties are new with Jackson 2.0:
+ * if provided, they should align with Maven artifact information.
+ */
+public class Version
+    implements Comparable<Version>,
+        java.io.Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    private final static Version UNKNOWN_VERSION = new Version(0, 0, 0, null, null, null);
+    
+    protected final int _majorVersion;
+
+    protected final int _minorVersion;
+
+    protected final int _patchLevel;
+
+    protected final String _groupId;
+    
+    protected final String _artifactId;
+    
+    /**
+     * Additional information for snapshot versions; null for non-snapshot
+     * (release) versions.
+     */
+    protected final String _snapshotInfo;
+
+    /**
+     * @deprecated Use variant that takes group and artifact ids
+     * 
+     * @since 2.1
+     */
+    @Deprecated
+    public Version(int major, int minor, int patchLevel, String snapshotInfo)
+    {
+        this(major, minor, patchLevel, snapshotInfo, null, null);
+    }
+    
+    public Version(int major, int minor, int patchLevel, String snapshotInfo,
+            String groupId, String artifactId)
+    {
+        _majorVersion = major;
+        _minorVersion = minor;
+        _patchLevel = patchLevel;
+        _snapshotInfo = snapshotInfo;
+        _groupId = (groupId == null) ? "" : groupId;
+        _artifactId = (artifactId == null) ? "" : artifactId;
+    }
+
+    /**
+     * Method returns canonical "not known" version, which is used as version
+     * in cases where actual version information is not known (instead of null).
+     */
+    public static Version unknownVersion() { return UNKNOWN_VERSION; }
+
+    public boolean isUknownVersion() { return (this == UNKNOWN_VERSION); }
+    public boolean isSnapshot() { return (_snapshotInfo != null && _snapshotInfo.length() > 0); }
+    
+    public int getMajorVersion() { return _majorVersion; }
+    public int getMinorVersion() { return _minorVersion; }
+    public int getPatchLevel() { return _patchLevel; }
+
+    public String getGroupId() { return _groupId; }
+    public String getArtifactId() { return _artifactId; }
+    
+    public String toFullString() {
+        return new StringBuilder()
+        	.append(_groupId)
+        	.append('/')
+        	.append(_artifactId)
+        	.append('/')
+        	.append(toString())
+        	.toString();
+    }
+    
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(_majorVersion).append('.');
+        sb.append(_minorVersion).append('.');
+        sb.append(_patchLevel);
+        if (isSnapshot()) {
+            sb.append('-').append(_snapshotInfo);
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return _artifactId.hashCode() ^ _groupId.hashCode() + _majorVersion - _minorVersion + _patchLevel;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (o == this) return true;
+        if (o == null) return false;
+        if (o.getClass() != getClass()) return false;
+        Version other = (Version) o;
+        return (other._majorVersion == _majorVersion)
+            && (other._minorVersion == _minorVersion)
+            && (other._patchLevel == _patchLevel)
+            && other._artifactId.equals(_artifactId)
+            && other._groupId.equals(_groupId)
+            ;
+    }
+
+    @Override
+    public int compareTo(Version other)
+    {
+        if (other == this) return 0;
+        
+        int diff = _groupId.compareTo(other._groupId);
+        if (diff == 0) {
+            diff = _artifactId.compareTo(other._artifactId);
+            if (diff == 0) {
+                diff = _majorVersion - other._majorVersion;
+                if (diff == 0) {
+                    diff = _minorVersion - other._minorVersion;
+                    if (diff == 0) {
+                        diff = _patchLevel - other._patchLevel;
+                    }
+                }
+            }
+        }
+        return diff;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/Versioned.java b/src/main/java/com/fasterxml/jackson/core/Versioned.java
new file mode 100644
index 0000000..637938f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Versioned.java
@@ -0,0 +1,23 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core;
+
+/**
+ * Interface that those Jackson components that are explicitly versioned will implement.
+ * Intention is to allow both plug-in components (custom extensions) and applications and
+ * frameworks that use Jackson to detect exact version of Jackson in use.
+ * This may be useful for example for ensuring that proper Jackson version is deployed
+ * (beyond mechanisms that deployment system may have), as well as for possible
+ * workarounds.
+ */
+public interface Versioned {
+    /**
+     * Method called to detect version of the component that implements this interface;
+     * returned version should never be null, but may return specific "not available"
+     * instance (see {@link Version} for details).
+     */
+    Version version();
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java b/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java
new file mode 100644
index 0000000..cc200cf
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java
@@ -0,0 +1,526 @@
+package com.fasterxml.jackson.core.base;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.json.JsonWriteContext;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * This base class implements part of API that a JSON generator exposes
+ * to applications, adds shared internal methods that sub-classes
+ * can use and adds some abstract methods sub-classes must implement.
+ */
+public abstract class GeneratorBase
+    extends JsonGenerator
+{
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    protected ObjectCodec _objectCodec;
+
+    /**
+     * Bit flag composed of bits that indicate which
+     * {@link com.fasterxml.jackson.core.JsonGenerator.Feature}s
+     * are enabled.
+     */
+    protected int _features;
+
+    /**
+     * Flag set to indicate that implicit conversion from number
+     * to JSON String is needed (as per
+     * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#WRITE_NUMBERS_AS_STRINGS}).
+     */
+    protected boolean _cfgNumbersAsStrings;
+    
+    /*
+    /**********************************************************
+    /* State
+    /**********************************************************
+     */
+
+    /**
+     * Object that keeps track of the current contextual state
+     * of the generator.
+     */
+    protected JsonWriteContext _writeContext;
+
+    /**
+     * Flag that indicates whether generator is closed or not. Gets
+     * set when it is closed by an explicit call
+     * ({@link #close}).
+     */
+    protected boolean _closed;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    protected GeneratorBase(int features, ObjectCodec codec)
+    {
+        super();
+        _features = features;
+        _writeContext = JsonWriteContext.createRootContext();
+        _objectCodec = codec;
+        _cfgNumbersAsStrings = isEnabled(Feature.WRITE_NUMBERS_AS_STRINGS);
+    }
+
+    /**
+     * Implemented with detection that tries to find "VERSION.txt" in same
+     * package as the implementation class.
+     */
+    @Override
+    public Version version() {
+        return VersionUtil.versionFor(getClass());
+    }
+    
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    @Override
+    public JsonGenerator enable(Feature f) {
+        _features |= f.getMask();
+        if (f == Feature.WRITE_NUMBERS_AS_STRINGS) {
+            _cfgNumbersAsStrings = true;
+        } else if (f == Feature.ESCAPE_NON_ASCII) {
+            setHighestNonEscapedChar(127);
+        }
+        return this;
+    }
+
+    @Override
+    public JsonGenerator disable(Feature f) {
+        _features &= ~f.getMask();
+        if (f == Feature.WRITE_NUMBERS_AS_STRINGS) {
+            _cfgNumbersAsStrings = false;
+        } else if (f == Feature.ESCAPE_NON_ASCII) {
+            setHighestNonEscapedChar(0);
+        }
+        return this;
+    }
+
+    //public JsonGenerator configure(Feature f, boolean state) { }
+
+    @Override
+    public final boolean isEnabled(Feature f) {
+        return (_features & f.getMask()) != 0;
+    }
+    
+    @Override
+    public JsonGenerator useDefaultPrettyPrinter() {
+        /* 28-Sep-2012, tatu: As per [Issue#84], should not override a
+         *  pretty printer if one already assigned.
+         */
+        if (getPrettyPrinter() != null) {
+            return this;
+        }
+        return setPrettyPrinter(new DefaultPrettyPrinter());
+    }
+    
+    @Override
+    public JsonGenerator setCodec(ObjectCodec oc) {
+        _objectCodec = oc;
+        return this;
+    }
+
+    @Override
+    public final ObjectCodec getCodec() { return _objectCodec; }
+
+    /*
+    /**********************************************************
+    /* Public API, accessors
+    /**********************************************************
+     */
+
+    /**
+     * Note: co-variant return type.
+     */
+    @Override
+    public final JsonWriteContext getOutputContext() { return _writeContext; }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, structural
+    /**********************************************************
+     */
+
+    //public void writeStartArray() throws IOException, JsonGenerationException
+    //public void writeEndArray() throws IOException, JsonGenerationException
+    //public void writeStartObject() throws IOException, JsonGenerationException
+    //public void writeEndObject() throws IOException, JsonGenerationException
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, textual
+    /**********************************************************
+     */
+
+    @Override
+    public void writeFieldName(SerializableString name) throws IOException, JsonGenerationException {
+        writeFieldName(name.getValue());
+    }
+    
+    //public abstract void writeString(String text) throws IOException, JsonGenerationException;
+
+    //public abstract void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException;
+
+    //public abstract void writeRaw(String text) throws IOException, JsonGenerationException;
+
+    //public abstract void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException;
+
+    @Override
+    public void writeString(SerializableString text) throws IOException, JsonGenerationException {
+        writeString(text.getValue());
+    }
+    
+    @Override
+    public void writeRawValue(String text) throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write raw value");
+        writeRaw(text);
+    }
+
+    @Override
+    public void writeRawValue(String text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write raw value");
+        writeRaw(text, offset, len);
+    }
+
+    @Override
+    public void writeRawValue(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write raw value");
+        writeRaw(text, offset, len);
+    }
+
+    @Override
+    public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength)
+        throws IOException, JsonGenerationException {
+        // Let's implement this as "unsupported" to make it easier to add new parser impls
+        _reportUnsupportedOperation();
+        return 0;
+    }
+
+    /*
+   /**********************************************************
+   /* Public API, write methods, primitive
+   /**********************************************************
+    */
+
+    // Not implemented at this level, added as placeholders
+
+     /*
+    public abstract void writeNumber(int i)
+    public abstract void writeNumber(long l)
+    public abstract void writeNumber(double d)
+    public abstract void writeNumber(float f)
+    public abstract void writeNumber(BigDecimal dec)
+    public abstract void writeBoolean(boolean state)
+    public abstract void writeNull()
+    */
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, POJOs, trees
+    /**********************************************************
+     */
+
+    @Override
+    public void writeObject(Object value)
+        throws IOException, JsonProcessingException
+    {
+        if (value == null) {
+            // important: call method that does check value write:
+            writeNull();
+        } else {
+            /* 02-Mar-2009, tatu: we are NOT to call _verifyValueWrite here,
+             *   because that will be done when codec actually serializes
+             *   contained POJO. If we did call it it would advance state
+             *   causing exception later on
+             */
+            if (_objectCodec != null) {
+                _objectCodec.writeValue(this, value);
+                return;
+            }
+            _writeSimpleObject(value);
+        }
+    }
+
+    @Override
+    public void writeTree(TreeNode rootNode)
+        throws IOException, JsonProcessingException
+    {
+        // As with 'writeObject()', we are not check if write would work
+        if (rootNode == null) {
+            writeNull();
+        } else {
+            if (_objectCodec == null) {
+                throw new IllegalStateException("No ObjectCodec defined");
+            }
+            _objectCodec.writeValue(this, rootNode);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, low-level output handling
+    /**********************************************************
+     */
+
+    @Override
+    public abstract void flush() throws IOException;
+
+    @Override
+    public void close() throws IOException
+    {
+        _closed = true;
+    }
+
+    @Override
+    public boolean isClosed() { return _closed; }
+
+    /*
+    /**********************************************************
+    /* Public API, copy-through methods
+    /**********************************************************
+     */
+
+    @Override
+    public final void copyCurrentEvent(JsonParser jp)
+        throws IOException, JsonProcessingException
+    {
+        JsonToken t = jp.getCurrentToken();
+        // sanity check; what to do?
+        if (t == null) {
+            _reportError("No current event to copy");
+        }
+        switch(t) {
+        case START_OBJECT:
+            writeStartObject();
+            break;
+        case END_OBJECT:
+            writeEndObject();
+            break;
+        case START_ARRAY:
+            writeStartArray();
+            break;
+        case END_ARRAY:
+            writeEndArray();
+            break;
+        case FIELD_NAME:
+            writeFieldName(jp.getCurrentName());
+            break;
+        case VALUE_STRING:
+            if (jp.hasTextCharacters()) {
+                writeString(jp.getTextCharacters(), jp.getTextOffset(), jp.getTextLength());
+            } else {
+                writeString(jp.getText());
+            }
+            break;
+        case VALUE_NUMBER_INT:
+            switch (jp.getNumberType()) {
+            case INT:
+                writeNumber(jp.getIntValue());
+                break;
+            case BIG_INTEGER:
+                writeNumber(jp.getBigIntegerValue());
+                break;
+            default:
+                writeNumber(jp.getLongValue());
+            }
+            break;
+        case VALUE_NUMBER_FLOAT:
+            switch (jp.getNumberType()) {
+            case BIG_DECIMAL:
+                writeNumber(jp.getDecimalValue());
+                break;
+            case FLOAT:
+                writeNumber(jp.getFloatValue());
+                break;
+            default:
+                writeNumber(jp.getDoubleValue());
+            }
+            break;
+        case VALUE_TRUE:
+            writeBoolean(true);
+            break;
+        case VALUE_FALSE:
+            writeBoolean(false);
+            break;
+        case VALUE_NULL:
+            writeNull();
+            break;
+        case VALUE_EMBEDDED_OBJECT:
+            writeObject(jp.getEmbeddedObject());
+            break;
+        default:
+            _throwInternal();
+        }
+    }
+
+    @Override
+    public final void copyCurrentStructure(JsonParser jp)
+        throws IOException, JsonProcessingException
+    {
+        JsonToken t = jp.getCurrentToken();
+
+        // Let's handle field-name separately first
+        if (t == JsonToken.FIELD_NAME) {
+            writeFieldName(jp.getCurrentName());
+            t = jp.nextToken();
+            // fall-through to copy the associated value
+        }
+
+        switch (t) {
+        case START_ARRAY:
+            writeStartArray();
+            while (jp.nextToken() != JsonToken.END_ARRAY) {
+                copyCurrentStructure(jp);
+            }
+            writeEndArray();
+            break;
+        case START_OBJECT:
+            writeStartObject();
+            while (jp.nextToken() != JsonToken.END_OBJECT) {
+                copyCurrentStructure(jp);
+            }
+            writeEndObject();
+            break;
+        default: // others are simple:
+            copyCurrentEvent(jp);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Package methods for this, sub-classes
+    /**********************************************************
+     */
+
+    /**
+     * Method called to release any buffers generator may be holding,
+     * once generator is being closed.
+     */
+    protected abstract void _releaseBuffers();
+
+    /**
+     * Method called before trying to write a value (scalar or structured),
+     * to verify that this is legal in current output state, as well as to
+     * output separators if and as necessary.
+     * 
+     * @param typeMsg Additional message used for generating exception message
+     *   if value output is NOT legal in current generator output state.
+     */
+    protected abstract void _verifyValueWrite(String typeMsg)
+        throws IOException, JsonGenerationException;
+
+    /**
+     * Helper method used for constructing and throwing
+     * {@link JsonGenerationException} with given base message.
+     *<p>
+     * Note that sub-classes may override this method to add more detail
+     * or use a {@link JsonGenerationException} sub-class.
+     */
+    protected void _reportError(String msg)
+        throws JsonGenerationException
+    {
+        throw new JsonGenerationException(msg);
+    }
+
+    /**
+     * Helper method to try to call appropriate write method for given
+     * untyped Object. At this point, no structural conversions should be done,
+     * only simple basic types are to be coerced as necessary.
+     *
+     * @param value Non-null value to write
+     */
+    protected void _writeSimpleObject(Object value) 
+        throws IOException, JsonGenerationException
+    {
+        /* 31-Dec-2009, tatu: Actually, we could just handle some basic
+         *    types even without codec. This can improve interoperability,
+         *    and specifically help with TokenBuffer.
+         */
+        if (value == null) {
+            writeNull();
+            return;
+        }
+        if (value instanceof String) {
+            writeString((String) value);
+            return;
+        }
+        if (value instanceof Number) {
+            Number n = (Number) value;
+            if (n instanceof Integer) {
+                writeNumber(n.intValue());
+                return;
+            } else if (n instanceof Long) {
+                writeNumber(n.longValue());
+                return;
+            } else if (n instanceof Double) {
+                writeNumber(n.doubleValue());
+                return;
+            } else if (n instanceof Float) {
+                writeNumber(n.floatValue());
+                return;
+            } else if (n instanceof Short) {
+                writeNumber(n.shortValue());
+                return;
+            } else if (n instanceof Byte) {
+                writeNumber(n.byteValue());
+                return;
+            } else if (n instanceof BigInteger) {
+                writeNumber((BigInteger) n);
+                return;
+            } else if (n instanceof BigDecimal) {
+                writeNumber((BigDecimal) n);
+                return;
+                
+            // then Atomic types
+                
+            } else if (n instanceof AtomicInteger) {
+                writeNumber(((AtomicInteger) n).get());
+                return;
+            } else if (n instanceof AtomicLong) {
+                writeNumber(((AtomicLong) n).get());
+                return;
+            }
+        } else if (value instanceof byte[]) {
+            writeBinary((byte[]) value);
+            return;
+        } else if (value instanceof Boolean) {
+            writeBoolean((Boolean) value);
+            return;
+        } else if (value instanceof AtomicBoolean) {
+            writeBoolean(((AtomicBoolean) value).get());
+            return;
+        }
+        throw new IllegalStateException("No ObjectCodec defined for the generator, can only serialize simple wrapper types (type passed "
+                +value.getClass().getName()+")");
+    }    
+
+    protected final void _throwInternal() {
+        VersionUtil.throwInternal();
+    }
+
+    protected void _reportUnsupportedOperation() {
+        throw new UnsupportedOperationException("Operation not supported by generator of type "+getClass().getName());
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
new file mode 100644
index 0000000..d62bbf3
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
@@ -0,0 +1,1094 @@
+package com.fasterxml.jackson.core.base;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.io.NumberInput;
+import com.fasterxml.jackson.core.json.JsonReadContext;
+import com.fasterxml.jackson.core.json.PackageVersion;
+import com.fasterxml.jackson.core.util.ByteArrayBuilder;
+import com.fasterxml.jackson.core.util.TextBuffer;
+
+/**
+ * Intermediate base class used by all Jackson {@link JsonParser}
+ * implementations. Contains most common things that are independent
+ * of actual underlying input source
+ *
+ * @author Tatu Saloranta
+ */
+public abstract class ParserBase
+    extends ParserMinimalBase
+{
+    /*
+    /**********************************************************
+    /* Generic I/O state
+    /**********************************************************
+     */
+
+    /**
+     * I/O context for this reader. It handles buffer allocation
+     * for the reader.
+     */
+    final protected IOContext _ioContext;
+
+    /**
+     * Flag that indicates whether parser is closed or not. Gets
+     * set when parser is either closed by explicit call
+     * ({@link #close}) or when end-of-input is reached.
+     */
+    protected boolean _closed;
+
+    /*
+    /**********************************************************
+    /* Current input data
+    /**********************************************************
+     */
+
+    // Note: type of actual buffer depends on sub-class, can't include
+
+    /**
+     * Pointer to next available character in buffer
+     */
+    protected int _inputPtr = 0;
+
+    /**
+     * Index of character after last available one in the buffer.
+     */
+    protected int _inputEnd = 0;
+    
+    /*
+    /**********************************************************
+    /* Current input location information
+    /**********************************************************
+     */
+
+    /**
+     * Number of characters/bytes that were contained in previous blocks
+     * (blocks that were already processed prior to the current buffer).
+     */
+    protected long _currInputProcessed = 0L;
+
+    /**
+     * Current row location of current point in input buffer, starting
+     * from 1, if available.
+     */
+    protected int _currInputRow = 1;
+
+    /**
+     * Current index of the first character of the current row in input
+     * buffer. Needed to calculate column position, if necessary; benefit
+     * of not having column itself is that this only has to be updated
+     * once per line.
+     */
+    protected int _currInputRowStart = 0;
+
+    /*
+    /**********************************************************
+    /* Information about starting location of event
+    /* Reader is pointing to; updated on-demand
+    /**********************************************************
+     */
+
+    // // // Location info at point when current token was started
+
+    /**
+     * Total number of bytes/characters read before start of current token.
+     * For big (gigabyte-sized) sizes are possible, needs to be long,
+     * unlike pointers and sizes related to in-memory buffers.
+     */
+    protected long _tokenInputTotal = 0; 
+
+    /**
+     * Input row on which current token starts, 1-based
+     */
+    protected int _tokenInputRow = 1;
+
+    /**
+     * Column on input row that current token starts; 0-based (although
+     * in the end it'll be converted to 1-based)
+     */
+    protected int _tokenInputCol = 0;
+
+    /*
+    /**********************************************************
+    /* Parsing state
+    /**********************************************************
+     */
+
+    /**
+     * Information about parser context, context in which
+     * the next token is to be parsed (root, array, object).
+     */
+    protected JsonReadContext _parsingContext;
+    
+    /**
+     * Secondary token related to the next token after current one;
+     * used if its type is known. This may be value token that
+     * follows FIELD_NAME, for example.
+     */
+    protected JsonToken _nextToken;
+
+    /*
+    /**********************************************************
+    /* Buffer(s) for local name(s) and text content
+    /**********************************************************
+     */
+
+    /**
+     * Buffer that contains contents of String values, including
+     * field names if necessary (name split across boundary,
+     * contains escape sequence, or access needed to char array)
+     */
+    protected final TextBuffer _textBuffer;
+
+    /**
+     * Temporary buffer that is needed if field name is accessed
+     * using {@link #getTextCharacters} method (instead of String
+     * returning alternatives)
+     */
+    protected char[] _nameCopyBuffer = null;
+
+    /**
+     * Flag set to indicate whether the field name is available
+     * from the name copy buffer or not (in addition to its String
+     * representation  being available via read context)
+     */
+    protected boolean _nameCopied = false;
+
+    /**
+     * ByteArrayBuilder is needed if 'getBinaryValue' is called. If so,
+     * we better reuse it for remainder of content.
+     */
+    protected ByteArrayBuilder _byteArrayBuilder = null;
+
+    /**
+     * We will hold on to decoded binary data, for duration of
+     * current event, so that multiple calls to
+     * {@link #getBinaryValue} will not need to decode data more
+     * than once.
+     */
+    protected byte[] _binaryValue;
+
+    /*
+    /**********************************************************
+    /* Constants and fields of former 'JsonNumericParserBase'
+    /**********************************************************
+     */
+
+    final protected static int NR_UNKNOWN = 0;
+
+    // First, integer types
+
+    final protected static int NR_INT = 0x0001;
+    final protected static int NR_LONG = 0x0002;
+    final protected static int NR_BIGINT = 0x0004;
+
+    // And then floating point types
+
+    final protected static int NR_DOUBLE = 0x008;
+    final protected static int NR_BIGDECIMAL = 0x0010;
+
+    // Also, we need some numeric constants
+
+    final static BigInteger BI_MIN_INT = BigInteger.valueOf(Integer.MIN_VALUE);
+    final static BigInteger BI_MAX_INT = BigInteger.valueOf(Integer.MAX_VALUE);
+
+    final static BigInteger BI_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
+    final static BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
+    
+    final static BigDecimal BD_MIN_LONG = new BigDecimal(BI_MIN_LONG);
+    final static BigDecimal BD_MAX_LONG = new BigDecimal(BI_MAX_LONG);
+
+    final static BigDecimal BD_MIN_INT = new BigDecimal(BI_MIN_INT);
+    final static BigDecimal BD_MAX_INT = new BigDecimal(BI_MAX_INT);
+
+    final static long MIN_INT_L = (long) Integer.MIN_VALUE;
+    final static long MAX_INT_L = (long) Integer.MAX_VALUE;
+
+    // These are not very accurate, but have to do... (for bounds checks)
+
+    final static double MIN_LONG_D = (double) Long.MIN_VALUE;
+    final static double MAX_LONG_D = (double) Long.MAX_VALUE;
+
+    final static double MIN_INT_D = (double) Integer.MIN_VALUE;
+    final static double MAX_INT_D = (double) Integer.MAX_VALUE;
+    
+    
+    // Digits, numeric
+    final protected static int INT_0 = '0';
+    final protected static int INT_1 = '1';
+    final protected static int INT_2 = '2';
+    final protected static int INT_3 = '3';
+    final protected static int INT_4 = '4';
+    final protected static int INT_5 = '5';
+    final protected static int INT_6 = '6';
+    final protected static int INT_7 = '7';
+    final protected static int INT_8 = '8';
+    final protected static int INT_9 = '9';
+
+    final protected static int INT_MINUS = '-';
+    final protected static int INT_PLUS = '+';
+    final protected static int INT_DECIMAL_POINT = '.';
+
+    final protected static int INT_e = 'e';
+    final protected static int INT_E = 'E';
+
+    final protected static char CHAR_NULL = '\0';
+    
+    // Numeric value holders: multiple fields used for
+    // for efficiency
+
+    /**
+     * Bitfield that indicates which numeric representations
+     * have been calculated for the current type
+     */
+    protected int _numTypesValid = NR_UNKNOWN;
+
+    // First primitives
+
+    protected int _numberInt;
+
+    protected long _numberLong;
+
+    protected double _numberDouble;
+
+    // And then object types
+
+    protected BigInteger _numberBigInt;
+
+    protected BigDecimal _numberBigDecimal;
+
+    // And then other information about value itself
+
+    /**
+     * Flag that indicates whether numeric value has a negative
+     * value. That is, whether its textual representation starts
+     * with minus character.
+     */
+    protected boolean _numberNegative;
+
+    /**
+     * Length of integer part of the number, in characters
+     */
+    protected int _intLength;
+
+    /**
+     * Length of the fractional part (not including decimal
+     * point or exponent), in characters.
+     * Not used for  pure integer values.
+     */
+    protected int _fractLength;
+
+    /**
+     * Length of the exponent part of the number, if any, not
+     * including 'e' marker or sign, just digits. 
+     * Not used for  pure integer values.
+     */
+    protected int _expLength;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    protected ParserBase(IOContext ctxt, int features)
+    {
+        super();
+        _features = features;
+        _ioContext = ctxt;
+        _textBuffer = ctxt.constructTextBuffer();
+        _parsingContext = JsonReadContext.createRootContext();
+    }
+
+    @Override
+    public Version version() {
+        return PackageVersion.VERSION;
+    }
+
+    /*
+    /**********************************************************
+    /* JsonParser impl
+    /**********************************************************
+     */
+    
+    /**
+     * Method that can be called to get the name associated with
+     * the current event.
+     */
+    @Override
+    public String getCurrentName()
+        throws IOException, JsonParseException
+    {
+        // [JACKSON-395]: start markers require information from parent
+        if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
+            JsonReadContext parent = _parsingContext.getParent();
+            return parent.getCurrentName();
+        }
+        return _parsingContext.getCurrentName();
+    }
+
+    @Override
+    public void overrideCurrentName(String name)
+    {
+        // Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
+        JsonReadContext ctxt = _parsingContext;
+        if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
+            ctxt = ctxt.getParent();
+        }
+        ctxt.setCurrentName(name);
+    }
+    
+    @Override
+    public void close() throws IOException
+    {
+        if (!_closed) {
+            _closed = true;
+            try {
+                _closeInput();
+            } finally {
+                // as per [JACKSON-324], do in finally block
+                // Also, internal buffer(s) can now be released as well
+                _releaseBuffers();
+            }
+        }
+    }
+
+    @Override
+    public boolean isClosed() { return _closed; }
+
+    @Override
+    public JsonReadContext getParsingContext()
+    {
+        return _parsingContext;
+    }
+
+    /**
+     * Method that return the <b>starting</b> location of the current
+     * token; that is, position of the first character from input
+     * that starts the current token.
+     */
+    @Override
+    public JsonLocation getTokenLocation()
+    {
+        return new JsonLocation(_ioContext.getSourceReference(),
+                                getTokenCharacterOffset(),
+                                getTokenLineNr(),
+                                getTokenColumnNr());
+    }
+
+    /**
+     * Method that returns location of the last processed character;
+     * usually for error reporting purposes
+     */
+    @Override
+    public JsonLocation getCurrentLocation()
+    {
+        int col = _inputPtr - _currInputRowStart + 1; // 1-based
+        return new JsonLocation(_ioContext.getSourceReference(),
+                                _currInputProcessed + _inputPtr - 1,
+                                _currInputRow, col);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, text and similar
+    /**********************************************************
+     */
+
+    @Override
+    public boolean hasTextCharacters()
+    {
+        if (_currToken == JsonToken.VALUE_STRING) {
+            return true; // usually true
+        }        
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return _nameCopied;
+        }
+        return false;
+    }
+
+    // No embedded objects with base impl...
+    @Override
+    public Object getEmbeddedObject() throws IOException, JsonParseException {
+        return null;
+    }
+    
+    /*
+    /**********************************************************
+    /* Public low-level accessors
+    /**********************************************************
+     */
+
+    public long getTokenCharacterOffset() { return _tokenInputTotal; }
+    public int getTokenLineNr() { return _tokenInputRow; }
+    public int getTokenColumnNr() {
+        // note: value of -1 means "not available"; otherwise convert from 0-based to 1-based
+        int col = _tokenInputCol;
+        return (col < 0) ? col : (col + 1);
+    }
+
+    /*
+    /**********************************************************
+    /* Low-level reading, other
+    /**********************************************************
+     */
+
+    protected final void loadMoreGuaranteed()
+        throws IOException
+    {
+        if (!loadMore()) {
+            _reportInvalidEOF();
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Abstract methods needed from sub-classes
+    /**********************************************************
+     */
+
+    protected abstract boolean loadMore() throws IOException;
+    
+    protected abstract void _finishString() throws IOException, JsonParseException;
+
+    protected abstract void _closeInput() throws IOException;
+    
+    /*
+    /**********************************************************
+    /* Low-level reading, other
+    /**********************************************************
+     */
+
+    /**
+     * Method called to release internal buffers owned by the base
+     * reader. This may be called along with {@link #_closeInput} (for
+     * example, when explicitly closing this reader instance), or
+     * separately (if need be).
+     */
+    protected void _releaseBuffers() throws IOException
+    {
+        _textBuffer.releaseBuffers();
+        char[] buf = _nameCopyBuffer;
+        if (buf != null) {
+            _nameCopyBuffer = null;
+            _ioContext.releaseNameCopyBuffer(buf);
+        }
+    }
+    
+    /**
+     * Method called when an EOF is encountered between tokens.
+     * If so, it may be a legitimate EOF, but only iff there
+     * is no open non-root context.
+     */
+    @Override
+    protected void _handleEOF() throws JsonParseException
+    {
+        if (!_parsingContext.inRoot()) {
+            _reportInvalidEOF(": expected close marker for "+_parsingContext.getTypeDesc()+" (from "+_parsingContext.getStartLocation(_ioContext.getSourceReference())+")");
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal/package methods: Error reporting
+    /**********************************************************
+     */
+    
+    protected void _reportMismatchedEndMarker(int actCh, char expCh)
+        throws JsonParseException
+    {
+        String startDesc = ""+_parsingContext.getStartLocation(_ioContext.getSourceReference());
+        _reportError("Unexpected close marker '"+((char) actCh)+"': expected '"+expCh+"' (for "+_parsingContext.getTypeDesc()+" starting at "+startDesc+")");
+    }
+
+    /*
+    /**********************************************************
+    /* Internal/package methods: shared/reusable builders
+    /**********************************************************
+     */
+    
+    public ByteArrayBuilder _getByteArrayBuilder()
+    {
+        if (_byteArrayBuilder == null) {
+            _byteArrayBuilder = new ByteArrayBuilder();
+        } else {
+            _byteArrayBuilder.reset();
+        }
+        return _byteArrayBuilder;
+    }
+
+    /*
+    /**********************************************************
+    /* Methods from former JsonNumericParserBase
+    /**********************************************************
+     */
+
+    // // // Life-cycle of number-parsing
+    
+    protected final JsonToken reset(boolean negative, int intLen, int fractLen, int expLen)
+    {
+        if (fractLen < 1 && expLen < 1) { // integer
+            return resetInt(negative, intLen);
+        }
+        return resetFloat(negative, intLen, fractLen, expLen);
+    }
+        
+    protected final JsonToken resetInt(boolean negative, int intLen)
+    {
+        _numberNegative = negative;
+        _intLength = intLen;
+        _fractLength = 0;
+        _expLength = 0;
+        _numTypesValid = NR_UNKNOWN; // to force parsing
+        return JsonToken.VALUE_NUMBER_INT;
+    }
+    
+    protected final JsonToken resetFloat(boolean negative, int intLen, int fractLen, int expLen)
+    {
+        _numberNegative = negative;
+        _intLength = intLen;
+        _fractLength = fractLen;
+        _expLength = expLen;
+        _numTypesValid = NR_UNKNOWN; // to force parsing
+        return JsonToken.VALUE_NUMBER_FLOAT;
+    }
+    
+    protected final JsonToken resetAsNaN(String valueStr, double value)
+    {
+        _textBuffer.resetWithString(valueStr);
+        _numberDouble = value;
+        _numTypesValid = NR_DOUBLE;
+        return JsonToken.VALUE_NUMBER_FLOAT;
+    }
+    
+    /*
+    /**********************************************************
+    /* Numeric accessors of public API
+    /**********************************************************
+     */
+    
+    @Override
+    public Number getNumberValue() throws IOException, JsonParseException
+    {
+        if (_numTypesValid == NR_UNKNOWN) {
+            _parseNumericValue(NR_UNKNOWN); // will also check event type
+        }
+        // Separate types for int types
+        if (_currToken == JsonToken.VALUE_NUMBER_INT) {
+            if ((_numTypesValid & NR_INT) != 0) {
+                return _numberInt;
+            }
+            if ((_numTypesValid & NR_LONG) != 0) {
+                return _numberLong;
+            }
+            if ((_numTypesValid & NR_BIGINT) != 0) {
+                return _numberBigInt;
+            }
+            // Shouldn't get this far but if we do
+            return _numberBigDecimal;
+        }
+    
+        /* And then floating point types. But here optimal type
+         * needs to be big decimal, to avoid losing any data?
+         */
+        if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
+            return _numberBigDecimal;
+        }
+        if ((_numTypesValid & NR_DOUBLE) == 0) { // sanity check
+            _throwInternal();
+        }
+        return _numberDouble;
+    }
+    
+    @Override
+    public NumberType getNumberType() throws IOException, JsonParseException
+    {
+        if (_numTypesValid == NR_UNKNOWN) {
+            _parseNumericValue(NR_UNKNOWN); // will also check event type
+        }
+        if (_currToken == JsonToken.VALUE_NUMBER_INT) {
+            if ((_numTypesValid & NR_INT) != 0) {
+                return NumberType.INT;
+            }
+            if ((_numTypesValid & NR_LONG) != 0) {
+                return NumberType.LONG;
+            }
+            return NumberType.BIG_INTEGER;
+        }
+    
+        /* And then floating point types. Here optimal type
+         * needs to be big decimal, to avoid losing any data?
+         * However... using BD is slow, so let's allow returning
+         * double as type if no explicit call has been made to access
+         * data as BD?
+         */
+        if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
+            return NumberType.BIG_DECIMAL;
+        }
+        return NumberType.DOUBLE;
+    }
+    
+    @Override
+    public int getIntValue() throws IOException, JsonParseException
+    {
+        if ((_numTypesValid & NR_INT) == 0) {
+            if (_numTypesValid == NR_UNKNOWN) { // not parsed at all
+                _parseNumericValue(NR_INT); // will also check event type
+            }
+            if ((_numTypesValid & NR_INT) == 0) { // wasn't an int natively?
+                convertNumberToInt(); // let's make it so, if possible
+            }
+        }
+        return _numberInt;
+    }
+    
+    @Override
+    public long getLongValue() throws IOException, JsonParseException
+    {
+        if ((_numTypesValid & NR_LONG) == 0) {
+            if (_numTypesValid == NR_UNKNOWN) {
+                _parseNumericValue(NR_LONG);
+            }
+            if ((_numTypesValid & NR_LONG) == 0) {
+                convertNumberToLong();
+            }
+        }
+        return _numberLong;
+    }
+    
+    @Override
+    public BigInteger getBigIntegerValue() throws IOException, JsonParseException
+    {
+        if ((_numTypesValid & NR_BIGINT) == 0) {
+            if (_numTypesValid == NR_UNKNOWN) {
+                _parseNumericValue(NR_BIGINT);
+            }
+            if ((_numTypesValid & NR_BIGINT) == 0) {
+                convertNumberToBigInteger();
+            }
+        }
+        return _numberBigInt;
+    }
+    
+    @Override
+    public float getFloatValue() throws IOException, JsonParseException
+    {
+        double value = getDoubleValue();
+        /* 22-Jan-2009, tatu: Bounds/range checks would be tricky
+         *   here, so let's not bother even trying...
+         */
+        /*
+        if (value < -Float.MAX_VALUE || value > MAX_FLOAT_D) {
+            _reportError("Numeric value ("+getText()+") out of range of Java float");
+        }
+        */
+        return (float) value;
+    }
+    
+    @Override
+    public double getDoubleValue() throws IOException, JsonParseException
+    {
+        if ((_numTypesValid & NR_DOUBLE) == 0) {
+            if (_numTypesValid == NR_UNKNOWN) {
+                _parseNumericValue(NR_DOUBLE);
+            }
+            if ((_numTypesValid & NR_DOUBLE) == 0) {
+                convertNumberToDouble();
+            }
+        }
+        return _numberDouble;
+    }
+    
+    @Override
+    public BigDecimal getDecimalValue() throws IOException, JsonParseException
+    {
+        if ((_numTypesValid & NR_BIGDECIMAL) == 0) {
+            if (_numTypesValid == NR_UNKNOWN) {
+                _parseNumericValue(NR_BIGDECIMAL);
+            }
+            if ((_numTypesValid & NR_BIGDECIMAL) == 0) {
+                convertNumberToBigDecimal();
+            }
+        }
+        return _numberBigDecimal;
+    }
+
+    /*
+    /**********************************************************
+    /* Conversion from textual to numeric representation
+    /**********************************************************
+     */
+    
+    /**
+     * Method that will parse actual numeric value out of a syntactically
+     * valid number value. Type it will parse into depends on whether
+     * it is a floating point number, as well as its magnitude: smallest
+     * legal type (of ones available) is used for efficiency.
+     *
+     * @param expType Numeric type that we will immediately need, if any;
+     *   mostly necessary to optimize handling of floating point numbers
+     */
+    protected void _parseNumericValue(int expType)
+        throws IOException, JsonParseException
+    {
+        // Int or float?
+        if (_currToken == JsonToken.VALUE_NUMBER_INT) {
+            char[] buf = _textBuffer.getTextBuffer();
+            int offset = _textBuffer.getTextOffset();
+            int len = _intLength;
+            if (_numberNegative) {
+                ++offset;
+            }
+            if (len <= 9) { // definitely fits in int
+                int i = NumberInput.parseInt(buf, offset, len);
+                _numberInt = _numberNegative ? -i : i;
+                _numTypesValid = NR_INT;
+                return;
+            }
+            if (len <= 18) { // definitely fits AND is easy to parse using 2 int parse calls
+                long l = NumberInput.parseLong(buf, offset, len);
+                if (_numberNegative) {
+                    l = -l;
+                }
+                // [JACKSON-230] Could still fit in int, need to check
+                if (len == 10) {
+                    if (_numberNegative) {
+                        if (l >= MIN_INT_L) {
+                            _numberInt = (int) l;
+                            _numTypesValid = NR_INT;
+                            return;
+                        }
+                    } else {
+                        if (l <= MAX_INT_L) {
+                            _numberInt = (int) l;
+                            _numTypesValid = NR_INT;
+                            return;
+                        }
+                    }
+                }
+                _numberLong = l;
+                _numTypesValid = NR_LONG;
+                return;
+            }
+            _parseSlowIntValue(expType, buf, offset, len);
+            return;
+        }
+        if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
+            _parseSlowFloatValue(expType);
+            return;
+        }
+        _reportError("Current token ("+_currToken+") not numeric, can not use numeric value accessors");
+    }
+    
+    private void _parseSlowFloatValue(int expType)
+        throws IOException, JsonParseException
+    {
+        /* Nope: floating point. Here we need to be careful to get
+         * optimal parsing strategy: choice is between accurate but
+         * slow (BigDecimal) and lossy but fast (Double). For now
+         * let's only use BD when explicitly requested -- it can
+         * still be constructed correctly at any point since we do
+         * retain textual representation
+         */
+        try {
+            if (expType == NR_BIGDECIMAL) {
+                _numberBigDecimal = _textBuffer.contentsAsDecimal();
+                _numTypesValid = NR_BIGDECIMAL;
+            } else {
+                // Otherwise double has to do
+                _numberDouble = _textBuffer.contentsAsDouble();
+                _numTypesValid = NR_DOUBLE;
+            }
+        } catch (NumberFormatException nex) {
+            // Can this ever occur? Due to overflow, maybe?
+            _wrapError("Malformed numeric value '"+_textBuffer.contentsAsString()+"'", nex);
+        }
+    }
+    
+    private void _parseSlowIntValue(int expType, char[] buf, int offset, int len)
+        throws IOException, JsonParseException
+    {
+        String numStr = _textBuffer.contentsAsString();
+        try {
+            // [JACKSON-230] Some long cases still...
+            if (NumberInput.inLongRange(buf, offset, len, _numberNegative)) {
+                // Probably faster to construct a String, call parse, than to use BigInteger
+                _numberLong = Long.parseLong(numStr);
+                _numTypesValid = NR_LONG;
+            } else {
+                // nope, need the heavy guns... (rare case)
+                _numberBigInt = new BigInteger(numStr);
+                _numTypesValid = NR_BIGINT;
+            }
+        } catch (NumberFormatException nex) {
+            // Can this ever occur? Due to overflow, maybe?
+            _wrapError("Malformed numeric value '"+numStr+"'", nex);
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Numeric conversions
+    /**********************************************************
+     */    
+    
+    protected void convertNumberToInt()
+        throws IOException, JsonParseException
+    {
+        // First, converting from long ought to be easy
+        if ((_numTypesValid & NR_LONG) != 0) {
+            // Let's verify it's lossless conversion by simple roundtrip
+            int result = (int) _numberLong;
+            if (((long) result) != _numberLong) {
+                _reportError("Numeric value ("+getText()+") out of range of int");
+            }
+            _numberInt = result;
+        } else if ((_numTypesValid & NR_BIGINT) != 0) {
+            if (BI_MIN_INT.compareTo(_numberBigInt) > 0 
+                    || BI_MAX_INT.compareTo(_numberBigInt) < 0) {
+                reportOverflowInt();
+            }
+            _numberInt = _numberBigInt.intValue();
+        } else if ((_numTypesValid & NR_DOUBLE) != 0) {
+            // Need to check boundaries
+            if (_numberDouble < MIN_INT_D || _numberDouble > MAX_INT_D) {
+                reportOverflowInt();
+            }
+            _numberInt = (int) _numberDouble;
+        } else if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
+            if (BD_MIN_INT.compareTo(_numberBigDecimal) > 0 
+                || BD_MAX_INT.compareTo(_numberBigDecimal) < 0) {
+                reportOverflowInt();
+            }
+            _numberInt = _numberBigDecimal.intValue();
+        } else {
+            _throwInternal();
+        }
+        _numTypesValid |= NR_INT;
+    }
+    
+    protected void convertNumberToLong()
+        throws IOException, JsonParseException
+    {
+        if ((_numTypesValid & NR_INT) != 0) {
+            _numberLong = (long) _numberInt;
+        } else if ((_numTypesValid & NR_BIGINT) != 0) {
+            if (BI_MIN_LONG.compareTo(_numberBigInt) > 0 
+                    || BI_MAX_LONG.compareTo(_numberBigInt) < 0) {
+                reportOverflowLong();
+            }
+            _numberLong = _numberBigInt.longValue();
+        } else if ((_numTypesValid & NR_DOUBLE) != 0) {
+            // Need to check boundaries
+            if (_numberDouble < MIN_LONG_D || _numberDouble > MAX_LONG_D) {
+                reportOverflowLong();
+            }
+            _numberLong = (long) _numberDouble;
+        } else if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
+            if (BD_MIN_LONG.compareTo(_numberBigDecimal) > 0 
+                || BD_MAX_LONG.compareTo(_numberBigDecimal) < 0) {
+                reportOverflowLong();
+            }
+            _numberLong = _numberBigDecimal.longValue();
+        } else {
+            _throwInternal();
+        }
+        _numTypesValid |= NR_LONG;
+    }
+    
+    protected void convertNumberToBigInteger()
+        throws IOException, JsonParseException
+    {
+        if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
+            // here it'll just get truncated, no exceptions thrown
+            _numberBigInt = _numberBigDecimal.toBigInteger();
+        } else if ((_numTypesValid & NR_LONG) != 0) {
+            _numberBigInt = BigInteger.valueOf(_numberLong);
+        } else if ((_numTypesValid & NR_INT) != 0) {
+            _numberBigInt = BigInteger.valueOf(_numberInt);
+        } else if ((_numTypesValid & NR_DOUBLE) != 0) {
+            _numberBigInt = BigDecimal.valueOf(_numberDouble).toBigInteger();
+        } else {
+            _throwInternal();
+        }
+        _numTypesValid |= NR_BIGINT;
+    }
+    
+    protected void convertNumberToDouble()
+        throws IOException, JsonParseException
+    {
+        /* 05-Aug-2008, tatus: Important note: this MUST start with
+         *   more accurate representations, since we don't know which
+         *   value is the original one (others get generated when
+         *   requested)
+         */
+    
+        if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
+            _numberDouble = _numberBigDecimal.doubleValue();
+        } else if ((_numTypesValid & NR_BIGINT) != 0) {
+            _numberDouble = _numberBigInt.doubleValue();
+        } else if ((_numTypesValid & NR_LONG) != 0) {
+            _numberDouble = (double) _numberLong;
+        } else if ((_numTypesValid & NR_INT) != 0) {
+            _numberDouble = (double) _numberInt;
+        } else {
+            _throwInternal();
+        }
+        _numTypesValid |= NR_DOUBLE;
+    }
+    
+    protected void convertNumberToBigDecimal()
+        throws IOException, JsonParseException
+    {
+        /* 05-Aug-2008, tatus: Important note: this MUST start with
+         *   more accurate representations, since we don't know which
+         *   value is the original one (others get generated when
+         *   requested)
+         */
+    
+        if ((_numTypesValid & NR_DOUBLE) != 0) {
+            /* Let's actually parse from String representation,
+             * to avoid rounding errors that non-decimal floating operations
+             * would incur
+             */
+            _numberBigDecimal = new BigDecimal(getText());
+        } else if ((_numTypesValid & NR_BIGINT) != 0) {
+            _numberBigDecimal = new BigDecimal(_numberBigInt);
+        } else if ((_numTypesValid & NR_LONG) != 0) {
+            _numberBigDecimal = BigDecimal.valueOf(_numberLong);
+        } else if ((_numTypesValid & NR_INT) != 0) {
+            _numberBigDecimal = BigDecimal.valueOf((long) _numberInt);
+        } else {
+            _throwInternal();
+        }
+        _numTypesValid |= NR_BIGDECIMAL;
+    }
+    
+    /*
+    /**********************************************************
+    /* Number handling exceptions
+    /**********************************************************
+     */    
+    
+    protected void reportUnexpectedNumberChar(int ch, String comment)
+        throws JsonParseException
+    {
+        String msg = "Unexpected character ("+_getCharDesc(ch)+") in numeric value";
+        if (comment != null) {
+            msg += ": "+comment;
+        }
+        _reportError(msg);
+    }
+    
+    protected void reportInvalidNumber(String msg)
+        throws JsonParseException
+    {
+        _reportError("Invalid numeric value: "+msg);
+    }
+    
+    protected void reportOverflowInt()
+        throws IOException, JsonParseException
+    {
+        _reportError("Numeric value ("+getText()+") out of range of int ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")");
+    }
+    
+    protected void reportOverflowLong()
+        throws IOException, JsonParseException
+    {
+        _reportError("Numeric value ("+getText()+") out of range of long ("+Long.MIN_VALUE+" - "+Long.MAX_VALUE+")");
+    }    
+
+    /*
+    /**********************************************************
+    /* Base64 handling support
+    /**********************************************************
+     */
+
+    /**
+     * Method that sub-classes must implement to support escaped sequences
+     * in base64-encoded sections.
+     * Sub-classes that do not need base64 support can leave this as is
+     */
+    protected char _decodeEscaped()
+        throws IOException, JsonParseException {
+        throw new UnsupportedOperationException();
+    }
+    
+    protected final int _decodeBase64Escape(Base64Variant b64variant, int ch, int index)
+        throws IOException, JsonParseException
+    {
+        // 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars
+        if (ch != '\\') {
+            throw reportInvalidBase64Char(b64variant, ch, index);
+        }
+        int unescaped = _decodeEscaped();
+        // if white space, skip if first triplet; otherwise errors
+        if (unescaped <= INT_SPACE) {
+            if (index == 0) { // whitespace only allowed to be skipped between triplets
+                return -1;
+            }
+        }
+        // otherwise try to find actual triplet value
+        int bits = b64variant.decodeBase64Char(unescaped);
+        if (bits < 0) {
+            throw reportInvalidBase64Char(b64variant, unescaped, index);
+        }
+        return bits;
+    }
+    
+    protected final int _decodeBase64Escape(Base64Variant b64variant, char ch, int index)
+        throws IOException, JsonParseException
+    {
+        // 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars
+        if (ch != '\\') {
+            throw reportInvalidBase64Char(b64variant, ch, index);
+        }
+        char unescaped = _decodeEscaped();
+        // if white space, skip if first triplet; otherwise errors
+        if (unescaped <= INT_SPACE) {
+            if (index == 0) { // whitespace only allowed to be skipped between triplets
+                return -1;
+            }
+        }
+        // otherwise try to find actual triplet value
+        int bits = b64variant.decodeBase64Char(unescaped);
+        if (bits < 0) {
+            throw reportInvalidBase64Char(b64variant, unescaped, index);
+        }
+        return bits;
+    }
+    
+    protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex)
+        throws IllegalArgumentException
+    {
+        return reportInvalidBase64Char(b64variant, ch, bindex, null);
+    }
+
+    /**
+     * @param bindex Relative index within base64 character unit; between 0
+     *   and 3 (as unit has exactly 4 characters)
+     */
+    protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex, String msg)
+        throws IllegalArgumentException
+    {
+        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 '"+((char)ch)+"' (code 0x"+Integer.toHexString(ch)+") in base64 content";
+        }
+        if (msg != null) {
+            base = base + ": " + msg;
+        }
+        return new IllegalArgumentException(base);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java
new file mode 100644
index 0000000..f76dc09
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java
@@ -0,0 +1,616 @@
+package com.fasterxml.jackson.core.base;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.JsonParser.Feature;
+import com.fasterxml.jackson.core.io.NumberInput;
+import com.fasterxml.jackson.core.util.ByteArrayBuilder;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Intermediate base class used by all Jackson {@link JsonParser}
+ * implementations, but does not add any additional fields that depend
+ * on particular method of obtaining input.
+ *<p>
+ * Note that 'minimal' here mostly refers to minimal number of fields
+ * (size) and functionality that is specific to certain types
+ * of parser implementations; but not necessarily to number of methods.
+ *
+ * @author Tatu Saloranta
+ */
+public abstract class ParserMinimalBase
+    extends JsonParser
+{
+    // Control chars:
+    protected final static int INT_TAB = '\t';
+    protected final static int INT_LF = '\n';
+    protected final static int INT_CR = '\r';
+    protected final static int INT_SPACE = 0x0020;
+
+    // Markup
+    protected final static int INT_LBRACKET = '[';
+    protected final static int INT_RBRACKET = ']';
+    protected final static int INT_LCURLY = '{';
+    protected final static int INT_RCURLY = '}';
+    protected final static int INT_QUOTE = '"';
+    protected final static int INT_BACKSLASH = '\\';
+    protected final static int INT_SLASH = '/';
+    protected final static int INT_COLON = ':';
+    protected final static int INT_COMMA = ',';
+    protected final static int INT_ASTERISK = '*';
+    protected final static int INT_APOSTROPHE = '\'';
+
+    // Letters we need
+    protected final static int INT_b = 'b';
+    protected final static int INT_f = 'f';
+    protected final static int INT_n = 'n';
+    protected final static int INT_r = 'r';
+    protected final static int INT_t = 't';
+    protected final static int INT_u = 'u';
+
+    /*
+    /**********************************************************
+    /* Minimal generally useful 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
+     * (by call to {@link #clearCurrentToken})
+     */
+    protected JsonToken _currToken;
+
+    /**
+     * Last cleared token, if any: that is, value that was in
+     * effect when {@link #clearCurrentToken} was called.
+     */
+    protected JsonToken _lastClearedToken;
+    
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    protected ParserMinimalBase() { }
+    protected ParserMinimalBase(int features) {
+        super(features);
+    }
+
+    @Override
+    public Version version() {
+        return VersionUtil.versionFor(getClass());
+    }
+    
+    /*
+    /**********************************************************
+    /* Configuration overrides if any
+    /**********************************************************
+     */
+
+    // from base class:
+
+    //public void enableFeature(Feature f)
+    //public void disableFeature(Feature f)
+    //public void setFeature(Feature f, boolean state)
+    //public boolean isFeatureEnabled(Feature f)
+
+    /*
+    /**********************************************************
+    /* JsonParser impl
+    /**********************************************************
+     */
+
+    @Override
+    public abstract JsonToken nextToken() throws IOException, JsonParseException;
+
+    @Override
+    public JsonToken getCurrentToken() {
+        return _currToken;
+    }
+
+    @Override
+    public boolean hasCurrentToken() {
+        return _currToken != null;
+    }
+    
+    @Override
+    public JsonToken nextValue()
+        throws IOException, JsonParseException
+    {
+        /* Implementation should be as trivial as follows; only
+         * needs to change if we are to skip other tokens (for
+         * example, if comments were exposed as tokens)
+         */
+        JsonToken t = nextToken();
+        if (t == JsonToken.FIELD_NAME) {
+            t = nextToken();
+        }
+        return t;
+    }
+
+    @Override
+    public JsonParser skipChildren() throws IOException, JsonParseException
+    {
+        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) {
+                _handleEOF();
+                /* given constraints, above should never return;
+                 * however, FindBugs doesn't know about it and
+                 * complains... so let's add dummy break here
+                 */
+                return this;
+            }
+            switch (t) {
+            case START_OBJECT:
+            case START_ARRAY:
+                ++open;
+                break;
+            case END_OBJECT:
+            case END_ARRAY:
+                if (--open == 0) {
+                    return this;
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Method sub-classes need to implement
+     */
+    protected abstract void _handleEOF() throws JsonParseException;
+
+    //public JsonToken getCurrentToken()
+
+    //public boolean hasCurrentToken()
+
+    @Override
+    public abstract String getCurrentName() throws IOException, JsonParseException;
+    
+    @Override
+    public abstract void close() throws IOException;
+
+    @Override
+    public abstract boolean isClosed();
+
+    @Override
+    public abstract JsonStreamContext getParsingContext();
+
+//    public abstract JsonLocation getTokenLocation();
+
+//   public abstract JsonLocation getCurrentLocation();
+
+    /*
+    /**********************************************************
+    /* Public API, token state overrides
+    /**********************************************************
+     */
+
+    @Override
+    public void clearCurrentToken() {
+        if (_currToken != null) {
+            _lastClearedToken = _currToken;
+            _currToken = null;
+        }
+    }
+
+    @Override
+    public JsonToken getLastClearedToken() {
+        return _lastClearedToken;
+    }
+
+    @Override
+    public abstract void overrideCurrentName(String name);
+    
+    /*
+    /**********************************************************
+    /* Public API, access to token information, text
+    /**********************************************************
+     */
+
+    @Override
+    public abstract String getText() throws IOException, JsonParseException;
+
+    @Override
+    public abstract char[] getTextCharacters() throws IOException, JsonParseException;
+
+    @Override
+    public abstract boolean hasTextCharacters();
+
+    @Override
+    public abstract int getTextLength() throws IOException, JsonParseException;
+
+    @Override
+    public abstract int getTextOffset() throws IOException, JsonParseException;  
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, binary
+    /**********************************************************
+     */
+
+    @Override
+    public abstract byte[] getBinaryValue(Base64Variant b64variant)
+        throws IOException, JsonParseException;
+
+    /*
+    /**********************************************************
+    /* Public API, access with conversion/coercion
+    /**********************************************************
+     */
+
+    @Override
+    public boolean getValueAsBoolean(boolean defaultValue) throws IOException, JsonParseException
+    {
+        if (_currToken != null) {
+            switch (_currToken) {
+            case VALUE_NUMBER_INT:
+                return getIntValue() != 0;
+            case VALUE_TRUE:
+                return true;
+            case VALUE_FALSE:
+            case VALUE_NULL:
+                return false;
+            case VALUE_EMBEDDED_OBJECT:
+                {
+                    Object value = this.getEmbeddedObject();
+                    if (value instanceof Boolean) {
+                        return (Boolean) value;
+                    }
+                }
+            case VALUE_STRING:
+                String str = getText().trim();
+                if ("true".equals(str)) {
+                    return true;
+                }
+                break;
+            }
+        }
+        return defaultValue;
+    }
+    
+    @Override
+    public int getValueAsInt(int defaultValue) throws IOException, JsonParseException
+    {
+        if (_currToken != null) {
+            switch (_currToken) {
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return getIntValue();
+            case VALUE_TRUE:
+                return 1;
+            case VALUE_FALSE:
+            case VALUE_NULL:
+                return 0;
+            case VALUE_STRING:
+                return NumberInput.parseAsInt(getText(), defaultValue);
+            case VALUE_EMBEDDED_OBJECT:
+                {
+                    Object value = this.getEmbeddedObject();
+                    if (value instanceof Number) {
+                        return ((Number) value).intValue();
+                    }
+                }
+            }
+        }
+        return defaultValue;
+    }
+    
+    @Override
+    public long getValueAsLong(long defaultValue) throws IOException, JsonParseException
+    {
+        if (_currToken != null) {
+            switch (_currToken) {
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return getLongValue();
+            case VALUE_TRUE:
+                return 1;
+            case VALUE_FALSE:
+            case VALUE_NULL:
+                return 0;
+            case VALUE_STRING:
+                return NumberInput.parseAsLong(getText(), defaultValue);
+            case VALUE_EMBEDDED_OBJECT:
+                {
+                    Object value = this.getEmbeddedObject();
+                    if (value instanceof Number) {
+                        return ((Number) value).longValue();
+                    }
+                }
+            }
+        }
+        return defaultValue;
+    }
+
+    @Override
+    public double getValueAsDouble(double defaultValue) throws IOException, JsonParseException
+    {
+        if (_currToken != null) {
+            switch (_currToken) {
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return getDoubleValue();
+            case VALUE_TRUE:
+                return 1;
+            case VALUE_FALSE:
+            case VALUE_NULL:
+                return 0;
+            case VALUE_STRING:
+                return NumberInput.parseAsDouble(getText(), defaultValue);
+            case VALUE_EMBEDDED_OBJECT:
+                {
+                    Object value = this.getEmbeddedObject();
+                    if (value instanceof Number) {
+                        return ((Number) value).doubleValue();
+                    }
+                }
+            }
+        }
+        return defaultValue;
+    }
+
+    @Override
+    public String getValueAsString(String defaultValue) throws IOException, JsonParseException
+    {
+        if (_currToken != JsonToken.VALUE_STRING) {
+            if (_currToken == null || _currToken == JsonToken.VALUE_NULL || !_currToken.isScalarValue()) {
+                return defaultValue;
+            }
+        }
+        return getText();
+    }
+    
+    /*
+    /**********************************************************
+    /* Base64 decoding
+    /**********************************************************
+     */
+
+    /**
+     * Helper method that can be used for base64 decoding in cases where
+     * encoded content has already been read as a String.
+     */
+    protected void _decodeBase64(String str, ByteArrayBuilder builder, Base64Variant b64variant)
+        throws IOException, JsonParseException
+    {
+        int ptr = 0;
+        int len = str.length();
+        
+        main_loop:
+        while (ptr < len) {
+            // first, we'll skip preceding white space, if any
+            char ch;
+            do {
+                ch = str.charAt(ptr++);
+                if (ptr >= len) {
+                    break main_loop;
+                }
+            } while (ch <= INT_SPACE);
+            int bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                _reportInvalidBase64(b64variant, ch, 0, null);
+            }
+            int decodedData = bits;
+            // then second base64 char; can't get padding yet, nor ws
+            if (ptr >= len) {
+                _reportBase64EOF();
+            }
+            ch = str.charAt(ptr++);
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                _reportInvalidBase64(b64variant, ch, 1, null);
+            }
+            decodedData = (decodedData << 6) | bits;
+            // third base64 char; can be padding, but not ws
+            if (ptr >= len) {
+                // but as per [JACKSON-631] can be end-of-input, iff not using padding
+                if (!b64variant.usesPadding()) {
+                    decodedData >>= 4;
+                    builder.append(decodedData);
+                    break;
+                }
+                _reportBase64EOF();
+            }
+            ch = str.charAt(ptr++);
+            bits = b64variant.decodeBase64Char(ch);
+            
+            // First branch: can get padding (-> 1 byte)
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    _reportInvalidBase64(b64variant, ch, 2, null);
+                }
+                // Ok, must get padding
+                if (ptr >= len) {
+                    _reportBase64EOF();
+                }
+                ch = str.charAt(ptr++);
+                if (!b64variant.usesPaddingChar(ch)) {
+                    _reportInvalidBase64(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
+                }
+                // Got 12 bits, only need 8, need to shift
+                decodedData >>= 4;
+                builder.append(decodedData);
+                continue;
+            }
+            // Nope, 2 or 3 bytes
+            decodedData = (decodedData << 6) | bits;
+            // fourth and last base64 char; can be padding, but not ws
+            if (ptr >= len) {
+                // but as per [JACKSON-631] can be end-of-input, iff not using padding
+                if (!b64variant.usesPadding()) {
+                    decodedData >>= 2;
+                    builder.appendTwoBytes(decodedData);
+                    break;
+                }
+                _reportBase64EOF();
+            }
+            ch = str.charAt(ptr++);
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    _reportInvalidBase64(b64variant, ch, 3, null);
+                }
+                decodedData >>= 2;
+                builder.appendTwoBytes(decodedData);
+            } else {
+                // otherwise, our triple is now complete
+                decodedData = (decodedData << 6) | bits;
+                builder.appendThreeBytes(decodedData);
+            }
+        }
+    }
+
+    /**
+     * @param bindex Relative index within base64 character unit; between 0
+     *   and 3 (as unit has exactly 4 characters)
+     */
+    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);
+    }
+
+    protected void _reportBase64EOF() throws JsonParseException {
+        throw _constructError("Unexpected end-of-String in base64 content");
+    }
+    
+    
+    /*
+    /**********************************************************
+    /* Error reporting
+    /**********************************************************
+     */
+    
+    protected void _reportUnexpectedChar(int ch, String comment)
+        throws JsonParseException
+    {
+        String msg = "Unexpected character ("+_getCharDesc(ch)+")";
+        if (comment != null) {
+            msg += ": "+comment;
+        }
+        _reportError(msg);
+    }
+
+    protected void _reportInvalidEOF()
+        throws JsonParseException
+    {
+        _reportInvalidEOF(" in "+_currToken);
+    }
+
+    protected void _reportInvalidEOF(String msg)
+        throws JsonParseException
+    {
+        _reportError("Unexpected end-of-input"+msg);
+    }
+
+    protected void _reportInvalidEOFInValue() throws JsonParseException
+    {
+        _reportInvalidEOF(" in a value");
+    }
+    
+    protected void _throwInvalidSpace(int i)
+        throws JsonParseException
+    {
+        char c = (char) i;
+        String msg = "Illegal character ("+_getCharDesc(c)+"): only regular white space (\\r, \\n, \\t) is allowed between tokens";
+        _reportError(msg);
+    }
+
+    /**
+     * Method called to report a problem with unquoted control character.
+     * Note: starting with version 1.4, it is possible to suppress
+     * exception by enabling {@link Feature#ALLOW_UNQUOTED_CONTROL_CHARS}.
+     */
+    protected void _throwUnquotedSpace(int i, String ctxtDesc)
+        throws JsonParseException
+    {
+        // JACKSON-208; possible to allow unquoted control chars:
+        if (!isEnabled(Feature.ALLOW_UNQUOTED_CONTROL_CHARS) || i >= INT_SPACE) {
+            char c = (char) i;
+            String msg = "Illegal unquoted character ("+_getCharDesc(c)+"): has to be escaped using backslash to be included in "+ctxtDesc;
+            _reportError(msg);
+        }
+    }
+
+    protected char _handleUnrecognizedCharacterEscape(char ch) throws JsonProcessingException
+    {
+        // as per [JACKSON-300]
+        if (isEnabled(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)) {
+            return ch;
+        }
+        // and [JACKSON-548]
+        if (ch == '\'' && isEnabled(Feature.ALLOW_SINGLE_QUOTES)) {
+            return ch;
+        }
+        _reportError("Unrecognized character escape "+_getCharDesc(ch));
+        return ch;
+    }
+    
+    /*
+    /**********************************************************
+    /* Error reporting, generic
+    /**********************************************************
+     */
+
+    protected final static String _getCharDesc(int ch)
+    {
+        char c = (char) ch;
+        if (Character.isISOControl(c)) {
+            return "(CTRL-CHAR, code "+ch+")";
+        }
+        if (ch > 255) {
+            return "'"+c+"' (code "+ch+" / 0x"+Integer.toHexString(ch)+")";
+        }
+        return "'"+c+"' (code "+ch+")";
+    }
+
+    protected final void _reportError(String msg)
+        throws JsonParseException
+    {
+        throw _constructError(msg);
+    }
+
+    protected final void _wrapError(String msg, Throwable t)
+        throws JsonParseException
+    {
+        throw _constructError(msg, t);
+    }
+
+    protected final void _throwInternal() {
+        VersionUtil.throwInternal();
+    }
+
+    protected final JsonParseException _constructError(String msg, Throwable t)
+    {
+        return new JsonParseException(msg, getCurrentLocation(), t);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/base/package-info.java b/src/main/java/com/fasterxml/jackson/core/base/package-info.java
new file mode 100644
index 0000000..ec4adbe
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/base/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * Base classes used by concrete Parser and Generator implementations;
+ * contain functionality that is not specific to JSON or input
+ * abstraction (byte vs char).
+ * Most formats extend these types, although it is also possible to
+ * directly extend {@link com.fasterxml.jackson.core.JsonParser} or
+ * {@link com.fasterxml.jackson.core.JsonGenerator}.
+ */
+package com.fasterxml.jackson.core.base;
diff --git a/src/main/java/com/fasterxml/jackson/core/format/DataFormatDetector.java b/src/main/java/com/fasterxml/jackson/core/format/DataFormatDetector.java
new file mode 100644
index 0000000..516bf3e
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/DataFormatDetector.java
@@ -0,0 +1,211 @@
+package com.fasterxml.jackson.core.format;
+
+import java.io.*;
+import java.util.*;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Simple helper class that allows data format (content type) auto-detection,
+ * given an ordered set of {@link JsonFactory} instances to use for actual low-level
+ * detection.
+ */
+public class DataFormatDetector
+{
+    /**
+     * By default we will look ahead at most 64 bytes; in most cases,
+     * much less (4 bytes or so) is needed, but we will allow bit more
+     * leniency to support data formats that need more complex heuristics.
+     */
+    public final static int DEFAULT_MAX_INPUT_LOOKAHEAD = 64;
+    
+    /**
+     * Ordered list of factories which both represent data formats to
+     * detect (in precedence order, starting with highest) and are used
+     * for actual detection.
+     */
+    protected final JsonFactory[] _detectors;
+
+    /**
+     * Strength of match we consider to be good enough to be used
+     * without checking any other formats.
+     * Default value is {@link MatchStrength#SOLID_MATCH}, 
+     */
+    protected final MatchStrength _optimalMatch;
+
+    /**
+     * Strength of minimal match we accept as the answer, unless
+     * better matches are found. 
+     * Default value is {@link MatchStrength#WEAK_MATCH}, 
+     */
+    protected final MatchStrength _minimalMatch;
+
+    /**
+     * Maximum number of leading bytes of the input that we can read
+     * to determine data format.
+     *<p>
+     * Default value is {@link #DEFAULT_MAX_INPUT_LOOKAHEAD}.
+     */
+    protected final int _maxInputLookahead;
+    
+    /*
+    /**********************************************************
+    /* Construction
+    /**********************************************************
+     */
+    
+    public DataFormatDetector(JsonFactory... detectors) {
+        this(detectors, MatchStrength.SOLID_MATCH, MatchStrength.WEAK_MATCH,
+            DEFAULT_MAX_INPUT_LOOKAHEAD);
+    }
+
+    public DataFormatDetector(Collection<JsonFactory> detectors) {
+        this(detectors.toArray(new JsonFactory[detectors.size()]));
+    }
+
+    /**
+     * Method that will return a detector instance that uses given
+     * optimal match level (match that is considered sufficient to return, without
+     * trying to find stronger matches with other formats).
+     */
+    public DataFormatDetector withOptimalMatch(MatchStrength optMatch) {
+        if (optMatch == _optimalMatch) {
+            return this;
+        }
+        return new DataFormatDetector(_detectors, optMatch, _minimalMatch, _maxInputLookahead);
+    }
+    /**
+     * Method that will return a detector instance that uses given
+     * minimal match level; match that may be returned unless a stronger match
+     * is found with other format detectors.
+     */
+    public DataFormatDetector withMinimalMatch(MatchStrength minMatch) {
+        if (minMatch == _minimalMatch) {
+            return this;
+        }
+        return new DataFormatDetector(_detectors, _optimalMatch, minMatch, _maxInputLookahead);
+    }
+
+    /**
+     * Method that will return a detector instance that allows detectors to
+     * read up to specified number of bytes when determining format match strength.
+     */
+    public DataFormatDetector withMaxInputLookahead(int lookaheadBytes)
+    {
+        if (lookaheadBytes == _maxInputLookahead) {
+            return this;
+        }
+        return new DataFormatDetector(_detectors, _optimalMatch, _minimalMatch, lookaheadBytes);
+    }
+    
+    private DataFormatDetector(JsonFactory[] detectors,
+            MatchStrength optMatch, MatchStrength minMatch,
+            int maxInputLookahead)
+    {
+        _detectors = detectors;
+        _optimalMatch = optMatch;
+        _minimalMatch = minMatch;
+        _maxInputLookahead = maxInputLookahead;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API
+    /**********************************************************
+     */
+
+    /**
+     * Method to call to find format that content (accessible via given
+     * {@link InputStream}) given has, as per configuration of this detector
+     * instance.
+     * 
+     * @return Matcher object which contains result; never null, even in cases
+     *    where no match (with specified minimal match strength) is found.
+     */
+    public DataFormatMatcher findFormat(InputStream in) throws IOException
+    {
+        return _findFormat(new InputAccessor.Std(in, new byte[_maxInputLookahead]));
+    }
+
+    /**
+     * Method to call to find format that given content (full document)
+     * has, as per configuration of this detector instance.
+     * 
+     * @return Matcher object which contains result; never null, even in cases
+     *    where no match (with specified minimal match strength) is found.
+     */
+    public DataFormatMatcher findFormat(byte[] fullInputData) throws IOException
+    {
+        return _findFormat(new InputAccessor.Std(fullInputData));
+    }
+
+    /**
+     * Method to call to find format that given content (full document)
+     * has, as per configuration of this detector instance.
+     * 
+     * @return Matcher object which contains result; never null, even in cases
+     *    where no match (with specified minimal match strength) is found.
+     * 
+     * @since 2.1
+     */
+    public DataFormatMatcher findFormat(byte[] fullInputData, int offset, int len) throws IOException
+    {
+        return _findFormat(new InputAccessor.Std(fullInputData, offset, len));
+    }
+    
+    /*
+    /**********************************************************
+    /* Overrides
+    /**********************************************************
+     */
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append('[');
+        final int len = _detectors.length;
+        if (len > 0) {
+            sb.append(_detectors[0].getFormatName());
+            for (int i = 1; i < len; ++i) {
+                sb.append(", ");
+                sb.append(_detectors[i].getFormatName());
+            }
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    private DataFormatMatcher _findFormat(InputAccessor.Std acc) throws IOException
+    {
+        JsonFactory bestMatch = null;
+        MatchStrength bestMatchStrength = null;
+        for (JsonFactory f : _detectors) {
+            acc.reset();
+            MatchStrength strength = f.hasFormat(acc);
+            // if not better than what we have so far (including minimal level limit), skip
+            if (strength == null || strength.ordinal() < _minimalMatch.ordinal()) {
+                continue;
+            }
+            // also, needs to better match than before
+            if (bestMatch != null) {
+                if (bestMatchStrength.ordinal() >= strength.ordinal()) {
+                    continue;
+                }
+            }
+            // finally: if it's good enough match, we are done
+            bestMatch = f;
+            bestMatchStrength = strength;
+            if (strength.ordinal() >= _optimalMatch.ordinal()) {
+                break;
+            }
+        }
+        return acc.createMatcher(bestMatch, bestMatchStrength);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/format/DataFormatMatcher.java b/src/main/java/com/fasterxml/jackson/core/format/DataFormatMatcher.java
new file mode 100644
index 0000000..de670e1
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/DataFormatMatcher.java
@@ -0,0 +1,124 @@
+package com.fasterxml.jackson.core.format;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.io.MergedStream;
+
+/**
+ * Result object constructed by {@link DataFormatDetector} when requested
+ * to detect format of given input data.
+ */
+public class DataFormatMatcher
+{
+    protected final InputStream _originalStream;
+
+    /**
+     * Content read during format matching process
+     */
+    protected final byte[] _bufferedData;
+
+    /**
+     * Pointer to the first byte in buffer available for reading
+     */
+    protected final int _bufferedStart;
+    
+    /**
+     * Number of bytes available in buffer.
+     */
+    protected final int _bufferedLength;
+
+    /**
+     * Factory that produced sufficient match (if any)
+     */
+    protected final JsonFactory _match;
+
+    /**
+     * Strength of match with {@link #_match}
+     */
+    protected final MatchStrength _matchStrength;
+    
+    protected DataFormatMatcher(InputStream in, byte[] buffered,
+            int bufferedStart, int bufferedLength,
+            JsonFactory match, MatchStrength strength)
+    {
+        _originalStream = in;
+        _bufferedData = buffered;
+        _bufferedStart = bufferedStart;
+        _bufferedLength = bufferedLength;
+        _match = match;
+        _matchStrength = strength;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, simple accessors
+    /**********************************************************
+     */
+
+    /**
+     * Accessor to use to see if any formats matched well enough with
+     * the input data.
+     */
+    public boolean hasMatch() { return _match != null; }
+
+    /**
+     * Method for accessing strength of the match, if any; if no match,
+     * will return {@link MatchStrength#INCONCLUSIVE}.
+     */
+    public MatchStrength getMatchStrength() {
+        return (_matchStrength == null) ? MatchStrength.INCONCLUSIVE : _matchStrength;
+    }
+
+    /**
+     * Accessor for {@link JsonFactory} that represents format that data matched.
+     */
+    public JsonFactory getMatch() { return _match; }
+
+    /**
+     * Accessor for getting brief textual name of matched format if any (null
+     * if none). Equivalent to:
+     *<pre>
+     *   return hasMatch() ? getMatch().getFormatName() : null;
+     *</pre>
+     */
+    public String getMatchedFormatName() {
+        return _match.getFormatName();
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, factory methods
+    /**********************************************************
+     */
+    
+    /**
+     * Convenience method for trying to construct a {@link JsonParser} for
+     * parsing content which is assumed to be in detected data format.
+     * If no match was found, returns null.
+     */
+    public JsonParser createParserWithMatch() throws IOException {
+        if (_match == null) {
+            return null;
+        }
+        if (_originalStream == null) {
+            return _match.createParser(_bufferedData, _bufferedStart, _bufferedLength);
+        }
+        return _match.createParser(getDataStream());
+    }
+    
+    /**
+     * Method to use for accessing input for which format detection has been done.
+     * This <b>must</b> be used instead of using stream passed to detector
+     * unless given stream itself can do buffering.
+     * Stream will return all content that was read during matching process, as well
+     * as remaining contents of the underlying stream.
+     */
+    public InputStream getDataStream() {
+        if (_originalStream == null) {
+            return new ByteArrayInputStream(_bufferedData, _bufferedStart, _bufferedLength);
+        }
+        return new MergedStream(null, _originalStream, _bufferedData, _bufferedStart, _bufferedLength);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/format/InputAccessor.java b/src/main/java/com/fasterxml/jackson/core/format/InputAccessor.java
new file mode 100644
index 0000000..00126d0
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/InputAccessor.java
@@ -0,0 +1,151 @@
+package com.fasterxml.jackson.core.format;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.JsonFactory;
+
+/**
+ * Interface used to expose beginning of a data file to data format
+ * detection code.
+ */
+public interface InputAccessor
+{
+    /**
+     * Method to call to check if more input is available.
+     * Since this may result in more content to be read (at least
+     * one more byte), a {@link IOException} may get thrown.
+     */
+    boolean hasMoreBytes() throws IOException;
+
+    /**
+     * Returns next byte available, if any; if no more bytes are
+     * available, will throw {@link java.io.EOFException}.
+     */
+    byte nextByte() throws IOException;
+
+    /**
+     * Method that can be called to reset accessor to read from beginning
+     * of input.
+     */
+    void reset();
+
+    /*
+    /**********************************************************
+    /* Standard implementation
+    /**********************************************************
+     */
+
+    /**
+     * Basic implementation that reads data from given
+     * {@link InputStream} and buffers it as necessary.
+     */
+    class Std implements InputAccessor
+    {
+        protected final InputStream _in;
+
+        protected final byte[] _buffer;
+
+        protected final int _bufferedStart;
+
+        /**
+         * End of valid bytes in the buffer (points to one past last valid)
+         */
+        protected int _bufferedEnd;
+        
+        /**
+         * Pointer to next available buffered byte in {@link #_buffer}.
+         */
+        protected int _ptr;
+        
+        /**
+         * Constructor used when content to check is available via
+         * input stream and must be read.
+         */
+        public Std(InputStream in, byte[] buffer)
+        {
+            _in = in;
+            _buffer = buffer;
+            _bufferedStart = 0;
+            _ptr = 0;
+            _bufferedEnd = 0;
+        }
+
+        /**
+         * Constructor used when the full input (or at least enough leading bytes
+         * of full input) is available.
+         */
+        public Std(byte[] inputDocument)
+        {
+            _in = null;
+            _buffer = inputDocument;
+            // we have it all:
+            _bufferedStart = 0;
+            _bufferedEnd = inputDocument.length;
+        }
+
+        /**
+         * Constructor used when the full input (or at least enough leading bytes
+         * of full input) is available.
+         * 
+         * @since 2.1
+         */
+        public Std(byte[] inputDocument, int start, int len)
+        {
+            _in = null;
+            _buffer = inputDocument;
+            _ptr = start;
+            _bufferedStart = start;
+            _bufferedEnd = start+len;
+        }
+        
+        @Override
+        public boolean hasMoreBytes() throws IOException
+        {
+            if (_ptr < _bufferedEnd) { // already got more
+                return true;
+            }
+            if (_in == null) { // nowhere to read from
+                return false;
+            }
+            int amount = _buffer.length - _ptr;
+            if (amount < 1) { // can not load any more
+                return false;
+            }
+            int count = _in.read(_buffer, _ptr, amount);
+            if (count <= 0) { // EOF
+                return false;
+            }
+            _bufferedEnd += count;
+            return true;
+        }
+
+       @Override
+        public byte nextByte() throws IOException
+        {
+            // should we just try loading more automatically?
+            if (_ptr >= _bufferedEnd) {
+                if (!hasMoreBytes()) {
+                    throw new EOFException("Failed auto-detect: could not read more than "+_ptr+" bytes (max buffer size: "+_buffer.length+")");
+                }
+            }
+            return _buffer[_ptr++];
+        }
+
+        @Override
+        public void reset() {
+            _ptr = _bufferedStart;
+        }
+
+        /*
+        /**********************************************************
+        /* Extended API for DataFormatDetector/Matcher
+        /**********************************************************
+         */
+
+        public DataFormatMatcher createMatcher(JsonFactory match, MatchStrength matchStrength)
+        {
+            return new DataFormatMatcher(_in, _buffer, _bufferedStart, (_bufferedEnd - _bufferedStart),
+                    match, matchStrength);
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/format/MatchStrength.java b/src/main/java/com/fasterxml/jackson/core/format/MatchStrength.java
new file mode 100644
index 0000000..d123708
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/MatchStrength.java
@@ -0,0 +1,62 @@
+package com.fasterxml.jackson.core.format;
+
+/**
+ * Enumeration used to indicate strength of match between data format
+ * and piece of data (typically beginning of a data file).
+ * Values are in increasing match strength; and detectors should return
+ * "strongest" value: that is, it should start with strongest match
+ * criteria, and downgrading if criteria is not fulfilled.
+ */
+public enum MatchStrength
+{
+    /**
+     * Value that indicates that given data can not be in given format.
+     */
+    NO_MATCH,
+    
+    /**
+     * Value that indicates that detector can not find out whether could
+     * be a match or not.
+     * This can occur for example for textual data formats t
+     * when there are so many leading spaces that detector can not
+     * find the first data byte (because detectors typically limit lookahead
+     * to some smallish value).
+     */
+    INCONCLUSIVE,
+
+    /**
+     * Value that indicates that given data could be of specified format (i.e.
+     * it can not be ruled out). This can occur for example when seen data
+     * is both not in canonical formats (for example: JSON data should be a JSON Array or Object
+     * not a scalar value, as per JSON specification) and there are known use case
+     * where a format detected is actually used (plain JSON Strings are actually used, even
+     * though specification does not indicate that as valid usage: as such, seeing a leading
+     * double-quote could indicate a JSON String, which plausibly <b>could</b> indicate
+     * non-standard JSON usage).
+     */
+    WEAK_MATCH,
+    
+    /**
+     * Value that indicates that given data conforms to (one of) canonical form(s) of
+     * the data format.
+     *<p>
+     * For example, when testing for XML data format,
+     * seeing a less-than character ("<") alone (with possible leading spaces)
+     * would be a strong indication that data could
+     * be in xml format (but see below for {@link #FULL_MATCH} description for more)
+     */
+    SOLID_MATCH,
+
+    /**
+     * Value that indicates that given data contains a signature that is deemed
+     * specific enough to uniquely indicate data format used.
+     *<p>
+     * For example, when testing for XML data format,
+     * seing "<xml" as the first data bytes ("XML declaration", as per XML specification)
+     * could give full confidence that data is indeed in XML format.
+     * Not all data formats have unique leading identifiers to allow full matches; for example,
+     * JSON only has heuristic matches and can have at most {@link #SOLID_MATCH}) match.
+     */
+    FULL_MATCH
+    ;
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/format/package-info.java b/src/main/java/com/fasterxml/jackson/core/format/package-info.java
new file mode 100644
index 0000000..a83e41f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Package that contains interfaces needed for dynamic, pluggable
+ * format (auto)detection; as well as basic utility classes for
+ * simple format detection functionality.
+ */
+package com.fasterxml.jackson.core.format;
diff --git a/src/main/java/com/fasterxml/jackson/core/io/BaseReader.java b/src/main/java/com/fasterxml/jackson/core/io/BaseReader.java
new file mode 100644
index 0000000..259c294
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/BaseReader.java
@@ -0,0 +1,116 @@
+
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+/**
+ * Simple basic class for optimized readers in this package; implements
+ * "cookie-cutter" methods that are used by all actual implementations.
+ */
+abstract class BaseReader
+    extends Reader
+{
+    /**
+     * JSON actually limits available Unicode range in the high end
+     * to the same as xml (to basically limit UTF-8 max byte sequence
+     * length to 4)
+     */
+    final protected static int LAST_VALID_UNICODE_CHAR = 0x10FFFF;
+
+    final protected static char NULL_CHAR = (char) 0;
+    final protected static char NULL_BYTE = (byte) 0;
+
+    final protected IOContext _context;
+
+    protected InputStream _in;
+
+    protected byte[] _buffer;
+
+    protected int _ptr;
+    protected int _length;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    protected BaseReader(IOContext context,
+            InputStream in, byte[] buf, int ptr, int len)
+    {
+        _context = context;
+        _in = in;
+        _buffer = buf;
+        _ptr = ptr;
+        _length = len;
+    }
+
+    /*
+    /**********************************************************
+    /* Reader API
+    /**********************************************************
+     */
+
+    @Override
+    public void close() throws IOException
+    {
+        InputStream in = _in;
+
+        if (in != null) {
+            _in = null;
+            freeBuffers();
+            in.close();
+        }
+    }
+
+    protected char[] _tmpBuf = null;
+
+    /**
+     * Although this method is implemented by the base class, AND it should
+     * never be called by main code, let's still implement it bit more
+     * efficiently just in case
+     */
+    @Override
+    public int read() throws IOException
+    {
+        if (_tmpBuf == null) {
+            _tmpBuf = new char[1];
+        }
+        if (read(_tmpBuf, 0, 1) < 1) {
+            return -1;
+        }
+        return _tmpBuf[0];
+    }
+
+    /*
+    /**********************************************************
+    /* Internal/package methods:
+    /**********************************************************
+     */
+
+    /**
+     * This method should be called along with (or instead of) normal
+     * close. After calling this method, no further reads should be tried.
+     * Method will try to recycle read buffers (if any).
+     */
+    public final void freeBuffers()
+    {
+        byte[] buf = _buffer;
+        if (buf != null) {
+            _buffer = null;
+            _context.releaseReadIOBuffer(buf);
+        }
+    }
+
+    protected void reportBounds(char[] cbuf, int start, int len)
+        throws IOException
+    {
+        throw new ArrayIndexOutOfBoundsException("read(buf,"+start+","+len+"), cbuf["+cbuf.length+"]");
+    }
+
+    protected void reportStrangeStream()
+        throws IOException
+    {
+        throw new IOException("Strange I/O stream, returned 0 bytes on read");
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java b/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java
new file mode 100644
index 0000000..4644c5e
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java
@@ -0,0 +1,228 @@
+package com.fasterxml.jackson.core.io;
+
+import java.util.Arrays;
+
+
+public final class CharTypes
+{
+    private final static char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+    private final static byte[] HEX_BYTES;
+    static {
+        int len = HEX_CHARS.length;
+        HEX_BYTES = new byte[len];
+        for (int i = 0; i < len; ++i) {
+            HEX_BYTES[i] = (byte) HEX_CHARS[i];
+        }
+    }
+
+
+    /**
+     * Lookup table used for determining which input characters
+     * need special handling when contained in text segment.
+     */
+    final static int[] sInputCodes;
+    static {
+        /* 96 would do for most cases (backslash is ascii 94)
+         * but if we want to do lookups by raw bytes it's better
+         * to have full table
+         */
+        int[] table = new int[256];
+        // Control chars and non-space white space are not allowed unquoted
+        for (int i = 0; i < 32; ++i) {
+            table[i] = -1;
+        }
+        // And then string end and quote markers are special too
+        table['"'] = 1;
+        table['\\'] = 1;
+        sInputCodes = table;
+    }
+
+    /**
+     * Additionally we can combine UTF-8 decoding info into similar
+     * data table.
+     */
+    final static int[] sInputCodesUtf8;
+    static {
+        int[] table = new int[sInputCodes.length];
+        System.arraycopy(sInputCodes, 0, table, 0, sInputCodes.length);
+        for (int c = 128; c < 256; ++c) {
+            int code;
+
+            // We'll add number of bytes needed for decoding
+            if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
+                code = 2;
+            } else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
+                code = 3;
+            } else if ((c & 0xF8) == 0xF0) {
+                // 4 bytes; double-char with surrogates and all...
+                code = 4;
+            } else {
+                // And -1 seems like a good "universal" error marker...
+                code = -1;
+            }
+            table[c] = code;
+        }
+        sInputCodesUtf8 = table;
+    }
+
+    /**
+     * To support non-default (and -standard) unquoted field names mode,
+     * need to have alternate checking.
+     * Basically this is list of 8-bit ASCII characters that are legal
+     * as part of Javascript identifier
+     */
+    final static int[] sInputCodesJsNames;
+    static {
+        int[] table = new int[256];
+        // Default is "not a name char", mark ones that are
+        Arrays.fill(table, -1);
+        // Assume rules with JS same as Java (change if/as needed)
+        for (int i = 33; i < 256; ++i) {
+            if (Character.isJavaIdentifierPart((char) i)) {
+                table[i] = 0;
+            }
+        }
+        /* As per [JACKSON-267], '@', '#' and '*' are also to be accepted as well.
+         * And '-' (for hyphenated names); and '+' for sake of symmetricity...
+         */
+        table['@'] = 0;
+        table['#'] = 0;
+        table['*'] = 0;
+        table['-'] = 0;
+        table['+'] = 0;
+        sInputCodesJsNames = table;
+    }
+
+    /**
+     * This table is similar to Latin-1, except that it marks all "high-bit"
+     * code as ok. They will be validated at a later point, when decoding
+     * name
+     */
+    final static int[] sInputCodesUtf8JsNames;
+    static {
+        int[] table = new int[256];
+        // start with 8-bit JS names 
+        System.arraycopy(sInputCodesJsNames, 0, table, 0, sInputCodesJsNames.length);
+        Arrays.fill(table, 128, 128, 0);
+        sInputCodesUtf8JsNames = table;
+    }
+
+    /**
+     * Decoding table used to quickly determine characters that are
+     * relevant within comment content
+     */
+    final static int[] sInputCodesComment = new int[256];
+    static {
+        // but first: let's start with UTF-8 multi-byte markers:
+        System.arraycopy(sInputCodesUtf8, 128, sInputCodesComment, 128, 128);
+    
+        // default (0) means "ok" (skip); -1 invalid, others marked by char itself
+        Arrays.fill(sInputCodesComment, 0, 32, -1); // invalid white space
+        sInputCodesComment['\t'] = 0; // tab is still fine
+        sInputCodesComment['\n'] = '\n'; // lf/cr need to be observed, ends cpp comment
+        sInputCodesComment['\r'] = '\r';
+        sInputCodesComment['*'] = '*'; // end marker for c-style comments
+    }
+
+    /**
+     * Lookup table used for determining which output characters in 
+     * 7-bit ASCII range need to be quoted.
+     */
+    final static int[] sOutputEscapes128;
+    static {
+        int[] table = new int[128];
+        // Control chars need generic escape sequence
+        for (int i = 0; i < 32; ++i) {
+            // 04-Mar-2011, tatu: Used to use "-(i + 1)", replaced with constants
+            table[i] = CharacterEscapes.ESCAPE_STANDARD;
+        }
+        /* Others (and some within that range too) have explicit shorter
+         * sequences
+         */
+        table['"'] = '"';
+        table['\\'] = '\\';
+        // Escaping of slash is optional, so let's not add it
+        table[0x08] = 'b';
+        table[0x09] = 't';
+        table[0x0C] = 'f';
+        table[0x0A] = 'n';
+        table[0x0D] = 'r';
+        sOutputEscapes128 = table;
+    }
+
+    /**
+     * Lookup table for the first 128 Unicode characters (7-bit ASCII)
+     * range. For actual hex digits, contains corresponding value;
+     * for others -1.
+     */
+    final static int[] sHexValues = new int[128];
+    static {
+        Arrays.fill(sHexValues, -1);
+        for (int i = 0; i < 10; ++i) {
+            sHexValues['0' + i] = i;
+        }
+        for (int i = 0; i < 6; ++i) {
+            sHexValues['a' + i] = 10 + i;
+            sHexValues['A' + i] = 10 + i;
+        }
+    }
+
+    public static int[] getInputCodeLatin1() { return sInputCodes; }
+    public static int[] getInputCodeUtf8() { return sInputCodesUtf8; }
+
+    public static int[] getInputCodeLatin1JsNames() { return sInputCodesJsNames; }
+    public static int[] getInputCodeUtf8JsNames() { return sInputCodesUtf8JsNames; }
+
+    public static int[] getInputCodeComment() { return sInputCodesComment; }
+    
+    /**
+     * Accessor for getting a read-only encoding table for first 128 Unicode
+     * code points (single-byte UTF-8 characters).
+     * Value of 0 means "no escaping"; other positive values that value is character
+     * to use after backslash; and negative values that generic (backslash - u)
+     * escaping is to be used.
+     */
+    public static int[] get7BitOutputEscapes() { return sOutputEscapes128; }
+
+    public static int charToHex(int ch)
+    {
+        return (ch > 127) ? -1 : sHexValues[ch];
+    }
+
+    public static void appendQuoted(StringBuilder sb, String content)
+    {
+        final int[] escCodes = sOutputEscapes128;
+        int escLen = escCodes.length;
+        for (int i = 0, len = content.length(); i < len; ++i) {
+            char c = content.charAt(i);
+            if (c >= escLen || escCodes[c] == 0) {
+                sb.append(c);
+                continue;
+            }
+            sb.append('\\');
+            int escCode = escCodes[c];
+            if (escCode < 0) { // generic quoting (hex value)
+                // We know that it has to fit in just 2 hex chars
+                sb.append('u');
+                sb.append('0');
+                sb.append('0');
+                int value = -(escCode + 1);
+                sb.append(HEX_CHARS[value >> 4]);
+                sb.append(HEX_CHARS[value & 0xF]);
+            } else { // "named", i.e. prepend with slash
+                sb.append((char) escCode);
+            }
+        }
+    }
+
+    public static char[] copyHexChars()
+    {
+        return (char[]) HEX_CHARS.clone();
+    }
+
+    public static byte[] copyHexBytes()
+    {
+        return (byte[]) HEX_BYTES.clone();
+    }
+}
+
diff --git a/src/main/java/com/fasterxml/jackson/core/io/CharacterEscapes.java b/src/main/java/com/fasterxml/jackson/core/io/CharacterEscapes.java
new file mode 100644
index 0000000..307e83e
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/CharacterEscapes.java
@@ -0,0 +1,73 @@
+package com.fasterxml.jackson.core.io;
+
+import com.fasterxml.jackson.core.SerializableString;
+
+/**
+ * Abstract base class that defines interface for customizing character
+ * escaping aspects for String values, for formats that use escaping.
+ * For JSON this applies to both property names and String values.
+ */
+public abstract class CharacterEscapes
+    implements java.io.Serializable // since 2.1
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Value used for lookup tables to indicate that matching characters
+     * do not need to be escaped.
+     */
+    public final static int ESCAPE_NONE = 0;
+
+    /**
+     * Value used for lookup tables to indicate that matching characters
+     * are to be escaped using standard escaping; for JSON this means
+     * (for example) using "backslash - u" escape method.
+     */
+    public final static int ESCAPE_STANDARD = -1;
+
+    /**
+     * Value used for lookup tables to indicate that matching characters
+     * will need custom escapes; and that another call
+     * to {@link #getEscapeSequence} is needed to figure out exact escape
+     * sequence to output.
+     */
+    public final static int ESCAPE_CUSTOM = -2;
+    
+    /**
+     * Method generators can call to get lookup table for determining
+     * escape handling for first 128 characters of Unicode (ASCII
+     * characters. Caller is not to modify contents of this array, since
+     * this is expected to be a shared copy.
+     * 
+     * @return Array with size of at least 128, where first 128 entries
+     *    have either one of <code>ESCAPE_xxx</code> constants, or non-zero positive
+     *    integer (meaning of which is data format specific; for JSON it means
+     *    that combination of backslash and character with that value is to be used)
+     *    to indicate that specific escape sequence is to be used.
+     */
+    public abstract int[] getEscapeCodesForAscii();
+
+    /**
+     * Method generators can call to get lookup table for determining
+     * exact escape sequence to use for given character.
+     * It can be called for any character, but typically is called for
+     * either for ASCII characters for which custom escape
+     * sequence is needed; or for any non-ASCII character.
+     */
+    public abstract SerializableString getEscapeSequence(int ch);
+
+    /**
+     * Helper method that can be used to get a copy of standard JSON
+     * escape definitions; this is useful when just wanting to slightly
+     * customize definitions. Caller can modify this array as it sees
+     * fit and usually returns modified instance via {@link #getEscapeCodesForAscii}
+     */
+    public static int[] standardAsciiEscapesForJSON()
+    {
+        int[] esc = CharTypes.get7BitOutputEscapes();
+        int len = esc.length;
+        int[] result = new int[len];
+        System.arraycopy(esc, 0, result, 0, esc.length);
+        return result;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/IOContext.java b/src/main/java/com/fasterxml/jackson/core/io/IOContext.java
new file mode 100644
index 0000000..de7180c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/IOContext.java
@@ -0,0 +1,257 @@
+package com.fasterxml.jackson.core.io;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+import com.fasterxml.jackson.core.util.TextBuffer;
+
+/**
+ * To limit number of configuration and state objects to pass, all
+ * contextual objects that need to be passed by the factory to
+ * readers and writers are combined under this object. One instance
+ * is created for each reader and writer.
+ */
+public final class IOContext
+{
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    /**
+     * Reference to the source object, which can be used for displaying
+     * location information
+     */
+    protected final Object _sourceRef;
+
+    /**
+     * Encoding used by the underlying stream, if known.
+     */
+    protected JsonEncoding _encoding;
+
+    /**
+     * Flag that indicates whether underlying input/output source/target
+     * object is fully managed by the owner of this context (parser or
+     * generator). If true, it is, and is to be closed by parser/generator;
+     * if false, calling application has to do closing (unless auto-closing
+     * feature is enabled for the parser/generator in question; in which
+     * case it acts like the owner).
+     */
+    protected final boolean _managedResource;
+
+    /*
+    /**********************************************************
+    /* Buffer handling, recycling
+    /**********************************************************
+     */
+
+    /**
+     * Recycler used for actual allocation/deallocation/reuse
+     */
+    protected final BufferRecycler _bufferRecycler;
+
+    /**
+     * Reference to the allocated I/O buffer for low-level input reading,
+     * if any allocated.
+     */
+    protected byte[] _readIOBuffer = null;
+
+    /**
+     * Reference to the allocated I/O buffer used for low-level
+     * encoding-related buffering.
+     */
+    protected byte[] _writeEncodingBuffer = null;
+    
+    /**
+     * Reference to the buffer allocated for temporary use with
+     * base64 encoding or decoding.
+     */
+    protected byte[] _base64Buffer = null;
+
+    /**
+     * Reference to the buffer allocated for tokenization purposes,
+     * in which character input is read, and from which it can be
+     * further returned.
+     */
+    protected char[] _tokenCBuffer = null;
+
+    /**
+     * Reference to the buffer allocated for buffering it for
+     * output, before being encoded: generally this means concatenating
+     * output, then encoding when buffer fills up.
+     */
+    protected char[] _concatCBuffer = null;
+
+    /**
+     * Reference temporary buffer Parser instances need if calling
+     * app decides it wants to access name via 'getTextCharacters' method.
+     * Regular text buffer can not be used as it may contain textual
+     * representation of the value token.
+     */
+    protected char[] _nameCopyBuffer = null;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public IOContext(BufferRecycler br, Object sourceRef, boolean managedResource)
+    {
+        _bufferRecycler = br;
+        _sourceRef = sourceRef;
+        _managedResource = managedResource;
+    }
+
+    public void setEncoding(JsonEncoding enc) {
+        _encoding = enc;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, accessors
+    /**********************************************************
+     */
+
+    public Object getSourceReference() { return _sourceRef; }
+    public JsonEncoding getEncoding() { return _encoding; }
+    public boolean isResourceManaged() { return _managedResource; }
+
+    /*
+    /**********************************************************
+    /* Public API, buffer management
+    /**********************************************************
+     */
+
+    public TextBuffer constructTextBuffer() {
+        return new TextBuffer(_bufferRecycler);
+    }
+
+    /**
+     *<p>
+     * Note: the method can only be called once during its life cycle.
+     * This is to protect against accidental sharing.
+     */
+    public byte[] allocReadIOBuffer()
+    {
+        _verifyAlloc(_readIOBuffer);
+        return (_readIOBuffer = _bufferRecycler.allocByteBuffer(BufferRecycler.ByteBufferType.READ_IO_BUFFER));
+    }
+
+    public byte[] allocWriteEncodingBuffer()
+    {
+        _verifyAlloc(_writeEncodingBuffer);
+        return (_writeEncodingBuffer = _bufferRecycler.allocByteBuffer(BufferRecycler.ByteBufferType.WRITE_ENCODING_BUFFER));
+    }
+
+    /**
+     * @since 2.1
+     */
+    public byte[] allocBase64Buffer()
+    {
+        _verifyAlloc(_base64Buffer);
+        return (_base64Buffer = _bufferRecycler.allocByteBuffer(BufferRecycler.ByteBufferType.BASE64_CODEC_BUFFER));
+    }
+    
+    public char[] allocTokenBuffer()
+    {
+        _verifyAlloc(_tokenCBuffer);
+        return (_tokenCBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CharBufferType.TOKEN_BUFFER));
+    }
+
+    public char[] allocConcatBuffer()
+    {
+        _verifyAlloc(_concatCBuffer);
+        return (_concatCBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CharBufferType.CONCAT_BUFFER));
+    }
+
+    public char[] allocNameCopyBuffer(int minSize)
+    {
+        _verifyAlloc(_nameCopyBuffer);
+        return (_nameCopyBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CharBufferType.NAME_COPY_BUFFER, minSize));
+    }
+
+    /**
+     * Method to call when all the processing buffers can be safely
+     * recycled.
+     */
+    public void releaseReadIOBuffer(byte[] buf)
+    {
+        if (buf != null) {
+            /* Let's do sanity checks to ensure once-and-only-once release,
+             * as well as avoiding trying to release buffers not owned
+             */
+            _verifyRelease(buf, _readIOBuffer);
+            _readIOBuffer = null;
+            _bufferRecycler.releaseByteBuffer(BufferRecycler.ByteBufferType.READ_IO_BUFFER, buf);
+        }
+    }
+
+    public void releaseWriteEncodingBuffer(byte[] buf)
+    {
+        if (buf != null) {
+            /* Let's do sanity checks to ensure once-and-only-once release,
+             * as well as avoiding trying to release buffers not owned
+             */
+            _verifyRelease(buf, _writeEncodingBuffer);
+            _writeEncodingBuffer = null;
+            _bufferRecycler.releaseByteBuffer(BufferRecycler.ByteBufferType.WRITE_ENCODING_BUFFER, buf);
+        }
+    }
+
+    public void releaseBase64Buffer(byte[] buf)
+    {
+        if (buf != null) { // sanity checks, release once-and-only-once, must be one owned
+            _verifyRelease(buf, _base64Buffer);
+            _base64Buffer = null;
+            _bufferRecycler.releaseByteBuffer(BufferRecycler.ByteBufferType.BASE64_CODEC_BUFFER, buf);
+        }
+    }
+    
+    public void releaseTokenBuffer(char[] buf)
+    {
+        if (buf != null) {
+            _verifyRelease(buf, _tokenCBuffer);
+            _tokenCBuffer = null;
+            _bufferRecycler.releaseCharBuffer(BufferRecycler.CharBufferType.TOKEN_BUFFER, buf);
+        }
+    }
+
+    public void releaseConcatBuffer(char[] buf)
+    {
+        if (buf != null) {
+            _verifyRelease(buf, _concatCBuffer);
+            _concatCBuffer = null;
+            _bufferRecycler.releaseCharBuffer(BufferRecycler.CharBufferType.CONCAT_BUFFER, buf);
+        }
+    }
+
+    public void releaseNameCopyBuffer(char[] buf)
+    {
+        if (buf != null) {
+            _verifyRelease(buf, _nameCopyBuffer);
+            _nameCopyBuffer = null;
+            _bufferRecycler.releaseCharBuffer(BufferRecycler.CharBufferType.NAME_COPY_BUFFER, buf);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal helpers
+    /**********************************************************
+     */
+
+    private final void _verifyAlloc(Object buffer)
+    {
+        if (buffer != null) {
+            throw new IllegalStateException("Trying to call same allocXxx() method second time");
+        }
+    }
+    
+    private final void _verifyRelease(Object toRelease, Object src)
+    {
+        if (toRelease != src) {
+            throw new IllegalArgumentException("Trying to release buffer not owned by the context");
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/InputDecorator.java b/src/main/java/com/fasterxml/jackson/core/io/InputDecorator.java
new file mode 100644
index 0000000..18b0b49
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/InputDecorator.java
@@ -0,0 +1,68 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+/**
+ * Handler class that can be used to decorate input sources.
+ * Typical use is to use a filter abstraction (filtered stream,
+ * reader) around original input source, and apply additional
+ * processing during read operations.
+ */
+public abstract class InputDecorator
+    implements java.io.Serializable // since 2.1
+{
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * Method called by {@link com.fasterxml.jackson.core.JsonFactory} instance when
+     * creating parser given an {@link InputStream}, when this decorator
+     * has been registered.
+     * 
+     * @param ctxt IO context in use (provides access to declared encoding).
+     *   NOTE: at this point context may not have all information initialized;
+     *   specifically auto-detected encoding is only available once parsing starts,
+     *   which may occur only after this method is called.
+     * @param in Original input source
+     * 
+     * @return InputStream to use; either passed in argument, or something that
+     *   calls it
+     */
+    public abstract InputStream decorate(IOContext ctxt, InputStream in)
+        throws IOException;
+
+    /**
+     * Method called by {@link com.fasterxml.jackson.core.JsonFactory} instance when
+     * creating parser on given "raw" byte source.
+     * Method can either construct a {@link InputStream} for reading; or return
+     * null to indicate that no wrapping should occur.
+     * 
+     * @param ctxt IO context in use (provides access to declared encoding)
+     *   NOTE: at this point context may not have all information initialized;
+     *   specifically auto-detected encoding is only available once parsing starts,
+     *   which may occur only after this method is called.
+     * @param src Input buffer that contains contents to parse
+     * @param offset Offset of the first available byte in the input buffer
+     * @param length Number of bytes available in the input buffer
+     * 
+     * @return Either {@link InputStream} to use as input source; or null to indicate
+     *   that contents are to be processed as-is by caller
+     */
+    public abstract InputStream decorate(IOContext ctxt, byte[] src, int offset, int length)
+        throws IOException;
+    
+    /**
+     * Method called by {@link com.fasterxml.jackson.core.JsonFactory} instance when
+     * creating parser given an {@link Reader}, when this decorator
+     * has been registered.
+     * 
+     * @param ctxt IO context in use (provides access to declared encoding)
+     *   NOTE: at this point context may not have all information initialized;
+     *   specifically auto-detected encoding is only available once parsing starts,
+     *   which may occur only after this method is called.
+     * @param src Original input source
+     * 
+     * @return Reader to use; either passed in argument, or something that
+     *   calls it (for example, a {@link FilterReader})
+     */
+    public abstract Reader decorate(IOContext ctxt, Reader src) throws IOException;
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java b/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java
new file mode 100644
index 0000000..e1ec0c7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java
@@ -0,0 +1,395 @@
+package com.fasterxml.jackson.core.io;
+
+import java.lang.ref.SoftReference;
+
+import com.fasterxml.jackson.core.util.BufferRecycler;
+import com.fasterxml.jackson.core.util.ByteArrayBuilder;
+import com.fasterxml.jackson.core.util.TextBuffer;
+
+/**
+ * Helper class used for efficient encoding of JSON String values (including
+ * JSON field names) into Strings or UTF-8 byte arrays.
+ *<p>
+ * Note that methods in here are somewhat optimized, but not ridiculously so.
+ * Reason is that conversion method results are expected to be cached so that
+ * these methods will not be hot spots during normal operation.
+ */
+public final class JsonStringEncoder
+{
+    private final static char[] HEX_CHARS = CharTypes.copyHexChars();
+
+    private final static byte[] HEX_BYTES = CharTypes.copyHexBytes();
+
+    private final static int SURR1_FIRST = 0xD800;
+    private final static int SURR1_LAST = 0xDBFF;
+    private final static int SURR2_FIRST = 0xDC00;
+    private final static int SURR2_LAST = 0xDFFF;
+
+    private final static int INT_BACKSLASH = '\\';
+    private final static int INT_U = 'u';
+    private final static int INT_0 = '0';
+    
+    /**
+     * This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftReference}
+     * to a {@link BufferRecycler} used to provide a low-cost
+     * buffer recycling between reader and writer instances.
+     */
+    final protected static ThreadLocal<SoftReference<JsonStringEncoder>> _threadEncoder
+        = new ThreadLocal<SoftReference<JsonStringEncoder>>();
+
+    /**
+     * Lazily constructed text buffer used to produce JSON encoded Strings
+     * as characters (without UTF-8 encoding)
+     */
+    protected TextBuffer _textBuffer;
+
+    /**
+     * Lazily-constructed builder used for UTF-8 encoding of text values
+     * (quoted and unquoted)
+     */
+    protected ByteArrayBuilder _byteBuilder;
+    
+    /**
+     * Temporary buffer used for composing quote/escape sequences
+     */
+    protected final char[] _quoteBuffer;
+    
+    /*
+    /**********************************************************
+    /* Construction, instance access
+    /**********************************************************
+     */
+    
+    public JsonStringEncoder()
+    {
+        _quoteBuffer = new char[6];
+        _quoteBuffer[0] = '\\';
+        _quoteBuffer[2] = '0';
+        _quoteBuffer[3] = '0';
+    }
+    
+    /**
+     * Factory method for getting an instance; this is either recycled per-thread instance,
+     * or a newly constructed one.
+     */
+    public static JsonStringEncoder getInstance()
+    {
+        SoftReference<JsonStringEncoder> ref = _threadEncoder.get();
+        JsonStringEncoder enc = (ref == null) ? null : ref.get();
+
+        if (enc == null) {
+            enc = new JsonStringEncoder();
+            _threadEncoder.set(new SoftReference<JsonStringEncoder>(enc));
+        }
+        return enc;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API
+    /**********************************************************
+     */
+
+    /**
+     * Method that will quote text contents using JSON standard quoting,
+     * and return results as a character array
+     */
+    public char[] quoteAsString(String input)
+    {
+        TextBuffer textBuffer = _textBuffer;
+        if (textBuffer == null) {
+            // no allocator; can add if we must, shouldn't need to
+            _textBuffer = textBuffer = new TextBuffer(null);
+        }
+        char[] outputBuffer = textBuffer.emptyAndGetCurrentSegment();
+        final int[] escCodes = CharTypes.get7BitOutputEscapes();
+        final int escCodeCount = escCodes.length;
+        int inPtr = 0;
+        final int inputLen = input.length();
+        int outPtr = 0;
+ 
+        outer_loop:
+        while (inPtr < inputLen) {
+            tight_loop:
+            while (true) {
+                char c = input.charAt(inPtr);
+                if (c < escCodeCount && escCodes[c] != 0) {
+                    break tight_loop;
+                }
+                if (outPtr >= outputBuffer.length) {
+                    outputBuffer = textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                outputBuffer[outPtr++] = c;
+                if (++inPtr >= inputLen) {
+                    break outer_loop;
+                }
+            }
+            // something to escape; 2 or 6-char variant? 
+            char d = input.charAt(inPtr++);
+            int escCode = escCodes[d];
+            int length = (escCode < 0)
+                    ? _appendNumericEscape(d, _quoteBuffer)
+                    : _appendNamedEscape(escCode, _quoteBuffer);
+                    ;
+            if ((outPtr + length) > outputBuffer.length) {
+                int first = outputBuffer.length - outPtr;
+                if (first > 0) {
+                    System.arraycopy(_quoteBuffer, 0, outputBuffer, outPtr, first);
+                }
+                outputBuffer = textBuffer.finishCurrentSegment();
+                int second = length - first;
+                System.arraycopy(_quoteBuffer, first, outputBuffer, 0, second);
+                outPtr = second;
+            } else {
+                System.arraycopy(_quoteBuffer, 0, outputBuffer, outPtr, length);
+                outPtr += length;
+            }
+        }
+        textBuffer.setCurrentLength(outPtr);
+        return textBuffer.contentsAsArray();
+    }
+
+    /**
+     * Will quote given JSON String value using standard quoting, encode
+     * results as UTF-8, and return result as a byte array.
+     */
+    public byte[] quoteAsUTF8(String text)
+    {
+        ByteArrayBuilder byteBuilder = _byteBuilder;
+        if (byteBuilder == null) {
+            // no allocator; can add if we must, shouldn't need to
+            _byteBuilder = byteBuilder = new ByteArrayBuilder(null);
+        }
+        int inputPtr = 0;
+        int inputEnd = text.length();
+        int outputPtr = 0;
+        byte[] outputBuffer = byteBuilder.resetAndGetFirstSegment();
+        
+        main_loop:
+        while (inputPtr < inputEnd) {
+            final int[] escCodes = CharTypes.get7BitOutputEscapes();
+
+            inner_loop: // ASCII and escapes
+            while (true) {
+                int ch = text.charAt(inputPtr);
+                if (ch > 0x7F || escCodes[ch] != 0) {
+                    break inner_loop;
+                }
+                if (outputPtr >= outputBuffer.length) {
+                    outputBuffer = byteBuilder.finishCurrentSegment();
+                    outputPtr = 0;
+                }
+                outputBuffer[outputPtr++] = (byte) ch;
+                if (++inputPtr >= inputEnd) {
+                    break main_loop;
+                }
+            }                
+            if (outputPtr >= outputBuffer.length) {
+                outputBuffer = byteBuilder.finishCurrentSegment();
+                outputPtr = 0;
+            }
+            // Ok, so what did we hit?
+            int ch = (int) text.charAt(inputPtr++);
+            if (ch <= 0x7F) { // needs quoting
+                int escape = escCodes[ch];
+                // ctrl-char, 6-byte escape...
+                outputPtr = _appendByteEscape(ch, escape, byteBuilder, outputPtr);
+                outputBuffer = byteBuilder.getCurrentSegment();
+                continue main_loop;
+            } else if (ch <= 0x7FF) { // fine, just needs 2 byte output
+                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
+                ch = (0x80 | (ch & 0x3f));
+            } else { // 3 or 4 bytes
+                // Surrogates?
+                if (ch < SURR1_FIRST || ch > SURR2_LAST) { // nope
+                    outputBuffer[outputPtr++] = (byte) (0xe0 | (ch >> 12));
+                    if (outputPtr >= outputBuffer.length) {
+                        outputBuffer = byteBuilder.finishCurrentSegment();
+                        outputPtr = 0;
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
+                    ch = (0x80 | (ch & 0x3f));
+                } else { // yes, surrogate pair
+                    if (ch > SURR1_LAST) { // must be from first range
+                        _illegalSurrogate(ch);
+                    }
+                    // and if so, followed by another from next range
+                    if (inputPtr >= inputEnd) {
+                        _illegalSurrogate(ch);
+                    }
+                    ch = _convertSurrogate(ch, text.charAt(inputPtr++));
+                    if (ch > 0x10FFFF) { // illegal, as per RFC 4627
+                        _illegalSurrogate(ch);
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0xf0 | (ch >> 18));
+                    if (outputPtr >= outputBuffer.length) {
+                        outputBuffer = byteBuilder.finishCurrentSegment();
+                        outputPtr = 0;
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0x80 | ((ch >> 12) & 0x3f));
+                    if (outputPtr >= outputBuffer.length) {
+                        outputBuffer = byteBuilder.finishCurrentSegment();
+                        outputPtr = 0;
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
+                    ch = (0x80 | (ch & 0x3f));
+                }
+            }
+            if (outputPtr >= outputBuffer.length) {
+                outputBuffer = byteBuilder.finishCurrentSegment();
+                outputPtr = 0;
+            }
+            outputBuffer[outputPtr++] = (byte) ch;
+        }
+        return _byteBuilder.completeAndCoalesce(outputPtr);
+    }
+    
+    /**
+     * Will encode given String as UTF-8 (without any quoting), return
+     * resulting byte array.
+     */
+    @SuppressWarnings("resource")
+    public byte[] encodeAsUTF8(String text)
+    {
+        ByteArrayBuilder byteBuilder = _byteBuilder;
+        if (byteBuilder == null) {
+            // no allocator; can add if we must, shouldn't need to
+            _byteBuilder = byteBuilder = new ByteArrayBuilder(null);
+        }
+        int inputPtr = 0;
+        int inputEnd = text.length();
+        int outputPtr = 0;
+        byte[] outputBuffer = byteBuilder.resetAndGetFirstSegment();
+        int outputEnd = outputBuffer.length;
+        
+        main_loop:
+        while (inputPtr < inputEnd) {
+            int c = text.charAt(inputPtr++);
+
+            // first tight loop for ascii
+            while (c <= 0x7F) {
+                if (outputPtr >= outputEnd) {
+                    outputBuffer = byteBuilder.finishCurrentSegment();
+                    outputEnd = outputBuffer.length;
+                    outputPtr = 0;
+                }
+                outputBuffer[outputPtr++] = (byte) c;
+                if (inputPtr >= inputEnd) {
+                    break main_loop;
+                }
+                c = text.charAt(inputPtr++);
+            }
+
+            // then multi-byte...
+            if (outputPtr >= outputEnd) {
+                outputBuffer = byteBuilder.finishCurrentSegment();
+                outputEnd = outputBuffer.length;
+                outputPtr = 0;
+            }
+            if (c < 0x800) { // 2-byte
+                outputBuffer[outputPtr++] = (byte) (0xc0 | (c >> 6));
+            } else { // 3 or 4 bytes
+                // Surrogates?
+                if (c < SURR1_FIRST || c > SURR2_LAST) { // nope
+                    outputBuffer[outputPtr++] = (byte) (0xe0 | (c >> 12));
+                    if (outputPtr >= outputEnd) {
+                        outputBuffer = byteBuilder.finishCurrentSegment();
+                        outputEnd = outputBuffer.length;
+                        outputPtr = 0;
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                } else { // yes, surrogate pair
+                    if (c > SURR1_LAST) { // must be from first range
+                        _illegalSurrogate(c);
+                    }
+                    // and if so, followed by another from next range
+                    if (inputPtr >= inputEnd) {
+                        _illegalSurrogate(c);
+                    }
+                    c = _convertSurrogate(c, text.charAt(inputPtr++));
+                    if (c > 0x10FFFF) { // illegal, as per RFC 4627
+                        _illegalSurrogate(c);
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0xf0 | (c >> 18));
+                    if (outputPtr >= outputEnd) {
+                        outputBuffer = byteBuilder.finishCurrentSegment();
+                        outputEnd = outputBuffer.length;
+                        outputPtr = 0;
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+                    if (outputPtr >= outputEnd) {
+                        outputBuffer = byteBuilder.finishCurrentSegment();
+                        outputEnd = outputBuffer.length;
+                        outputPtr = 0;
+                    }
+                    outputBuffer[outputPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                }
+            }
+            if (outputPtr >= outputEnd) {
+                outputBuffer = byteBuilder.finishCurrentSegment();
+                outputEnd = outputBuffer.length;
+                outputPtr = 0;
+            }
+            outputBuffer[outputPtr++] = (byte) (0x80 | (c & 0x3f));
+        }
+        return _byteBuilder.completeAndCoalesce(outputPtr);
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    private int _appendNumericEscape(int value, char[] quoteBuffer)
+    {
+        quoteBuffer[1] = 'u';
+        // We know it's a control char, so only the last 2 chars are non-0
+        quoteBuffer[4] = HEX_CHARS[value >> 4];
+        quoteBuffer[5] = HEX_CHARS[value & 0xF];
+        return 6;
+    }
+
+    private int _appendNamedEscape(int escCode, char[] quoteBuffer)
+    {
+        quoteBuffer[1] = (char) escCode;
+        return 2;
+    }
+
+    private int _appendByteEscape(int ch, int escCode, ByteArrayBuilder byteBuilder, int ptr)
+    {
+        byteBuilder.setCurrentSegmentLength(ptr);
+        byteBuilder.append(INT_BACKSLASH);
+        if (escCode < 0) { // standard escape
+            byteBuilder.append(INT_U);
+            if (ch > 0xFF) {
+                int hi = (ch >> 8);
+                byteBuilder.append(HEX_BYTES[hi >> 4]);
+                byteBuilder.append(HEX_BYTES[hi & 0xF]);
+                ch &= 0xFF;
+            } else {
+                byteBuilder.append(INT_0);
+                byteBuilder.append(INT_0);
+            }
+            byteBuilder.append(HEX_BYTES[ch >> 4]);
+            byteBuilder.append(HEX_BYTES[ch & 0xF]);
+        } else { // 2-char simple escape
+            byteBuilder.append((byte) escCode);
+        }
+        return byteBuilder.getCurrentSegmentLength();
+    }
+
+    protected static int _convertSurrogate(int firstPart, int secondPart)
+    {
+        // Ok, then, is the second part valid?
+        if (secondPart < SURR2_FIRST || secondPart > SURR2_LAST) {
+            throw new IllegalArgumentException("Broken surrogate pair: first char 0x"+Integer.toHexString(firstPart)+", second 0x"+Integer.toHexString(secondPart)+"; illegal combination");
+        }
+        return 0x10000 + ((firstPart - SURR1_FIRST) << 10) + (secondPart - SURR2_FIRST);
+    }
+
+    protected static void _illegalSurrogate(int code) {
+        throw new IllegalArgumentException(UTF8Writer.illegalSurrogateDesc(code));
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/MergedStream.java b/src/main/java/com/fasterxml/jackson/core/io/MergedStream.java
new file mode 100644
index 0000000..3645097
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/MergedStream.java
@@ -0,0 +1,145 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+/**
+ * Simple {@link InputStream} implementation that is used to "unwind" some
+ * data previously read from an input stream; so that as long as some of
+ * that data remains, it's returned; but as long as it's read, we'll
+ * just use data from the underlying original stream. 
+ * This is similar to {@link java.io.PushbackInputStream}, but here there's
+ * only one implicit pushback, when instance is constructed.
+ */
+public final class MergedStream
+    extends InputStream
+{
+    final protected IOContext _context;
+
+    final InputStream _in;
+
+    byte[] _buffer;
+
+    int _ptr;
+
+    final int _end;
+
+    public MergedStream(IOContext context,
+            InputStream in, byte[] buf, int start, int end)
+    {
+        _context = context;
+        _in = in;
+        _buffer = buf;
+        _ptr = start;
+        _end = end;
+    }
+
+    @Override
+    public int available() throws IOException
+    {
+        if (_buffer != null) {
+            return _end - _ptr;
+        }
+        return _in.available();
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        freeMergedBuffer();
+        _in.close();
+    }
+
+    @Override
+    public void mark(int readlimit)
+    {
+        if (_buffer == null) {
+            _in.mark(readlimit);
+        }
+    }
+    
+    @Override
+    public boolean markSupported()
+    {
+        // Only supports marks past the initial rewindable section...
+        return (_buffer == null) && _in.markSupported();
+    }
+    
+    @Override
+    public int read() throws IOException
+    {
+        if (_buffer != null) {
+            int c = _buffer[_ptr++] & 0xFF;
+            if (_ptr >= _end) {
+                freeMergedBuffer();
+            }
+            return c;
+        }
+        return _in.read();
+    }
+    
+    @Override
+    public int read(byte[] b) throws IOException
+    {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int 	read(byte[] b, int off, int len) throws IOException
+    {
+        if (_buffer != null) {
+            int avail = _end - _ptr;
+            if (len > avail) {
+                len = avail;
+            }
+            System.arraycopy(_buffer, _ptr, b, off, len);
+            _ptr += len;
+            if (_ptr >= _end) {
+                freeMergedBuffer();
+            }
+            return len;
+        }
+        return _in.read(b, off, len);
+    }
+
+    @Override
+    public void reset() throws IOException
+    {
+        if (_buffer == null) {
+            _in.reset();
+        }
+    }
+
+    @Override
+    public long skip(long n) throws IOException
+    {
+        long count = 0L;
+
+        if (_buffer != null) {
+            int amount = _end - _ptr;
+
+            if (amount > n) { // all in pushed back segment?
+                _ptr += (int) n;
+                return n;
+            }
+            freeMergedBuffer();
+            count += amount;
+            n -= amount;
+        }
+
+        if (n > 0) {
+            count += _in.skip(n);
+        }
+        return count;
+    }
+
+    private void freeMergedBuffer()
+    {
+        byte[] buf = _buffer;
+        if (buf != null) {
+            _buffer = null;
+            if (_context != null) {
+                _context.releaseReadIOBuffer(buf);
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java
new file mode 100644
index 0000000..c8ba515
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java
@@ -0,0 +1,290 @@
+package com.fasterxml.jackson.core.io;
+
+public final class NumberInput
+{
+    /**
+     * Textual representation of a double constant that can cause nasty problems
+     * with JDK (see http://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308).
+     */
+    public final static String NASTY_SMALL_DOUBLE = "2.2250738585072012e-308";
+
+    /**
+     * Constants needed for parsing longs from basic int parsing methods
+     */
+    final static long L_BILLION = 1000000000;
+
+    final static String MIN_LONG_STR_NO_SIGN = String.valueOf(Long.MIN_VALUE).substring(1);
+    final static String MAX_LONG_STR = String.valueOf(Long.MAX_VALUE);
+    
+    /**
+     * Fast method for parsing integers that are known to fit into
+     * regular 32-bit signed int type. This means that length is
+     * between 1 and 9 digits (inclusive)
+     *<p>
+     * Note: public to let unit tests call it
+     */
+    public static int parseInt(char[] digitChars, int offset, int len)
+    {
+        int num = digitChars[offset] - '0';
+        len += offset;
+        // This looks ugly, but appears the fastest way (as per measurements)
+        if (++offset < len) {
+            num = (num * 10) + (digitChars[offset] - '0');
+            if (++offset < len) {
+                num = (num * 10) + (digitChars[offset] - '0');
+                if (++offset < len) {
+                    num = (num * 10) + (digitChars[offset] - '0');
+                    if (++offset < len) {
+                        num = (num * 10) + (digitChars[offset] - '0');
+                        if (++offset < len) {
+                            num = (num * 10) + (digitChars[offset] - '0');
+                            if (++offset < len) {
+                                num = (num * 10) + (digitChars[offset] - '0');
+                                if (++offset < len) {
+                                    num = (num * 10) + (digitChars[offset] - '0');
+                                    if (++offset < len) {
+                                        num = (num * 10) + (digitChars[offset] - '0');
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return num;
+    }
+
+    /**
+     * Helper method to (more) efficiently parse integer numbers from
+     * String values.
+     */
+    public static int parseInt(String str)
+    {
+        /* Ok: let's keep strategy simple: ignoring optional minus sign,
+         * we'll accept 1 - 9 digits and parse things efficiently;
+         * otherwise just defer to JDK parse functionality.
+         */
+        char c = str.charAt(0);
+        int length = str.length();
+        boolean negative = (c == '-');
+        int offset = 1;
+        // must have 1 - 9 digits after optional sign:
+        // negative?
+        if (negative) {
+            if (length == 1 || length > 10) {
+                return Integer.parseInt(str);
+            }
+            c = str.charAt(offset++);
+        } else {
+            if (length > 9) {
+                return Integer.parseInt(str);
+            }
+        }
+        if (c > '9' || c < '0') {
+            return Integer.parseInt(str);
+        }
+        int num = c - '0';
+        if (offset < length) {
+            c = str.charAt(offset++);
+            if (c > '9' || c < '0') {
+                return Integer.parseInt(str);
+            }
+            num = (num * 10) + (c - '0');
+            if (offset < length) {
+                c = str.charAt(offset++);
+                if (c > '9' || c < '0') {
+                    return Integer.parseInt(str);
+                }
+                num = (num * 10) + (c - '0');
+                // Let's just loop if we have more than 3 digits:
+                if (offset < length) {
+                    do {
+                        c = str.charAt(offset++);
+                        if (c > '9' || c < '0') {
+                            return Integer.parseInt(str);
+                        }
+                        num = (num * 10) + (c - '0');
+                    } while (offset < length);
+                }
+            }
+        }
+        return negative ? -num : num;
+    }
+    
+    public static long parseLong(char[] digitChars, int offset, int len)
+    {
+        // Note: caller must ensure length is [10, 18]
+        int len1 = len-9;
+        long val = parseInt(digitChars, offset, len1) * L_BILLION;
+        return val + (long) parseInt(digitChars, offset+len1, 9);
+    }
+
+    public static long parseLong(String str)
+    {
+        /* Ok, now; as the very first thing, let's just optimize case of "fake longs";
+         * that is, if we know they must be ints, call int parsing
+         */
+        int length = str.length();
+        if (length <= 9) {
+            return (long) parseInt(str);
+        }
+        // !!! TODO: implement efficient 2-int parsing...
+        return Long.parseLong(str);
+    }
+    
+    /**
+     * Helper method for determining if given String representation of
+     * an integral number would fit in 64-bit Java long or not.
+     * Note that input String must NOT contain leading minus sign (even
+     * if 'negative' is set to true).
+     *
+     * @param negative Whether original number had a minus sign (which is
+     *    NOT passed to this method) or not
+     */
+    public static boolean inLongRange(char[] digitChars, int offset, int len,
+            boolean negative)
+    {
+        String cmpStr = negative ? MIN_LONG_STR_NO_SIGN : MAX_LONG_STR;
+        int cmpLen = cmpStr.length();
+        if (len < cmpLen) return true;
+        if (len > cmpLen) return false;
+
+        for (int i = 0; i < cmpLen; ++i) {
+            int diff = digitChars[offset+i] - cmpStr.charAt(i);
+            if (diff != 0) {
+                return (diff < 0);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Similar to {@link #inLongRange(char[],int,int,boolean)}, but
+     * with String argument
+     *
+     * @param negative Whether original number had a minus sign (which is
+     *    NOT passed to this method) or not
+     */
+    public static boolean inLongRange(String numberStr, boolean negative)
+    {
+        String cmpStr = negative ? MIN_LONG_STR_NO_SIGN : MAX_LONG_STR;
+        int cmpLen = cmpStr.length();
+        int actualLen = numberStr.length();
+        if (actualLen < cmpLen) return true;
+        if (actualLen > cmpLen) return false;
+
+        // could perhaps just use String.compareTo()?
+        for (int i = 0; i < cmpLen; ++i) {
+            int diff = numberStr.charAt(i) - cmpStr.charAt(i);
+            if (diff != 0) {
+                return (diff < 0);
+            }
+        }
+        return true;
+    }
+
+    public static int parseAsInt(String input, int defaultValue)
+    {
+        if (input == null) {
+            return defaultValue;
+        }
+        input = input.trim();
+        int len = input.length();
+        if (len == 0) {
+            return defaultValue;
+        }
+        // One more thing: use integer parsing for 'simple'
+        int i = 0;
+        if (i < len) { // skip leading sign:
+            char c = input.charAt(0);
+            if (c == '+') { // for plus, actually physically remove
+                input = input.substring(1);
+                len = input.length();
+            } else if (c == '-') { // minus, just skip for checks, must retain
+                ++i;
+            }
+        }
+        for (; i < len; ++i) {
+            char c = input.charAt(i);
+            // if other symbols, parse as Double, coerce
+            if (c > '9' || c < '0') {
+                try {
+                    return (int) parseDouble(input);
+                } catch (NumberFormatException e) {
+                    return defaultValue;
+                }
+            }
+        }
+        try {
+            return Integer.parseInt(input);
+        } catch (NumberFormatException e) { }
+        return defaultValue;
+    }
+
+    public static long parseAsLong(String input, long defaultValue)
+    {
+        if (input == null) {
+            return defaultValue;
+        }
+        input = input.trim();
+        int len = input.length();
+        if (len == 0) {
+            return defaultValue;
+        }
+        // One more thing: use long parsing for 'simple'
+        int i = 0;
+        if (i < len) { // skip leading sign:
+            char c = input.charAt(0);
+            if (c == '+') { // for plus, actually physically remove
+                input = input.substring(1);
+                len = input.length();
+            } else if (c == '-') { // minus, just skip for checks, must retain
+                ++i;
+            }
+        }
+        for (; i < len; ++i) {
+            char c = input.charAt(i);
+            // if other symbols, parse as Double, coerce
+            if (c > '9' || c < '0') {
+                try {
+                    return (long) parseDouble(input);
+                } catch (NumberFormatException e) {
+                    return defaultValue;
+                }
+            }
+        }
+        try {
+            return Long.parseLong(input);
+        } catch (NumberFormatException e) { }
+        return defaultValue;
+    }
+    
+    public static double parseAsDouble(String input, double defaultValue)
+    {
+        if (input == null) {
+            return defaultValue;
+        }
+        input = input.trim();
+        int len = input.length();
+        if (len == 0) {
+            return defaultValue;
+        }
+        try {
+            return parseDouble(input);
+        } catch (NumberFormatException e) { }
+        return defaultValue;
+    }
+    
+    public static double parseDouble(String numStr) throws NumberFormatException
+    {
+        // [JACKSON-486]: avoid some nasty float representations... but should it be MIN_NORMAL or MIN_VALUE?
+        /* as per [JACKSON-827], let's use MIN_VALUE as it is available on all JDKs; normalized
+         * only in JDK 1.6. In practice, should not really matter.
+         */
+        if (NASTY_SMALL_DOUBLE.equals(numStr)) {
+            return Double.MIN_VALUE;
+        }
+        return Double.parseDouble(numStr);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java
new file mode 100644
index 0000000..cb9bb8d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java
@@ -0,0 +1,398 @@
+package com.fasterxml.jackson.core.io;
+
+public final class NumberOutput
+{
+    private final static char NULL_CHAR = (char) 0;
+
+    private static int MILLION = 1000000;
+    private static int BILLION = 1000000000;
+    private static long TEN_BILLION_L = 10000000000L;
+    private static long THOUSAND_L = 1000L;
+
+    private static long MIN_INT_AS_LONG = (long) Integer.MIN_VALUE;
+    private static long MAX_INT_AS_LONG = (long) Integer.MAX_VALUE;
+
+    final static String SMALLEST_LONG = String.valueOf(Long.MIN_VALUE);
+
+    final static char[] LEADING_TRIPLETS = new char[4000];
+    final static char[] FULL_TRIPLETS = new char[4000];
+    static {
+        /* Let's fill it with NULLs for ignorable leading digits,
+         * and digit chars for others
+         */
+        int ix = 0;
+        for (int i1 = 0; i1 < 10; ++i1) {
+            char f1 = (char) ('0' + i1);
+            char l1 = (i1 == 0) ? NULL_CHAR : f1;
+            for (int i2 = 0; i2 < 10; ++i2) {
+                char f2 = (char) ('0' + i2);
+                char l2 = (i1 == 0 && i2 == 0) ? NULL_CHAR : f2;
+                for (int i3 = 0; i3 < 10; ++i3) {
+                    // Last is never to be empty
+                    char f3 = (char) ('0' + i3);
+                    LEADING_TRIPLETS[ix] = l1;
+                    LEADING_TRIPLETS[ix+1] = l2;
+                    LEADING_TRIPLETS[ix+2] = f3;
+                    FULL_TRIPLETS[ix] = f1;
+                    FULL_TRIPLETS[ix+1] = f2;
+                    FULL_TRIPLETS[ix+2] = f3;
+                    ix += 4;
+                }
+            }
+        }
+    }
+
+    final static byte[] FULL_TRIPLETS_B = new byte[4000];
+    static {
+        for (int i = 0; i < 4000; ++i) {
+            FULL_TRIPLETS_B[i] = (byte) FULL_TRIPLETS[i];
+        }
+    }
+    
+    final static String[] sSmallIntStrs = new String[] {
+        "0","1","2","3","4","5","6","7","8","9","10"
+    };
+    final static String[] sSmallIntStrs2 = new String[] {
+        "-1","-2","-3","-4","-5","-6","-7","-8","-9","-10"
+    };
+
+    /*
+    /**********************************************************
+    /* Efficient serialization methods using raw buffers
+    /**********************************************************
+     */
+
+    /**
+     * @return Offset within buffer after outputting int
+     */
+    public static int outputInt(int value, char[] buffer, int offset)
+    {
+        if (value < 0) {
+            if (value == Integer.MIN_VALUE) {
+                /* Special case: no matching positive value within range;
+                 * let's then "upgrade" to long and output as such.
+                 */
+                return outputLong((long) value, buffer, offset);
+            }
+            buffer[offset++] = '-';
+            value = -value;
+        }
+
+        if (value < MILLION) { // at most 2 triplets...
+            if (value < 1000) {
+                if (value < 10) {
+                    buffer[offset++] = (char) ('0' + value);
+                } else {
+                    offset = outputLeadingTriplet(value, buffer, offset);
+                }
+            } else {
+                int thousands = value / 1000;
+                value -= (thousands * 1000); // == value % 1000
+                offset = outputLeadingTriplet(thousands, buffer, offset);
+                offset = outputFullTriplet(value, buffer, offset);
+            }
+            return offset;
+        }
+
+        // ok, all 3 triplets included
+        /* Let's first hand possible billions separately before
+         * handling 3 triplets. This is possible since we know we
+         * can have at most '2' as billion count.
+         */
+        boolean hasBillions = (value >= BILLION);
+        if (hasBillions) {
+            value -= BILLION;
+            if (value >= BILLION) {
+                value -= BILLION;
+                buffer[offset++] = '2';
+            } else {
+                buffer[offset++] = '1';
+            }
+        }
+        int newValue = value / 1000;
+        int ones = (value - (newValue * 1000)); // == value % 1000
+        value = newValue;
+        newValue /= 1000;
+        int thousands = (value - (newValue * 1000));
+        
+        // value now has millions, which have 1, 2 or 3 digits
+        if (hasBillions) {
+            offset = outputFullTriplet(newValue, buffer, offset);
+        } else {
+            offset = outputLeadingTriplet(newValue, buffer, offset);
+        }
+        offset = outputFullTriplet(thousands, buffer, offset);
+        offset = outputFullTriplet(ones, buffer, offset);
+        return offset;
+    }
+
+    public static int outputInt(int value, byte[] buffer, int offset)
+    {
+        if (value < 0) {
+            if (value == Integer.MIN_VALUE) {
+                return outputLong((long) value, buffer, offset);
+            }
+            buffer[offset++] = '-';
+            value = -value;
+        }
+
+        if (value < MILLION) { // at most 2 triplets...
+            if (value < 1000) {
+                if (value < 10) {
+                    buffer[offset++] = (byte) ('0' + value);
+                } else {
+                    offset = outputLeadingTriplet(value, buffer, offset);
+                }
+            } else {
+                int thousands = value / 1000;
+                value -= (thousands * 1000); // == value % 1000
+                offset = outputLeadingTriplet(thousands, buffer, offset);
+                offset = outputFullTriplet(value, buffer, offset);
+            }
+            return offset;
+        }
+        boolean hasBillions = (value >= BILLION);
+        if (hasBillions) {
+            value -= BILLION;
+            if (value >= BILLION) {
+                value -= BILLION;
+                buffer[offset++] = '2';
+            } else {
+                buffer[offset++] = '1';
+            }
+        }
+        int newValue = value / 1000;
+        int ones = (value - (newValue * 1000)); // == value % 1000
+        value = newValue;
+        newValue /= 1000;
+        int thousands = (value - (newValue * 1000));
+        
+        if (hasBillions) {
+            offset = outputFullTriplet(newValue, buffer, offset);
+        } else {
+            offset = outputLeadingTriplet(newValue, buffer, offset);
+        }
+        offset = outputFullTriplet(thousands, buffer, offset);
+        offset = outputFullTriplet(ones, buffer, offset);
+        return offset;
+    }
+    
+    /**
+     * @return Offset within buffer after outputting int
+     */
+    public static int outputLong(long value, char[] buffer, int offset)
+    {
+        // First: does it actually fit in an int?
+        if (value < 0L) {
+            /* MIN_INT is actually printed as long, just because its
+             * negation is not an int but long
+             */
+            if (value > MIN_INT_AS_LONG) {
+                return outputInt((int) value, buffer, offset);
+            }
+            if (value == Long.MIN_VALUE) {
+                // Special case: no matching positive value within range
+                int len = SMALLEST_LONG.length();
+                SMALLEST_LONG.getChars(0, len, buffer, offset);
+                return (offset + len);
+            }
+            buffer[offset++] = '-';
+            value = -value;
+        } else {
+            if (value <= MAX_INT_AS_LONG) {
+                return outputInt((int) value, buffer, offset);
+            }
+        }
+
+        /* Ok: real long print. Need to first figure out length
+         * in characters, and then print in from end to beginning
+         */
+        int origOffset = offset;
+        offset += calcLongStrLength(value);
+        int ptr = offset;
+
+        // First, with long arithmetics:
+        while (value > MAX_INT_AS_LONG) { // full triplet
+            ptr -= 3;
+            long newValue = value / THOUSAND_L;
+            int triplet = (int) (value - newValue * THOUSAND_L);
+            outputFullTriplet(triplet, buffer, ptr);
+            value = newValue;
+        }
+        // Then with int arithmetics:
+        int ivalue = (int) value;
+        while (ivalue >= 1000) { // still full triplet
+            ptr -= 3;
+            int newValue = ivalue / 1000;
+            int triplet = ivalue - (newValue * 1000);
+            outputFullTriplet(triplet, buffer, ptr);
+            ivalue = newValue;
+        }
+        // And finally, if anything remains, partial triplet
+        outputLeadingTriplet(ivalue, buffer, origOffset);
+
+        return offset;
+    }
+
+    public static int outputLong(long value, byte[] buffer, int offset)
+    {
+        if (value < 0L) {
+            if (value > MIN_INT_AS_LONG) {
+                return outputInt((int) value, buffer, offset);
+            }
+            if (value == Long.MIN_VALUE) {
+                // Special case: no matching positive value within range
+                int len = SMALLEST_LONG.length();
+                for (int i = 0; i < len; ++i) {
+                    buffer[offset++] = (byte) SMALLEST_LONG.charAt(i);
+                }
+                return offset;
+            }
+            buffer[offset++] = '-';
+            value = -value;
+        } else {
+            if (value <= MAX_INT_AS_LONG) {
+                return outputInt((int) value, buffer, offset);
+            }
+        }
+        int origOffset = offset;
+        offset += calcLongStrLength(value);
+        int ptr = offset;
+
+        // First, with long arithmetics:
+        while (value > MAX_INT_AS_LONG) { // full triplet
+            ptr -= 3;
+            long newValue = value / THOUSAND_L;
+            int triplet = (int) (value - newValue * THOUSAND_L);
+            outputFullTriplet(triplet, buffer, ptr);
+            value = newValue;
+        }
+        // Then with int arithmetics:
+        int ivalue = (int) value;
+        while (ivalue >= 1000) { // still full triplet
+            ptr -= 3;
+            int newValue = ivalue / 1000;
+            int triplet = ivalue - (newValue * 1000);
+            outputFullTriplet(triplet, buffer, ptr);
+            ivalue = newValue;
+        }
+        outputLeadingTriplet(ivalue, buffer, origOffset);
+        return offset;
+    }
+    
+    /*
+    /**********************************************************
+    /* Secondary convenience serialization methods
+    /**********************************************************
+     */
+
+    /* !!! 05-Aug-2008, tatus: Any ways to further optimize
+     *   these? (or need: only called by diagnostics methods?)
+     */
+
+    public static String toString(int value)
+    {
+        // Lookup table for small values
+        if (value < sSmallIntStrs.length) {
+            if (value >= 0) {
+                return sSmallIntStrs[value];
+            }
+            int v2 = -value - 1;
+            if (v2 < sSmallIntStrs2.length) {
+                return sSmallIntStrs2[v2];
+            }
+        }
+        return Integer.toString(value);
+    }
+
+    public static String toString(long value)
+    {
+        if (value <= Integer.MAX_VALUE &&
+            value >= Integer.MIN_VALUE) {
+            return toString((int) value);
+        }
+        return Long.toString(value);
+    }
+
+    public static String toString(double value)
+    {
+        return Double.toString(value);
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    private static int outputLeadingTriplet(int triplet, char[] buffer, int offset)
+    {
+        int digitOffset = (triplet << 2);
+        char c = LEADING_TRIPLETS[digitOffset++];
+        if (c != NULL_CHAR) {
+            buffer[offset++] = c;
+        }
+        c = LEADING_TRIPLETS[digitOffset++];
+        if (c != NULL_CHAR) {
+            buffer[offset++] = c;
+        }
+        // Last is required to be non-empty
+        buffer[offset++] = LEADING_TRIPLETS[digitOffset];
+        return offset;
+    }
+
+    private static int outputLeadingTriplet(int triplet, byte[] buffer, int offset)
+    {
+        int digitOffset = (triplet << 2);
+        char c = LEADING_TRIPLETS[digitOffset++];
+        if (c != NULL_CHAR) {
+            buffer[offset++] = (byte) c;
+        }
+        c = LEADING_TRIPLETS[digitOffset++];
+        if (c != NULL_CHAR) {
+            buffer[offset++] = (byte) c;
+        }
+        // Last is required to be non-empty
+        buffer[offset++] = (byte) LEADING_TRIPLETS[digitOffset];
+        return offset;
+    }
+    
+    private static int outputFullTriplet(int triplet, char[] buffer, int offset)
+    {
+        int digitOffset = (triplet << 2);
+        buffer[offset++] = FULL_TRIPLETS[digitOffset++];
+        buffer[offset++] = FULL_TRIPLETS[digitOffset++];
+        buffer[offset++] = FULL_TRIPLETS[digitOffset];
+        return offset;
+    }
+
+    private static int outputFullTriplet(int triplet, byte[] buffer, int offset)
+    {
+        int digitOffset = (triplet << 2);
+        buffer[offset++] = FULL_TRIPLETS_B[digitOffset++];
+        buffer[offset++] = FULL_TRIPLETS_B[digitOffset++];
+        buffer[offset++] = FULL_TRIPLETS_B[digitOffset];
+        return offset;
+    }
+    
+    /**
+     *<p>
+     * Pre-conditions: posValue is positive, and larger than
+     * Integer.MAX_VALUE (about 2 billions).
+     */
+    private static int calcLongStrLength(long posValue)
+    {
+        int len = 10;
+        long comp = TEN_BILLION_L;
+
+        // 19 is longest, need to worry about overflow
+        while (posValue >= comp) {
+            if (len == 19) {
+                break;
+            }
+            ++len;
+            comp = (comp << 3) + (comp << 1); // 10x
+        }
+        return len;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/OutputDecorator.java b/src/main/java/com/fasterxml/jackson/core/io/OutputDecorator.java
new file mode 100644
index 0000000..7a48c42
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/OutputDecorator.java
@@ -0,0 +1,41 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+/**
+ * Handler class that can be used to decorate output destinations.
+ * Typical use is to use a filter abstraction (filtered output stream,
+ * writer) around original output destination, and apply additional
+ * processing during write operations.
+ */
+public abstract class OutputDecorator
+    implements java.io.Serializable // since 2.1
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Method called by {@link com.fasterxml.jackson.core.JsonFactory} instance when
+     * creating generator for given {@link OutputStream}, when this decorator
+     * has been registered.
+     * 
+     * @param ctxt IO context in use (provides access to declared encoding)
+     * @param out Original output destination
+     * 
+     * @return OutputStream to use; either passed in argument, or something that
+     *   calls it
+     */
+    public abstract OutputStream decorate(IOContext ctxt, OutputStream out)
+        throws IOException;
+
+    /**
+     * Method called by {@link com.fasterxml.jackson.core.JsonFactory} instance when
+     * creating generator for given {@link Writer}, when this decorator
+     * has been registered.
+     * 
+     * @param ctxt IO context in use (provides access to declared encoding)
+     * @param w Original output writer
+     * 
+     * @return Writer to use; either passed in argument, or something that calls it
+     */
+    public abstract Writer decorate(IOContext ctxt, Writer w) throws IOException;
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java b/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java
new file mode 100644
index 0000000..cce34bd
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java
@@ -0,0 +1,102 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.util.BufferRecycler;
+import com.fasterxml.jackson.core.util.TextBuffer;
+
+/**
+ * Efficient alternative to {@link StringWriter}, based on using segmented
+ * internal buffer. Initial input buffer is also recyclable.
+ *<p>
+ * This class is most useful when serializing JSON content as a String:
+ * if so, instance of this class can be given as the writer to
+ * <code>JsonGenerator</code>.
+ */
+public final class SegmentedStringWriter
+    extends Writer
+{
+    final protected TextBuffer _buffer;
+
+    public SegmentedStringWriter(BufferRecycler br)
+    {
+        super();
+        _buffer = new TextBuffer(br);
+    }
+
+    /*
+    /**********************************************************
+    /* java.io.Writer implementation
+    /**********************************************************
+     */
+
+    @Override
+    public Writer append(char c)
+    {
+        write(c);
+        return this;
+    }
+
+    @Override
+    public Writer append(CharSequence csq)
+    {
+        String str = csq.toString();
+        _buffer.append(str, 0, str.length());
+        return this;
+    }
+
+    @Override
+    public Writer append(CharSequence csq, int start, int end)
+    {
+        String str = csq.subSequence(start, end).toString();
+        _buffer.append(str, 0, str.length());
+        return this;
+    }
+
+    @Override public void close() { } // NOP
+
+    @Override public void flush() { } // NOP
+
+    @Override
+    public void write(char[] cbuf) {
+        _buffer.append(cbuf, 0, cbuf.length);
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) {
+        _buffer.append(cbuf, off, len);
+    }
+
+    @Override
+    public void write(int c) {
+        _buffer.append((char) c);
+    }
+
+    @Override
+    public void write(String str) { _buffer.append(str, 0, str.length()); }
+
+    @Override
+    public void write(String str, int off, int len) {
+        _buffer.append(str, off, len);
+    }
+
+    /*
+    /**********************************************************
+    /* Extended API
+    /**********************************************************
+     */
+
+    /**
+     * Main access method that will construct a String that contains
+     * all the contents, release all internal buffers we may have,
+     * and return result String.
+     * Note that the method is not idempotent -- if called second time,
+     * will just return an empty String.
+     */
+    public String getAndClear()
+    {
+        String result = _buffer.contentsAsString();
+        _buffer.releaseBuffers();
+        return result;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/SerializedString.java b/src/main/java/com/fasterxml/jackson/core/io/SerializedString.java
new file mode 100644
index 0000000..df39aac
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/SerializedString.java
@@ -0,0 +1,272 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+
+import com.fasterxml.jackson.core.SerializableString;
+
+/**
+ * String token that can lazily serialize String contained and then reuse that
+ * serialization later on. This is similar to JDBC prepared statements, for example,
+ * in that instances should only be created when they are used more than use;
+ * prime candidates are various serializers.
+ *<p>
+ * Class is final for performance reasons and since this is not designed to
+ * be extensible or customizable (customizations would occur in calling code)
+ */
+public class SerializedString
+    implements SerializableString, java.io.Serializable
+{
+    protected final String _value;
+
+    /* 13-Dec-2010, tatu: Whether use volatile or not is actually an important
+     *   decision for multi-core use cases. Cost of volatility can be non-trivial
+     *   for heavy use cases, and serialized-string instances are accessed often.
+     *   Given that all code paths with common Jackson usage patterns go through
+     *   a few memory barriers (mostly with cache/reuse pool access) it seems safe
+     *   enough to omit volatiles here, given how simple lazy initialization is.
+     *   This can be compared to how {@link String#intern} works; lazily and
+     *   without synchronization or use of volatile keyword.
+     */
+    
+    protected /*volatile*/ byte[] _quotedUTF8Ref;
+
+    protected /*volatile*/ byte[] _unquotedUTF8Ref;
+
+    protected /*volatile*/ char[] _quotedChars;
+
+    public SerializedString(String v) {
+        if (v == null) {
+            throw new IllegalStateException("Null String illegal for SerializedString");
+        }
+        _value = v;
+    }
+    
+    /*
+    /**********************************************************
+    /* Serializable overrides
+    /**********************************************************
+     */
+
+    /**
+     * Ugly hack, to work through the requirement that _value is indeed final,
+     * and that JDK serialization won't call ctor(s).
+     * 
+     * @since 2.1
+     */
+    protected transient String _jdkSerializeValue;
+
+    private void readObject(ObjectInputStream in) throws IOException {
+        _jdkSerializeValue = in.readUTF();
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.writeUTF(_value);
+    }
+
+    protected Object readResolve() {
+        return new SerializedString(_jdkSerializeValue);
+    }
+
+    /*
+    /**********************************************************
+    /* API
+    /**********************************************************
+     */
+
+    @Override
+    public final String getValue() { return _value; }
+    
+    /**
+     * Returns length of the String as characters
+     */
+    @Override
+    public final int charLength() { return _value.length(); }
+    
+    @Override
+    public final char[] asQuotedChars()
+    {
+        char[] result = _quotedChars;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().quoteAsString(_value);
+            _quotedChars = result;
+        }
+        return result;
+    }
+
+    /**
+     * Accessor for accessing value that has been quoted using JSON
+     * quoting rules, and encoded using UTF-8 encoding.
+     */
+    @Override
+    public final byte[] asUnquotedUTF8()
+    {
+        byte[] result = _unquotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().encodeAsUTF8(_value);
+            _unquotedUTF8Ref  = result;
+        }
+        return result;
+    }
+
+    /**
+     * Accessor for accessing value as is (without JSON quoting)
+     * encoded using UTF-8 encoding.
+     */
+    @Override
+    public final byte[] asQuotedUTF8()
+    {
+        byte[] result = _quotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().quoteAsUTF8(_value);
+            _quotedUTF8Ref = result;
+        }
+        return result;
+    }
+
+    /*
+    /**********************************************************
+    /* Additional 2.0 methods for appending/writing contents
+    /**********************************************************
+     */
+
+    @Override
+    public int appendQuotedUTF8(byte[] buffer, int offset)
+    {
+        byte[] result = _quotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().quoteAsUTF8(_value);
+            _quotedUTF8Ref = result;
+        }
+        final int length = result.length;
+        if ((offset + length) > buffer.length) {
+            return -1;
+        }
+        System.arraycopy(result, 0, buffer, offset, length);
+        return length;
+    }
+
+    @Override
+    public int appendQuoted(char[] buffer, int offset)
+    {
+        char[] result = _quotedChars;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().quoteAsString(_value);
+            _quotedChars = result;
+        }
+        final int length = result.length;
+        if ((offset + length) > buffer.length) {
+            return -1;
+        }
+        System.arraycopy(result, 0, buffer, offset, length);
+        return length;
+    }
+
+    @Override
+    public int appendUnquotedUTF8(byte[] buffer, int offset)
+    {
+        byte[] result = _unquotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().encodeAsUTF8(_value);
+            _unquotedUTF8Ref  = result;
+        }
+        final int length = result.length;
+        if ((offset + length) > buffer.length) {
+            return -1;
+        }
+        System.arraycopy(result, 0, buffer, offset, length);
+        return length;
+    }
+
+    @Override
+    public int appendUnquoted(char[] buffer, int offset)
+    {
+        String str = _value;
+        final int length = str.length();
+        if ((offset + length) > buffer.length) {
+            return -1;
+        }
+        str.getChars(0,  length, buffer, offset);
+        return length;
+    }
+
+    @Override
+    public int writeQuotedUTF8(OutputStream out) throws IOException
+    {
+        byte[] result = _quotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().quoteAsUTF8(_value);
+            _quotedUTF8Ref = result;
+        }
+        final int length = result.length;
+        out.write(result, 0, length);
+        return length;
+    }
+
+    @Override
+    public int writeUnquotedUTF8(OutputStream out) throws IOException
+    {
+        byte[] result = _unquotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().encodeAsUTF8(_value);
+            _unquotedUTF8Ref  = result;
+        }
+        final int length = result.length;
+        out.write(result, 0, length);
+        return length;
+    }
+
+    @Override
+    public int putQuotedUTF8(ByteBuffer buffer)
+    {
+        byte[] result = _quotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().quoteAsUTF8(_value);
+            _quotedUTF8Ref = result;
+        }
+        final int length = result.length;
+        if (length > buffer.remaining()) {
+            return -1;
+        }
+        buffer.put(result, 0, length);
+        return length;
+    }
+
+    @Override
+    public int putUnquotedUTF8(ByteBuffer buffer)
+    {
+        byte[] result = _unquotedUTF8Ref;
+        if (result == null) {
+            result = JsonStringEncoder.getInstance().encodeAsUTF8(_value);
+            _unquotedUTF8Ref  = result;
+        }
+        final int length = result.length;
+        if (length > buffer.remaining()) {
+            return -1;
+        }
+        buffer.put(result, 0, length);
+        return length;
+    }
+
+    
+    /*
+    /**********************************************************
+    /* Standard method overrides
+    /**********************************************************
+     */
+
+    @Override
+    public final String toString() { return _value; }
+    
+    @Override
+    public final int hashCode() { return _value.hashCode(); }
+
+    @Override
+    public final boolean equals(Object o)
+    {
+        if (o == this) return true;
+        if (o == null || o.getClass() != getClass()) return false;
+        SerializedString other = (SerializedString) o;
+        return _value.equals(other._value);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/UTF32Reader.java b/src/main/java/com/fasterxml/jackson/core/io/UTF32Reader.java
new file mode 100644
index 0000000..bfa7a37
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/UTF32Reader.java
@@ -0,0 +1,218 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+
+/**
+ * Since JDK does not come with UTF-32/UCS-4, let's implement a simple
+ * decoder to use.
+ */
+public class UTF32Reader
+    extends BaseReader
+{
+    protected final boolean _bigEndian;
+
+    /**
+     * Although input is fine with full Unicode set, Java still uses
+     * 16-bit chars, so we may have to split high-order chars into
+     * surrogate pairs.
+     */
+    protected char _surrogate = NULL_CHAR;
+
+    /**
+     * Total read character count; used for error reporting purposes
+     */
+    protected int _charCount = 0;
+
+    /**
+     * Total read byte count; used for error reporting purposes
+     */
+    protected int _byteCount = 0;
+
+    protected final boolean _managedBuffers;
+    
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public UTF32Reader(IOContext ctxt,
+            InputStream in, byte[] buf, int ptr, int len,
+            boolean isBigEndian)
+    {
+        super(ctxt, in, buf, ptr, len);
+        _bigEndian = isBigEndian;
+        _managedBuffers = (in != null);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API
+    /**********************************************************
+     */
+
+    @Override
+	public int read(char[] cbuf, int start, int len)
+        throws IOException
+    {
+        // Already EOF?
+        if (_buffer == null) {
+            return -1;
+        }
+        if (len < 1) {
+            return len;
+        }
+        // Let's then ensure there's enough room...
+        if (start < 0 || (start+len) > cbuf.length) {
+            reportBounds(cbuf, start, len);
+        }
+
+        len += start;
+        int outPtr = start;
+
+        // Ok, first; do we have a surrogate from last round?
+        if (_surrogate != NULL_CHAR) {
+            cbuf[outPtr++] = _surrogate;
+            _surrogate = NULL_CHAR;
+            // No need to load more, already got one char
+        } else {
+            /* Note: we'll try to avoid blocking as much as possible. As a
+             * result, we only need to get 4 bytes for a full char.
+             */
+            int left = (_length - _ptr);
+            if (left < 4) {
+                if (!loadMore(left)) { // (legal) EOF?
+                    return -1;
+                }
+            }
+        }
+
+        main_loop:
+        while (outPtr < len) {
+            int ptr = _ptr;
+            int ch;
+
+            if (_bigEndian) {
+                ch = (_buffer[ptr] << 24) | ((_buffer[ptr+1] & 0xFF) << 16)
+                    | ((_buffer[ptr+2] & 0xFF) << 8) | (_buffer[ptr+3] & 0xFF);
+            } else {
+                ch = (_buffer[ptr] & 0xFF) | ((_buffer[ptr+1] & 0xFF) << 8)
+                    | ((_buffer[ptr+2] & 0xFF) << 16) | (_buffer[ptr+3] << 24);
+            }
+            _ptr += 4;
+
+            // Does it need to be split to surrogates?
+            // (also, we can and need to verify illegal chars)
+            if (ch > 0xFFFF) { // need to split into surrogates?
+                if (ch > LAST_VALID_UNICODE_CHAR) {
+                    reportInvalid(ch, outPtr-start,
+                                  "(above "+Integer.toHexString(LAST_VALID_UNICODE_CHAR)+") ");
+                }
+                ch -= 0x10000; // to normalize it starting with 0x0
+                cbuf[outPtr++] = (char) (0xD800 + (ch >> 10));
+                // hmmh. can this ever be 0? (not legal, at least?)
+                ch = (0xDC00 | (ch & 0x03FF));
+                // Room for second part?
+                if (outPtr >= len) { // nope
+                    _surrogate = (char) ch;
+                    break main_loop;
+                }
+            }
+            cbuf[outPtr++] = (char) ch;
+            if (_ptr >= _length) {
+                break main_loop;
+            }
+        }
+
+        len = outPtr - start;
+        _charCount += len;
+        return len;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    private void reportUnexpectedEOF(int gotBytes, int needed)
+        throws IOException
+    {
+        int bytePos = _byteCount + gotBytes;
+        int charPos = _charCount;
+
+        throw new CharConversionException("Unexpected EOF in the middle of a 4-byte UTF-32 char: got "
+                +gotBytes+", needed "+needed+", at char #"+charPos+", byte #"+bytePos+")");
+    }
+
+    private void reportInvalid(int value, int offset, String msg)
+        throws IOException
+    {
+        int bytePos = _byteCount + _ptr - 1;
+        int charPos = _charCount + offset;
+
+        throw new CharConversionException("Invalid UTF-32 character 0x"
+                +Integer.toHexString(value)+msg+" at char #"+charPos+", byte #"+bytePos+")");
+    }
+
+    /**
+     * @param available Number of "unused" bytes in the input buffer
+     *
+     * @return True, if enough bytes were read to allow decoding of at least
+     *   one full character; false if EOF was encountered instead.
+     */
+    private boolean loadMore(int available)
+        throws IOException
+    {
+        _byteCount += (_length - available);
+
+        // Bytes that need to be moved to the beginning of buffer?
+        if (available > 0) {
+            if (_ptr > 0) {
+                for (int i = 0; i < available; ++i) {
+                    _buffer[i] = _buffer[_ptr+i];
+                }
+                _ptr = 0;
+            }
+            _length = available;
+        } else {
+            /* Ok; here we can actually reasonably expect an EOF,
+             * so let's do a separate read right away:
+             */
+            _ptr = 0;
+            int count = (_in == null) ? -1 : _in.read(_buffer);
+            if (count < 1) {
+                _length = 0;
+                if (count < 0) { // -1
+                    if (_managedBuffers) {
+                        freeBuffers(); // to help GC?
+                    }
+                    return false;
+                }
+                // 0 count is no good; let's err out
+                reportStrangeStream();
+            }
+            _length = count;
+        }
+
+        /* Need at least 4 bytes; if we don't get that many, it's an
+         * error.
+         */
+        while (_length < 4) {
+            int count = (_in == null) ? -1 : _in.read(_buffer, _length, _buffer.length - _length);
+            if (count < 1) {
+                if (count < 0) { // -1, EOF... no good!
+                    if (_managedBuffers) {
+                        freeBuffers(); // to help GC?
+                    }
+                    reportUnexpectedEOF(_length, 4);
+                }
+                // 0 count is no good; let's err out
+                reportStrangeStream();
+            }
+            _length += count;
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/io/UTF8Writer.java b/src/main/java/com/fasterxml/jackson/core/io/UTF8Writer.java
new file mode 100644
index 0000000..ab358d9
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/UTF8Writer.java
@@ -0,0 +1,387 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+public final class UTF8Writer extends Writer
+{
+    final static int SURR1_FIRST = 0xD800;
+    final static int SURR1_LAST = 0xDBFF;
+    final static int SURR2_FIRST = 0xDC00;
+    final static int SURR2_LAST = 0xDFFF;
+
+    final private IOContext _context;
+
+    private OutputStream _out;
+
+    private byte[] _outBuffer;
+
+    final private int _outBufferEnd;
+
+    private int _outPtr;
+
+    /**
+     * When outputting chars from BMP, surrogate pairs need to be coalesced.
+     * To do this, both pairs must be known first; and since it is possible
+     * pairs may be split, we need temporary storage for the first half
+     */
+    private int _surrogate = 0;
+
+    public UTF8Writer(IOContext ctxt, OutputStream out)
+    {
+        _context = ctxt;
+        _out = out;
+
+        _outBuffer = ctxt.allocWriteEncodingBuffer();
+        /* Max. expansion for a single char (in unmodified UTF-8) is
+         * 4 bytes (or 3 depending on how you view it -- 4 when recombining
+         * surrogate pairs)
+         */
+        _outBufferEnd = _outBuffer.length - 4;
+        _outPtr = 0;
+    }
+
+    @Override
+    public Writer append(char c)
+        throws IOException
+    {
+        write(c);
+        return this;
+    }
+
+    @Override
+    public void close()
+        throws IOException
+    {
+        if (_out != null) {
+            if (_outPtr > 0) {
+                _out.write(_outBuffer, 0, _outPtr);
+                _outPtr = 0;
+            }
+            OutputStream out = _out;
+            _out = null;
+
+            byte[] buf = _outBuffer;
+            if (buf != null) {
+                _outBuffer = null;
+                _context.releaseWriteEncodingBuffer(buf);
+            }
+
+            out.close();
+
+            /* Let's 'flush' orphan surrogate, no matter what; but only
+             * after cleanly closing everything else.
+             */
+            int code = _surrogate;
+            _surrogate = 0;
+            if (code > 0) {
+                illegalSurrogate(code);
+            }
+        }
+    }
+
+    @Override
+    public void flush()
+        throws IOException
+    {
+        if (_out != null) {
+            if (_outPtr > 0) {
+                _out.write(_outBuffer, 0, _outPtr);
+                _outPtr = 0;
+            }
+            _out.flush();
+        }
+    }
+
+    @Override
+    public void write(char[] cbuf)
+        throws IOException
+    {
+        write(cbuf, 0, cbuf.length);
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len)
+        throws IOException
+    {
+        if (len < 2) {
+            if (len == 1) {
+                write(cbuf[off]);
+            }
+            return;
+        }
+
+        // First: do we have a leftover surrogate to deal with?
+        if (_surrogate > 0) {
+            char second = cbuf[off++];
+            --len;
+            write(convertSurrogate(second));
+            // will have at least one more char
+        }
+
+        int outPtr = _outPtr;
+        byte[] outBuf = _outBuffer;
+        int outBufLast = _outBufferEnd; // has 4 'spare' bytes
+
+        // All right; can just loop it nice and easy now:
+        len += off; // len will now be the end of input buffer
+
+        output_loop:
+        for (; off < len; ) {
+            /* First, let's ensure we can output at least 4 bytes
+             * (longest UTF-8 encoded codepoint):
+             */
+            if (outPtr >= outBufLast) {
+                _out.write(outBuf, 0, outPtr);
+                outPtr = 0;
+            }
+
+            int c = cbuf[off++];
+            // And then see if we have an Ascii char:
+            if (c < 0x80) { // If so, can do a tight inner loop:
+                outBuf[outPtr++] = (byte)c;
+                // Let's calc how many ascii chars we can copy at most:
+                int maxInCount = (len - off);
+                int maxOutCount = (outBufLast - outPtr);
+
+                if (maxInCount > maxOutCount) {
+                    maxInCount = maxOutCount;
+                }
+                maxInCount += off;
+                ascii_loop:
+                while (true) {
+                    if (off >= maxInCount) { // done with max. ascii seq
+                        continue output_loop;
+                    }
+                    c = cbuf[off++];
+                    if (c >= 0x80) {
+                        break ascii_loop;
+                    }
+                    outBuf[outPtr++] = (byte) c;
+                }
+            }
+
+            // Nope, multi-byte:
+            if (c < 0x800) { // 2-byte
+                outBuf[outPtr++] = (byte) (0xc0 | (c >> 6));
+                outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+            } else { // 3 or 4 bytes
+                // Surrogates?
+                if (c < SURR1_FIRST || c > SURR2_LAST) {
+                    outBuf[outPtr++] = (byte) (0xe0 | (c >> 12));
+                    outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                    outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+                    continue;
+                }
+                // Yup, a surrogate:
+                if (c > SURR1_LAST) { // must be from first range
+                    _outPtr = outPtr;
+                    illegalSurrogate(c);
+                }
+                _surrogate = c;
+                // and if so, followed by another from next range
+                if (off >= len) { // unless we hit the end?
+                    break;
+                }
+                c = convertSurrogate(cbuf[off++]);
+                if (c > 0x10FFFF) { // illegal in JSON as well as in XML
+                    _outPtr = outPtr;
+                    illegalSurrogate(c);
+                }
+                outBuf[outPtr++] = (byte) (0xf0 | (c >> 18));
+                outBuf[outPtr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+                outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+            }
+        }
+        _outPtr = outPtr;
+    }
+    
+    @Override
+    public void write(int c) throws IOException
+    {
+        // First; do we have a left over surrogate?
+        if (_surrogate > 0) {
+            c = convertSurrogate(c);
+            // If not, do we start with a surrogate?
+        } else if (c >= SURR1_FIRST && c <= SURR2_LAST) {
+            // Illegal to get second part without first:
+            if (c > SURR1_LAST) {
+                illegalSurrogate(c);
+            }
+            // First part just needs to be held for now
+            _surrogate = c;
+            return;
+        }
+
+        if (_outPtr >= _outBufferEnd) { // let's require enough room, first
+            _out.write(_outBuffer, 0, _outPtr);
+            _outPtr = 0;
+        }
+
+        if (c < 0x80) { // ascii
+            _outBuffer[_outPtr++] = (byte) c;
+        } else {
+            int ptr = _outPtr;
+            if (c < 0x800) { // 2-byte
+                _outBuffer[ptr++] = (byte) (0xc0 | (c >> 6));
+                _outBuffer[ptr++] = (byte) (0x80 | (c & 0x3f));
+            } else if (c <= 0xFFFF) { // 3 bytes
+                _outBuffer[ptr++] = (byte) (0xe0 | (c >> 12));
+                _outBuffer[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                _outBuffer[ptr++] = (byte) (0x80 | (c & 0x3f));
+            } else { // 4 bytes
+                if (c > 0x10FFFF) { // illegal
+                    illegalSurrogate(c);
+                }
+                _outBuffer[ptr++] = (byte) (0xf0 | (c >> 18));
+                _outBuffer[ptr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+                _outBuffer[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                _outBuffer[ptr++] = (byte) (0x80 | (c & 0x3f));
+            }
+            _outPtr = ptr;
+        }
+    }
+
+    @Override
+    public void write(String str) throws IOException
+    {
+        write(str, 0, str.length());
+    }
+
+    @Override
+    public void write(String str, int off, int len)  throws IOException
+    {
+        if (len < 2) {
+            if (len == 1) {
+                write(str.charAt(off));
+            }
+            return;
+        }
+
+        // First: do we have a leftover surrogate to deal with?
+        if (_surrogate > 0) {
+            char second = str.charAt(off++);
+            --len;
+            write(convertSurrogate(second));
+            // will have at least one more char (case of 1 char was checked earlier on)
+        }
+
+        int outPtr = _outPtr;
+        byte[] outBuf = _outBuffer;
+        int outBufLast = _outBufferEnd; // has 4 'spare' bytes
+
+        // All right; can just loop it nice and easy now:
+        len += off; // len will now be the end of input buffer
+
+        output_loop:
+        for (; off < len; ) {
+            /* First, let's ensure we can output at least 4 bytes
+             * (longest UTF-8 encoded codepoint):
+             */
+            if (outPtr >= outBufLast) {
+                _out.write(outBuf, 0, outPtr);
+                outPtr = 0;
+            }
+
+            int c = str.charAt(off++);
+            // And then see if we have an Ascii char:
+            if (c < 0x80) { // If so, can do a tight inner loop:
+                outBuf[outPtr++] = (byte)c;
+                // Let's calc how many ascii chars we can copy at most:
+                int maxInCount = (len - off);
+                int maxOutCount = (outBufLast - outPtr);
+
+                if (maxInCount > maxOutCount) {
+                    maxInCount = maxOutCount;
+                }
+                maxInCount += off;
+                ascii_loop:
+                while (true) {
+                    if (off >= maxInCount) { // done with max. ascii seq
+                        continue output_loop;
+                    }
+                    c = str.charAt(off++);
+                    if (c >= 0x80) {
+                        break ascii_loop;
+                    }
+                    outBuf[outPtr++] = (byte) c;
+                }
+            }
+
+            // Nope, multi-byte:
+            if (c < 0x800) { // 2-byte
+                outBuf[outPtr++] = (byte) (0xc0 | (c >> 6));
+                outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+            } else { // 3 or 4 bytes
+                // Surrogates?
+                if (c < SURR1_FIRST || c > SURR2_LAST) {
+                    outBuf[outPtr++] = (byte) (0xe0 | (c >> 12));
+                    outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                    outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+                    continue;
+                }
+                // Yup, a surrogate:
+                if (c > SURR1_LAST) { // must be from first range
+                    _outPtr = outPtr;
+                    illegalSurrogate(c);
+                }
+                _surrogate = c;
+                // and if so, followed by another from next range
+                if (off >= len) { // unless we hit the end?
+                    break;
+                }
+                c = convertSurrogate(str.charAt(off++));
+                if (c > 0x10FFFF) { // illegal, as per RFC 4627
+                    _outPtr = outPtr;
+                    illegalSurrogate(c);
+                }
+                outBuf[outPtr++] = (byte) (0xf0 | (c >> 18));
+                outBuf[outPtr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+                outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+                outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+            }
+        }
+        _outPtr = outPtr;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    /**
+     * Method called to calculate UTF codepoint, from a surrogate pair.
+     */
+    protected int convertSurrogate(int secondPart)
+        throws IOException
+    {
+        int firstPart = _surrogate;
+        _surrogate = 0;
+
+        // Ok, then, is the second part valid?
+        if (secondPart < SURR2_FIRST || secondPart > SURR2_LAST) {
+            throw new IOException("Broken surrogate pair: first char 0x"+Integer.toHexString(firstPart)+", second 0x"+Integer.toHexString(secondPart)+"; illegal combination");
+        }
+        return 0x10000 + ((firstPart - SURR1_FIRST) << 10) + (secondPart - SURR2_FIRST);
+    }
+    
+    protected static void illegalSurrogate(int code) throws IOException {
+        throw new IOException(illegalSurrogateDesc(code));
+    }
+    
+    protected static String illegalSurrogateDesc(int code)
+    {
+        if (code > 0x10FFFF) { // over max?
+            return "Illegal character point (0x"+Integer.toHexString(code)+") to output; max is 0x10FFFF as per RFC 4627";
+        }
+        if (code >= SURR1_FIRST) {
+            if (code <= SURR1_LAST) { // Unmatched first part (closing without second part?)
+                return "Unmatched first part of surrogate pair (0x"+Integer.toHexString(code)+")";
+            }
+            return "Unmatched second part of surrogate pair (0x"+Integer.toHexString(code)+")";
+        }
+        // should we ever get this?
+        return "Illegal character point (0x"+Integer.toHexString(code)+") to output";
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java b/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java
new file mode 100644
index 0000000..b7b9835
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java
@@ -0,0 +1,516 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+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.CharsToNameCanonicalizer;
+
+/**
+ * This class is used to determine the encoding of byte stream
+ * that is to contain JSON content. Rules are fairly simple, and
+ * defined in JSON specification (RFC-4627 or newer), except
+ * for BOM handling, which is a property of underlying
+ * streams.
+ */
+public final class ByteSourceJsonBootstrapper
+{
+    final static byte UTF8_BOM_1 = (byte) 0xEF;
+    final static byte UTF8_BOM_2 = (byte) 0xBB;
+    final static byte UTF8_BOM_3 = (byte) 0xBF;
+    
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    protected final IOContext _context;
+
+    protected final InputStream _in;
+
+    /*
+    /**********************************************************
+    /* Input buffering
+    /**********************************************************
+     */
+
+    protected final byte[] _inputBuffer;
+
+    private int _inputPtr;
+
+    private int _inputEnd;
+
+    /**
+     * Flag that indicates whether buffer above is to be recycled
+     * after being used or not.
+     */
+    private final boolean _bufferRecyclable;
+
+    /*
+    /**********************************************************
+    /* Input location
+    /**********************************************************
+     */
+
+    /**
+     * Current number of input units (bytes or chars) that were processed in
+     * previous blocks,
+     * before contents of current input buffer.
+     *<p>
+     * Note: includes possible BOMs, if those were part of the input.
+     */
+    protected int _inputProcessed;
+
+    /*
+    /**********************************************************
+    /* Data gathered
+    /**********************************************************
+     */
+
+    protected boolean _bigEndian = true;
+
+    protected int _bytesPerChar = 0; // 0 means "dunno yet"
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public ByteSourceJsonBootstrapper(IOContext ctxt, InputStream in)
+    {
+        _context = ctxt;
+        _in = in;
+        _inputBuffer = ctxt.allocReadIOBuffer();
+        _inputEnd = _inputPtr = 0;
+        _inputProcessed = 0;
+        _bufferRecyclable = true;
+    }
+
+    public ByteSourceJsonBootstrapper(IOContext ctxt, byte[] inputBuffer, int inputStart, int inputLen)
+    {
+        _context = ctxt;
+        _in = null;
+        _inputBuffer = inputBuffer;
+        _inputPtr = inputStart;
+        _inputEnd = (inputStart + inputLen);
+        // Need to offset this for correct location info
+        _inputProcessed = -inputStart;
+        _bufferRecyclable = false;
+    }
+
+    /*
+    /**********************************************************
+    /*  Encoding detection during bootstrapping
+    /**********************************************************
+     */
+    
+    /**
+     * Method that should be called after constructing an instace.
+     * It will figure out encoding that content uses, to allow
+     * for instantiating a proper scanner object.
+     */
+    public JsonEncoding detectEncoding()
+        throws IOException, JsonParseException
+    {
+        boolean foundEncoding = false;
+
+        // First things first: BOM handling
+        /* Note: we can require 4 bytes to be read, since no
+         * combination of BOM + valid JSON content can have
+         * shorter length (shortest valid JSON content is single
+         * digit char, but BOMs are chosen such that combination
+         * is always at least 4 chars long)
+         */
+        if (ensureLoaded(4)) {
+            int quad =  (_inputBuffer[_inputPtr] << 24)
+                | ((_inputBuffer[_inputPtr+1] & 0xFF) << 16)
+                | ((_inputBuffer[_inputPtr+2] & 0xFF) << 8)
+                | (_inputBuffer[_inputPtr+3] & 0xFF);
+            
+            if (handleBOM(quad)) {
+                foundEncoding = true;
+            } else {
+                /* If no BOM, need to auto-detect based on first char;
+                 * this works since it must be 7-bit ascii (wrt. unicode
+                 * compatible encodings, only ones JSON can be transferred
+                 * over)
+                 */
+                // UTF-32?
+                if (checkUTF32(quad)) {
+                    foundEncoding = true;
+                } else if (checkUTF16(quad >>> 16)) {
+                    foundEncoding = true;
+                }
+            }
+        } else if (ensureLoaded(2)) {
+            int i16 = ((_inputBuffer[_inputPtr] & 0xFF) << 8)
+                | (_inputBuffer[_inputPtr+1] & 0xFF);
+            if (checkUTF16(i16)) {
+                foundEncoding = true;
+            }
+        }
+
+        JsonEncoding enc;
+
+        /* Not found yet? As per specs, this means it must be UTF-8. */
+        if (!foundEncoding) {
+            enc = JsonEncoding.UTF8;
+        } else {
+            switch (_bytesPerChar) {
+            case 1:
+                enc = JsonEncoding.UTF8;
+                break;
+            case 2:
+                enc = _bigEndian ? JsonEncoding.UTF16_BE : JsonEncoding.UTF16_LE;
+                break;
+            case 4:
+                enc = _bigEndian ? JsonEncoding.UTF32_BE : JsonEncoding.UTF32_LE;
+                break;
+            default:
+                throw new RuntimeException("Internal error"); // should never get here
+            }
+        }
+        _context.setEncoding(enc);
+        return enc;
+    }
+
+    /*
+    /**********************************************************
+    /* Constructing a Reader
+    /**********************************************************
+     */
+    
+    @SuppressWarnings("resource")
+    public Reader constructReader()
+        throws IOException
+    {
+        JsonEncoding enc = _context.getEncoding();
+        switch (enc) { 
+        case UTF32_BE:
+        case UTF32_LE:
+            return new UTF32Reader(_context, _in, _inputBuffer, _inputPtr, _inputEnd,
+                                   _context.getEncoding().isBigEndian());
+
+        case UTF16_BE:
+        case UTF16_LE:
+        case UTF8: // only in non-common case where we don't want to do direct mapping
+            {
+                // First: do we have a Stream? If not, need to create one:
+                InputStream in = _in;
+
+                if (in == null) {
+                    in = new ByteArrayInputStream(_inputBuffer, _inputPtr, _inputEnd);
+                } else {
+                    /* Also, if we have any read but unused input (usually true),
+                     * need to merge that input in:
+                     */
+                    if (_inputPtr < _inputEnd) {
+                        in = new MergedStream(_context, in, _inputBuffer, _inputPtr, _inputEnd);
+                    }
+                }
+                return new InputStreamReader(in, enc.getJavaName());
+            }
+        }
+        throw new RuntimeException("Internal error"); // should never get here
+    }
+
+    public JsonParser constructParser(int parserFeatures, ObjectCodec codec,
+            BytesToNameCanonicalizer rootByteSymbols, CharsToNameCanonicalizer rootCharSymbols,
+            boolean canonicalize, boolean intern)
+        throws IOException, JsonParseException
+    {
+        JsonEncoding enc = detectEncoding();
+
+        if (enc == JsonEncoding.UTF8) {
+            /* and without canonicalization, byte-based approach is not performance; just use std UTF-8 reader
+             * (which is ok for larger input; not so hot for smaller; but this is not a common case)
+             */
+            if (canonicalize) {
+                BytesToNameCanonicalizer can = rootByteSymbols.makeChild(canonicalize, intern);
+                return new UTF8StreamJsonParser(_context, parserFeatures, _in, codec, can, _inputBuffer, _inputPtr, _inputEnd, _bufferRecyclable);
+            }
+        }
+        return new ReaderBasedJsonParser(_context, parserFeatures, constructReader(), codec,
+                rootCharSymbols.makeChild(canonicalize, intern));
+    }
+
+    /*
+    /**********************************************************
+    /*  Encoding detection for data format auto-detection
+    /**********************************************************
+     */
+
+    /**
+     * Current implementation is not as thorough as other functionality
+     * ({@link com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper}); 
+     * supports UTF-8, for example. But it should work, for now, and can
+     * be improved as necessary.
+     */
+    public static MatchStrength hasJSONFormat(InputAccessor acc) throws IOException
+    {
+        // Ideally we should see "[" or "{"; but if not, we'll accept double-quote (String)
+        // in future could also consider accepting non-standard matches?
+        
+        if (!acc.hasMoreBytes()) {
+            return MatchStrength.INCONCLUSIVE;
+        }
+        byte b = acc.nextByte();
+        // Very first thing, a UTF-8 BOM?
+        if (b == UTF8_BOM_1) { // yes, looks like UTF-8 BOM
+            if (!acc.hasMoreBytes()) {
+                return MatchStrength.INCONCLUSIVE;
+            }
+            if (acc.nextByte() != UTF8_BOM_2) {
+                return MatchStrength.NO_MATCH;
+            }
+            if (!acc.hasMoreBytes()) {
+                return MatchStrength.INCONCLUSIVE;
+            }
+            if (acc.nextByte() != UTF8_BOM_3) {
+                return MatchStrength.NO_MATCH;
+            }
+            if (!acc.hasMoreBytes()) {
+                return MatchStrength.INCONCLUSIVE;
+            }
+            b = acc.nextByte();
+        }
+        // Then possible leading space
+        int ch = skipSpace(acc, b);
+        if (ch < 0) {
+            return MatchStrength.INCONCLUSIVE;
+        }
+        // First, let's see if it looks like a structured type:
+        if (ch == '{') { // JSON object?
+            // Ideally we need to find either double-quote or closing bracket
+            ch = skipSpace(acc);
+            if (ch < 0) {
+                return MatchStrength.INCONCLUSIVE;
+            }
+            if (ch == '"' || ch == '}') {
+                return MatchStrength.SOLID_MATCH;
+            }
+            // ... should we allow non-standard? Let's not yet... can add if need be
+            return MatchStrength.NO_MATCH;
+        }
+        MatchStrength strength;
+        
+        if (ch == '[') {
+            ch = skipSpace(acc);
+            if (ch < 0) {
+                return MatchStrength.INCONCLUSIVE;
+            }
+            // closing brackets is easy; but for now, let's also accept opening...
+            if (ch == ']' || ch == '[') {
+                return MatchStrength.SOLID_MATCH;
+            }
+            return MatchStrength.SOLID_MATCH;
+        } else {
+            // plain old value is not very convincing...
+            strength = MatchStrength.WEAK_MATCH;
+        }
+
+        if (ch == '"') { // string value
+            return strength;
+        }
+        if (ch <= '9' && ch >= '0') { // number
+            return strength;
+        }
+        if (ch == '-') { // negative number
+            ch = skipSpace(acc);
+            if (ch < 0) {
+                return MatchStrength.INCONCLUSIVE;
+            }
+            return (ch <= '9' && ch >= '0') ? strength : MatchStrength.NO_MATCH;
+        }
+        // or one of literals
+        if (ch == 'n') { // null
+            return tryMatch(acc, "ull", strength);
+        }
+        if (ch == 't') { // true
+            return tryMatch(acc, "rue", strength);
+        }
+        if (ch == 'f') { // false
+            return tryMatch(acc, "alse", strength);
+        }
+        return MatchStrength.NO_MATCH;
+    }
+
+    private static MatchStrength tryMatch(InputAccessor acc, String matchStr, MatchStrength fullMatchStrength)
+        throws IOException
+    {
+        for (int i = 0, len = matchStr.length(); i < len; ++i) {
+            if (!acc.hasMoreBytes()) {
+                return MatchStrength.INCONCLUSIVE;
+            }
+            if (acc.nextByte() != matchStr.charAt(i)) {
+                return MatchStrength.NO_MATCH;
+            }
+        }
+        return fullMatchStrength;
+    }
+    
+    private static int skipSpace(InputAccessor acc) throws IOException
+    {
+        if (!acc.hasMoreBytes()) {
+            return -1;
+        }
+        return skipSpace(acc, acc.nextByte());
+    }
+    
+    private static int skipSpace(InputAccessor acc, byte b) throws IOException
+    {
+        while (true) {
+            int ch = (int) b & 0xFF;
+            if (!(ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t')) {
+                return ch;
+            }
+            if (!acc.hasMoreBytes()) {
+                return -1;
+            }
+            b = acc.nextByte();
+            ch = (int) b & 0xFF;
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods, parsing
+    /**********************************************************
+     */
+
+    /**
+     * @return True if a BOM was succesfully found, and encoding
+     *   thereby recognized.
+     */
+    private boolean handleBOM(int quad)
+        throws IOException
+    {
+        /* Handling of (usually) optional BOM (required for
+         * multi-byte formats); first 32-bit charsets:
+         */
+        switch (quad) {
+        case 0x0000FEFF:
+            _bigEndian = true;
+            _inputPtr += 4;
+            _bytesPerChar = 4;
+            return true;
+        case 0xFFFE0000: // UCS-4, LE?
+            _inputPtr += 4;
+            _bytesPerChar = 4;
+            _bigEndian = false;
+            return true;
+        case 0x0000FFFE: // UCS-4, in-order...
+            reportWeirdUCS4("2143"); // throws exception
+        case 0xFEFF0000: // UCS-4, in-order...
+            reportWeirdUCS4("3412"); // throws exception
+        }
+        // Ok, if not, how about 16-bit encoding BOMs?
+        int msw = quad >>> 16;
+        if (msw == 0xFEFF) { // UTF-16, BE
+            _inputPtr += 2;
+            _bytesPerChar = 2;
+            _bigEndian = true;
+            return true;
+        }
+        if (msw == 0xFFFE) { // UTF-16, LE
+            _inputPtr += 2;
+            _bytesPerChar = 2;
+            _bigEndian = false;
+            return true;
+        }
+        // And if not, then UTF-8 BOM?
+        if ((quad >>> 8) == 0xEFBBBF) { // UTF-8
+            _inputPtr += 3;
+            _bytesPerChar = 1;
+            _bigEndian = true; // doesn't really matter
+            return true;
+        }
+        return false;
+    }
+
+    private boolean checkUTF32(int quad)
+        throws IOException
+    {
+        /* Handling of (usually) optional BOM (required for
+         * multi-byte formats); first 32-bit charsets:
+         */
+        if ((quad >> 8) == 0) { // 0x000000?? -> UTF32-BE
+            _bigEndian = true;
+        } else if ((quad & 0x00FFFFFF) == 0) { // 0x??000000 -> UTF32-LE
+            _bigEndian = false;
+        } else if ((quad & ~0x00FF0000) == 0) { // 0x00??0000 -> UTF32-in-order
+            reportWeirdUCS4("3412");
+        } else if ((quad & ~0x0000FF00) == 0) { // 0x0000??00 -> UTF32-in-order
+            reportWeirdUCS4("2143");
+        } else {
+            // Can not be valid UTF-32 encoded JSON...
+            return false;
+        }
+        // Not BOM (just regular content), nothing to skip past:
+        //_inputPtr += 4;
+        _bytesPerChar = 4;
+        return true;
+    }
+
+    private boolean checkUTF16(int i16)
+    {
+        if ((i16 & 0xFF00) == 0) { // UTF-16BE
+            _bigEndian = true;
+        } else if ((i16 & 0x00FF) == 0) { // UTF-16LE
+            _bigEndian = false;
+        } else { // nope, not  UTF-16
+            return false;
+        }
+        // Not BOM (just regular content), nothing to skip past:
+        //_inputPtr += 2;
+        _bytesPerChar = 2;
+        return true;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, problem reporting
+    /**********************************************************
+     */
+
+    private void reportWeirdUCS4(String type)
+        throws IOException
+    {
+        throw new CharConversionException("Unsupported UCS-4 endianness ("+type+") detected");
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, raw input access
+    /**********************************************************
+     */
+
+    protected boolean ensureLoaded(int minimum)
+        throws IOException
+    {
+        /* Let's assume here buffer has enough room -- this will always
+         * be true for the limited used this method gets
+         */
+        int gotten = (_inputEnd - _inputPtr);
+        while (gotten < minimum) {
+            int count;
+
+            if (_in == null) { // block source
+                count = -1;
+            } else {
+                count = _in.read(_inputBuffer, _inputEnd, _inputBuffer.length - _inputEnd);
+            }
+            if (count < 1) {
+                return false;
+            }
+            _inputEnd += count;
+            gotten += count;
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java
new file mode 100644
index 0000000..dbe7c9d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java
@@ -0,0 +1,172 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.base.GeneratorBase;
+import com.fasterxml.jackson.core.io.CharTypes;
+import com.fasterxml.jackson.core.io.CharacterEscapes;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Intermediate base class shared by JSON-backed generators
+ * like {@link UTF8JsonGenerator} and {@link WriterBasedJsonGenerator}.
+ * 
+ * @since 2.1
+ */
+public abstract class JsonGeneratorImpl extends GeneratorBase
+{
+    /*
+    /**********************************************************
+    /* Constants
+    /**********************************************************
+     */
+
+    /**
+     * This is the default set of escape codes, over 7-bit ASCII range
+     * (first 128 character codes), used for single-byte UTF-8 characters.
+     */
+    protected final static int[] sOutputEscapes = CharTypes.get7BitOutputEscapes();
+    
+    /*
+    /**********************************************************
+    /* Configuration, basic I/O
+    /**********************************************************
+     */
+
+    final protected IOContext _ioContext;
+
+    /*
+    /**********************************************************
+    /* Configuration, output escaping
+    /**********************************************************
+     */
+
+    /**
+     * Currently active set of output escape code definitions (whether
+     * and how to escape or not) for 7-bit ASCII range (first 128
+     * character codes). Defined separately to make potentially
+     * customizable
+     */
+    protected int[] _outputEscapes = sOutputEscapes;
+
+    /**
+     * Value between 128 (0x80) and 65535 (0xFFFF) that indicates highest
+     * Unicode code point that will not need escaping; or 0 to indicate
+     * that all characters can be represented without escaping.
+     * Typically used to force escaping of some portion of character set;
+     * for example to always escape non-ASCII characters (if value was 127).
+     *<p>
+     * NOTE: not all sub-classes make use of this setting.
+     */
+    protected int _maximumNonEscapedChar;
+
+    /**
+     * Definition of custom character escapes to use for generators created
+     * by this factory, if any. If null, standard data format specific
+     * escapes are used.
+     */
+    protected CharacterEscapes _characterEscapes;
+    
+    /*
+    /**********************************************************
+    /* Configuration, other
+    /**********************************************************
+     */
+
+    /**
+     * Separator to use, if any, between root-level values.
+     * 
+     * @since 2.1
+     */
+    protected SerializableString _rootValueSeparator
+        = DefaultPrettyPrinter.DEFAULT_ROOT_VALUE_SEPARATOR;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public JsonGeneratorImpl(IOContext ctxt, int features, ObjectCodec codec)
+    {
+        super(features, codec);
+        _ioContext = ctxt;
+        if (isEnabled(Feature.ESCAPE_NON_ASCII)) {
+            setHighestNonEscapedChar(127);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Overridden configuration methods
+    /**********************************************************
+     */
+
+    @Override
+    public JsonGenerator setHighestNonEscapedChar(int charCode) {
+        _maximumNonEscapedChar = (charCode < 0) ? 0 : charCode;
+        return this;
+    }
+
+    @Override
+    public int getHighestEscapedChar() {
+        return _maximumNonEscapedChar;
+    }
+
+    @Override
+    public JsonGenerator setCharacterEscapes(CharacterEscapes esc)
+    {
+        _characterEscapes = esc;
+        if (esc == null) { // revert to standard escapes
+            _outputEscapes = sOutputEscapes;
+        } else {
+            _outputEscapes = esc.getEscapeCodesForAscii();
+        }
+        return this;
+    }
+
+    /**
+     * Method for accessing custom escapes factory uses for {@link JsonGenerator}s
+     * it creates.
+     */
+    @Override
+    public CharacterEscapes getCharacterEscapes() {
+        return _characterEscapes;
+    }
+    
+    @Override
+    public JsonGenerator setRootValueSeparator(SerializableString sep) {
+        _rootValueSeparator = sep;
+        return this;
+    }
+    
+    /*
+    /**********************************************************
+    /* Versioned
+    /**********************************************************
+     */
+
+    @Override
+    public Version version() {
+        return VersionUtil.versionFor(getClass());
+    }
+
+    /*
+    /**********************************************************
+    /* Partial API
+    /**********************************************************
+     */
+
+    // // Overrides just to make things final, to possibly help with inlining
+    
+    @Override
+    public final void writeStringField(String fieldName, String value)
+        throws IOException, JsonGenerationException
+    {
+        writeFieldName(fieldName);
+        writeString(value);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java b/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java
new file mode 100644
index 0000000..564a934
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java
@@ -0,0 +1,185 @@
+package com.fasterxml.jackson.core.json;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.CharTypes;
+
+/**
+ * Extension of {@link JsonStreamContext}, which implements
+ * core methods needed, and also exposes
+ * more complete API to parser implementation classes.
+ */
+public final class JsonReadContext
+    extends JsonStreamContext
+{
+    // // // Configuration
+
+    protected final JsonReadContext _parent;
+
+    // // // Location information (minus source reference)
+
+    protected int _lineNr;
+    protected int _columnNr;
+
+    protected String _currentName;
+
+    /*
+    /**********************************************************
+    /* Simple instance reuse slots; speeds up things
+    /* a bit (10-15%) for docs with lots of small
+    /* arrays/objects (for which allocation was
+    /* visible in profile stack frames)
+    /**********************************************************
+     */
+
+    protected JsonReadContext _child = null;
+
+    /*
+    /**********************************************************
+    /* Instance construction, reuse
+    /**********************************************************
+     */
+
+    public JsonReadContext(JsonReadContext parent, int type, int lineNr, int colNr)
+    {
+        super();
+        _type = type;
+        _parent = parent;
+        _lineNr = lineNr;
+        _columnNr = colNr;
+        _index = -1;
+    }
+
+    protected void reset(int type, int lineNr, int colNr)
+    {
+        _type = type;
+        _index = -1;
+        _lineNr = lineNr;
+        _columnNr = colNr;
+        _currentName = null;
+    }
+
+    // // // Factory methods
+
+    public static JsonReadContext createRootContext(int lineNr, int colNr)
+    {
+        return new JsonReadContext(null, TYPE_ROOT, lineNr, colNr);
+    }
+
+    public static JsonReadContext createRootContext()
+    {
+        return new JsonReadContext(null, TYPE_ROOT, 1, 0);
+    }
+    
+    public JsonReadContext createChildArrayContext(int lineNr, int colNr)
+    {
+        JsonReadContext ctxt = _child;
+        if (ctxt == null) {
+            _child = ctxt = new JsonReadContext(this, TYPE_ARRAY, lineNr, colNr);
+            return ctxt;
+        }
+        ctxt.reset(TYPE_ARRAY, lineNr, colNr);
+        return ctxt;
+    }
+
+    public JsonReadContext createChildObjectContext(int lineNr, int colNr)
+    {
+        JsonReadContext ctxt = _child;
+        if (ctxt == null) {
+            _child = ctxt = new JsonReadContext(this, TYPE_OBJECT, lineNr, colNr);
+            return ctxt;
+        }
+        ctxt.reset(TYPE_OBJECT, lineNr, colNr);
+        return ctxt;
+    }
+
+    /*
+    /**********************************************************
+    /* Abstract method implementation
+    /**********************************************************
+     */
+
+    @Override
+    public String getCurrentName() { return _currentName; }
+
+    @Override
+    public JsonReadContext getParent() { return _parent; }
+
+    /*
+    /**********************************************************
+    /* Extended API
+    /**********************************************************
+     */
+
+    /**
+     * @return Location pointing to the point where the context
+     *   start marker was found
+     */
+    public JsonLocation getStartLocation(Object srcRef)
+    {
+        /* We don't keep track of offsets at this level (only
+         * reader does)
+         */
+        long totalChars = -1L;
+
+        return new JsonLocation(srcRef, totalChars, _lineNr, _columnNr);
+    }
+
+    /*
+    /**********************************************************
+    /* State changes
+    /**********************************************************
+     */
+
+    public boolean expectComma()
+    {
+        /* Assumption here is that we will be getting a value (at least
+         * before calling this method again), and
+         * so will auto-increment index to avoid having to do another call
+         */
+        int ix = ++_index; // starts from -1
+        return (_type != TYPE_ROOT && ix > 0);
+    }
+
+    public void setCurrentName(String name)
+    {
+        _currentName = name;
+    }
+
+    /*
+    /**********************************************************
+    /* Overridden standard methods
+    /**********************************************************
+     */
+
+    /**
+     * Overridden to provide developer readable "JsonPath" representation
+     * of the context.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder(64);
+        switch (_type) {
+        case TYPE_ROOT:
+            sb.append("/");
+            break;
+        case TYPE_ARRAY:
+            sb.append('[');
+            sb.append(getCurrentIndex());
+            sb.append(']');
+            break;
+        case TYPE_OBJECT:
+            sb.append('{');
+            if (_currentName != null) {
+                sb.append('"');
+                CharTypes.appendQuoted(sb, _currentName);
+                sb.append('"');
+            } else {
+                sb.append('?');
+            }
+            sb.append('}');
+            break;
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java
new file mode 100644
index 0000000..0c85d2a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java
@@ -0,0 +1,178 @@
+package com.fasterxml.jackson.core.json;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Extension of {@link JsonStreamContext}, which implements
+ * core methods needed, and also exposes
+ * more complete API to generator implementation classes.
+ */
+public class JsonWriteContext
+    extends JsonStreamContext
+{
+    // // // Return values for writeValue()
+
+    public final static int STATUS_OK_AS_IS = 0;
+    public final static int STATUS_OK_AFTER_COMMA = 1;
+    public final static int STATUS_OK_AFTER_COLON = 2;
+    public final static int STATUS_OK_AFTER_SPACE = 3; // in root context
+    public final static int STATUS_EXPECT_VALUE = 4;
+    public final static int STATUS_EXPECT_NAME = 5;
+
+    protected final JsonWriteContext _parent;
+
+    /**
+     * Name of the field of which value is to be parsed; only
+     * used for OBJECT contexts
+     */
+    protected String _currentName;
+    
+    /*
+    /**********************************************************
+    /* Simple instance reuse slots; speed up things
+    /* a bit (10-15%) for docs with lots of small
+    /* arrays/objects
+    /**********************************************************
+     */
+
+    protected JsonWriteContext _child = null;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    protected JsonWriteContext(int type, JsonWriteContext parent)
+    {
+        super();
+        _type = type;
+        _parent = parent;
+        _index = -1;
+    }
+    
+    // // // Factory methods
+
+    public static JsonWriteContext createRootContext()
+    {
+        return new JsonWriteContext(TYPE_ROOT, null);
+    }
+
+    private JsonWriteContext reset(int type) {
+        _type = type;
+        _index = -1;
+        _currentName = null;
+        return this;
+    }
+    
+    public final JsonWriteContext createChildArrayContext()
+    {
+        JsonWriteContext ctxt = _child;
+        if (ctxt == null) {
+            _child = ctxt = new JsonWriteContext(TYPE_ARRAY, this);
+            return ctxt;
+        }
+        return ctxt.reset(TYPE_ARRAY);
+    }
+
+    public final JsonWriteContext createChildObjectContext()
+    {
+        JsonWriteContext ctxt = _child;
+        if (ctxt == null) {
+            _child = ctxt = new JsonWriteContext(TYPE_OBJECT, this);
+            return ctxt;
+        }
+        return ctxt.reset(TYPE_OBJECT);
+    }
+
+    // // // Shared API
+
+    @Override
+    public final JsonWriteContext getParent() { return _parent; }
+
+    @Override
+    public final String getCurrentName() { return _currentName; }
+    
+    // // // API sub-classes are to implement
+
+    /**
+     * Method that writer is to call before it writes a field name.
+     *
+     * @return Index of the field entry (0-based)
+     */
+    public final int writeFieldName(String name)
+    {
+        if (_type == TYPE_OBJECT) {
+            if (_currentName != null) { // just wrote a name...
+                return STATUS_EXPECT_VALUE;
+            }
+            _currentName = name;
+            return (_index < 0) ? STATUS_OK_AS_IS : STATUS_OK_AFTER_COMMA;
+        }
+        return STATUS_EXPECT_VALUE;
+    }
+    
+    public final int writeValue()
+    {
+        // Most likely, object:
+        if (_type == TYPE_OBJECT) {
+            if (_currentName == null) {
+                return STATUS_EXPECT_NAME;
+            }
+            _currentName = null;
+            ++_index;
+            return STATUS_OK_AFTER_COLON;
+        }
+
+        // Ok, array?
+        if (_type == TYPE_ARRAY) {
+            int ix = _index;
+            ++_index;
+            return (ix < 0) ? STATUS_OK_AS_IS : STATUS_OK_AFTER_COMMA;
+        }
+        
+        // Nope, root context
+        // No commas within root context, but need space
+        ++_index;
+        return (_index == 0) ? STATUS_OK_AS_IS : STATUS_OK_AFTER_SPACE;
+    }
+
+    // // // Internally used abstract methods
+
+    protected final void appendDesc(StringBuilder 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 final String toString()
+    {
+        StringBuilder sb = new StringBuilder(64);
+        appendDesc(sb);
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/PackageVersion.java.in b/src/main/java/com/fasterxml/jackson/core/json/PackageVersion.java.in
new file mode 100644
index 0000000..7860aa1
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/PackageVersion.java.in
@@ -0,0 +1,20 @@
+package @package@;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Automatically generated from PackageVersion.java.in during
+ * packageVersion-generate execution of maven-replacer-plugin in
+ * pom.xml.
+ */
+public final class PackageVersion implements Versioned {
+    public final static Version VERSION = VersionUtil.parseVersion(
+        "@projectversion@", "@projectgroupid@", "@projectartifactid@");
+
+    @Override
+    public Version version() {
+        return VERSION;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
new file mode 100644
index 0000000..ded50af
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
@@ -0,0 +1,1992 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+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.CharsToNameCanonicalizer;
+import com.fasterxml.jackson.core.util.*;
+
+/**
+ * This is a concrete implementation of {@link JsonParser}, which is
+ * based on a {@link java.io.Reader} to handle low-level character
+ * conversion tasks.
+ */
+public final class ReaderBasedJsonParser
+    extends ParserBase
+{
+    /*
+    /**********************************************************
+    /* Input configuration
+    /**********************************************************
+     */
+
+    /**
+     * Reader that can be used for reading more content, if one
+     * buffer from input source, but in some cases pre-loaded buffer
+     * is handed to the parser.
+     */
+    protected Reader _reader;
+
+    /**
+     * Current buffer from which data is read; generally data is read into
+     * buffer from input source.
+     */
+    protected char[] _inputBuffer;
+
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    protected ObjectCodec _objectCodec;
+
+    final protected CharsToNameCanonicalizer _symbols;
+    
+    final protected int _hashSeed;
+
+    /*
+    /**********************************************************
+    /* Parsing state
+    /**********************************************************
+     */
+    
+    /**
+     * Flag that indicates that the current token has not yet
+     * been fully processed, and needs to be finished for
+     * some access (or skipped to obtain the next token)
+     */
+    protected boolean _tokenIncomplete = false;
+    
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public ReaderBasedJsonParser(IOContext ctxt, int features, Reader r,
+            ObjectCodec codec, CharsToNameCanonicalizer st)
+    {
+        super(ctxt, features);
+        _reader = r;
+        _inputBuffer = ctxt.allocTokenBuffer();
+        _objectCodec = codec;
+        _symbols = st;
+        _hashSeed = st.hashSeed();
+    }
+    
+    /*
+    /**********************************************************
+    /* Base method defs, overrides
+    /**********************************************************
+     */
+
+    @Override
+    public ObjectCodec getCodec() {
+        return _objectCodec;
+    }
+
+    @Override
+    public void setCodec(ObjectCodec c) {
+        _objectCodec = c;
+    }
+    
+    @Override
+    public int releaseBuffered(Writer w) throws IOException
+    {
+        int count = _inputEnd - _inputPtr;
+        if (count < 1) {
+            return 0;
+        }
+        // let's just advance ptr to end
+        int origPtr = _inputPtr;
+        w.write(_inputBuffer, origPtr, count);
+        return count;
+    }
+
+    @Override
+    public Object getInputSource() {
+        return _reader;
+    }
+
+    @Override
+    protected boolean loadMore() throws IOException
+    {
+        _currInputProcessed += _inputEnd;
+        _currInputRowStart -= _inputEnd;
+
+        if (_reader != null) {
+            int count = _reader.read(_inputBuffer, 0, _inputBuffer.length);
+            if (count > 0) {
+                _inputPtr = 0;
+                _inputEnd = count;
+                return true;
+            }
+            // End of input
+            _closeInput();
+            // Should never return 0, so let's fail
+            if (count == 0) {
+                throw new IOException("Reader returned 0 characters when trying to read "+_inputEnd);
+            }
+        }
+        return false;
+    }
+
+    protected char getNextChar(String eofMsg)
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            if (!loadMore()) {
+                _reportInvalidEOF(eofMsg);
+            }
+        }
+        return _inputBuffer[_inputPtr++];
+    }
+
+    @Override
+    protected void _closeInput() throws IOException
+    {
+        /* 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
+         *   feature is enabled.
+         *   One downside is that when using our optimized
+         *   Reader (granted, we only do that for UTF-32...) this
+         *   means that buffer recycling won't work correctly.
+         */
+        if (_reader != null) {
+            if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_SOURCE)) {
+                _reader.close();
+            }
+            _reader = null;
+        }
+    }
+
+    /**
+     * Method called to release internal buffers owned by the base
+     * reader. This may be called along with {@link #_closeInput} (for
+     * example, when explicitly closing this reader instance), or
+     * separately (if need be).
+     */
+    @Override
+    protected void _releaseBuffers()
+        throws IOException
+    {
+        super._releaseBuffers();
+        char[] buf = _inputBuffer;
+        if (buf != null) {
+            _inputBuffer = null;
+            _ioContext.releaseTokenBuffer(buf);
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, data access
+    /**********************************************************
+     */
+    
+    /**
+     * Method for accessing textual representation of the current event;
+     * if no current event (before first call to {@link #nextToken}, or
+     * after encountering end-of-input), returns null.
+     * Method can be called for any event.
+     */
+    @Override
+    public String getText()
+        throws IOException, JsonParseException
+    {
+        JsonToken t = _currToken;
+        if (t == JsonToken.VALUE_STRING) {
+            if (_tokenIncomplete) {
+                _tokenIncomplete = false;
+                _finishString(); // only strings can be incomplete
+            }
+            return _textBuffer.contentsAsString();
+        }
+        return _getText2(t);
+    }
+
+    // // // Let's override default impls for improved performance
+    
+    // @since 2.1
+    @Override
+    public String getValueAsString() throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.VALUE_STRING) {
+            if (_tokenIncomplete) {
+                _tokenIncomplete = false;
+                _finishString(); // only strings can be incomplete
+            }
+            return _textBuffer.contentsAsString();
+        }
+        return super.getValueAsString(null);
+    }
+    
+    // @since 2.1
+    @Override
+    public String getValueAsString(String defValue) throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.VALUE_STRING) {
+            if (_tokenIncomplete) {
+                _tokenIncomplete = false;
+                _finishString(); // only strings can be incomplete
+            }
+            return _textBuffer.contentsAsString();
+        }
+        return super.getValueAsString(defValue);
+    }
+    
+    
+    protected String _getText2(JsonToken t)
+    {
+        if (t == null) {
+            return null;
+        }
+        switch (t) {
+        case FIELD_NAME:
+            return _parsingContext.getCurrentName();
+
+        case VALUE_STRING:
+            // fall through
+        case VALUE_NUMBER_INT:
+        case VALUE_NUMBER_FLOAT:
+            return _textBuffer.contentsAsString();
+        default:
+            return t.asString();
+        }
+    }
+
+    @Override
+    public char[] getTextCharacters()
+        throws IOException, JsonParseException
+    {
+        if (_currToken != null) { // null only before/after document
+            switch (_currToken) {
+                
+            case FIELD_NAME:
+                if (!_nameCopied) {
+                    String name = _parsingContext.getCurrentName();
+                    int nameLen = name.length();
+                    if (_nameCopyBuffer == null) {
+                        _nameCopyBuffer = _ioContext.allocNameCopyBuffer(nameLen);
+                    } else if (_nameCopyBuffer.length < nameLen) {
+                        _nameCopyBuffer = new char[nameLen];
+                    }
+                    name.getChars(0, nameLen, _nameCopyBuffer, 0);
+                    _nameCopied = true;
+                }
+                return _nameCopyBuffer;
+    
+            case VALUE_STRING:
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString(); // only strings can be incomplete
+                }
+                // fall through
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return _textBuffer.getTextBuffer();
+                
+            default:
+                return _currToken.asCharArray();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public int getTextLength()
+        throws IOException, JsonParseException
+    {
+        if (_currToken != null) { // null only before/after document
+            switch (_currToken) {
+                
+            case FIELD_NAME:
+                return _parsingContext.getCurrentName().length();
+            case VALUE_STRING:
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString(); // only strings can be incomplete
+                }
+                // fall through
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return _textBuffer.size();
+                
+            default:
+                return _currToken.asCharArray().length;
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public int getTextOffset() throws IOException, JsonParseException
+    {
+        // Most have offset of 0, only some may have other values:
+        if (_currToken != null) {
+            switch (_currToken) {
+            case FIELD_NAME:
+                return 0;
+            case VALUE_STRING:
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString(); // only strings can be incomplete
+                }
+                // fall through
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return _textBuffer.getTextOffset();
+            default:
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public byte[] getBinaryValue(Base64Variant b64variant)
+        throws IOException, JsonParseException
+    {
+        if (_currToken != JsonToken.VALUE_STRING &&
+                (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT || _binaryValue == null)) {
+            _reportError("Current token ("+_currToken+") not VALUE_STRING or VALUE_EMBEDDED_OBJECT, can not access as binary");
+        }
+        /* To ensure that we won't see inconsistent data, better clear up
+         * state...
+         */
+        if (_tokenIncomplete) {
+            try {
+                _binaryValue = _decodeBase64(b64variant);
+            } catch (IllegalArgumentException iae) {
+                throw _constructError("Failed to decode VALUE_STRING as base64 ("+b64variant+"): "+iae.getMessage());
+            }
+            /* let's clear incomplete only now; allows for accessing other
+             * textual content in error cases
+             */
+            _tokenIncomplete = false;
+        } else { // may actually require conversion...
+            if (_binaryValue == null) {
+                ByteArrayBuilder builder = _getByteArrayBuilder();
+                _decodeBase64(getText(), builder, b64variant);
+                _binaryValue = builder.toByteArray();
+            }
+        }
+        return _binaryValue;
+    }
+    
+    @Override
+    public int readBinaryValue(Base64Variant b64variant, OutputStream out)
+        throws IOException, JsonParseException
+    {
+        // if we have already read the token, just use whatever we may have
+        if (!_tokenIncomplete || _currToken != JsonToken.VALUE_STRING) {
+            byte[] b = getBinaryValue(b64variant);
+            out.write(b);
+            return b.length;
+        }
+        // otherwise do "real" incremental parsing...
+        byte[] buf = _ioContext.allocBase64Buffer();
+        try {
+            return _readBinary(b64variant, out, buf);
+        } finally {
+            _ioContext.releaseBase64Buffer(buf);
+        }
+    }
+
+    protected int _readBinary(Base64Variant b64variant, OutputStream out, byte[] buffer)
+            throws IOException, JsonParseException
+    {
+        int outputPtr = 0;
+        final int outputEnd = buffer.length - 3;
+        int outputCount = 0;
+
+        while (true) {
+            // first, we'll skip preceding white space, if any
+            char ch;
+            do {
+                if (_inputPtr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                }
+                ch = _inputBuffer[_inputPtr++];
+            } while (ch <= INT_SPACE);
+            int bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) { // reached the end, fair and square?
+                if (ch == '"') {
+                    break;
+                }
+                bits = _decodeBase64Escape(b64variant, ch, 0);
+                if (bits < 0) { // white space to skip
+                    continue;
+                }
+            }
+
+            // enough room? If not, flush
+            if (outputPtr > outputEnd) {
+                outputCount += outputPtr;
+                out.write(buffer, 0, outputPtr);
+                outputPtr = 0;
+            }
+
+            int decodedData = bits;
+
+            // then second base64 char; can't get padding yet, nor ws
+
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++];
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                bits = _decodeBase64Escape(b64variant, ch, 1);
+            }
+            decodedData = (decodedData << 6) | bits;
+
+            // third base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++];
+            bits = b64variant.decodeBase64Char(ch);
+
+            // First branch: can get padding (-> 1 byte)
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 4;
+                        buffer[outputPtr++] = (byte) decodedData;
+                        break;
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 2);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    // Ok, must get padding
+                    if (_inputPtr >= _inputEnd) {
+                        loadMoreGuaranteed();
+                    }
+                    ch = _inputBuffer[_inputPtr++];
+                    if (!b64variant.usesPaddingChar(ch)) {
+                        throw reportInvalidBase64Char(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
+                    }
+                    // Got 12 bits, only need 8, need to shift
+                    decodedData >>= 4;
+                    buffer[outputPtr++] = (byte) decodedData;
+                    continue;
+                }
+            }
+            // Nope, 2 or 3 bytes
+            decodedData = (decodedData << 6) | bits;
+            // fourth and last base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++];
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 2;
+                        buffer[outputPtr++] = (byte) (decodedData >> 8);
+                        buffer[outputPtr++] = (byte) decodedData;
+                        break;
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 3);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    /* With padding we only get 2 bytes; but we have
+                     * to shift it a bit so it is identical to triplet
+                     * case with partial output.
+                     * 3 chars gives 3x6 == 18 bits, of which 2 are
+                     * dummies, need to discard:
+                     */
+                    decodedData >>= 2;
+                    buffer[outputPtr++] = (byte) (decodedData >> 8);
+                    buffer[outputPtr++] = (byte) decodedData;
+                    continue;
+                }
+            }
+            // otherwise, our triplet is now complete
+            decodedData = (decodedData << 6) | bits;
+            buffer[outputPtr++] = (byte) (decodedData >> 16);
+            buffer[outputPtr++] = (byte) (decodedData >> 8);
+            buffer[outputPtr++] = (byte) decodedData;
+        }
+        _tokenIncomplete = false;
+        if (outputPtr > 0) {
+            outputCount += outputPtr;
+            out.write(buffer, 0, outputPtr);
+        }
+        return outputCount;
+    }
+
+    /*
+   /**********************************************************
+   /* Public API, traversal
+   /**********************************************************
+    */
+
+    /**
+     * @return Next token from the stream, if any found, or null
+     *   to indicate end-of-input
+     */
+    @Override
+    public JsonToken nextToken()
+        throws IOException, JsonParseException
+    {
+        _numTypesValid = NR_UNKNOWN;
+
+        /* First: field names are special -- we will always tokenize
+         * (part of) value along with field name to simplify
+         * state handling. If so, can and need to use secondary token:
+         */
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return _nextAfterName();
+        }
+        if (_tokenIncomplete) {
+            _skipString(); // only strings can be partial
+        }
+        int i = _skipWSOrEnd();
+        if (i < 0) { // end-of-input
+            /* 19-Feb-2009, tatu: Should actually close/release things
+             *    like input source, symbol table and recyclable buffers now.
+             */
+            close();
+            return (_currToken = null);
+        }
+
+        /* First, need to ensure we know the starting location of token
+         * after skipping leading white space
+         */
+        _tokenInputTotal = _currInputProcessed + _inputPtr - 1;
+        _tokenInputRow = _currInputRow;
+        _tokenInputCol = _inputPtr - _currInputRowStart - 1;
+
+        // finally: clear any data retained so far
+        _binaryValue = null;
+
+        // Closing scope?
+        if (i == INT_RBRACKET) {
+            if (!_parsingContext.inArray()) {
+                _reportMismatchedEndMarker(i, '}');
+            }
+            _parsingContext = _parsingContext.getParent();
+            return (_currToken = JsonToken.END_ARRAY);
+        }
+        if (i == INT_RCURLY) {
+            if (!_parsingContext.inObject()) {
+                _reportMismatchedEndMarker(i, ']');
+            }
+            _parsingContext = _parsingContext.getParent();
+            return (_currToken = JsonToken.END_OBJECT);
+        }
+
+        // Nope: do we then expect a comma?
+        if (_parsingContext.expectComma()) {
+            if (i != INT_COMMA) {
+                _reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.getTypeDesc()+" entries");
+            }
+            i = _skipWS();
+        }
+
+        /* And should we now have a name? Always true for
+         * Object contexts, since the intermediate 'expect-value'
+         * state is never retained.
+         */
+        boolean inObject = _parsingContext.inObject();
+        if (inObject) {
+           // First, field name itself:
+            String name = _parseFieldName(i);
+            _parsingContext.setCurrentName(name);
+            _currToken = JsonToken.FIELD_NAME;
+            i = _skipWS();
+            if (i != INT_COLON) {
+                _reportUnexpectedChar(i, "was expecting a colon to separate field name and value");
+            }
+            i = _skipWS();
+        }
+
+        // Ok: we must have a value... what is it?
+
+        JsonToken t;
+
+        switch (i) {
+        case INT_QUOTE:
+            _tokenIncomplete = true;
+            t = JsonToken.VALUE_STRING;
+            break;
+        case INT_LBRACKET:
+            if (!inObject) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            }
+            t = JsonToken.START_ARRAY;
+            break;
+        case INT_LCURLY:
+            if (!inObject) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            t = JsonToken.START_OBJECT;
+            break;
+        case INT_RBRACKET:
+        case INT_RCURLY:
+            // Error: neither is valid at this point; valid closers have
+            // been handled earlier
+            _reportUnexpectedChar(i, "expected a value");
+        case INT_t:
+            _matchToken("true", 1);
+            t = JsonToken.VALUE_TRUE;
+            break;
+        case INT_f:
+            _matchToken("false", 1);
+            t = JsonToken.VALUE_FALSE;
+            break;
+        case INT_n:
+            _matchToken("null", 1);
+            t = JsonToken.VALUE_NULL;
+            break;
+
+        case INT_MINUS:
+            /* 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 INT_0:
+        case INT_1:
+        case INT_2:
+        case INT_3:
+        case INT_4:
+        case INT_5:
+        case INT_6:
+        case INT_7:
+        case INT_8:
+        case INT_9:
+            t = parseNumberText(i);
+            break;
+        default:
+            t = _handleUnexpectedValue(i);
+            break;
+        }
+
+        if (inObject) {
+            _nextToken = t;
+            return _currToken;
+        }
+        _currToken = t;
+        return t;
+    }
+
+    private JsonToken _nextAfterName()
+    {
+        _nameCopied = false; // need to invalidate if it was copied
+        JsonToken t = _nextToken;
+        _nextToken = null;
+        // Also: may need to start new context?
+        if (t == JsonToken.START_ARRAY) {
+            _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+        } else if (t == JsonToken.START_OBJECT) {
+            _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+        }
+        return (_currToken = t);
+    }
+
+    /*
+    @Override
+    public boolean nextFieldName(SerializableString str)
+         throws IOException, JsonParseException
+     */
+
+    // note: identical to one in Utf8StreamParser
+    @Override
+    public String nextTextValue()
+        throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_STRING) {
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString();
+                }
+                return _textBuffer.contentsAsString();
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return null;
+        }
+        // !!! TODO: optimize this case as well
+        return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
+    }
+
+    // note: identical to one in Utf8StreamParser
+    @Override
+    public int nextIntValue(int defaultValue)
+        throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.FIELD_NAME) {
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_NUMBER_INT) {
+                return getIntValue();
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return defaultValue;
+        }
+        // !!! TODO: optimize this case as well
+        return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
+    }
+
+    // note: identical to one in Utf8StreamParser
+    @Override
+    public long nextLongValue(long defaultValue)
+        throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_NUMBER_INT) {
+                return getLongValue();
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return defaultValue;
+        }
+        // !!! TODO: optimize this case as well
+        return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getLongValue() : defaultValue;
+    }
+
+    // note: identical to one in Utf8StreamParser
+    @Override
+    public Boolean nextBooleanValue()
+        throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_TRUE) {
+                return Boolean.TRUE;
+            }
+            if (t == JsonToken.VALUE_FALSE) {
+                return Boolean.FALSE;
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return null;
+        }
+        switch (nextToken()) {
+        case VALUE_TRUE:
+            return Boolean.TRUE;
+        case VALUE_FALSE:
+            return Boolean.FALSE;
+        default:
+        	return null;
+        }
+    }
+    
+    @Override
+    public void close() throws IOException
+    {
+        super.close();
+        _symbols.release();
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, number parsing
+    /* (note: in 1.8 and prior, part of "ReaderBasedNumericParser"
+    /**********************************************************
+     */
+
+    /**
+     * Initial parsing method for number values. It needs to be able
+     * to parse enough input to be able to determine whether the
+     * value is to be considered a simple integer value, or a more
+     * generic decimal value: latter of which needs to be expressed
+     * as a floating point number. The basic rule is that if the number
+     * has no fractional or exponential part, it is an integer; otherwise
+     * a floating point number.
+     *<p>
+     * Because much of input has to be processed in any case, no partial
+     * parsing is done: all input text will be stored for further
+     * processing. However, actual numeric value conversion will be
+     * deferred, since it is usually the most complicated and costliest
+     * part of processing.
+     */
+    protected JsonToken parseNumberText(int ch)
+        throws IOException, JsonParseException
+    {
+        /* Although we will always be complete with respect to textual
+         * representation (that is, all characters will be parsed),
+         * actual conversion to a number is deferred. Thus, need to
+         * note that no representations are valid yet
+         */
+        boolean negative = (ch == INT_MINUS);
+        int ptr = _inputPtr;
+        int startPtr = ptr-1; // to include sign/digit already read
+        final int inputLen = _inputEnd;
+
+        dummy_loop:
+        do { // dummy loop, to be able to break out
+            if (negative) { // need to read the next digit
+                if (ptr >= _inputEnd) {
+                    break dummy_loop;
+                }
+                ch = _inputBuffer[ptr++];
+                // First check: must have a digit to follow minus sign
+                if (ch > INT_9 || ch < INT_0) {
+                    _inputPtr = ptr;
+                    return _handleInvalidNumberStart(ch, true);
+                }
+                /* (note: has been checked for non-negative already, in
+                 * the dispatching code that determined it should be
+                 * a numeric value)
+                 */
+            }
+            // One special case, leading zero(es):
+            if (ch == INT_0) {
+                break dummy_loop;
+            }
+            
+            /* First, let's see if the whole number is contained within
+             * the input buffer unsplit. This should be the common case;
+             * and to simplify processing, we will just reparse contents
+             * in the alternative case (number split on buffer boundary)
+             */
+            
+            int intLen = 1; // already got one
+            
+            // First let's get the obligatory integer part:
+            
+            int_loop:
+            while (true) {
+                if (ptr >= _inputEnd) {
+                    break dummy_loop;
+                }
+                ch = (int) _inputBuffer[ptr++];
+                if (ch < INT_0 || ch > INT_9) {
+                    break int_loop;
+                }
+                ++intLen;
+            }
+
+            int fractLen = 0;
+            
+            // And then see if we get other parts
+            if (ch == INT_DECIMAL_POINT) { // yes, fraction
+                fract_loop:
+                while (true) {
+                    if (ptr >= inputLen) {
+                        break dummy_loop;
+                    }
+                    ch = (int) _inputBuffer[ptr++];
+                    if (ch < INT_0 || ch > INT_9) {
+                        break fract_loop;
+                    }
+                    ++fractLen;
+                }
+                // must be followed by sequence of ints, one minimum
+                if (fractLen == 0) {
+                    reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit");
+                }
+            }
+
+            int expLen = 0;
+            if (ch == INT_e || ch == INT_E) { // and/or exponent
+                if (ptr >= inputLen) {
+                    break dummy_loop;
+                }
+                // Sign indicator?
+                ch = (int) _inputBuffer[ptr++];
+                if (ch == INT_MINUS || ch == INT_PLUS) { // yup, skip for now
+                    if (ptr >= inputLen) {
+                        break dummy_loop;
+                    }
+                    ch = (int) _inputBuffer[ptr++];
+                }
+                while (ch <= INT_9 && ch >= INT_0) {
+                    ++expLen;
+                    if (ptr >= inputLen) {
+                        break dummy_loop;
+                    }
+                    ch = (int) _inputBuffer[ptr++];
+                }
+                // must be followed by sequence of ints, one minimum
+                if (expLen == 0) {
+                    reportUnexpectedNumberChar(ch, "Exponent indicator not followed by a digit");
+                }
+            }
+
+            // Got it all: let's add to text buffer for parsing, access
+            --ptr; // need to push back following separator
+            _inputPtr = ptr;
+            int len = ptr-startPtr;
+            _textBuffer.resetWithShared(_inputBuffer, startPtr, len);
+            return reset(negative, intLen, fractLen, expLen);
+        } while (false);
+
+        _inputPtr = negative ? (startPtr+1) : startPtr;
+        return parseNumberText2(negative);
+    }
+
+    /**
+     * Method called to parse a number, when the primary parse
+     * method has failed to parse it, due to it being split on
+     * buffer boundary. As a result code is very similar, except
+     * that it has to explicitly copy contents to the text buffer
+     * instead of just sharing the main input buffer.
+     */
+    private JsonToken parseNumberText2(boolean negative)
+        throws IOException, JsonParseException
+    {
+        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+        int outPtr = 0;
+
+        // Need to prepend sign?
+        if (negative) {
+            outBuf[outPtr++] = '-';
+        }
+
+        // This is the place to do leading-zero check(s) too:
+        int intLen = 0;
+        char c = (_inputPtr < _inputEnd) ? _inputBuffer[_inputPtr++] : getNextChar("No digit following minus sign");
+        if (c == '0') {
+            c = _verifyNoLeadingZeroes();
+        }
+        boolean eof = false;
+
+        // Ok, first the obligatory integer part:
+        int_loop:
+        while (c >= '0' && c <= '9') {
+            ++intLen;
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            outBuf[outPtr++] = c;
+            if (_inputPtr >= _inputEnd && !loadMore()) {
+                // EOF is legal for main level int values
+                c = CHAR_NULL;
+                eof = true;
+                break int_loop;
+            }
+            c = _inputBuffer[_inputPtr++];
+        }
+        // Also, integer part is not optional
+        if (intLen == 0) {
+            reportInvalidNumber("Missing integer part (next char "+_getCharDesc(c)+")");
+        }
+
+        int fractLen = 0;
+        // And then see if we get other parts
+        if (c == '.') { // yes, fraction
+            outBuf[outPtr++] = c;
+
+            fract_loop:
+            while (true) {
+                if (_inputPtr >= _inputEnd && !loadMore()) {
+                    eof = true;
+                    break fract_loop;
+                }
+                c = _inputBuffer[_inputPtr++];
+                if (c < INT_0 || c > INT_9) {
+                    break fract_loop;
+                }
+                ++fractLen;
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                outBuf[outPtr++] = c;
+            }
+            // must be followed by sequence of ints, one minimum
+            if (fractLen == 0) {
+                reportUnexpectedNumberChar(c, "Decimal point not followed by a digit");
+            }
+        }
+
+        int expLen = 0;
+        if (c == 'e' || c == 'E') { // exponent?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            outBuf[outPtr++] = c;
+            // Not optional, can require that we get one more char
+            c = (_inputPtr < _inputEnd) ? _inputBuffer[_inputPtr++]
+                : getNextChar("expected a digit for number exponent");
+            // Sign indicator?
+            if (c == '-' || c == '+') {
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                outBuf[outPtr++] = c;
+                // Likewise, non optional:
+                c = (_inputPtr < _inputEnd) ? _inputBuffer[_inputPtr++]
+                    : getNextChar("expected a digit for number exponent");
+            }
+
+            exp_loop:
+            while (c <= INT_9 && c >= INT_0) {
+                ++expLen;
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                outBuf[outPtr++] = c;
+                if (_inputPtr >= _inputEnd && !loadMore()) {
+                    eof = true;
+                    break exp_loop;
+                }
+                c = _inputBuffer[_inputPtr++];
+            }
+            // must be followed by sequence of ints, one minimum
+            if (expLen == 0) {
+                reportUnexpectedNumberChar(c, "Exponent indicator not followed by a digit");
+            }
+        }
+
+        // Ok; unless we hit end-of-input, need to push last char read back
+        if (!eof) {
+            --_inputPtr;
+        }
+        _textBuffer.setCurrentLength(outPtr);
+        // And there we have it!
+        return reset(negative, intLen, fractLen, expLen);
+    }
+
+    /**
+     * Method called when we have seen one zero, and want to ensure
+     * it is not followed by another
+     */
+    private char _verifyNoLeadingZeroes()
+        throws IOException, JsonParseException
+    {
+        // Ok to have plain "0"
+        if (_inputPtr >= _inputEnd && !loadMore()) {
+            return '0';
+        }
+        char ch = _inputBuffer[_inputPtr];
+        // if not followed by a number (probably '.'); return zero as is, to be included
+        if (ch < '0' || ch > '9') {
+            return '0';
+        }
+        if (!isEnabled(Feature.ALLOW_NUMERIC_LEADING_ZEROS)) {
+            reportInvalidNumber("Leading zeroes not allowed");
+        }
+        // if so, just need to skip either all zeroes (if followed by number); or all but one (if non-number)
+        ++_inputPtr; // Leading zero to be skipped
+        if (ch == INT_0) {
+            while (_inputPtr < _inputEnd || loadMore()) {
+                ch = _inputBuffer[_inputPtr];
+                if (ch < '0' || ch > '9') { // followed by non-number; retain one zero
+                    return '0';
+                }
+                ++_inputPtr; // skip previous zero
+                if (ch != '0') { // followed by other number; return 
+                    break;
+                }
+            }
+        }
+        return ch;
+    }
+
+    /**
+     * Method called if expected numeric value (due to leading sign) does not
+     * look like a number
+     */
+    protected JsonToken _handleInvalidNumberStart(int ch, boolean negative)
+        throws IOException, JsonParseException
+    {
+        if (ch == 'I') {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOFInValue();
+                }
+            }
+            ch = _inputBuffer[_inputPtr++];
+            if (ch == 'N') {
+                String match = negative ? "-INF" :"+INF";
+                _matchToken(match, 3);
+                if (isEnabled(Feature.ALLOW_NON_NUMERIC_NUMBERS)) {
+                    return resetAsNaN(match, negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
+                }
+                _reportError("Non-standard token '"+match+"': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
+            } else if (ch == 'n') {
+                String match = negative ? "-Infinity" :"+Infinity";
+                _matchToken(match, 3);
+                if (isEnabled(Feature.ALLOW_NON_NUMERIC_NUMBERS)) {
+                    return resetAsNaN(match, negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
+                }
+                _reportError("Non-standard token '"+match+"': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
+            }
+        }
+        reportUnexpectedNumberChar(ch, "expected digit (0-9) to follow minus sign, for valid numeric value");
+        return null;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, secondary parsing
+    /**********************************************************
+     */
+
+    protected String _parseFieldName(int i)
+        throws IOException, JsonParseException
+    {
+        if (i != INT_QUOTE) {
+            return _handleUnusualFieldName(i);
+        }
+        /* First: let's try to see if we have a simple name: one that does
+         * not cross input buffer boundary, and does not contain escape
+         * sequences.
+         */
+        int ptr = _inputPtr;
+        int hash = _hashSeed;
+        final int inputLen = _inputEnd;
+
+        if (ptr < inputLen) {
+            final int[] codes = CharTypes.getInputCodeLatin1();
+            final int maxCode = codes.length;
+
+            do {
+                int ch = _inputBuffer[ptr];
+                if (ch < maxCode && codes[ch] != 0) {
+                    if (ch == '"') {
+                        int start = _inputPtr;
+                        _inputPtr = ptr+1; // to skip the quote
+                        return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
+                    }
+                    break;
+                }
+                hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch;
+                ++ptr;
+            } while (ptr < inputLen);
+        }
+
+        int start = _inputPtr;
+        _inputPtr = ptr;
+        return _parseFieldName2(start, hash, INT_QUOTE);
+    }
+
+    private String _parseFieldName2(int startPtr, int hash, int endChar)
+        throws IOException, JsonParseException
+    {
+        _textBuffer.resetWithShared(_inputBuffer, startPtr, (_inputPtr - startPtr));
+
+        /* Output pointers; calls will also ensure that the buffer is
+         * not shared and has room for at least one more char.
+         */
+        char[] outBuf = _textBuffer.getCurrentSegment();
+        int outPtr = _textBuffer.getCurrentSegmentSize();
+
+        while (true) {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(": was expecting closing '"+((char) endChar)+"' for name");
+                }
+            }
+            char c = _inputBuffer[_inputPtr++];
+            int i = (int) c;
+            if (i <= INT_BACKSLASH) {
+                if (i == INT_BACKSLASH) {
+                    /* Although chars outside of BMP are to be escaped as
+                     * an UTF-16 surrogate pair, does that affect decoding?
+                     * For now let's assume it does not.
+                     */
+                    c = _decodeEscaped();
+                } else if (i <= endChar) {
+                    if (i == endChar) {
+                        break;
+                    }
+                    if (i < INT_SPACE) {
+                        _throwUnquotedSpace(i, "name");
+                    }
+                }
+            }
+            hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + i;
+            // Ok, let's add char to output:
+            outBuf[outPtr++] = c;
+
+            // Need more room?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+        }
+        _textBuffer.setCurrentLength(outPtr);
+        {
+            TextBuffer tb = _textBuffer;
+            char[] buf = tb.getTextBuffer();
+            int start = tb.getTextOffset();
+            int len = tb.size();
+
+            return _symbols.findSymbol(buf, start, len, hash);
+        }
+    }
+
+    /**
+     * Method called when we see non-white space character other
+     * than double quote, when expecting a field name.
+     * In standard mode will just throw an expection; but
+     * in non-standard modes may be able to parse name.
+     */
+    protected String _handleUnusualFieldName(int i)
+        throws IOException, JsonParseException
+    {
+        // [JACKSON-173]: allow single quotes
+        if (i == INT_APOSTROPHE && isEnabled(Feature.ALLOW_SINGLE_QUOTES)) {
+            return _parseApostropheFieldName();
+        }
+        // [JACKSON-69]: allow unquoted names if feature enabled:
+        if (!isEnabled(Feature.ALLOW_UNQUOTED_FIELD_NAMES)) {
+            _reportUnexpectedChar(i, "was expecting double-quote to start field name");
+        }
+        final int[] codes = CharTypes.getInputCodeLatin1JsNames();
+        final int maxCode = codes.length;
+
+        // Also: first char must be a valid name char, but NOT be number
+        boolean firstOk;
+
+        if (i < maxCode) { // identifier, and not a number
+            firstOk = (codes[i] == 0) && (i < INT_0 || i > INT_9);
+        } else {
+            firstOk = Character.isJavaIdentifierPart((char) i);
+        }
+        if (!firstOk) {
+            _reportUnexpectedChar(i, "was expecting either valid name character (for unquoted name) or double-quote (for quoted) to start field name");
+        }
+        int ptr = _inputPtr;
+        int hash = _hashSeed;
+        final int inputLen = _inputEnd;
+
+        if (ptr < inputLen) {
+            do {
+                int ch = _inputBuffer[ptr];
+                if (ch < maxCode) {
+                    if (codes[ch] != 0) {
+                        int start = _inputPtr-1; // -1 to bring back first char
+                        _inputPtr = ptr;
+                        return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
+                    }
+                } else if (!Character.isJavaIdentifierPart((char) ch)) {
+                    int start = _inputPtr-1; // -1 to bring back first char
+                    _inputPtr = ptr;
+                    return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
+                }
+                hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch;
+                ++ptr;
+            } while (ptr < inputLen);
+        }
+        int start = _inputPtr-1;
+        _inputPtr = ptr;
+        return _parseUnusualFieldName2(start, hash, codes);
+    }
+
+    protected String _parseApostropheFieldName()
+        throws IOException, JsonParseException
+    {
+        // Note: mostly copy of_parseFieldName
+        int ptr = _inputPtr;
+        int hash = _hashSeed;
+        final int inputLen = _inputEnd;
+
+        if (ptr < inputLen) {
+            final int[] codes = CharTypes.getInputCodeLatin1();
+            final int maxCode = codes.length;
+
+            do {
+                int ch = _inputBuffer[ptr];
+                if (ch == '\'') {
+                    int start = _inputPtr;
+                    _inputPtr = ptr+1; // to skip the quote
+                    return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
+                }
+                if (ch < maxCode && codes[ch] != 0) {
+                    break;
+                }
+                hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch;
+                ++ptr;
+            } while (ptr < inputLen);
+        }
+
+        int start = _inputPtr;
+        _inputPtr = ptr;
+
+        return _parseFieldName2(start, hash, INT_APOSTROPHE);
+    }
+
+    /**
+     * Method for handling cases where first non-space character
+     * of an expected value token is not legal for standard JSON content.
+     */
+    protected JsonToken _handleUnexpectedValue(int i)
+        throws IOException, JsonParseException
+    {
+        // Most likely an error, unless we are to allow single-quote-strings
+        switch (i) {
+        case '\'':
+            /* [JACKSON-173]: allow single quotes. Unlike with regular
+             * Strings, we'll eagerly parse contents; this so that there's
+             * no need to store information on quote char used.
+             *
+             * Also, no separation to fast/slow parsing; we'll just do
+             * one regular (~= slowish) parsing, to keep code simple
+             */
+            if (isEnabled(Feature.ALLOW_SINGLE_QUOTES)) {
+                return _handleApostropheValue();
+            }
+            break;
+        case 'N':
+            _matchToken("NaN", 1);
+            if (isEnabled(Feature.ALLOW_NON_NUMERIC_NUMBERS)) {
+                return resetAsNaN("NaN", Double.NaN);
+            }
+            _reportError("Non-standard token 'NaN': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
+            break;
+        case '+': // note: '-' is taken as number
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOFInValue();
+                }
+            }
+            return _handleInvalidNumberStart(_inputBuffer[_inputPtr++], false);
+        }
+        _reportUnexpectedChar(i, "expected a valid value (number, String, array, object, 'true', 'false' or 'null')");
+        return null;
+    }
+    
+    protected JsonToken _handleApostropheValue()
+        throws IOException, JsonParseException
+    {
+        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+        int outPtr = _textBuffer.getCurrentSegmentSize();
+
+        while (true) {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(": was expecting closing quote for a string value");
+                }
+            }
+            char c = _inputBuffer[_inputPtr++];
+            int i = (int) c;
+            if (i <= INT_BACKSLASH) {
+                if (i == INT_BACKSLASH) {
+                    /* Although chars outside of BMP are to be escaped as
+                     * an UTF-16 surrogate pair, does that affect decoding?
+                     * For now let's assume it does not.
+                     */
+                    c = _decodeEscaped();
+                } else if (i <= INT_APOSTROPHE) {
+                    if (i == INT_APOSTROPHE) {
+                        break;
+                    }
+                    if (i < INT_SPACE) {
+                        _throwUnquotedSpace(i, "string value");
+                    }
+                }
+            }
+            // Need more room?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            // Ok, let's add char to output:
+            outBuf[outPtr++] = c;
+        }
+        _textBuffer.setCurrentLength(outPtr);
+        return JsonToken.VALUE_STRING;
+    }
+    
+    private String _parseUnusualFieldName2(int startPtr, int hash, int[] codes)
+        throws IOException, JsonParseException
+    {
+        _textBuffer.resetWithShared(_inputBuffer, startPtr, (_inputPtr - startPtr));
+        char[] outBuf = _textBuffer.getCurrentSegment();
+        int outPtr = _textBuffer.getCurrentSegmentSize();
+        final int maxCode = codes.length;
+
+        while (true) {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) { // acceptable for now (will error out later)
+                    break;
+                }
+            }
+            char c = _inputBuffer[_inputPtr];
+            int i = (int) c;
+            if (i <= maxCode) {
+                if (codes[i] != 0) {
+                    break;
+                }
+            } else if (!Character.isJavaIdentifierPart(c)) {
+                break;
+            }
+            ++_inputPtr;
+            hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + i;
+            // Ok, let's add char to output:
+            outBuf[outPtr++] = c;
+
+            // Need more room?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+        }
+        _textBuffer.setCurrentLength(outPtr);
+        {
+            TextBuffer tb = _textBuffer;
+            char[] buf = tb.getTextBuffer();
+            int start = tb.getTextOffset();
+            int len = tb.size();
+
+            return _symbols.findSymbol(buf, start, len, hash);
+        }
+    }
+  
+    @Override
+    protected void _finishString()
+        throws IOException, JsonParseException
+    {
+        /* First: let's try to see if we have simple String value: one
+         * that does not cross input buffer boundary, and does not
+         * contain escape sequences.
+         */
+        int ptr = _inputPtr;
+        final int inputLen = _inputEnd;
+
+        if (ptr < inputLen) {
+            final int[] codes = CharTypes.getInputCodeLatin1();
+            final int maxCode = codes.length;
+
+            do {
+                int ch = _inputBuffer[ptr];
+                if (ch < maxCode && codes[ch] != 0) {
+                    if (ch == '"') {
+                        _textBuffer.resetWithShared(_inputBuffer, _inputPtr, (ptr-_inputPtr));
+                        _inputPtr = ptr+1;
+                        // Yes, we got it all
+                        return;
+                    }
+                    break;
+                }
+                ++ptr;
+            } while (ptr < inputLen);
+        }
+
+        /* Either ran out of input, or bumped into an escape
+         * sequence...
+         */
+        _textBuffer.resetWithCopy(_inputBuffer, _inputPtr, (ptr-_inputPtr));
+        _inputPtr = ptr;
+        _finishString2();
+    }
+
+    protected void _finishString2()
+        throws IOException, JsonParseException
+    {
+        char[] outBuf = _textBuffer.getCurrentSegment();
+        int outPtr = _textBuffer.getCurrentSegmentSize();
+
+        while (true) {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(": was expecting closing quote for a string value");
+                }
+            }
+            char c = _inputBuffer[_inputPtr++];
+            int i = (int) c;
+            if (i <= INT_BACKSLASH) {
+                if (i == INT_BACKSLASH) {
+                    /* Although chars outside of BMP are to be escaped as
+                     * an UTF-16 surrogate pair, does that affect decoding?
+                     * For now let's assume it does not.
+                     */
+                    c = _decodeEscaped();
+                } else if (i <= INT_QUOTE) {
+                    if (i == INT_QUOTE) {
+                        break;
+                    }
+                    if (i < INT_SPACE) {
+                        _throwUnquotedSpace(i, "string value");
+                    }
+                }
+            }
+            // Need more room?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            // Ok, let's add char to output:
+            outBuf[outPtr++] = c;
+        }
+        _textBuffer.setCurrentLength(outPtr);
+    }
+
+    /**
+     * Method called to skim through rest of unparsed String value,
+     * if it is not needed. This can be done bit faster if contents
+     * need not be stored for future access.
+     */
+    protected void _skipString()
+        throws IOException, JsonParseException
+    {
+        _tokenIncomplete = false;
+
+        int inputPtr = _inputPtr;
+        int inputLen = _inputEnd;
+        char[] inputBuffer = _inputBuffer;
+
+        while (true) {
+            if (inputPtr >= inputLen) {
+                _inputPtr = inputPtr;
+                if (!loadMore()) {
+                    _reportInvalidEOF(": was expecting closing quote for a string value");
+                }
+                inputPtr = _inputPtr;
+                inputLen = _inputEnd;
+            }
+            char c = inputBuffer[inputPtr++];
+            int i = (int) c;
+            if (i <= INT_BACKSLASH) {
+                if (i == INT_BACKSLASH) {
+                    /* Although chars outside of BMP are to be escaped as
+                     * an UTF-16 surrogate pair, does that affect decoding?
+                     * For now let's assume it does not.
+                     */
+                    _inputPtr = inputPtr;
+                    c = _decodeEscaped();
+                    inputPtr = _inputPtr;
+                    inputLen = _inputEnd;
+                } else if (i <= INT_QUOTE) {
+                    if (i == INT_QUOTE) {
+                        _inputPtr = inputPtr;
+                        break;
+                    }
+                    if (i < INT_SPACE) {
+                        _inputPtr = inputPtr;
+                        _throwUnquotedSpace(i, "string value");
+                    }
+                }
+            }
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, other parsing
+    /**********************************************************
+     */
+    
+    /**
+     * We actually need to check the character value here
+     * (to see if we have \n following \r).
+     */
+    protected void _skipCR() throws IOException
+    {
+        if (_inputPtr < _inputEnd || loadMore()) {
+            if (_inputBuffer[_inputPtr] == '\n') {
+                ++_inputPtr;
+            }
+        }
+        ++_currInputRow;
+        _currInputRowStart = _inputPtr;
+    }
+
+    protected void _skipLF() throws IOException
+    {
+        ++_currInputRow;
+        _currInputRowStart = _inputPtr;
+    }
+
+    private int _skipWS()
+        throws IOException, JsonParseException
+    {
+        while (_inputPtr < _inputEnd || loadMore()) {
+            int i = (int) _inputBuffer[_inputPtr++];
+            if (i > INT_SPACE) {
+                if (i != INT_SLASH) {
+                    return i;
+                }
+                _skipComment();
+            } else if (i != INT_SPACE) {
+                if (i == INT_LF) {
+                    _skipLF();
+                } else if (i == INT_CR) {
+                    _skipCR();
+                } else if (i != INT_TAB) {
+                    _throwInvalidSpace(i);
+                }
+            }
+        }
+        throw _constructError("Unexpected end-of-input within/between "+_parsingContext.getTypeDesc()+" entries");
+    }
+
+    private int _skipWSOrEnd()
+        throws IOException, JsonParseException
+    {
+        while ((_inputPtr < _inputEnd) || loadMore()) {
+            int i = (int) _inputBuffer[_inputPtr++];
+            if (i > INT_SPACE) {
+                 if (i == INT_SLASH) {
+                     _skipComment();
+                     continue;
+                }
+                 return i;
+            }
+            if (i != INT_SPACE) {
+                if (i == INT_LF) {
+                    _skipLF();
+                } else if (i == INT_CR) {
+                    _skipCR();
+                } else if (i != INT_TAB) {
+                    _throwInvalidSpace(i);
+                }
+            }
+        }
+        // We ran out of input...
+        _handleEOF();
+        return -1;
+    }
+
+    private void _skipComment()
+        throws IOException, JsonParseException
+    {
+        if (!isEnabled(Feature.ALLOW_COMMENTS)) {
+            _reportUnexpectedChar('/', "maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)");
+        }
+        // First: check which comment (if either) it is:
+        if (_inputPtr >= _inputEnd && !loadMore()) {
+            _reportInvalidEOF(" in a comment");
+        }
+        char c = _inputBuffer[_inputPtr++];
+        if (c == '/') {
+            _skipCppComment();
+        } else if (c == '*') {
+            _skipCComment();
+        } else {
+            _reportUnexpectedChar(c, "was expecting either '*' or '/' for a comment");
+        }
+    }
+
+    private void _skipCComment()
+        throws IOException, JsonParseException
+    {
+        // Ok: need the matching '*/'
+        main_loop:
+        while ((_inputPtr < _inputEnd) || loadMore()) {
+            int i = (int) _inputBuffer[_inputPtr++];
+            if (i <= INT_ASTERISK) {
+                if (i == INT_ASTERISK) { // end?
+                    if ((_inputPtr >= _inputEnd) && !loadMore()) {
+                        break main_loop;
+                    }
+                    if (_inputBuffer[_inputPtr] == INT_SLASH) {
+                        ++_inputPtr;
+                        return;
+                    }
+                    continue;
+                }
+                if (i < INT_SPACE) {
+                    if (i == INT_LF) {
+                        _skipLF();
+                    } else if (i == INT_CR) {
+                        _skipCR();
+                    } else if (i != INT_TAB) {
+                        _throwInvalidSpace(i);
+                    }
+                }
+            }
+        }
+        _reportInvalidEOF(" in a comment");
+    }
+
+    private void _skipCppComment()
+        throws IOException, JsonParseException
+    {
+        // Ok: need to find EOF or linefeed
+        while ((_inputPtr < _inputEnd) || loadMore()) {
+            int i = (int) _inputBuffer[_inputPtr++];
+            if (i < INT_SPACE) {
+                if (i == INT_LF) {
+                    _skipLF();
+                    break;
+                } else if (i == INT_CR) {
+                    _skipCR();
+                    break;
+                } else if (i != INT_TAB) {
+                    _throwInvalidSpace(i);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected char _decodeEscaped()
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            if (!loadMore()) {
+                _reportInvalidEOF(" in character escape sequence");
+            }
+        }
+        char c = _inputBuffer[_inputPtr++];
+
+        switch ((int) c) {
+            // First, ones that are mapped
+        case INT_b:
+            return '\b';
+        case INT_t:
+            return '\t';
+        case INT_n:
+            return '\n';
+        case INT_f:
+            return '\f';
+        case INT_r:
+            return '\r';
+
+            // And these are to be returned as they are
+        case INT_QUOTE:
+        case INT_SLASH:
+        case INT_BACKSLASH:
+            return c;
+
+        case INT_u: // and finally hex-escaped
+            break;
+
+        default:
+            return _handleUnrecognizedCharacterEscape(c);
+        }
+
+        // Ok, a hex escape. Need 4 characters
+        int value = 0;
+        for (int i = 0; i < 4; ++i) {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(" in character escape sequence");
+                }
+            }
+            int ch = (int) _inputBuffer[_inputPtr++];
+            int digit = CharTypes.charToHex(ch);
+            if (digit < 0) {
+                _reportUnexpectedChar(ch, "expected a hex-digit for character escape sequence");
+            }
+            value = (value << 4) | digit;
+        }
+        return (char) value;
+    }
+    
+    /**
+     * Helper method for checking whether input matches expected token
+     */
+    protected void _matchToken(String matchStr, int i)
+        throws IOException, JsonParseException
+    {
+        final int len = matchStr.length();
+
+        do {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidToken(matchStr.substring(0, i));
+                }
+            }
+            if (_inputBuffer[_inputPtr] != matchStr.charAt(i)) {
+                _reportInvalidToken(matchStr.substring(0, i));
+            }
+            ++_inputPtr;
+        } while (++i < len);
+
+        // but let's also ensure we either get EOF, or non-alphanum char...
+        if (_inputPtr >= _inputEnd) {
+            if (!loadMore()) {
+                return;
+            }
+        }
+        char c = _inputBuffer[_inputPtr];
+        if (c < '0' || c == ']' || c == '}') { // expected/allowed chars
+            return;
+        }
+        // if Java letter, it's a problem tho
+        if (Character.isJavaIdentifierPart(c)) {
+            _reportInvalidToken(matchStr.substring(0, i));
+        }
+        return;
+    }
+
+    /*
+    /**********************************************************
+    /* Binary access
+    /**********************************************************
+     */
+
+    /**
+     * Efficient handling for incremental parsing of base64-encoded
+     * textual content.
+     */
+    protected byte[] _decodeBase64(Base64Variant b64variant)
+        throws IOException, JsonParseException
+    {
+        ByteArrayBuilder builder = _getByteArrayBuilder();
+
+        //main_loop:
+        while (true) {
+            // first, we'll skip preceding white space, if any
+            char ch;
+            do {
+                if (_inputPtr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                }
+                ch = _inputBuffer[_inputPtr++];
+            } while (ch <= INT_SPACE);
+            int bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                if (ch == '"') { // reached the end, fair and square?
+                    return builder.toByteArray();
+                }
+                bits = _decodeBase64Escape(b64variant, ch, 0);
+                if (bits < 0) { // white space to skip
+                    continue;
+                }
+            }
+            int decodedData = bits;
+            
+            // then second base64 char; can't get padding yet, nor ws
+            
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++];
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                bits = _decodeBase64Escape(b64variant, ch, 1);
+            }
+            decodedData = (decodedData << 6) | bits;
+            
+            // third base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++];
+            bits = b64variant.decodeBase64Char(ch);
+
+            // First branch: can get padding (-> 1 byte)
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 4;
+                        builder.append(decodedData);
+                        return builder.toByteArray();
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 2);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    // Ok, must get more padding chars, then
+                    if (_inputPtr >= _inputEnd) {
+                        loadMoreGuaranteed();
+                    }
+                    ch = _inputBuffer[_inputPtr++];
+                    if (!b64variant.usesPaddingChar(ch)) {
+                        throw reportInvalidBase64Char(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
+                    }
+                    // Got 12 bits, only need 8, need to shift
+                    decodedData >>= 4;
+                    builder.append(decodedData);
+                    continue;
+                }
+                // otherwise we got escaped other char, to be processed below
+            }
+            // Nope, 2 or 3 bytes
+            decodedData = (decodedData << 6) | bits;
+            // fourth and last base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++];
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 2;
+                        builder.appendTwoBytes(decodedData);
+                        return builder.toByteArray();
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 3);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    // With padding we only get 2 bytes; but we have
+                    // to shift it a bit so it is identical to triplet
+                    // case with partial output.
+                    // 3 chars gives 3x6 == 18 bits, of which 2 are
+                    // dummies, need to discard:
+                    decodedData >>= 2;
+                    builder.appendTwoBytes(decodedData);
+                    continue;
+                }
+                // otherwise we got escaped other char, to be processed below
+            }
+            // otherwise, our triplet is now complete
+            decodedData = (decodedData << 6) | bits;
+            builder.appendThreeBytes(decodedData);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Error reporting
+    /**********************************************************
+     */
+
+    protected void _reportInvalidToken(String matchedPart)
+            throws IOException, JsonParseException {
+        _reportInvalidToken(matchedPart, "'null', 'true', 'false' or NaN");
+    }
+    
+    protected void _reportInvalidToken(String matchedPart, String msg)
+        throws IOException, JsonParseException
+    {
+        StringBuilder sb = new StringBuilder(matchedPart);
+        /* Let's just try to find what appears to be the token, using
+         * regular Java identifier character rules. It's just a heuristic,
+         * nothing fancy here.
+         */
+        while (true) {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    break;
+                }
+            }
+            char c = _inputBuffer[_inputPtr];
+            if (!Character.isJavaIdentifierPart(c)) {
+                break;
+            }
+            ++_inputPtr;
+            sb.append(c);
+        }
+        _reportError("Unrecognized token '"+sb.toString()+"': was expecting ");
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java
new file mode 100644
index 0000000..a0002d6
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java
@@ -0,0 +1,1851 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.*;
+
+public class UTF8JsonGenerator
+    extends JsonGeneratorImpl
+{
+    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) ':';
+    private final static byte BYTE_QUOTE = (byte) '"';
+
+    protected final static int SURR1_FIRST = 0xD800;
+    protected final static int SURR1_LAST = 0xDBFF;
+    protected final static int SURR2_FIRST = 0xDC00;
+    protected final static int SURR2_LAST = 0xDFFF;
+
+    // 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' };
+    private final static byte[] TRUE_BYTES = { 't', 'r', 'u', 'e' };
+    private final static byte[] FALSE_BYTES = { 'f', 'a', 'l', 's', 'e' };
+
+    /*
+    /**********************************************************
+    /* Output buffering
+    /**********************************************************
+     */
+    
+    /**
+     * Underlying output stream used for writing JSON content.
+     */
+    final protected OutputStream _outputStream;
+
+    /**
+     * Intermediate buffer in which contents are buffered before
+     * being written using {@link #_outputStream}.
+     */
+    protected byte[] _outputBuffer;
+
+    /**
+     * Pointer to the position right beyond the last character to output
+     * (end marker; may be past the buffer)
+     */
+    protected int _outputTail = 0;
+
+    /**
+     * End marker of the output buffer; one past the last valid position
+     * within the buffer.
+     */
+    protected final int _outputEnd;
+
+    /**
+     * Maximum number of <code>char</code>s that we know will always fit
+     * 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
+     */
+    protected byte[] _entityBuffer;
+
+    /**
+     * Flag that indicates whether the output buffer is recycable (and
+     * needs to be returned to recycler once we are done) or not.
+     */
+    protected boolean _bufferRecyclable;
+    
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec,
+            OutputStream out)
+    {
+        super(ctxt, features, codec);
+        _outputStream = out;
+        _bufferRecyclable = true;
+        _outputBuffer = ctxt.allocWriteEncodingBuffer();
+        _outputEnd = _outputBuffer.length;
+
+        /* To be exact, each char can take up to 6 bytes when escaped (Unicode
+         * escape with backslash, 'u' and 4 hex digits); but to avoid fluctuation,
+         * we will actually round down to only do up to 1/8 number of chars
+         */
+        _outputMaxContiguous = _outputEnd >> 3;
+        _charBuffer = ctxt.allocConcatBuffer();
+        _charBufferLength = _charBuffer.length;
+
+        // By default we use this feature to determine additional quoting
+        if (isEnabled(Feature.ESCAPE_NON_ASCII)) {
+            setHighestNonEscapedChar(127);
+        }
+    }
+    
+    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec,
+            OutputStream out,
+            byte[] outputBuffer, int outputOffset, boolean bufferRecyclable)
+    {
+        
+        super(ctxt, features, codec);
+        _outputStream = out;
+        _bufferRecyclable = bufferRecyclable;
+        _outputTail = outputOffset;
+        _outputBuffer = outputBuffer;
+        _outputEnd = _outputBuffer.length;
+        // up to 6 bytes per char (see above), rounded up to 1/8
+        _outputMaxContiguous = _outputEnd >> 3;
+        _charBuffer = ctxt.allocConcatBuffer();
+        _charBufferLength = _charBuffer.length;
+    }
+
+    /*
+    /**********************************************************
+    /* Overridden configuration methods
+    /**********************************************************
+     */
+    
+    @Override
+    public Object getOutputTarget() {
+        return _outputStream;
+    }
+
+    /*
+    /**********************************************************
+    /* Overridden methods
+    /**********************************************************
+     */
+
+    @Override
+    public final void writeFieldName(String name)  throws IOException, JsonGenerationException
+    {
+        int status = _writeContext.writeFieldName(name);
+        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
+            _reportError("Can not write a field name, expecting a value");
+        }
+        if (_cfgPrettyPrinter != null) {
+            _writePPFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
+            return;
+        }
+        if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) { // need comma
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_COMMA;
+        }
+        _writeFieldName(name);
+    }
+
+    @Override
+    public final void writeFieldName(SerializableString name)
+        throws IOException, JsonGenerationException
+    {
+        // Object is a value, need to verify it's allowed
+        int status = _writeContext.writeFieldName(name.getValue());
+        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
+            _reportError("Can not write a field name, expecting a value");
+        }
+        if (_cfgPrettyPrinter != null) {
+            _writePPFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
+            return;
+        }
+        if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_COMMA;
+        }
+        _writeFieldName(name);
+    }
+
+    /*
+    /**********************************************************
+    /* Output method implementations, structural
+    /**********************************************************
+     */
+
+    @Override
+    public final void writeStartArray() throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("start an array");
+        _writeContext = _writeContext.createChildArrayContext();
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeStartArray(this);
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_LBRACKET;
+        }
+    }
+
+    @Override
+    public final void writeEndArray() throws IOException, JsonGenerationException
+    {
+        if (!_writeContext.inArray()) {
+            _reportError("Current context not an ARRAY but "+_writeContext.getTypeDesc());
+        }
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeEndArray(this, _writeContext.getEntryCount());
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_RBRACKET;
+        }
+        _writeContext = _writeContext.getParent();
+    }
+
+    @Override
+    public final void writeStartObject() throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("start an object");
+        _writeContext = _writeContext.createChildObjectContext();
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeStartObject(this);
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_LCURLY;
+        }
+    }
+
+    @Override
+    public final void writeEndObject() throws IOException, JsonGenerationException
+    {
+        if (!_writeContext.inObject()) {
+            _reportError("Current context not an object but "+_writeContext.getTypeDesc());
+        }
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeEndObject(this, _writeContext.getEntryCount());
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_RCURLY;
+        }
+        _writeContext = _writeContext.getParent();
+    }
+
+    protected final void _writeFieldName(String name)
+        throws IOException, JsonGenerationException
+    {
+        /* To support [JACKSON-46], we'll do this:
+         * (Question: should quoting of spaces (etc) still be enabled?)
+         */
+        if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
+            _writeStringSegments(name);
+            return;
+        }
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        // The beef:
+        final int len = name.length();
+        if (len <= _charBufferLength) { // yes, fits right in
+            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);
+            } else {
+                _writeStringSegments(_charBuffer, 0, len);
+            }
+        } else {
+            _writeStringSegments(name);
+        }
+
+        // and closing quotes; need room for one more char:
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    protected final void _writeFieldName(SerializableString name)
+        throws IOException, JsonGenerationException
+    {
+        if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
+            int len = name.appendQuotedUTF8(_outputBuffer, _outputTail); // different quoting (escaping)
+            if (len < 0) {
+                _writeBytes(name.asQuotedUTF8());
+            } else {
+                _outputTail += len;
+            }
+            return;
+        }
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        int len = name.appendQuotedUTF8(_outputBuffer, _outputTail);
+        if (len < 0) { // couldn't append, bit longer processing
+            _writeBytes(name.asQuotedUTF8());
+        } else {
+            _outputTail += len;
+        }
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }    
+    
+    /**
+     * Specialized version of <code>_writeFieldName</code>, off-lined
+     * to keep the "fast path" as simple (and hopefully fast) as possible.
+     */
+    protected final void _writePPFieldName(String name, boolean commaBefore)
+        throws IOException, JsonGenerationException
+    {
+        if (commaBefore) {
+            _cfgPrettyPrinter.writeObjectEntrySeparator(this);
+        } else {
+            _cfgPrettyPrinter.beforeObjectEntries(this);
+        }
+
+        if (isEnabled(Feature.QUOTE_FIELD_NAMES)) { // standard
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_QUOTE;
+            final int len = name.length();
+            if (len <= _charBufferLength) { // yes, fits right in
+                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);
+                } else {
+                    _writeStringSegments(_charBuffer, 0, len);
+                }
+            } else {
+                _writeStringSegments(name);
+            }
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        } else { // non-standard, omit quotes
+            _writeStringSegments(name);
+        }
+    }
+
+    protected final void _writePPFieldName(SerializableString name, boolean commaBefore)
+        throws IOException, JsonGenerationException
+    {
+        if (commaBefore) {
+            _cfgPrettyPrinter.writeObjectEntrySeparator(this);
+        } else {
+            _cfgPrettyPrinter.beforeObjectEntries(this);
+        }
+
+        boolean addQuotes = isEnabled(Feature.QUOTE_FIELD_NAMES); // standard
+        if (addQuotes) {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        }
+        _writeBytes(name.asQuotedUTF8());
+        if (addQuotes) {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Output method implementations, textual
+    /**********************************************************
+     */
+
+    @Override
+    public void writeString(String text)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (text == null) {
+            _writeNull();
+            return;
+        }
+        // First: can we make a local copy of chars that make up text?
+        final int len = text.length();
+        if (len > _charBufferLength) { // nope: off-line handling
+            _writeLongString(text);
+            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(String text)
+        throws IOException, JsonGenerationException
+    {
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        _writeStringSegments(text);
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    private void _writeLongString(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        _writeStringSegments(_charBuffer, 0, len);
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    @Override
+    public void writeString(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        // One or multiple segments?
+        if (len <= _outputMaxContiguous) {
+            if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
+                _flushBuffer();
+            }
+            _writeStringSegment(text, offset, len);
+        } else {
+            _writeStringSegments(text, offset, len);
+        }
+        // And finally, closing quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    @Override
+    public final void writeString(SerializableString text)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        int len = text.appendQuotedUTF8(_outputBuffer, _outputTail);
+        if (len < 0) {
+            _writeBytes(text.asQuotedUTF8());
+        } else {
+            _outputTail += len;
+        }
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    @Override
+    public void writeRawUTF8String(byte[] text, int offset, int length)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        _writeBytes(text, offset, length);
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    @Override
+    public void writeUTF8String(byte[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        // One or multiple segments?
+        if (len <= _outputMaxContiguous) {
+            _writeUTF8Segment(text, offset, len);
+        } else {
+            _writeUTF8Segments(text, offset, len);
+        }
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+    
+    /*
+    /**********************************************************
+    /* Output method implementations, unprocessed ("raw")
+    /**********************************************************
+     */
+
+    @Override
+    public void writeRaw(String text)
+        throws IOException, JsonGenerationException
+    {
+        int start = 0;
+        int len = text.length();
+        while (len > 0) {
+            char[] buf = _charBuffer;
+            final int blen = buf.length;
+            final int len2 = (len < blen) ? len : blen;
+            text.getChars(start, start+len2, buf, 0);
+            writeRaw(buf, 0, len2);
+            start += len2;
+            len -= len2;
+        }
+    }
+
+    @Override
+    public void writeRaw(String text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        while (len > 0) {
+            char[] buf = _charBuffer;
+            final int blen = buf.length;
+            final int len2 = (len < blen) ? len : blen;
+            text.getChars(offset, offset+len2, buf, 0);
+            writeRaw(buf, 0, len2);
+            offset += len2;
+            len -= len2;
+        }
+    }
+
+    @Override
+    public void writeRaw(SerializableString text) throws IOException, JsonGenerationException
+    {
+        byte[] raw = text.asUnquotedUTF8();
+        if (raw.length > 0) {
+            _writeBytes(raw);
+        }
+    }
+    
+    // @TODO: rewrite for speed...
+    @Override
+    public final void writeRaw(char[] cbuf, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        // First: if we have 3 x charCount spaces, we know it'll fit just fine
+        {
+            int len3 = len+len+len;
+            if ((_outputTail + len3) > _outputEnd) {
+                // maybe we could flush?
+                if (_outputEnd < len3) { // wouldn't be enough...
+                    _writeSegmentedRaw(cbuf, offset, len);
+                    return;
+                }
+                // yes, flushing brings enough space
+                _flushBuffer();
+            }
+        }
+        len += offset; // now marks the end
+
+        // Note: here we know there is enough room, hence no output boundary checks
+        main_loop:
+        while (offset < len) {
+            inner_loop:
+            while (true) {
+                int ch = (int) cbuf[offset];
+                if (ch > 0x7F) {
+                    break inner_loop;
+                }
+                _outputBuffer[_outputTail++] = (byte) ch;
+                if (++offset >= len) {
+                    break main_loop;
+                }
+            }
+            char ch = cbuf[offset++];
+            if (ch < 0x800) { // 2-byte?
+                _outputBuffer[_outputTail++] = (byte) (0xc0 | (ch >> 6));
+                _outputBuffer[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
+            } else {
+                _outputRawMultiByteChar(ch, cbuf, offset, len);
+            }
+        }
+    }
+
+    @Override
+    public void writeRaw(char ch)
+        throws IOException, JsonGenerationException
+    {
+        if ((_outputTail + 3) >= _outputEnd) {
+            _flushBuffer();
+        }
+        final byte[] bbuf = _outputBuffer;
+        if (ch <= 0x7F) {
+            bbuf[_outputTail++] = (byte) ch;
+        } else  if (ch < 0x800) { // 2-byte?
+            bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
+            bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
+        } else {
+            _outputRawMultiByteChar(ch, null, 0, 0);
+        }
+    }
+
+    /**
+     * Helper method called when it is possible that output of raw section
+     * to output may cross buffer boundary
+     */
+    private final void _writeSegmentedRaw(char[] cbuf, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        final int end = _outputEnd;
+        final byte[] bbuf = _outputBuffer;
+        
+        main_loop:
+        while (offset < len) {
+            inner_loop:
+            while (true) {
+                int ch = (int) cbuf[offset];
+                if (ch >= 0x80) {
+                    break inner_loop;
+                }
+                // !!! TODO: fast(er) writes (roll input, output checks in one)
+                if (_outputTail >= end) {
+                    _flushBuffer();
+                }
+                bbuf[_outputTail++] = (byte) ch;
+                if (++offset >= len) {
+                    break main_loop;
+                }
+            }
+            if ((_outputTail + 3) >= _outputEnd) {
+                _flushBuffer();
+            }
+            char ch = cbuf[offset++];
+            if (ch < 0x800) { // 2-byte?
+                bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
+                bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
+            } else {
+                _outputRawMultiByteChar(ch, cbuf, offset, len);
+            }
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Output method implementations, base64-encoded binary
+    /**********************************************************
+     */
+
+    @Override
+    public void writeBinary(Base64Variant b64variant,
+            byte[] data, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write binary value");
+        // Starting quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        _writeBinary(b64variant, data, offset, offset+len);
+        // and closing quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    @Override
+    public int writeBinary(Base64Variant b64variant,
+            InputStream data, int dataLength)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write binary value");
+        // Starting quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        byte[] encodingBuffer = _ioContext.allocBase64Buffer();
+        int bytes;
+        try {
+            if (dataLength < 0) { // length unknown
+                bytes = _writeBinary(b64variant, data, encodingBuffer);
+            } else {
+                int missing = _writeBinary(b64variant, data, encodingBuffer, dataLength);
+                if (missing > 0) {
+                    _reportError("Too few bytes available: missing "+missing+" bytes (out of "+dataLength+")");
+                }
+                bytes = dataLength;
+            }
+        } finally {
+            _ioContext.releaseBase64Buffer(encodingBuffer);
+        }
+        // and closing quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        return bytes;
+    }
+    
+    /*
+    /**********************************************************
+    /* Output method implementations, primitive
+    /**********************************************************
+     */
+
+    @Override
+    public void writeNumber(short s)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        // up to 5 digits and possible minus sign
+        if ((_outputTail + 6) >= _outputEnd) {
+            _flushBuffer();
+        }
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedShort(s);
+            return;
+        }
+        _outputTail = NumberOutput.outputInt(s, _outputBuffer, _outputTail);
+    }
+    
+    private void _writeQuotedShort(short s) throws IOException {
+        if ((_outputTail + 8) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        _outputTail = NumberOutput.outputInt(s, _outputBuffer, _outputTail);
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    } 
+    
+    @Override
+    public void writeNumber(int i)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        // up to 10 digits and possible minus sign
+        if ((_outputTail + 11) >= _outputEnd) {
+            _flushBuffer();
+        }
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedInt(i);
+            return;
+        }
+        _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
+    }
+
+    private void _writeQuotedInt(int i) throws IOException {
+        if ((_outputTail + 13) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }    
+
+    @Override
+    public void writeNumber(long l)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedLong(l);
+            return;
+        }
+        if ((_outputTail + 21) >= _outputEnd) {
+            // up to 20 digits, minus sign
+            _flushBuffer();
+        }
+        _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
+    }
+
+    private void _writeQuotedLong(long l) throws IOException {
+        if ((_outputTail + 23) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+
+    @Override
+    public void writeNumber(BigInteger value)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (value == null) {
+            _writeNull();
+        } else if (_cfgNumbersAsStrings) {
+            _writeQuotedRaw(value);
+        } else {
+            writeRaw(value.toString());
+        }
+    }
+
+    
+    @Override
+    public void writeNumber(double d)
+        throws IOException, JsonGenerationException
+    {
+        if (_cfgNumbersAsStrings ||
+            // [JACKSON-139]
+            (((Double.isNaN(d) || Double.isInfinite(d))
+                && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
+            writeString(String.valueOf(d));
+            return;
+        }
+        // What is the max length for doubles? 40 chars?
+        _verifyValueWrite("write number");
+        writeRaw(String.valueOf(d));
+    }
+
+    @Override
+    public void writeNumber(float f)
+        throws IOException, JsonGenerationException
+    {
+        if (_cfgNumbersAsStrings ||
+            // [JACKSON-139]
+            (((Float.isNaN(f) || Float.isInfinite(f))
+                && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
+            writeString(String.valueOf(f));
+            return;
+        }
+        // What is the max length for floats?
+        _verifyValueWrite("write number");
+        writeRaw(String.valueOf(f));
+    }
+
+    @Override
+    public void writeNumber(BigDecimal value)
+        throws IOException, JsonGenerationException
+    {
+        // Don't really know max length for big decimal, no point checking
+        _verifyValueWrite("write number");
+        if (value == null) {
+            _writeNull();
+        } else if (_cfgNumbersAsStrings) {
+            _writeQuotedRaw(value);
+        } else {
+            writeRaw(value.toString());
+        }
+    }
+
+    @Override
+    public void writeNumber(String encodedValue)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedRaw(encodedValue);            
+        } else {
+            writeRaw(encodedValue);
+        }
+    }
+
+    private void _writeQuotedRaw(Object value) throws IOException
+    {
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+        writeRaw(value.toString());
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = BYTE_QUOTE;
+    }
+    
+    @Override
+    public void writeBoolean(boolean state)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write boolean value");
+        if ((_outputTail + 5) >= _outputEnd) {
+            _flushBuffer();
+        }
+        byte[] keyword = state ? TRUE_BYTES : FALSE_BYTES;
+        int len = keyword.length;
+        System.arraycopy(keyword, 0, _outputBuffer, _outputTail, len);
+        _outputTail += len;
+    }
+
+    @Override
+    public void writeNull()
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write null value");
+        _writeNull();
+    }
+
+    /*
+    /**********************************************************
+    /* Implementations for other methods
+    /**********************************************************
+     */
+
+    @Override
+    protected final void _verifyValueWrite(String typeMsg)
+        throws IOException, JsonGenerationException
+    {
+        int status = _writeContext.writeValue();
+        if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
+            _reportError("Can not "+typeMsg+", expecting field name");
+        }
+        if (_cfgPrettyPrinter == null) {
+            byte b;
+            switch (status) {
+            case JsonWriteContext.STATUS_OK_AFTER_COMMA:
+                b = BYTE_COMMA;
+                break;
+            case JsonWriteContext.STATUS_OK_AFTER_COLON:
+                b = BYTE_COLON;
+                break;
+            case JsonWriteContext.STATUS_OK_AFTER_SPACE: // root-value separator
+                if (_rootValueSeparator != null) {
+                    byte[] raw = _rootValueSeparator.asUnquotedUTF8();
+                    if (raw.length > 0) {
+                        _writeBytes(raw);
+                    }
+                }
+                return;
+            case JsonWriteContext.STATUS_OK_AS_IS:
+            default:
+                return;
+            }
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail] = b;
+            ++_outputTail;
+            return;
+        }
+        // Otherwise, pretty printer knows what to do...
+        _verifyPrettyValueWrite(typeMsg, status);
+    }
+
+    protected final void _verifyPrettyValueWrite(String typeMsg, int status)
+        throws IOException, JsonGenerationException
+    {
+        // If we have a pretty printer, it knows what to do:
+        switch (status) {
+        case JsonWriteContext.STATUS_OK_AFTER_COMMA: // array
+            _cfgPrettyPrinter.writeArrayValueSeparator(this);
+            break;
+        case JsonWriteContext.STATUS_OK_AFTER_COLON:
+            _cfgPrettyPrinter.writeObjectFieldValueSeparator(this);
+            break;
+        case JsonWriteContext.STATUS_OK_AFTER_SPACE:
+            _cfgPrettyPrinter.writeRootValueSeparator(this);
+            break;
+        case JsonWriteContext.STATUS_OK_AS_IS:
+            // First entry, but of which context?
+            if (_writeContext.inArray()) {
+                _cfgPrettyPrinter.beforeArrayValues(this);
+            } else if (_writeContext.inObject()) {
+                _cfgPrettyPrinter.beforeObjectEntries(this);
+            }
+            break;
+        default:
+            _throwInternal();
+            break;
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Low-level output handling
+    /**********************************************************
+     */
+
+    @Override
+    public final void flush()
+        throws IOException
+    {
+        _flushBuffer();
+        if (_outputStream != null) {
+            if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
+                _outputStream.flush();
+            }
+        }
+    }
+
+    @Override
+    public void close()
+        throws IOException
+    {
+        super.close();
+
+        /* 05-Dec-2008, tatu: To add [JACKSON-27], need to close open
+         *   scopes.
+         */
+        // First: let's see that we still have buffers...
+        if (_outputBuffer != null
+            && isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
+            while (true) {
+                JsonStreamContext ctxt = getOutputContext();
+                if (ctxt.inArray()) {
+                    writeEndArray();
+                } else if (ctxt.inObject()) {
+                    writeEndObject();
+                } else {
+                    break;
+                }
+            }
+        }
+        _flushBuffer();
+
+        /* 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
+         *   feature is enabled.
+         *   One downside: when using UTF8Writer, underlying buffer(s)
+         *   may not be properly recycled if we don't close the writer.
+         */
+        if (_outputStream != null) {
+            if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
+                _outputStream.close();
+            } else  if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
+                // If we can't close it, we should at least flush
+                _outputStream.flush();
+            }
+        }
+        // Internal buffer(s) generator has can now be released as well
+        _releaseBuffers();
+    }
+
+    @Override
+    protected void _releaseBuffers()
+    {
+        byte[] buf = _outputBuffer;
+        if (buf != null && _bufferRecyclable) {
+            _outputBuffer = null;
+            _ioContext.releaseWriteEncodingBuffer(buf);
+        }
+        char[] cbuf = _charBuffer;
+        if (cbuf != null) {
+            _charBuffer = null;
+            _ioContext.releaseConcatBuffer(cbuf);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, raw bytes
+    /**********************************************************
+     */
+
+    private final void _writeBytes(byte[] bytes) throws IOException
+    {
+        final int len = bytes.length;
+        if ((_outputTail + len) > _outputEnd) {
+            _flushBuffer();
+            // still not enough?
+            if (len > MAX_BYTES_TO_BUFFER) {
+                _outputStream.write(bytes, 0, len);
+                return;
+            }
+        }
+        System.arraycopy(bytes, 0, _outputBuffer, _outputTail, len);
+        _outputTail += len;
+    }
+
+    private final void _writeBytes(byte[] bytes, int offset, int len) throws IOException
+    {
+        if ((_outputTail + len) > _outputEnd) {
+            _flushBuffer();
+            // still not enough?
+            if (len > MAX_BYTES_TO_BUFFER) {
+                _outputStream.write(bytes, offset, len);
+                return;
+            }
+        }
+        System.arraycopy(bytes, offset, _outputBuffer, _outputTail, len);
+        _outputTail += len;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, mid-level writing, String segments
+    /**********************************************************
+     */
+    
+    /**
+     * Method called when String to write is long enough not to fit
+     * completely in temporary copy buffer. If so, we will actually
+     * copy it in small enough chunks so it can be directly fed
+     * to single-segment writes (instead of maximum slices that
+     * would fit in copy buffer)
+     */
+    private final void _writeStringSegments(String text)
+        throws IOException, JsonGenerationException
+    {
+        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);
+            offset += len;
+            left -= len;
+        }
+    }
+
+    /**
+     * Method called when character sequence to write is long enough that
+     * its maximum encoded and escaped form is not guaranteed to fit in
+     * 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
+    {
+        do {
+            int len = Math.min(_outputMaxContiguous, totalLen);
+            if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
+                _flushBuffer();
+            }
+            _writeStringSegment(cbuf, offset, len);
+            offset += len;
+            totalLen -= len;
+        } while (totalLen > 0);
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, text segments
+    /**********************************************************
+     */
+
+    /**
+     * This method called when the string content is already in
+     * a char buffer, and its maximum total encoded and escaped length
+     * can not exceed size of the output buffer.
+     * Caller must ensure that there is enough space in output buffer,
+     * assuming case of all non-escaped ASCII characters, as well as
+     * potentially enough space for other cases (but not necessarily flushed)
+     */
+    private final void _writeStringSegment(char[] cbuf, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        // 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 = cbuf[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) {
+            // [JACKSON-106]
+            if (_characterEscapes != null) {
+                _writeCustomStringSegment2(cbuf, offset, len);
+            // [JACKSON-102]
+            } else if (_maximumNonEscapedChar == 0) {
+                _writeStringSegment2(cbuf, offset, len);
+            } else {
+                _writeStringSegmentASCII2(cbuf, 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
+    {
+        // 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;
+        
+        while (offset < end) {
+            int ch = cbuf[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
+    /* with additional escaping (ASCII or such)
+    /**********************************************************
+     */
+
+    /**
+     * 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
+    {
+        // 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 = cbuf[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
+    /* with fully custom escaping (and possibly escaping of non-ASCII
+    /**********************************************************
+     */
+
+    /**
+     * Same as <code>_writeStringSegmentASCII2(char[], ...)</code., but with
+     * additional checking for completely custom escapes
+     */
+    private void _writeCustomStringSegment2(final char[] cbuf, int offset, final int end)
+        throws IOException, JsonGenerationException
+    {
+        // 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 = cbuf[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 int _writeCustomEscape(byte[] outputBuffer, int outputPtr, SerializableString esc, int remainingChars)
+        throws IOException, JsonGenerationException
+    {
+        byte[] raw = esc.asUnquotedUTF8(); // must be escaped at this point, shouldn't double-quote
+        int len = raw.length;
+        if (len > 6) { // may violate constraints we have, do offline
+            return _handleLongCustomEscape(outputBuffer, outputPtr, _outputEnd, raw, remainingChars);
+        }
+        // otherwise will fit without issues, so:
+        System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
+        return (outputPtr + len);
+    }
+    
+    private int _handleLongCustomEscape(byte[] outputBuffer, int outputPtr, int outputEnd, byte[] raw,
+            int remainingChars)
+        throws IOException, JsonGenerationException
+    {
+        int len = raw.length;
+        if ((outputPtr + len) > outputEnd) {
+            _outputTail = outputPtr;
+            _flushBuffer();
+            outputPtr = _outputTail;
+            if (len > outputBuffer.length) { // very unlikely, but possible...
+                _outputStream.write(raw, 0, len);
+                return outputPtr;
+            }
+            System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
+            outputPtr += len;
+        }
+        // but is the invariant still obeyed? If not, flush once more
+        if ((outputPtr +  6 * remainingChars) > outputEnd) {
+            _flushBuffer();
+            return _outputTail;
+        }
+        return outputPtr;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, "raw UTF-8" segments
+    /**********************************************************
+     */
+    
+    /**
+     * Method called when UTF-8 encoded (but NOT yet escaped!) content is not guaranteed
+     * to fit in the output buffer after escaping; as such, we just need to
+     * chunk writes.
+     */
+    private void _writeUTF8Segments(byte[] utf8, int offset, int totalLen)
+        throws IOException, JsonGenerationException
+    {
+        do {
+            int len = Math.min(_outputMaxContiguous, totalLen);
+            _writeUTF8Segment(utf8, offset, len);
+            offset += len;
+            totalLen -= len;
+        } while (totalLen > 0);
+    }
+    
+    private void _writeUTF8Segment(byte[] utf8, final int offset, final int len)
+        throws IOException, JsonGenerationException
+    {
+        // fast loop to see if escaping is needed; don't copy, just look
+        final int[] escCodes = _outputEscapes;
+
+        for (int ptr = offset, end = offset + len; ptr < end; ) {
+            // 28-Feb-2011, tatu: escape codes just cover 7-bit range, so:
+            int ch = utf8[ptr++];
+            if ((ch >= 0) && escCodes[ch] != 0) {
+                _writeUTF8Segment2(utf8, offset, len);
+                return;
+            }
+        }
+        
+        // yes, fine, just copy the sucker
+        if ((_outputTail + len) > _outputEnd) { // enough room or need to flush?
+            _flushBuffer(); // but yes once we flush (caller guarantees length restriction)
+        }
+        System.arraycopy(utf8, offset, _outputBuffer, _outputTail, len);
+        _outputTail += len;
+    }
+
+    private void _writeUTF8Segment2(final byte[] utf8, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        int outputPtr = _outputTail;
+
+        // Ok: caller guarantees buffer can have room; but that may require flushing:
+        if ((outputPtr + (len * 6)) > _outputEnd) {
+            _flushBuffer();
+            outputPtr = _outputTail;
+        }
+        
+        final byte[] outputBuffer = _outputBuffer;
+        final int[] escCodes = _outputEscapes;
+        len += offset; // so 'len' becomes 'end'
+        
+        while (offset < len) {
+            byte b = utf8[offset++];
+            int ch = b;
+            if (ch < 0 || escCodes[ch] == 0) {
+                outputBuffer[outputPtr++] = b;
+                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);
+            }
+        }
+        _outputTail = outputPtr;
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, base64 encoded
+    /**********************************************************
+     */
+    
+    protected void _writeBinary(Base64Variant b64variant,
+            byte[] input, int inputPtr, final int inputEnd)
+        throws IOException, JsonGenerationException
+    {
+        // Encoding is by chunks of 3 input, 4 output chars, so:
+        int safeInputEnd = inputEnd - 3;
+        // Let's also reserve room for possible (and quoted) lf char each round
+        int safeOutputEnd = _outputEnd - 6;
+        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+
+        // Ok, first we loop through all full triplets of data:
+        while (inputPtr <= safeInputEnd) {
+            if (_outputTail > safeOutputEnd) { // need to flush
+                _flushBuffer();
+            }
+            // First, mash 3 bytes into lsb of 32-bit int
+            int b24 = ((int) input[inputPtr++]) << 8;
+            b24 |= ((int) input[inputPtr++]) & 0xFF;
+            b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF);
+            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
+            if (--chunksBeforeLF <= 0) {
+                // note: must quote in JSON value
+                _outputBuffer[_outputTail++] = '\\';
+                _outputBuffer[_outputTail++] = 'n';
+                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+            }
+        }
+
+        // And then we may have 1 or 2 leftover bytes to encode
+        int inputLeft = inputEnd - inputPtr; // 0, 1 or 2
+        if (inputLeft > 0) { // yes, but do we have room for output?
+            if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
+                _flushBuffer();
+            }
+            int b24 = ((int) input[inputPtr++]) << 16;
+            if (inputLeft == 2) {
+                b24 |= (((int) input[inputPtr++]) & 0xFF) << 8;
+            }
+            _outputTail = b64variant.encodeBase64Partial(b24, inputLeft, _outputBuffer, _outputTail);
+        }
+    }
+
+    // write-method called when length is definitely known
+    protected int _writeBinary(Base64Variant b64variant,
+            InputStream data, byte[] readBuffer, int bytesLeft)
+        throws IOException, JsonGenerationException
+    {
+        int inputPtr = 0;
+        int inputEnd = 0;
+        int lastFullOffset = -3;       
+        
+        // Let's also reserve room for possible (and quoted) LF char each round
+        int safeOutputEnd = _outputEnd - 6;
+        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+
+        while (bytesLeft > 2) { // main loop for full triplets
+            if (inputPtr > lastFullOffset) {
+                inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
+                inputPtr = 0;
+                if (inputEnd < 3) { // required to try to read to have at least 3 bytes
+                    break;
+                }
+                lastFullOffset = inputEnd-3;
+            }
+            if (_outputTail > safeOutputEnd) { // need to flush
+                _flushBuffer();
+            }
+            int b24 = ((int) readBuffer[inputPtr++]) << 8;
+            b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
+            b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
+            bytesLeft -= 3;
+            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
+            if (--chunksBeforeLF <= 0) {
+                _outputBuffer[_outputTail++] = '\\';
+                _outputBuffer[_outputTail++] = 'n';
+                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+            }
+        }
+        
+        // And then we may have 1 or 2 leftover bytes to encode
+        if (bytesLeft > 0) {
+            inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
+            inputPtr = 0;
+            if (inputEnd > 0) { // yes, but do we have room for output?
+                if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
+                    _flushBuffer();
+                }
+                int b24 = ((int) readBuffer[inputPtr++]) << 16;
+                int amount;
+                if (inputPtr < inputEnd) {
+                    b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
+                    amount = 2;
+                } else {
+                    amount = 1;
+                }
+                _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
+                bytesLeft -= amount;
+            }
+        }
+        return bytesLeft;
+    }
+
+    // write method when length is unknown
+    protected int _writeBinary(Base64Variant b64variant,
+            InputStream data, byte[] readBuffer)
+        throws IOException, JsonGenerationException
+    {
+        int inputPtr = 0;
+        int inputEnd = 0;
+        int lastFullOffset = -3;
+        int bytesDone = 0;
+        
+        // Let's also reserve room for possible (and quoted) LF char each round
+        int safeOutputEnd = _outputEnd - 6;
+        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+
+        // Ok, first we loop through all full triplets of data:
+        while (true) {
+            if (inputPtr > lastFullOffset) { // need to load more
+                inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, readBuffer.length);
+                inputPtr = 0;
+                if (inputEnd < 3) { // required to try to read to have at least 3 bytes
+                    break;
+                }
+                lastFullOffset = inputEnd-3;
+            }
+            if (_outputTail > safeOutputEnd) { // need to flush
+                _flushBuffer();
+            }
+            // First, mash 3 bytes into lsb of 32-bit int
+            int b24 = ((int) readBuffer[inputPtr++]) << 8;
+            b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
+            b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
+            bytesDone += 3;
+            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
+            if (--chunksBeforeLF <= 0) {
+                _outputBuffer[_outputTail++] = '\\';
+                _outputBuffer[_outputTail++] = 'n';
+                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+            }
+        }
+
+        // And then we may have 1 or 2 leftover bytes to encode
+        if (inputPtr < inputEnd) { // yes, but do we have room for output?
+            if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
+                _flushBuffer();
+            }
+            int b24 = ((int) readBuffer[inputPtr++]) << 16;
+            int amount = 1;
+            if (inputPtr < inputEnd) {
+                b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
+                amount = 2;
+            }
+            bytesDone += amount;
+            _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
+        }
+        return bytesDone;
+    }
+    
+    private int _readMore(InputStream in,
+            byte[] readBuffer, int inputPtr, int inputEnd,
+            int maxRead) throws IOException
+    {
+        // anything to shift to front?
+        int i = 0;
+        while (inputPtr < inputEnd) {
+            readBuffer[i++]  = readBuffer[inputPtr++];
+        }
+        inputPtr = 0;
+        inputEnd = i;
+        maxRead = Math.min(maxRead, readBuffer.length);
+        
+        do {
+            int length = maxRead - inputEnd;
+            if (length == 0) {
+                break;
+            }
+            int count = in.read(readBuffer, inputEnd, length);            
+            if (count < 0) {
+                return inputEnd;
+            }
+            inputEnd += count;
+        } while (inputEnd < 3);
+        return inputEnd;
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods, character escapes/encoding
+    /**********************************************************
+     */
+    
+    /**
+     * Method called to output a character that is beyond range of
+     * 1- and 2-byte UTF-8 encodings, when outputting "raw" 
+     * text (meaning it is not to be escaped or quoted)
+     */
+    private int _outputRawMultiByteChar(int ch, char[] cbuf, int inputOffset, int inputLen)
+        throws IOException
+    {
+        // Let's handle surrogates gracefully (as 4 byte output):
+        if (ch >= SURR1_FIRST) {
+            if (ch <= SURR2_LAST) { // yes, outside of BMP
+                // Do we have second part?
+                if (inputOffset >= inputLen) { // nope... have to note down
+                    _reportError("Split surrogate on writeRaw() input (last character)");
+                }
+                _outputSurrogates(ch, cbuf[inputOffset]);
+                return (inputOffset+1);
+            }
+        }
+        final byte[] bbuf = _outputBuffer;
+        bbuf[_outputTail++] = (byte) (0xe0 | (ch >> 12));
+        bbuf[_outputTail++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
+        bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
+        return inputOffset;
+    }
+
+    protected final void _outputSurrogates(int surr1, int surr2)
+        throws IOException
+    {
+        int c = _decodeSurrogate(surr1, surr2);
+        if ((_outputTail + 4) > _outputEnd) {
+            _flushBuffer();
+        }
+        final byte[] bbuf = _outputBuffer;
+        bbuf[_outputTail++] = (byte) (0xf0 | (c >> 18));
+        bbuf[_outputTail++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+        bbuf[_outputTail++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+        bbuf[_outputTail++] = (byte) (0x80 | (c & 0x3f));
+    }
+    
+    /**
+     * 
+     * @param ch
+     * @param outputPtr Position within output buffer to append multi-byte in
+     * 
+     * @return New output position after appending
+     * 
+     * @throws IOException
+     */
+    private int _outputMultiByteChar(int ch, int outputPtr)
+        throws IOException
+    {
+        byte[] bbuf = _outputBuffer;
+        if (ch >= SURR1_FIRST && ch <= SURR2_LAST) { // yes, outside of BMP; add an escape
+            bbuf[outputPtr++] = BYTE_BACKSLASH;
+            bbuf[outputPtr++] = BYTE_u;
+            
+            bbuf[outputPtr++] = HEX_CHARS[(ch >> 12) & 0xF];
+            bbuf[outputPtr++] = HEX_CHARS[(ch >> 8) & 0xF];
+            bbuf[outputPtr++] = HEX_CHARS[(ch >> 4) & 0xF];
+            bbuf[outputPtr++] = HEX_CHARS[ch & 0xF];
+        } else {
+            bbuf[outputPtr++] = (byte) (0xe0 | (ch >> 12));
+            bbuf[outputPtr++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
+            bbuf[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
+        }
+        return outputPtr;
+    }
+
+    protected final int _decodeSurrogate(int surr1, int surr2) throws IOException
+    {
+        // First is known to be valid, but how about the other?
+        if (surr2 < SURR2_FIRST || surr2 > SURR2_LAST) {
+            String msg = "Incomplete surrogate pair: first char 0x"+Integer.toHexString(surr1)+", second 0x"+Integer.toHexString(surr2);
+            _reportError(msg);
+        }
+        int c = 0x10000 + ((surr1 - SURR1_FIRST) << 10) + (surr2 - SURR2_FIRST);
+        return c;
+    }
+    
+    private void _writeNull() throws IOException
+    {
+        if ((_outputTail + 4) >= _outputEnd) {
+            _flushBuffer();
+        }
+        System.arraycopy(NULL_BYTES, 0, _outputBuffer, _outputTail, 4);
+        _outputTail += 4;
+    }
+        
+    /**
+     * Method called to write a generic Unicode escape for given character.
+     * 
+     * @param charToEscape Character to escape using escape sequence (\\uXXXX)
+     */
+    private int _writeGenericEscape(int charToEscape, int outputPtr)
+        throws IOException
+    {
+        final byte[] bbuf = _outputBuffer;
+        bbuf[outputPtr++] = BYTE_BACKSLASH;
+        bbuf[outputPtr++] = BYTE_u;
+        if (charToEscape > 0xFF) {
+            int hi = (charToEscape >> 8) & 0xFF;
+            bbuf[outputPtr++] = HEX_CHARS[hi >> 4];
+            bbuf[outputPtr++] = HEX_CHARS[hi & 0xF];
+            charToEscape &= 0xFF;
+        } else {
+            bbuf[outputPtr++] = BYTE_0;
+            bbuf[outputPtr++] = BYTE_0;
+        }
+        // We know it's a control char, so only the last 2 chars are non-0
+        bbuf[outputPtr++] = HEX_CHARS[charToEscape >> 4];
+        bbuf[outputPtr++] = HEX_CHARS[charToEscape & 0xF];
+        return outputPtr;
+    }
+
+    protected final void _flushBuffer() throws IOException
+    {
+        int len = _outputTail;
+        if (len > 0) {
+            _outputTail = 0;
+            _outputStream.write(_outputBuffer, 0, len);
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
new file mode 100644
index 0000000..c46e325
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
@@ -0,0 +1,3143 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+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.util.*;
+
+/**
+ * This is a concrete implementation of {@link JsonParser}, which is
+ * based on a {@link java.io.InputStream} as the input source.
+ */
+public final class UTF8StreamJsonParser
+    extends ParserBase
+{
+    final static byte BYTE_LF = (byte) '\n';
+
+    private final static int[] sInputCodesUtf8 = CharTypes.getInputCodeUtf8();
+
+    /**
+     * Latin1 encoding is not supported, but we do use 8-bit subset for
+     * pre-processing task, to simplify first pass, keep it fast.
+     */
+    private final static int[] sInputCodesLatin1 = CharTypes.getInputCodeLatin1();
+    
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    /**
+     * Codec used for data binding when (if) requested; typically full
+     * <code>ObjectMapper</code>, but that abstract is not part of core
+     * package.
+     */
+    protected ObjectCodec _objectCodec;
+
+    /**
+     * Symbol table that contains field names encountered so far
+     */
+    final protected BytesToNameCanonicalizer _symbols;
+    
+    /*
+    /**********************************************************
+    /* Parsing state
+    /**********************************************************
+     */
+    
+    /**
+     * Temporary buffer used for name parsing.
+     */
+    protected int[] _quadBuffer = new int[16];
+
+    /**
+     * Flag that indicates that the current token has not yet
+     * been fully processed, and needs to be finished for
+     * some access (or skipped to obtain the next token)
+     */
+    protected boolean _tokenIncomplete = false;
+
+    /**
+     * Temporary storage for partially parsed name bytes.
+     */
+    private int _quad1;
+    
+    /*
+    /**********************************************************
+    /* Input buffering (from former 'StreamBasedParserBase')
+    /**********************************************************
+     */
+    
+    protected InputStream _inputStream;
+
+    /*
+    /**********************************************************
+    /* Current input data
+    /**********************************************************
+     */
+
+    /**
+     * Current buffer from which data is read; generally data is read into
+     * buffer from input source, but in some cases pre-loaded buffer
+     * is handed to the parser.
+     */
+    protected byte[] _inputBuffer;
+
+    /**
+     * Flag that indicates whether the input buffer is recycable (and
+     * needs to be returned to recycler once we are done) or not.
+     *<p>
+     * If it is not, it also means that parser can NOT modify underlying
+     * buffer.
+     */
+    protected boolean _bufferRecyclable;
+    
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public UTF8StreamJsonParser(IOContext ctxt, int features, InputStream in,
+            ObjectCodec codec, BytesToNameCanonicalizer sym,
+            byte[] inputBuffer, int start, int end,
+            boolean bufferRecyclable)
+    {
+        super(ctxt, features);
+        _inputStream = in;
+        _objectCodec = codec;
+        _symbols = sym;
+        _inputBuffer = inputBuffer;
+        _inputPtr = start;
+        _inputEnd = end;
+        _bufferRecyclable = bufferRecyclable;
+    }
+
+    @Override
+    public ObjectCodec getCodec() {
+        return _objectCodec;
+    }
+
+    @Override
+    public void setCodec(ObjectCodec c) {
+        _objectCodec = c;
+    }
+    
+    /*
+    /**********************************************************
+    /* Overrides
+    /**********************************************************
+     */
+
+    @Override
+    public int releaseBuffered(OutputStream out) throws IOException
+    {
+        int count = _inputEnd - _inputPtr;
+        if (count < 1) {
+            return 0;
+        }
+        // let's just advance ptr to end
+        int origPtr = _inputPtr;
+        out.write(_inputBuffer, origPtr, count);
+        return count;
+    }
+
+    @Override
+    public Object getInputSource() {
+        return _inputStream;
+    }
+    
+    /*
+    /**********************************************************
+    /* Low-level reading, other
+    /**********************************************************
+     */
+
+    @Override
+    protected boolean loadMore()
+        throws IOException
+    {
+        _currInputProcessed += _inputEnd;
+        _currInputRowStart -= _inputEnd;
+        
+        if (_inputStream != null) {
+            int count = _inputStream.read(_inputBuffer, 0, _inputBuffer.length);
+            if (count > 0) {
+                _inputPtr = 0;
+                _inputEnd = count;
+                return true;
+            }
+            // End of input
+            _closeInput();
+            // Should never return 0, so let's fail
+            if (count == 0) {
+                throw new IOException("InputStream.read() returned 0 characters when trying to read "+_inputBuffer.length+" bytes");
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Helper method that will try to load at least specified number bytes in
+     * input buffer, possible moving existing data around if necessary
+     */
+    protected boolean _loadToHaveAtLeast(int minAvailable)
+        throws IOException
+    {
+        // No input stream, no leading (either we are closed, or have non-stream input source)
+        if (_inputStream == null) {
+            return false;
+        }
+        // Need to move remaining data in front?
+        int amount = _inputEnd - _inputPtr;
+        if (amount > 0 && _inputPtr > 0) {
+            _currInputProcessed += _inputPtr;
+            _currInputRowStart -= _inputPtr;
+            System.arraycopy(_inputBuffer, _inputPtr, _inputBuffer, 0, amount);
+            _inputEnd = amount;
+        } else {
+            _inputEnd = 0;
+        }
+        _inputPtr = 0;
+        while (_inputEnd < minAvailable) {
+            int count = _inputStream.read(_inputBuffer, _inputEnd, _inputBuffer.length - _inputEnd);
+            if (count < 1) {
+                // End of input
+                _closeInput();
+                // Should never return 0, so let's fail
+                if (count == 0) {
+                    throw new IOException("InputStream.read() returned 0 characters when trying to read "+amount+" bytes");
+                }
+                return false;
+            }
+            _inputEnd += count;
+        }
+        return true;
+    }
+    
+    @Override
+    protected void _closeInput() throws IOException
+    {
+        /* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
+         *   on the underlying InputStream, unless we "own" it, or auto-closing
+         *   feature is enabled.
+         */
+        if (_inputStream != null) {
+            if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_SOURCE)) {
+                _inputStream.close();
+            }
+            _inputStream = null;
+        }
+    }
+
+    /**
+     * Method called to release internal buffers owned by the base
+     * reader. This may be called along with {@link #_closeInput} (for
+     * example, when explicitly closing this reader instance), or
+     * separately (if need be).
+     */
+    @Override
+    protected void _releaseBuffers() throws IOException
+    {
+        super._releaseBuffers();
+        if (_bufferRecyclable) {
+            byte[] buf = _inputBuffer;
+            if (buf != null) {
+                _inputBuffer = null;
+                _ioContext.releaseReadIOBuffer(buf);
+            }
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, data access
+    /**********************************************************
+     */
+
+    @Override
+    public String getText()
+        throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.VALUE_STRING) {
+            if (_tokenIncomplete) {
+                _tokenIncomplete = false;
+                _finishString(); // only strings can be incomplete
+            }
+            return _textBuffer.contentsAsString();
+        }
+        return _getText2(_currToken);
+    }
+
+    // // // Let's override default impls for improved performance
+    
+    // @since 2.1
+    @Override
+    public String getValueAsString() throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.VALUE_STRING) {
+            if (_tokenIncomplete) {
+                _tokenIncomplete = false;
+                _finishString(); // only strings can be incomplete
+            }
+            return _textBuffer.contentsAsString();
+        }
+        return super.getValueAsString(null);
+    }
+    
+    // @since 2.1
+    @Override
+    public String getValueAsString(String defValue) throws IOException, JsonParseException
+    {
+        if (_currToken == JsonToken.VALUE_STRING) {
+            if (_tokenIncomplete) {
+                _tokenIncomplete = false;
+                _finishString(); // only strings can be incomplete
+            }
+            return _textBuffer.contentsAsString();
+        }
+        return super.getValueAsString(defValue);
+    }
+    
+    protected String _getText2(JsonToken t)
+    {
+        if (t == null) {
+            return null;
+        }
+        switch (t) {
+        case FIELD_NAME:
+            return _parsingContext.getCurrentName();
+
+        case VALUE_STRING:
+            // fall through
+        case VALUE_NUMBER_INT:
+        case VALUE_NUMBER_FLOAT:
+            return _textBuffer.contentsAsString();
+        default:
+        	return t.asString();
+        }
+    }
+
+    @Override
+    public char[] getTextCharacters()
+        throws IOException, JsonParseException
+    {
+        if (_currToken != null) { // null only before/after document
+            switch (_currToken) {
+                
+            case FIELD_NAME:
+                if (!_nameCopied) {
+                    String name = _parsingContext.getCurrentName();
+                    int nameLen = name.length();
+                    if (_nameCopyBuffer == null) {
+                        _nameCopyBuffer = _ioContext.allocNameCopyBuffer(nameLen);
+                    } else if (_nameCopyBuffer.length < nameLen) {
+                        _nameCopyBuffer = new char[nameLen];
+                    }
+                    name.getChars(0, nameLen, _nameCopyBuffer, 0);
+                    _nameCopied = true;
+                }
+                return _nameCopyBuffer;
+    
+            case VALUE_STRING:
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString(); // only strings can be incomplete
+                }
+                // fall through
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return _textBuffer.getTextBuffer();
+                
+            default:
+                return _currToken.asCharArray();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public int getTextLength()
+        throws IOException, JsonParseException
+    {
+        if (_currToken != null) { // null only before/after document
+            switch (_currToken) {
+                
+            case FIELD_NAME:
+                return _parsingContext.getCurrentName().length();
+            case VALUE_STRING:
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString(); // only strings can be incomplete
+                }
+                // fall through
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return _textBuffer.size();
+                
+            default:
+                return _currToken.asCharArray().length;
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public int getTextOffset() throws IOException, JsonParseException
+    {
+        // Most have offset of 0, only some may have other values:
+        if (_currToken != null) {
+            switch (_currToken) {
+            case FIELD_NAME:
+                return 0;
+            case VALUE_STRING:
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString(); // only strings can be incomplete
+                }
+                // fall through
+            case VALUE_NUMBER_INT:
+            case VALUE_NUMBER_FLOAT:
+                return _textBuffer.getTextOffset();
+            default:
+            }
+        }
+        return 0;
+    }
+    
+    @Override
+    public byte[] getBinaryValue(Base64Variant b64variant)
+        throws IOException, JsonParseException
+    {
+        if (_currToken != JsonToken.VALUE_STRING &&
+                (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT || _binaryValue == null)) {
+            _reportError("Current token ("+_currToken+") not VALUE_STRING or VALUE_EMBEDDED_OBJECT, can not access as binary");
+        }
+        /* To ensure that we won't see inconsistent data, better clear up
+         * state...
+         */
+        if (_tokenIncomplete) {
+            try {
+                _binaryValue = _decodeBase64(b64variant);
+            } catch (IllegalArgumentException iae) {
+                throw _constructError("Failed to decode VALUE_STRING as base64 ("+b64variant+"): "+iae.getMessage());
+            }
+            /* let's clear incomplete only now; allows for accessing other
+             * textual content in error cases
+             */
+            _tokenIncomplete = false;
+        } else { // may actually require conversion...
+            if (_binaryValue == null) {
+                ByteArrayBuilder builder = _getByteArrayBuilder();
+                _decodeBase64(getText(), builder, b64variant);
+                _binaryValue = builder.toByteArray();
+            }
+        }
+        return _binaryValue;
+    }
+
+    @Override
+    public int readBinaryValue(Base64Variant b64variant, OutputStream out)
+        throws IOException, JsonParseException
+    {
+        // if we have already read the token, just use whatever we may have
+        if (!_tokenIncomplete || _currToken != JsonToken.VALUE_STRING) {
+            byte[] b = getBinaryValue(b64variant);
+            out.write(b);
+            return b.length;
+        }
+        // otherwise do "real" incremental parsing...
+        byte[] buf = _ioContext.allocBase64Buffer();
+        try {
+            return _readBinary(b64variant, out, buf);
+        } finally {
+            _ioContext.releaseBase64Buffer(buf);
+        }
+    }
+
+    protected int _readBinary(Base64Variant b64variant, OutputStream out,
+                              byte[] buffer)
+        throws IOException, JsonParseException
+    {
+        int outputPtr = 0;
+        final int outputEnd = buffer.length - 3;
+        int outputCount = 0;
+
+        while (true) {
+            // first, we'll skip preceding white space, if any
+            int ch;
+            do {
+                if (_inputPtr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                }
+                ch = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            } while (ch <= INT_SPACE);
+            int bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) { // reached the end, fair and square?
+                if (ch == INT_QUOTE) {
+                    break;
+                }
+                bits = _decodeBase64Escape(b64variant, ch, 0);
+                if (bits < 0) { // white space to skip
+                    continue;
+                }
+            }
+
+            // enough room? If not, flush
+            if (outputPtr > outputEnd) {
+                outputCount += outputPtr;
+                out.write(buffer, 0, outputPtr);
+                outputPtr = 0;
+            }
+
+            int decodedData = bits;
+
+            // then second base64 char; can't get padding yet, nor ws
+
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                bits = _decodeBase64Escape(b64variant, ch, 1);
+            }
+            decodedData = (decodedData << 6) | bits;
+
+            // third base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+            bits = b64variant.decodeBase64Char(ch);
+
+            // First branch: can get padding (-> 1 byte)
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 4;
+                        buffer[outputPtr++] = (byte) decodedData;
+                        break;
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 2);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    // Ok, must get padding
+                    if (_inputPtr >= _inputEnd) {
+                        loadMoreGuaranteed();
+                    }
+                    ch = _inputBuffer[_inputPtr++] & 0xFF;
+                    if (!b64variant.usesPaddingChar(ch)) {
+                        throw reportInvalidBase64Char(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
+                    }
+                    // Got 12 bits, only need 8, need to shift
+                    decodedData >>= 4;
+                    buffer[outputPtr++] = (byte) decodedData;
+                    continue;
+                }
+            }
+            // Nope, 2 or 3 bytes
+            decodedData = (decodedData << 6) | bits;
+            // fourth and last base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 2;
+                        buffer[outputPtr++] = (byte) (decodedData >> 8);
+                        buffer[outputPtr++] = (byte) decodedData;
+                        break;
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 3);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    /* With padding we only get 2 bytes; but we have
+                     * to shift it a bit so it is identical to triplet
+                     * case with partial output.
+                     * 3 chars gives 3x6 == 18 bits, of which 2 are
+                     * dummies, need to discard:
+                     */
+                    decodedData >>= 2;
+                    buffer[outputPtr++] = (byte) (decodedData >> 8);
+                    buffer[outputPtr++] = (byte) decodedData;
+                    continue;
+                }
+            }
+            // otherwise, our triplet is now complete
+            decodedData = (decodedData << 6) | bits;
+            buffer[outputPtr++] = (byte) (decodedData >> 16);
+            buffer[outputPtr++] = (byte) (decodedData >> 8);
+            buffer[outputPtr++] = (byte) decodedData;
+        }
+        _tokenIncomplete = false;
+        if (outputPtr > 0) {
+            outputCount += outputPtr;
+            out.write(buffer, 0, outputPtr);
+        }
+        return outputCount;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, traversal, basic
+    /**********************************************************
+     */
+
+    /**
+     * @return Next token from the stream, if any found, or null
+     *   to indicate end-of-input
+     */
+    @Override
+    public JsonToken nextToken()
+        throws IOException, JsonParseException
+    {
+        _numTypesValid = NR_UNKNOWN;
+        /* First: field names are special -- we will always tokenize
+         * (part of) value along with field name to simplify
+         * state handling. If so, can and need to use secondary token:
+         */
+        if (_currToken == JsonToken.FIELD_NAME) {
+            return _nextAfterName();
+        }
+        if (_tokenIncomplete) {
+            _skipString(); // only strings can be partial
+        }
+
+        int i = _skipWSOrEnd();
+        if (i < 0) { // end-of-input
+            /* 19-Feb-2009, tatu: Should actually close/release things
+             *    like input source, symbol table and recyclable buffers now.
+             */
+            close();
+            return (_currToken = null);
+        }
+
+        /* First, need to ensure we know the starting location of token
+         * after skipping leading white space
+         */
+        _tokenInputTotal = _currInputProcessed + _inputPtr - 1;
+        _tokenInputRow = _currInputRow;
+        _tokenInputCol = _inputPtr - _currInputRowStart - 1;
+
+        // finally: clear any data retained so far
+        _binaryValue = null;
+
+        // Closing scope?
+        if (i == INT_RBRACKET) {
+            if (!_parsingContext.inArray()) {
+                _reportMismatchedEndMarker(i, '}');
+            }
+            _parsingContext = _parsingContext.getParent();
+            return (_currToken = JsonToken.END_ARRAY);
+        }
+        if (i == INT_RCURLY) {
+            if (!_parsingContext.inObject()) {
+                _reportMismatchedEndMarker(i, ']');
+            }
+            _parsingContext = _parsingContext.getParent();
+            return (_currToken = JsonToken.END_OBJECT);
+        }
+
+        // Nope: do we then expect a comma?
+        if (_parsingContext.expectComma()) {
+            if (i != INT_COMMA) {
+                _reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.getTypeDesc()+" entries");
+            }
+            i = _skipWS();
+        }
+
+        /* And should we now have a name? Always true for
+         * Object contexts, since the intermediate 'expect-value'
+         * state is never retained.
+         */
+        if (!_parsingContext.inObject()) {
+            return _nextTokenNotInObject(i);
+        }
+        // So first parse the field name itself:
+        Name n = _parseFieldName(i);
+        _parsingContext.setCurrentName(n.getName());
+        _currToken = JsonToken.FIELD_NAME;
+        i = _skipWS();
+        if (i != INT_COLON) {
+            _reportUnexpectedChar(i, "was expecting a colon to separate field name and value");
+        }
+        i = _skipWS();
+
+        // Ok: we must have a value... what is it? Strings are very common, check first:
+        if (i == INT_QUOTE) {
+            _tokenIncomplete = true;
+            _nextToken = JsonToken.VALUE_STRING;
+            return _currToken;
+        }        
+        JsonToken t;
+
+        switch (i) {
+        case INT_LBRACKET:
+            t = JsonToken.START_ARRAY;
+            break;
+        case INT_LCURLY:
+            t = JsonToken.START_OBJECT;
+            break;
+        case INT_RBRACKET:
+        case INT_RCURLY:
+            // Error: neither is valid at this point; valid closers have
+            // been handled earlier
+            _reportUnexpectedChar(i, "expected a value");
+        case INT_t:
+            _matchToken("true", 1);
+            t = JsonToken.VALUE_TRUE;
+            break;
+        case INT_f:
+            _matchToken("false", 1);
+             t = JsonToken.VALUE_FALSE;
+            break;
+        case INT_n:
+            _matchToken("null", 1);
+            t = JsonToken.VALUE_NULL;
+            break;
+
+        case INT_MINUS:
+            /* 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 INT_0:
+        case INT_1:
+        case INT_2:
+        case INT_3:
+        case INT_4:
+        case INT_5:
+        case INT_6:
+        case INT_7:
+        case INT_8:
+        case INT_9:
+            t = parseNumberText(i);
+            break;
+        default:
+            t = _handleUnexpectedValue(i);
+        }
+        _nextToken = t;
+        return _currToken;
+    }
+
+    private JsonToken _nextTokenNotInObject(int i)
+        throws IOException, JsonParseException
+    {
+        if (i == INT_QUOTE) {
+            _tokenIncomplete = true;
+            return (_currToken = JsonToken.VALUE_STRING);
+        }
+        switch (i) {
+        case INT_LBRACKET:
+            _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            return (_currToken = JsonToken.START_ARRAY);
+        case INT_LCURLY:
+            _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            return (_currToken = JsonToken.START_OBJECT);
+        case INT_RBRACKET:
+        case INT_RCURLY:
+            // Error: neither is valid at this point; valid closers have
+            // been handled earlier
+            _reportUnexpectedChar(i, "expected a value");
+        case INT_t:
+            _matchToken("true", 1);
+            return (_currToken = JsonToken.VALUE_TRUE);
+        case INT_f:
+            _matchToken("false", 1);
+            return (_currToken = JsonToken.VALUE_FALSE);
+        case INT_n:
+            _matchToken("null", 1);
+            return (_currToken = JsonToken.VALUE_NULL);
+        case INT_MINUS:
+            /* 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 INT_0:
+        case INT_1:
+        case INT_2:
+        case INT_3:
+        case INT_4:
+        case INT_5:
+        case INT_6:
+        case INT_7:
+        case INT_8:
+        case INT_9:
+            return (_currToken = parseNumberText(i));
+        }
+        return (_currToken = _handleUnexpectedValue(i));
+    }
+    
+    private JsonToken _nextAfterName()
+    {
+        _nameCopied = false; // need to invalidate if it was copied
+        JsonToken t = _nextToken;
+        _nextToken = null;
+        // Also: may need to start new context?
+        if (t == JsonToken.START_ARRAY) {
+            _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+        } else if (t == JsonToken.START_OBJECT) {
+            _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+        }
+        return (_currToken = t);
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        super.close();
+        // Merge found symbols, if any:
+        _symbols.release();
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, traversal, nextXxxValue/nextFieldName
+    /**********************************************************
+     */
+    
+    @Override
+    public boolean nextFieldName(SerializableString str)
+        throws IOException, JsonParseException
+    {
+        // // // Note: most of code below is copied from nextToken()
+        
+        _numTypesValid = NR_UNKNOWN;
+        if (_currToken == JsonToken.FIELD_NAME) { // can't have name right after name
+            _nextAfterName();
+            return false;
+        }
+        if (_tokenIncomplete) {
+            _skipString();
+        }
+        int i = _skipWSOrEnd();
+        if (i < 0) { // end-of-input
+            close();
+            _currToken = null;
+            return false;
+        }
+        _tokenInputTotal = _currInputProcessed + _inputPtr - 1;
+        _tokenInputRow = _currInputRow;
+        _tokenInputCol = _inputPtr - _currInputRowStart - 1;
+
+        // finally: clear any data retained so far
+        _binaryValue = null;
+
+        // Closing scope?
+        if (i == INT_RBRACKET) {
+            if (!_parsingContext.inArray()) {
+                _reportMismatchedEndMarker(i, '}');
+            }
+            _parsingContext = _parsingContext.getParent();
+            _currToken = JsonToken.END_ARRAY;
+            return false;
+        }
+        if (i == INT_RCURLY) {
+            if (!_parsingContext.inObject()) {
+                _reportMismatchedEndMarker(i, ']');
+            }
+            _parsingContext = _parsingContext.getParent();
+            _currToken = JsonToken.END_OBJECT;
+            return false;
+        }
+
+        // Nope: do we then expect a comma?
+        if (_parsingContext.expectComma()) {
+            if (i != INT_COMMA) {
+                _reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.getTypeDesc()+" entries");
+            }
+            i = _skipWS();
+        }
+
+        if (!_parsingContext.inObject()) {
+            _nextTokenNotInObject(i);
+            return false;
+        }
+        
+        // // // This part differs, name parsing
+        if (i == INT_QUOTE) {
+            // when doing literal match, must consider escaping:
+            byte[] nameBytes = str.asQuotedUTF8();
+            final int len = nameBytes.length;
+            if ((_inputPtr + len) < _inputEnd) { // maybe...
+                // first check length match by
+                final int end = _inputPtr+len;
+                if (_inputBuffer[end] == INT_QUOTE) {
+                    int offset = 0;
+                    final int ptr = _inputPtr;
+                    while (true) {
+                        if (offset == len) { // yes, match!
+                            _inputPtr = end+1; // skip current value first
+                            // First part is simple; setting of name
+                            _parsingContext.setCurrentName(str.getValue());
+                            _currToken = JsonToken.FIELD_NAME;
+                            // But then we also must handle following value etc
+                            _isNextTokenNameYes();
+                            return true;
+                        }
+                        if (nameBytes[offset] != _inputBuffer[ptr+offset]) {
+                            break;
+                        }
+                        ++offset;
+                    }
+                }
+            }
+        }
+        return _isNextTokenNameMaybe(i, str);
+    }
+
+    private void _isNextTokenNameYes()
+        throws IOException, JsonParseException
+    {
+        // very first thing: common case, colon, value, no white space
+        int i;
+        if (_inputPtr < (_inputEnd-1) && _inputBuffer[_inputPtr] == INT_COLON) { // fast case first
+            i = _inputBuffer[++_inputPtr];
+            ++_inputPtr;
+            if (i == INT_QUOTE) {
+                _tokenIncomplete = true;
+                _nextToken = JsonToken.VALUE_STRING;
+                return;
+            }
+            if (i == INT_LCURLY) {
+                _nextToken = JsonToken.START_OBJECT;
+                return;
+            }
+            if (i == INT_LBRACKET) {
+                _nextToken = JsonToken.START_ARRAY;
+                return;
+            }
+            i &= 0xFF;
+            if (i <= INT_SPACE || i == INT_SLASH) {
+            	--_inputPtr;
+                i = _skipWS();
+            }
+        } else {
+            i = _skipColon();
+        }
+        switch (i) {
+        case INT_QUOTE:
+            _tokenIncomplete = true;
+            _nextToken = JsonToken.VALUE_STRING;
+            return;
+        case INT_LBRACKET:
+            _nextToken = JsonToken.START_ARRAY;
+            return;
+        case INT_LCURLY:
+            _nextToken = JsonToken.START_OBJECT;
+            return;
+        case INT_RBRACKET:
+        case INT_RCURLY:
+            _reportUnexpectedChar(i, "expected a value");
+        case INT_t:
+            _matchToken("true", 1);
+            _nextToken = JsonToken.VALUE_TRUE;
+            return;
+        case INT_f:
+            _matchToken("false", 1);
+            _nextToken = JsonToken.VALUE_FALSE;
+            return;
+        case INT_n:
+            _matchToken("null", 1);
+            _nextToken = JsonToken.VALUE_NULL;
+            return;
+        case INT_MINUS:
+        case INT_0:
+        case INT_1:
+        case INT_2:
+        case INT_3:
+        case INT_4:
+        case INT_5:
+        case INT_6:
+        case INT_7:
+        case INT_8:
+        case INT_9:
+            _nextToken = parseNumberText(i);
+            return;
+        }
+        _nextToken = _handleUnexpectedValue(i);
+    }
+    
+    private boolean _isNextTokenNameMaybe(int i, SerializableString str)
+        throws IOException, JsonParseException
+    {
+        // // // and this is back to standard nextToken()
+            
+        Name n = _parseFieldName(i);
+        final boolean match;
+        {
+            String nameStr = n.getName();
+            _parsingContext.setCurrentName(nameStr);
+            match = nameStr.equals(str.getValue());
+        }
+        _currToken = JsonToken.FIELD_NAME;
+        i = _skipWS();
+        if (i != INT_COLON) {
+            _reportUnexpectedChar(i, "was expecting a colon to separate field name and value");
+        }
+        i = _skipWS();
+
+        // Ok: we must have a value... what is it? Strings are very common, check first:
+        if (i == INT_QUOTE) {
+            _tokenIncomplete = true;
+            _nextToken = JsonToken.VALUE_STRING;
+            return match;
+        }
+        JsonToken t;
+
+        switch (i) {
+        case INT_LBRACKET:
+            t = JsonToken.START_ARRAY;
+            break;
+        case INT_LCURLY:
+            t = JsonToken.START_OBJECT;
+            break;
+        case INT_RBRACKET:
+        case INT_RCURLY:
+            _reportUnexpectedChar(i, "expected a value");
+        case INT_t:
+            _matchToken("true", 1);
+            t = JsonToken.VALUE_TRUE;
+            break;
+        case INT_f:
+            _matchToken("false", 1);
+             t = JsonToken.VALUE_FALSE;
+            break;
+        case INT_n:
+            _matchToken("null", 1);
+            t = JsonToken.VALUE_NULL;
+            break;
+
+        case INT_MINUS:
+        case INT_0:
+        case INT_1:
+        case INT_2:
+        case INT_3:
+        case INT_4:
+        case INT_5:
+        case INT_6:
+        case INT_7:
+        case INT_8:
+        case INT_9:
+            t = parseNumberText(i);
+            break;
+        default:
+            t = _handleUnexpectedValue(i);
+        }
+        _nextToken = t;
+        return match;
+    }
+
+    @Override
+    public String nextTextValue()
+        throws IOException, JsonParseException
+    {
+        // two distinct cases; either got name and we know next type, or 'other'
+        if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_STRING) {
+                if (_tokenIncomplete) {
+                    _tokenIncomplete = false;
+                    _finishString();
+                }
+                return _textBuffer.contentsAsString();
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return null;
+        }
+        // !!! TODO: optimize this case as well
+        return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
+    }
+
+    @Override
+    public int nextIntValue(int defaultValue)
+        throws IOException, JsonParseException
+    {
+        // two distinct cases; either got name and we know next type, or 'other'
+        if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_NUMBER_INT) {
+                return getIntValue();
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return defaultValue;
+        }
+        // !!! TODO: optimize this case as well
+        return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
+    }
+
+    @Override
+    public long nextLongValue(long defaultValue)
+        throws IOException, JsonParseException
+    {
+        // two distinct cases; either got name and we know next type, or 'other'
+        if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_NUMBER_INT) {
+                return getLongValue();
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return defaultValue;
+        }
+        // !!! TODO: optimize this case as well
+        return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getLongValue() : defaultValue;
+    }
+
+    @Override
+    public Boolean nextBooleanValue()
+        throws IOException, JsonParseException
+    {
+        // two distinct cases; either got name and we know next type, or 'other'
+        if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
+            _nameCopied = false;
+            JsonToken t = _nextToken;
+            _nextToken = null;
+            _currToken = t;
+            if (t == JsonToken.VALUE_TRUE) {
+                return Boolean.TRUE;
+            }
+            if (t == JsonToken.VALUE_FALSE) {
+                return Boolean.FALSE;
+            }
+            if (t == JsonToken.START_ARRAY) {
+                _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
+            } else if (t == JsonToken.START_OBJECT) {
+                _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
+            }
+            return null;
+        }
+        switch (nextToken()) {
+        case VALUE_TRUE:
+            return Boolean.TRUE;
+        case VALUE_FALSE:
+            return Boolean.FALSE;
+        default:
+        	return null;
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods, number parsing
+    /* (note: in 1.6 and prior, part of "Utf8NumericParser"
+    /**********************************************************
+     */
+
+    /**
+     * Initial parsing method for number values. It needs to be able
+     * to parse enough input to be able to determine whether the
+     * value is to be considered a simple integer value, or a more
+     * generic decimal value: latter of which needs to be expressed
+     * as a floating point number. The basic rule is that if the number
+     * has no fractional or exponential part, it is an integer; otherwise
+     * a floating point number.
+     *<p>
+     * Because much of input has to be processed in any case, no partial
+     * parsing is done: all input text will be stored for further
+     * processing. However, actual numeric value conversion will be
+     * deferred, since it is usually the most complicated and costliest
+     * part of processing.
+     */
+    protected JsonToken parseNumberText(int c)
+        throws IOException, JsonParseException
+    {
+        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+        int outPtr = 0;
+        boolean negative = (c == INT_MINUS);
+
+        // Need to prepend sign?
+        if (negative) {
+            outBuf[outPtr++] = '-';
+            // Must have something after sign too
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            c = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            // Note: must be followed by a digit
+            if (c < INT_0 || c > INT_9) {
+                return _handleInvalidNumberStart(c, true);
+            }
+        }
+
+        // One special case: if first char is 0, must not be followed by a digit
+        if (c == INT_0) {
+            c = _verifyNoLeadingZeroes();
+        }
+        
+        // Ok: we can first just add digit we saw first:
+        outBuf[outPtr++] = (char) c;
+        int intLen = 1;
+
+        // And then figure out how far we can read without further checks:
+        int end = _inputPtr + outBuf.length;
+        if (end > _inputEnd) {
+            end = _inputEnd;
+        }
+
+        // With this, we have a nice and tight loop:
+        while (true) {
+            if (_inputPtr >= end) {
+                // Long enough to be split across boundary, so:
+                return _parserNumber2(outBuf, outPtr, negative, intLen);
+            }
+            c = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            if (c < INT_0 || c > INT_9) {
+                break;
+            }
+            ++intLen;
+            outBuf[outPtr++] = (char) c;
+        }
+        if (c == '.' || c == 'e' || c == 'E') {
+            return _parseFloatText(outBuf, outPtr, c, negative, intLen);
+        }
+
+        --_inputPtr; // to push back trailing char (comma etc)
+        _textBuffer.setCurrentLength(outPtr);
+
+        // And there we have it!
+        return resetInt(negative, intLen);
+    }
+    
+    /**
+     * Method called to handle parsing when input is split across buffer boundary
+     * (or output is longer than segment used to store it)
+     */
+    private JsonToken _parserNumber2(char[] outBuf, int outPtr, boolean negative,
+            int intPartLength)
+        throws IOException, JsonParseException
+    {
+        // Ok, parse the rest
+        while (true) {
+            if (_inputPtr >= _inputEnd && !loadMore()) {
+                _textBuffer.setCurrentLength(outPtr);
+                return resetInt(negative, intPartLength);
+            }
+            int c = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            if (c > INT_9 || c < INT_0) {
+                if (c == '.' || c == 'e' || c == 'E') {
+                    return _parseFloatText(outBuf, outPtr, c, negative, intPartLength);
+                }
+                break;
+            }
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            outBuf[outPtr++] = (char) c;
+            ++intPartLength;
+        }
+        --_inputPtr; // to push back trailing char (comma etc)
+        _textBuffer.setCurrentLength(outPtr);
+
+        // And there we have it!
+        return resetInt(negative, intPartLength);
+        
+    }
+    
+    /**
+     * Method called when we have seen one zero, and want to ensure
+     * it is not followed by another
+     */
+    private int _verifyNoLeadingZeroes()
+        throws IOException, JsonParseException
+    {
+        // Ok to have plain "0"
+        if (_inputPtr >= _inputEnd && !loadMore()) {
+            return INT_0;
+        }
+        int ch = _inputBuffer[_inputPtr] & 0xFF;
+        // if not followed by a number (probably '.'); return zero as is, to be included
+        if (ch < INT_0 || ch > INT_9) {
+            return INT_0;
+        }
+        // [JACKSON-358]: we may want to allow them, after all...
+        if (!isEnabled(Feature.ALLOW_NUMERIC_LEADING_ZEROS)) {
+            reportInvalidNumber("Leading zeroes not allowed");
+        }
+        // if so, just need to skip either all zeroes (if followed by number); or all but one (if non-number)
+        ++_inputPtr; // Leading zero to be skipped
+        if (ch == INT_0) {
+            while (_inputPtr < _inputEnd || loadMore()) {
+                ch = _inputBuffer[_inputPtr] & 0xFF;
+                if (ch < INT_0 || ch > INT_9) { // followed by non-number; retain one zero
+                    return INT_0;
+                }
+                ++_inputPtr; // skip previous zeroes
+                if (ch != INT_0) { // followed by other number; return 
+                    break;
+                }
+            }
+        }
+        return ch;
+    }
+    
+    private JsonToken _parseFloatText(char[] outBuf, int outPtr, int c,
+            boolean negative, int integerPartLength)
+        throws IOException, JsonParseException
+    {
+        int fractLen = 0;
+        boolean eof = false;
+
+        // And then see if we get other parts
+        if (c == '.') { // yes, fraction
+            outBuf[outPtr++] = (char) c;
+
+            fract_loop:
+            while (true) {
+                if (_inputPtr >= _inputEnd && !loadMore()) {
+                    eof = true;
+                    break fract_loop;
+                }
+                c = (int) _inputBuffer[_inputPtr++] & 0xFF;
+                if (c < INT_0 || c > INT_9) {
+                    break fract_loop;
+                }
+                ++fractLen;
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                outBuf[outPtr++] = (char) c;
+            }
+            // must be followed by sequence of ints, one minimum
+            if (fractLen == 0) {
+                reportUnexpectedNumberChar(c, "Decimal point not followed by a digit");
+            }
+        }
+
+        int expLen = 0;
+        if (c == 'e' || c == 'E') { // exponent?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            outBuf[outPtr++] = (char) c;
+            // Not optional, can require that we get one more char
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            c = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            // Sign indicator?
+            if (c == '-' || c == '+') {
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                outBuf[outPtr++] = (char) c;
+                // Likewise, non optional:
+                if (_inputPtr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                }
+                c = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            }
+
+            exp_loop:
+            while (c <= INT_9 && c >= INT_0) {
+                ++expLen;
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                outBuf[outPtr++] = (char) c;
+                if (_inputPtr >= _inputEnd && !loadMore()) {
+                    eof = true;
+                    break exp_loop;
+                }
+                c = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            }
+            // must be followed by sequence of ints, one minimum
+            if (expLen == 0) {
+                reportUnexpectedNumberChar(c, "Exponent indicator not followed by a digit");
+            }
+        }
+
+        // Ok; unless we hit end-of-input, need to push last char read back
+        if (!eof) {
+            --_inputPtr;
+        }
+        _textBuffer.setCurrentLength(outPtr);
+
+        // And there we have it!
+        return resetFloat(negative, integerPartLength, fractLen, expLen);
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods, secondary parsing
+    /**********************************************************
+     */
+    
+    protected Name _parseFieldName(int i)
+        throws IOException, JsonParseException
+    {
+        if (i != INT_QUOTE) {
+            return _handleUnusualFieldName(i);
+        }
+        // First: can we optimize out bounds checks?
+        if ((_inputPtr + 9) > _inputEnd) { // Need 8 chars, plus one trailing (quote)
+            return slowParseFieldName();
+        }
+
+        // If so, can also unroll loops nicely
+        /* 25-Nov-2008, tatu: This may seem weird, but here we do
+         *   NOT want to worry about UTF-8 decoding. Rather, we'll
+         *   assume that part is ok (if not it will get caught
+         *   later on), and just handle quotes and backslashes here.
+         */
+        final byte[] input = _inputBuffer;
+        final int[] codes = sInputCodesLatin1;
+
+        int q = input[_inputPtr++] & 0xFF;
+
+        if (codes[q] == 0) {
+            i = input[_inputPtr++] & 0xFF;
+            if (codes[i] == 0) {
+                q = (q << 8) | i;
+                i = input[_inputPtr++] & 0xFF;
+                if (codes[i] == 0) {
+                    q = (q << 8) | i;
+                    i = input[_inputPtr++] & 0xFF;
+                    if (codes[i] == 0) {
+                        q = (q << 8) | i;
+                        i = input[_inputPtr++] & 0xFF;
+                        if (codes[i] == 0) {
+                            _quad1 = q;
+                            return parseMediumFieldName(i, codes);
+                        }
+                        if (i == INT_QUOTE) { // one byte/char case or broken
+                            return findName(q, 4);
+                        }
+                        return parseFieldName(q, i, 4);
+                    }
+                    if (i == INT_QUOTE) { // one byte/char case or broken
+                        return findName(q, 3);
+                    }
+                    return parseFieldName(q, i, 3);
+                }                
+                if (i == INT_QUOTE) { // one byte/char case or broken
+                    return findName(q, 2);
+                }
+                return parseFieldName(q, i, 2);
+            }
+            if (i == INT_QUOTE) { // one byte/char case or broken
+                return findName(q, 1);
+            }
+            return parseFieldName(q, i, 1);
+        }     
+        if (q == INT_QUOTE) { // special case, ""
+            return BytesToNameCanonicalizer.getEmptyName();
+        }
+        return parseFieldName(0, q, 0); // quoting or invalid char
+    }
+
+    protected Name parseMediumFieldName(int q2, final int[] codes)
+        throws IOException, JsonParseException
+    {
+        // Ok, got 5 name bytes so far
+        int i = _inputBuffer[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 5 bytes
+                return findName(_quad1, q2, 1);
+            }
+            return parseFieldName(_quad1, q2, i, 1); // quoting or invalid char
+        }
+        q2 = (q2 << 8) | i;
+        i = _inputBuffer[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 6 bytes
+                return findName(_quad1, q2, 2);
+            }
+            return parseFieldName(_quad1, q2, i, 2);
+        }
+        q2 = (q2 << 8) | i;
+        i = _inputBuffer[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 7 bytes
+                return findName(_quad1, q2, 3);
+            }
+            return parseFieldName(_quad1, q2, i, 3);
+        }
+        q2 = (q2 << 8) | i;
+        i = _inputBuffer[_inputPtr++] & 0xFF;
+        if (codes[i] != 0) {
+            if (i == INT_QUOTE) { // 8 bytes
+                return findName(_quad1, q2, 4);
+            }
+            return parseFieldName(_quad1, q2, i, 4);
+        }
+        _quadBuffer[0] = _quad1;
+        _quadBuffer[1] = q2;
+        return parseLongFieldName(i);
+    }
+
+    protected Name parseLongFieldName(int q)
+        throws IOException, JsonParseException
+    {
+        // As explained above, will ignore UTF-8 encoding at this point
+        final int[] codes = sInputCodesLatin1;
+        int qlen = 2;
+
+        while (true) {
+            /* Let's offline if we hit buffer boundary (otherwise would
+             * need to [try to] align input, which is bit complicated
+             * and may not always be possible)
+             */
+            if ((_inputEnd - _inputPtr) < 4) {
+                return parseEscapedFieldName(_quadBuffer, qlen, 0, q, 0);
+            }
+            // Otherwise can skip boundary checks for 4 bytes in loop
+
+            int i = _inputBuffer[_inputPtr++] & 0xFF;
+            if (codes[i] != 0) {
+                if (i == INT_QUOTE) {
+                    return findName(_quadBuffer, qlen, q, 1);
+                }
+                return parseEscapedFieldName(_quadBuffer, qlen, q, i, 1);
+            }
+
+            q = (q << 8) | i;
+            i = _inputBuffer[_inputPtr++] & 0xFF;
+            if (codes[i] != 0) {
+                if (i == INT_QUOTE) {
+                    return findName(_quadBuffer, qlen, q, 2);
+                }
+                return parseEscapedFieldName(_quadBuffer, qlen, q, i, 2);
+            }
+
+            q = (q << 8) | i;
+            i = _inputBuffer[_inputPtr++] & 0xFF;
+            if (codes[i] != 0) {
+                if (i == INT_QUOTE) {
+                    return findName(_quadBuffer, qlen, q, 3);
+                }
+                return parseEscapedFieldName(_quadBuffer, qlen, q, i, 3);
+            }
+
+            q = (q << 8) | i;
+            i = _inputBuffer[_inputPtr++] & 0xFF;
+            if (codes[i] != 0) {
+                if (i == INT_QUOTE) {
+                    return findName(_quadBuffer, qlen, q, 4);
+                }
+                return parseEscapedFieldName(_quadBuffer, qlen, q, i, 4);
+            }
+
+            // Nope, no end in sight. Need to grow quad array etc
+            if (qlen >= _quadBuffer.length) {
+                _quadBuffer = growArrayBy(_quadBuffer, qlen);
+            }
+            _quadBuffer[qlen++] = q;
+            q = i;
+        }
+    }
+
+    /**
+     * Method called when not even first 8 bytes are guaranteed
+     * to come consequtively. Happens rarely, so this is offlined;
+     * plus we'll also do full checks for escaping etc.
+     */
+    protected Name slowParseFieldName()
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            if (!loadMore()) {
+                _reportInvalidEOF(": was expecting closing '\"' for name");
+            }
+        }
+        int i = _inputBuffer[_inputPtr++] & 0xFF;
+        if (i == INT_QUOTE) { // special case, ""
+            return BytesToNameCanonicalizer.getEmptyName();
+        }
+        return parseEscapedFieldName(_quadBuffer, 0, 0, i, 0);
+    }
+
+    private Name parseFieldName(int q1, int ch, int lastQuadBytes)
+        throws IOException, JsonParseException
+    {
+        return parseEscapedFieldName(_quadBuffer, 0, q1, ch, lastQuadBytes);
+    }
+
+    private Name parseFieldName(int q1, int q2, int ch, int lastQuadBytes)
+        throws IOException, JsonParseException
+    {
+        _quadBuffer[0] = q1;
+        return parseEscapedFieldName(_quadBuffer, 1, q2, 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.
+     */
+    protected Name parseEscapedFieldName(int[] quads, int qlen, int currQuad, int ch,
+                                         int currQuadBytes)
+        throws IOException, JsonParseException
+    {
+        /* 25-Nov-2008, tatu: This may seem weird, but here we do
+         *   NOT want to worry about UTF-8 decoding. Rather, we'll
+         *   assume that part is ok (if not it will get caught
+         *   later on), and just handle quotes and backslashes here.
+         */
+        final int[] codes = sInputCodesLatin1;
+
+        while (true) {
+            if (codes[ch] != 0) {
+                if (ch == INT_QUOTE) { // we are done
+                    break;
+                }
+                // Unquoted white space?
+                if (ch != INT_BACKSLASH) {
+                    // As per [JACKSON-208], call can now return:
+                    _throwUnquotedSpace(ch, "name");
+                } else {
+                    // Nope, escape sequence
+                    ch = _decodeEscaped();
+                }
+                /* Oh crap. May need to UTF-8 (re-)encode it, if it's
+                 * beyond 7-bit ascii. Gets pretty messy.
+                 * If this happens often, may want to use different name
+                 * canonicalization to avoid these hits.
+                 */
+                if (ch > 127) {
+                    // Ok, we'll need room for first byte right away
+                    if (currQuadBytes >= 4) {
+                        if (qlen >= quads.length) {
+                            _quadBuffer = quads = growArrayBy(quads, quads.length);
+                        }
+                        quads[qlen++] = currQuad;
+                        currQuad = 0;
+                        currQuadBytes = 0;
+                    }
+                    if (ch < 0x800) { // 2-byte
+                        currQuad = (currQuad << 8) | (0xc0 | (ch >> 6));
+                        ++currQuadBytes;
+                        // Second byte gets output below:
+                    } else { // 3 bytes; no need to worry about surrogates here
+                        currQuad = (currQuad << 8) | (0xe0 | (ch >> 12));
+                        ++currQuadBytes;
+                        // need room for middle byte?
+                        if (currQuadBytes >= 4) {
+                            if (qlen >= quads.length) {
+                                _quadBuffer = quads = growArrayBy(quads, quads.length);
+                            }
+                            quads[qlen++] = currQuad;
+                            currQuad = 0;
+                            currQuadBytes = 0;
+                        }
+                        currQuad = (currQuad << 8) | (0x80 | ((ch >> 6) & 0x3f));
+                        ++currQuadBytes;
+                    }
+                    // And same last byte in both cases, gets output below:
+                    ch = 0x80 | (ch & 0x3f);
+                }
+            }
+            // Ok, we have one more byte to add at any rate:
+            if (currQuadBytes < 4) {
+                ++currQuadBytes;
+                currQuad = (currQuad << 8) | ch;
+            } else {
+                if (qlen >= quads.length) {
+                    _quadBuffer = quads = growArrayBy(quads, quads.length);
+                }
+                quads[qlen++] = currQuad;
+                currQuad = ch;
+                currQuadBytes = 1;
+            }
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(" in field name");
+                }
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+        }
+
+        if (currQuadBytes > 0) {
+            if (qlen >= quads.length) {
+                _quadBuffer = quads = growArrayBy(quads, quads.length);
+            }
+            quads[qlen++] = currQuad;
+        }
+        Name name = _symbols.findName(quads, qlen);
+        if (name == null) {
+            name = addName(quads, qlen, currQuadBytes);
+        }
+        return name;
+    }
+
+    /**
+     * Method called when we see non-white space character other
+     * than double quote, when expecting a field name.
+     * In standard mode will just throw an expection; but
+     * in non-standard modes may be able to parse name.
+     */
+    protected Name _handleUnusualFieldName(int ch)
+        throws IOException, JsonParseException
+    {
+        // [JACKSON-173]: allow single quotes
+        if (ch == INT_APOSTROPHE && isEnabled(Feature.ALLOW_SINGLE_QUOTES)) {
+            return _parseApostropheFieldName();
+        }
+        // [JACKSON-69]: allow unquoted names if feature enabled:
+        if (!isEnabled(Feature.ALLOW_UNQUOTED_FIELD_NAMES)) {
+            _reportUnexpectedChar(ch, "was expecting double-quote to start field name");
+        }
+        /* Also: note that although we use a different table here,
+         * it does NOT handle UTF-8 decoding. It'll just pass those
+         * high-bit codes as acceptable for later decoding.
+         */
+        final int[] codes = CharTypes.getInputCodeUtf8JsNames();
+        // Also: must start with a valid character...
+        if (codes[ch] != 0) {
+            _reportUnexpectedChar(ch, "was expecting either valid name character (for unquoted name) or double-quote (for quoted) to start field name");
+        }
+
+        /* Ok, now; instead of ultra-optimizing parsing here (as with
+         * regular JSON names), let's just use the generic "slow"
+         * variant. Can measure its impact later on if need be
+         */
+        int[] quads = _quadBuffer;
+        int qlen = 0;
+        int currQuad = 0;
+        int currQuadBytes = 0;
+
+        while (true) {
+            // Ok, we have one more byte to add at any rate:
+            if (currQuadBytes < 4) {
+                ++currQuadBytes;
+                currQuad = (currQuad << 8) | ch;
+            } else {
+                if (qlen >= quads.length) {
+                    _quadBuffer = quads = growArrayBy(quads, quads.length);
+                }
+                quads[qlen++] = currQuad;
+                currQuad = ch;
+                currQuadBytes = 1;
+            }
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(" in field name");
+                }
+            }
+            ch = _inputBuffer[_inputPtr] & 0xFF;
+            if (codes[ch] != 0) {
+                break;
+            }
+            ++_inputPtr;
+        }
+
+        if (currQuadBytes > 0) {
+            if (qlen >= quads.length) {
+                _quadBuffer = quads = growArrayBy(quads, quads.length);
+            }
+            quads[qlen++] = currQuad;
+        }
+        Name name = _symbols.findName(quads, qlen);
+        if (name == null) {
+            name = addName(quads, qlen, currQuadBytes);
+        }
+        return name;
+    }
+
+    /* Parsing to support [JACKSON-173]. Plenty of duplicated code;
+     * main reason being to try to avoid slowing down fast path
+     * for valid JSON -- more alternatives, more code, generally
+     * bit slower execution.
+     */
+    protected Name _parseApostropheFieldName()
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            if (!loadMore()) {
+                _reportInvalidEOF(": was expecting closing '\'' for name");
+            }
+        }
+        int ch = _inputBuffer[_inputPtr++] & 0xFF;
+        if (ch == INT_APOSTROPHE) { // special case, ''
+            return BytesToNameCanonicalizer.getEmptyName();
+        }
+        int[] quads = _quadBuffer;
+        int qlen = 0;
+        int currQuad = 0;
+        int currQuadBytes = 0;
+
+        // Copied from parseEscapedFieldName, with minor mods:
+
+        final int[] codes = sInputCodesLatin1;
+
+        while (true) {
+            if (ch == INT_APOSTROPHE) {
+                break;
+            }
+            // additional check to skip handling of double-quotes
+            if (ch != INT_QUOTE && codes[ch] != 0) {
+                if (ch != INT_BACKSLASH) {
+                    // Unquoted white space?
+                    // As per [JACKSON-208], call can now return:
+                    _throwUnquotedSpace(ch, "name");
+                } else {
+                    // Nope, escape sequence
+                    ch = _decodeEscaped();
+                }
+                /* Oh crap. May need to UTF-8 (re-)encode it, if it's
+                 * beyond 7-bit ascii. Gets pretty messy.
+                 * If this happens often, may want to use different name
+                 * canonicalization to avoid these hits.
+                 */
+                if (ch > 127) {
+                    // Ok, we'll need room for first byte right away
+                    if (currQuadBytes >= 4) {
+                        if (qlen >= quads.length) {
+                            _quadBuffer = quads = growArrayBy(quads, quads.length);
+                        }
+                        quads[qlen++] = currQuad;
+                        currQuad = 0;
+                        currQuadBytes = 0;
+                    }
+                    if (ch < 0x800) { // 2-byte
+                        currQuad = (currQuad << 8) | (0xc0 | (ch >> 6));
+                        ++currQuadBytes;
+                        // Second byte gets output below:
+                    } else { // 3 bytes; no need to worry about surrogates here
+                        currQuad = (currQuad << 8) | (0xe0 | (ch >> 12));
+                        ++currQuadBytes;
+                        // need room for middle byte?
+                        if (currQuadBytes >= 4) {
+                            if (qlen >= quads.length) {
+                                _quadBuffer = quads = growArrayBy(quads, quads.length);
+                            }
+                            quads[qlen++] = currQuad;
+                            currQuad = 0;
+                            currQuadBytes = 0;
+                        }
+                        currQuad = (currQuad << 8) | (0x80 | ((ch >> 6) & 0x3f));
+                        ++currQuadBytes;
+                    }
+                    // And same last byte in both cases, gets output below:
+                    ch = 0x80 | (ch & 0x3f);
+                }
+            }
+            // Ok, we have one more byte to add at any rate:
+            if (currQuadBytes < 4) {
+                ++currQuadBytes;
+                currQuad = (currQuad << 8) | ch;
+            } else {
+                if (qlen >= quads.length) {
+                    _quadBuffer = quads = growArrayBy(quads, quads.length);
+                }
+                quads[qlen++] = currQuad;
+                currQuad = ch;
+                currQuadBytes = 1;
+            }
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(" in field name");
+                }
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+        }
+
+        if (currQuadBytes > 0) {
+            if (qlen >= quads.length) {
+                _quadBuffer = quads = growArrayBy(quads, quads.length);
+            }
+            quads[qlen++] = currQuad;
+        }
+        Name name = _symbols.findName(quads, qlen);
+        if (name == null) {
+            name = addName(quads, qlen, currQuadBytes);
+        }
+        return name;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, symbol (name) handling
+    /**********************************************************
+     */
+
+    private Name findName(int q1, int lastQuadBytes)
+        throws JsonParseException
+    {
+        // Usually we'll find it from the canonical symbol table already
+        Name name = _symbols.findName(q1);
+        if (name != null) {
+            return name;
+        }
+        // If not, more work. We'll need add stuff to buffer
+        _quadBuffer[0] = q1;
+        return addName(_quadBuffer, 1, lastQuadBytes);
+    }
+
+    private Name findName(int q1, int q2, int lastQuadBytes)
+        throws JsonParseException
+    {
+        // Usually we'll find it from the canonical symbol table already
+        Name name = _symbols.findName(q1, q2);
+        if (name != null) {
+            return name;
+        }
+        // If not, more work. We'll need add stuff to buffer
+        _quadBuffer[0] = q1;
+        _quadBuffer[1] = q2;
+        return addName(_quadBuffer, 2, lastQuadBytes);
+    }
+
+    private Name findName(int[] quads, int qlen, int lastQuad, int lastQuadBytes)
+        throws JsonParseException
+    {
+        if (qlen >= quads.length) {
+            _quadBuffer = quads = growArrayBy(quads, quads.length);
+        }
+        quads[qlen++] = lastQuad;
+        Name name = _symbols.findName(quads, qlen);
+        if (name == null) {
+            return addName(quads, qlen, lastQuadBytes);
+        }
+        return name;
+    }
+
+    /**
+     * This is the main workhorse method used when we take a symbol
+     * table miss. It needs to demultiplex individual bytes, decode
+     * multi-byte chars (if any), and then construct Name instance
+     * and add it to the symbol table.
+     */
+    private Name 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
+         * (as well as error reporting for unescaped control chars)
+         */
+        // 4 bytes per quad, except last one maybe less
+        int byteLen = (qlen << 2) - 4 + lastQuadBytes;
+
+        /* And last one is not correctly aligned (leading zero bytes instead
+         * need to shift a bit, instead of trailing). Only need to shift it
+         * for UTF-8 decoding; need revert for storage (since key will not
+         * be aligned, to optimize lookup speed)
+         */
+        int lastQuad;
+
+        if (lastQuadBytes < 4) {
+            lastQuad = quads[qlen-1];
+            // 8/16/24 bit left shift
+            quads[qlen-1] = (lastQuad << ((4 - lastQuadBytes) << 3));
+        } else {
+            lastQuad = 0;
+        }
+
+        // Need some working space, TextBuffer works well:
+        char[] cbuf = _textBuffer.emptyAndGetCurrentSegment();
+        int cix = 0;
+
+        for (int ix = 0; ix < byteLen; ) {
+            int ch = quads[ix >> 2]; // current quad, need to shift+mask
+            int byteIx = (ix & 3);
+            ch = (ch >> ((3 - byteIx) << 3)) & 0xFF;
+            ++ix;
+
+            if (ch > 127) { // multi-byte
+                int needed;
+                if ((ch & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
+                    ch &= 0x1F;
+                    needed = 1;
+                } else if ((ch & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
+                    ch &= 0x0F;
+                    needed = 2;
+                } else if ((ch & 0xF8) == 0xF0) { // 4 bytes; double-char with surrogates and all...
+                    ch &= 0x07;
+                    needed = 3;
+                } else { // 5- and 6-byte chars not valid xml chars
+                    _reportInvalidInitial(ch);
+                    needed = ch = 1; // never really gets this far
+                }
+                if ((ix + needed) > byteLen) {
+                    _reportInvalidEOF(" in field name");
+                }
+                
+                // Ok, always need at least one more:
+                int ch2 = quads[ix >> 2]; // current quad, need to shift+mask
+                byteIx = (ix & 3);
+                ch2 = (ch2 >> ((3 - byteIx) << 3));
+                ++ix;
+                
+                if ((ch2 & 0xC0) != 0x080) {
+                    _reportInvalidOther(ch2);
+                }
+                ch = (ch << 6) | (ch2 & 0x3F);
+                if (needed > 1) {
+                    ch2 = quads[ix >> 2];
+                    byteIx = (ix & 3);
+                    ch2 = (ch2 >> ((3 - byteIx) << 3));
+                    ++ix;
+                    
+                    if ((ch2 & 0xC0) != 0x080) {
+                        _reportInvalidOther(ch2);
+                    }
+                    ch = (ch << 6) | (ch2 & 0x3F);
+                    if (needed > 2) { // 4 bytes? (need surrogates on output)
+                        ch2 = quads[ix >> 2];
+                        byteIx = (ix & 3);
+                        ch2 = (ch2 >> ((3 - byteIx) << 3));
+                        ++ix;
+                        if ((ch2 & 0xC0) != 0x080) {
+                            _reportInvalidOther(ch2 & 0xFF);
+                        }
+                        ch = (ch << 6) | (ch2 & 0x3F);
+                    }
+                }
+                if (needed > 2) { // surrogate pair? once again, let's output one here, one later on
+                    ch -= 0x10000; // to normalize it starting with 0x0
+                    if (cix >= cbuf.length) {
+                        cbuf = _textBuffer.expandCurrentSegment();
+                    }
+                    cbuf[cix++] = (char) (0xD800 + (ch >> 10));
+                    ch = 0xDC00 | (ch & 0x03FF);
+                }
+            }
+            if (cix >= cbuf.length) {
+                cbuf = _textBuffer.expandCurrentSegment();
+            }
+            cbuf[cix++] = (char) ch;
+        }
+
+        // Ok. Now we have the character array, and can construct the String
+        String baseName = new String(cbuf, 0, cix);
+        // And finally, un-align if necessary
+        if (lastQuadBytes < 4) {
+            quads[qlen-1] = lastQuad;
+        }
+        return _symbols.addName(baseName, quads, qlen);
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, String value parsing
+    /**********************************************************
+     */
+
+    @Override
+    protected void _finishString()
+        throws IOException, JsonParseException
+    {
+        // 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 = sInputCodesUtf8;
+
+        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;
+                    _textBuffer.setCurrentLength(outPtr);
+                    return;
+                }
+                break;
+            }
+            ++ptr;
+            outBuf[outPtr++] = (char) c;
+        }
+        _inputPtr = ptr;
+        _finishString2(outBuf, outPtr);
+    }
+
+    private void _finishString2(char[] outBuf, int outPtr)
+        throws IOException, JsonParseException
+    {
+        int c;
+
+        // Here we do want to do full decoding, hence:
+        final int[] codes = sInputCodesUtf8;
+        final byte[] inputBuffer = _inputBuffer;
+
+        main_loop:
+        while (true) {
+            // Then the tight ASCII non-funny-char loop:
+            ascii_loop:
+            while (true) {
+                int ptr = _inputPtr;
+                if (ptr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                    ptr = _inputPtr;
+                }
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                final int max = Math.min(_inputEnd, (ptr + (outBuf.length - outPtr)));
+                while (ptr < max) {
+                    c = (int) inputBuffer[ptr++] & 0xFF;
+                    if (codes[c] != 0) {
+                        _inputPtr = ptr;
+                        break ascii_loop;
+                    }
+                    outBuf[outPtr++] = (char) c;
+                }
+                _inputPtr = ptr;
+            }
+            // Ok: end marker, escape or multi-byte?
+            if (c == INT_QUOTE) {
+                break main_loop;
+            }
+
+            switch (codes[c]) {
+            case 1: // backslash
+                c = _decodeEscaped();
+                break;
+            case 2: // 2-byte UTF
+                c = _decodeUtf8_2(c);
+                break;
+            case 3: // 3-byte UTF
+                if ((_inputEnd - _inputPtr) >= 2) {
+                    c = _decodeUtf8_3fast(c);
+                } else {
+                    c = _decodeUtf8_3(c);
+                }
+                break;
+            case 4: // 4-byte UTF
+                c = _decodeUtf8_4(c);
+                // Let's add first part right away:
+                outBuf[outPtr++] = (char) (0xD800 | (c >> 10));
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                c = 0xDC00 | (c & 0x3FF);
+                // And let the other char output down below
+                break;
+            default:
+                if (c < INT_SPACE) {
+                    // As per [JACKSON-208], call can now return:
+                    _throwUnquotedSpace(c, "string value");
+                } else {
+                    // Is this good enough error message?
+                    _reportInvalidChar(c);
+                }
+            }
+            // Need more room?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            // Ok, let's add char to output:
+            outBuf[outPtr++] = (char) c;
+        }
+        _textBuffer.setCurrentLength(outPtr);
+    }
+
+    /**
+     * Method called to skim through rest of unparsed String value,
+     * if it is not needed. This can be done bit faster if contents
+     * need not be stored for future access.
+     */
+    protected void _skipString()
+        throws IOException, JsonParseException
+    {
+        _tokenIncomplete = false;
+
+        // Need to be fully UTF-8 aware here:
+        final int[] codes = sInputCodesUtf8;
+        final byte[] inputBuffer = _inputBuffer;
+
+        main_loop:
+        while (true) {
+            int c;
+
+            ascii_loop:
+            while (true) {
+                int ptr = _inputPtr;
+                int max = _inputEnd;
+                if (ptr >= max) {
+                    loadMoreGuaranteed();
+                    ptr = _inputPtr;
+                    max = _inputEnd;
+                }
+                while (ptr < max) {
+                    c = (int) inputBuffer[ptr++] & 0xFF;
+                    if (codes[c] != 0) {
+                        _inputPtr = ptr;
+                        break ascii_loop;
+                    }
+                }
+                _inputPtr = ptr;
+            }
+            // Ok: end marker, escape or multi-byte?
+            if (c == INT_QUOTE) {
+                break main_loop;
+            }
+            
+            switch (codes[c]) {
+            case 1: // backslash
+                _decodeEscaped();
+                break;
+            case 2: // 2-byte UTF
+                _skipUtf8_2(c);
+                break;
+            case 3: // 3-byte UTF
+                _skipUtf8_3(c);
+                break;
+            case 4: // 4-byte UTF
+                _skipUtf8_4(c);
+                break;
+            default:
+                if (c < INT_SPACE) {
+                    // As per [JACKSON-208], call can now return:
+                    _throwUnquotedSpace(c, "string value");
+                } else {
+                    // Is this good enough error message?
+                    _reportInvalidChar(c);
+                }
+            }
+        }
+    }
+
+    /**
+     * Method for handling cases where first non-space character
+     * of an expected value token is not legal for standard JSON content.
+     */
+    protected JsonToken _handleUnexpectedValue(int c)
+        throws IOException, JsonParseException
+    {
+        // Most likely an error, unless we are to allow single-quote-strings
+        switch (c) {
+        case '\'':
+            if (isEnabled(Feature.ALLOW_SINGLE_QUOTES)) {
+                return _handleApostropheValue();
+            }
+            break;
+        case 'N':
+            _matchToken("NaN", 1);
+            if (isEnabled(Feature.ALLOW_NON_NUMERIC_NUMBERS)) {
+                return resetAsNaN("NaN", Double.NaN);
+            }
+            _reportError("Non-standard token 'NaN': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
+            break;
+        case '+': // note: '-' is taken as number
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOFInValue();
+                }
+            }
+            return _handleInvalidNumberStart(_inputBuffer[_inputPtr++] & 0xFF, false);
+        }
+
+        _reportUnexpectedChar(c, "expected a valid value (number, String, array, object, 'true', 'false' or 'null')");
+        return null;
+    }
+    
+    protected JsonToken _handleApostropheValue()
+        throws IOException, JsonParseException
+    {
+        int c = 0;
+        // Otherwise almost verbatim copy of _finishString()
+        int outPtr = 0;
+        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
+
+        // Here we do want to do full decoding, hence:
+        final int[] codes = sInputCodesUtf8;
+        final byte[] inputBuffer = _inputBuffer;
+
+        main_loop:
+        while (true) {
+            // Then the tight ascii non-funny-char loop:
+            ascii_loop:
+            while (true) {
+                if (_inputPtr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                }
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                int max = _inputEnd;
+                {
+                    int max2 = _inputPtr + (outBuf.length - outPtr);
+                    if (max2 < max) {
+                        max = max2;
+                    }
+                }
+                while (_inputPtr < max) {
+                    c = (int) inputBuffer[_inputPtr++] & 0xFF;
+                    if (c == INT_APOSTROPHE || codes[c] != 0) {
+                        break ascii_loop;
+                    }
+                    outBuf[outPtr++] = (char) c;
+                }
+            }
+
+            // Ok: end marker, escape or multi-byte?
+            if (c == INT_APOSTROPHE) {
+                break main_loop;
+            }
+
+            switch (codes[c]) {
+            case 1: // backslash
+                if (c != INT_QUOTE) { // marked as special, isn't here
+                    c = _decodeEscaped();
+                }
+                break;
+            case 2: // 2-byte UTF
+                c = _decodeUtf8_2(c);
+                break;
+            case 3: // 3-byte UTF
+                if ((_inputEnd - _inputPtr) >= 2) {
+                    c = _decodeUtf8_3fast(c);
+                } else {
+                    c = _decodeUtf8_3(c);
+                }
+                break;
+            case 4: // 4-byte UTF
+                c = _decodeUtf8_4(c);
+                // Let's add first part right away:
+                outBuf[outPtr++] = (char) (0xD800 | (c >> 10));
+                if (outPtr >= outBuf.length) {
+                    outBuf = _textBuffer.finishCurrentSegment();
+                    outPtr = 0;
+                }
+                c = 0xDC00 | (c & 0x3FF);
+                // And let the other char output down below
+                break;
+            default:
+                if (c < INT_SPACE) {
+                    _throwUnquotedSpace(c, "string value");
+                }
+                // Is this good enough error message?
+                _reportInvalidChar(c);
+            }
+            // Need more room?
+            if (outPtr >= outBuf.length) {
+                outBuf = _textBuffer.finishCurrentSegment();
+                outPtr = 0;
+            }
+            // Ok, let's add char to output:
+            outBuf[outPtr++] = (char) c;
+        }
+        _textBuffer.setCurrentLength(outPtr);
+
+        return JsonToken.VALUE_STRING;
+    }
+
+    /**
+     * Method called if expected numeric value (due to leading sign) does not
+     * look like a number
+     */
+    protected JsonToken _handleInvalidNumberStart(int ch, boolean neg)
+        throws IOException, JsonParseException
+    {
+        while (ch == 'I') {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOFInValue();
+                }
+            }
+            ch = _inputBuffer[_inputPtr++];
+            String match;
+            if (ch == 'N') {
+                match = neg ? "-INF" :"+INF";
+            } else if (ch == 'n') {
+                match = neg ? "-Infinity" :"+Infinity";
+            } else {
+                break;
+            }
+            _matchToken(match, 3);
+            if (isEnabled(Feature.ALLOW_NON_NUMERIC_NUMBERS)) {
+                return resetAsNaN(match, neg ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
+            }
+            _reportError("Non-standard token '"+match+"': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
+        }
+        reportUnexpectedNumberChar(ch, "expected digit (0-9) to follow minus sign, for valid numeric value");
+        return null;
+    }
+    
+    protected void _matchToken(String matchStr, int i)
+        throws IOException, JsonParseException
+    {
+        final int len = matchStr.length();
+    
+        do {
+            if (((_inputPtr >= _inputEnd) && !loadMore())
+                ||  (_inputBuffer[_inputPtr] != matchStr.charAt(i))) {
+                _reportInvalidToken(matchStr.substring(0, i));
+            }
+            ++_inputPtr;
+        } while (++i < len);
+    
+        // but let's also ensure we either get EOF, or non-alphanum char...
+        if (_inputPtr >= _inputEnd && !loadMore()) {
+            return;
+        }
+        int ch = _inputBuffer[_inputPtr] & 0xFF;
+        if (ch < '0' || ch == ']' || ch == '}') { // expected/allowed chars
+            return;
+        }
+        // but actually only alphanums are problematic
+        char c = (char) _decodeCharForError(ch);
+        if (Character.isJavaIdentifierPart(c)) {
+            _reportInvalidToken(matchStr.substring(0, i));
+        }
+    }
+
+    protected void _reportInvalidToken(String matchedPart)
+       throws IOException, JsonParseException
+    {
+        _reportInvalidToken(matchedPart, "'null', 'true', 'false' or NaN");
+    }
+    
+    protected void _reportInvalidToken(String matchedPart, String msg)
+        throws IOException, JsonParseException
+    {
+        StringBuilder sb = new StringBuilder(matchedPart);
+
+        /* Let's just try to find what appears to be the token, using
+         * regular Java identifier character rules. It's just a heuristic,
+         * nothing fancy here (nor fast).
+         */
+        while (true) {
+            if (_inputPtr >= _inputEnd && !loadMore()) {
+                break;
+            }
+            int i = (int) _inputBuffer[_inputPtr++];
+            char c = (char) _decodeCharForError(i);
+            if (!Character.isJavaIdentifierPart(c)) {
+                break;
+            }
+            sb.append(c);
+        }
+        _reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg);
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, ws skipping, escape/unescape
+    /**********************************************************
+     */
+
+    private int _skipWS()
+        throws IOException, JsonParseException
+    {
+        while (_inputPtr < _inputEnd || loadMore()) {
+            int i = _inputBuffer[_inputPtr++] & 0xFF;
+            if (i > INT_SPACE) {
+                if (i != INT_SLASH) {
+                    return i;
+                }
+                _skipComment();
+            } else if (i != INT_SPACE) {
+                if (i == INT_LF) {
+                    _skipLF();
+                } else if (i == INT_CR) {
+                    _skipCR();
+                } else if (i != INT_TAB) {
+                    _throwInvalidSpace(i);
+                }
+            }
+        }
+        throw _constructError("Unexpected end-of-input within/between "+_parsingContext.getTypeDesc()+" entries");
+    }
+
+    private int _skipWSOrEnd()
+        throws IOException, JsonParseException
+    {
+        while ((_inputPtr < _inputEnd) || loadMore()) {
+            int i = _inputBuffer[_inputPtr++] & 0xFF;
+            if (i > INT_SPACE) {
+                if (i != INT_SLASH) {
+                    return i;
+                }
+                _skipComment();
+            } else if (i != INT_SPACE) {
+                if (i == INT_LF) {
+                    _skipLF();
+                } else if (i == INT_CR) {
+                    _skipCR();
+                } else if (i != INT_TAB) {
+                    _throwInvalidSpace(i);
+                }
+            }
+        }
+        // We ran out of input...
+        _handleEOF();
+        return -1;
+    }
+
+    /**
+     * Helper method for matching and skipping a colon character,
+     * optionally surrounded by white space
+     */
+    private int _skipColon()
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        // first fast case: we just got a colon without white space:
+        int i = _inputBuffer[_inputPtr++];
+        if (i == INT_COLON) {
+            if (_inputPtr < _inputEnd) {
+                i = _inputBuffer[_inputPtr] & 0xFF;
+                if (i > INT_SPACE && i != INT_SLASH) {
+                    ++_inputPtr;
+                    return i;
+                }
+            }
+        } else {
+            // need to skip potential leading space
+            i &= 0xFF;
+            
+            space_loop:
+            while (true) {
+                switch (i) {
+                case INT_SPACE:
+                case INT_TAB:
+                    break;
+                case INT_CR:
+                    _skipCR();
+                    break;
+                case INT_LF:
+                    _skipLF();
+                    break;
+                case INT_SLASH:
+                    _skipComment();
+                    break;
+                default:
+                    if (i < INT_SPACE) {
+                        _throwInvalidSpace(i);
+                    }
+                    break space_loop;
+                }
+                if (_inputPtr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                }
+                i = _inputBuffer[_inputPtr++] & 0xFF;
+            }
+            if (i != INT_COLON) {
+                _reportUnexpectedChar(i, "was expecting a colon to separate field name and value");
+            }
+        }
+
+            // either way, found colon, skip through trailing WS
+        while (_inputPtr < _inputEnd || loadMore()) {
+            i = _inputBuffer[_inputPtr++] & 0xFF;
+            if (i > INT_SPACE) {
+                if (i != INT_SLASH) {
+                    return i;
+                }
+                _skipComment();
+            } else if (i != INT_SPACE) {
+                if (i == INT_LF) {
+                    _skipLF();
+                } else if (i == INT_CR) {
+                    _skipCR();
+                } else if (i != INT_TAB) {
+                    _throwInvalidSpace(i);
+                }
+            }
+        }
+        throw _constructError("Unexpected end-of-input within/between "+_parsingContext.getTypeDesc()+" entries");
+    }
+    
+    private void _skipComment()
+        throws IOException, JsonParseException
+    {
+        if (!isEnabled(Feature.ALLOW_COMMENTS)) {
+            _reportUnexpectedChar('/', "maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)");
+        }
+        // First: check which comment (if either) it is:
+        if (_inputPtr >= _inputEnd && !loadMore()) {
+            _reportInvalidEOF(" in a comment");
+        }
+        int c = _inputBuffer[_inputPtr++] & 0xFF;
+        if (c == INT_SLASH) {
+            _skipCppComment();
+        } else if (c == INT_ASTERISK) {
+            _skipCComment();
+        } else {
+            _reportUnexpectedChar(c, "was expecting either '*' or '/' for a comment");
+        }
+    }
+
+    private void _skipCComment()
+        throws IOException, JsonParseException
+    {
+        // Need to be UTF-8 aware here to decode content (for skipping)
+        final int[] codes = CharTypes.getInputCodeComment();
+
+        // Ok: need the matching '*/'
+        main_loop:
+        while ((_inputPtr < _inputEnd) || loadMore()) {
+            int i = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            int code = codes[i];
+            if (code != 0) {
+                switch (code) {
+                case INT_ASTERISK:
+                    if (_inputPtr >= _inputEnd && !loadMore()) {
+                        break main_loop;
+                    }
+                    if (_inputBuffer[_inputPtr] == INT_SLASH) {
+                        ++_inputPtr;
+                        return;
+                    }
+                    break;
+                case INT_LF:
+                    _skipLF();
+                    break;
+                case INT_CR:
+                    _skipCR();
+                    break;
+                case 2: // 2-byte UTF
+                    _skipUtf8_2(i);
+                    break;
+                case 3: // 3-byte UTF
+                    _skipUtf8_3(i);
+                    break;
+                case 4: // 4-byte UTF
+                    _skipUtf8_4(i);
+                    break;
+                default: // e.g. -1
+                    // Is this good enough error message?
+                    _reportInvalidChar(i);
+                }
+            }
+        }
+        _reportInvalidEOF(" in a comment");
+    }
+
+    private void _skipCppComment()
+        throws IOException, JsonParseException
+    {
+        // Ok: need to find EOF or linefeed
+        final int[] codes = CharTypes.getInputCodeComment();
+        while ((_inputPtr < _inputEnd) || loadMore()) {
+            int i = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            int code = codes[i];
+            if (code != 0) {
+                switch (code) {
+                case INT_LF:
+                    _skipLF();
+                    return;
+                case INT_CR:
+                    _skipCR();
+                    return;
+                case INT_ASTERISK: // nop for these comments
+                    break;
+                case 2: // 2-byte UTF
+                    _skipUtf8_2(i);
+                    break;
+                case 3: // 3-byte UTF
+                    _skipUtf8_3(i);
+                    break;
+                case 4: // 4-byte UTF
+                    _skipUtf8_4(i);
+                    break;
+                default: // e.g. -1
+                    // Is this good enough error message?
+                    _reportInvalidChar(i);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected char _decodeEscaped()
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            if (!loadMore()) {
+                _reportInvalidEOF(" in character escape sequence");
+            }
+        }
+        int c = (int) _inputBuffer[_inputPtr++];
+
+        switch ((int) c) {
+            // First, ones that are mapped
+        case INT_b:
+            return '\b';
+        case INT_t:
+            return '\t';
+        case INT_n:
+            return '\n';
+        case INT_f:
+            return '\f';
+        case INT_r:
+            return '\r';
+
+            // And these are to be returned as they are
+        case INT_QUOTE:
+        case INT_SLASH:
+        case INT_BACKSLASH:
+            return (char) c;
+
+        case INT_u: // and finally hex-escaped
+            break;
+
+        default:
+            return _handleUnrecognizedCharacterEscape((char) _decodeCharForError(c));
+        }
+
+        // Ok, a hex escape. Need 4 characters
+        int value = 0;
+        for (int i = 0; i < 4; ++i) {
+            if (_inputPtr >= _inputEnd) {
+                if (!loadMore()) {
+                    _reportInvalidEOF(" in character escape sequence");
+                }
+            }
+            int ch = (int) _inputBuffer[_inputPtr++];
+            int digit = CharTypes.charToHex(ch);
+            if (digit < 0) {
+                _reportUnexpectedChar(ch, "expected a hex-digit for character escape sequence");
+            }
+            value = (value << 4) | digit;
+        }
+        return (char) value;
+    }
+
+    protected int _decodeCharForError(int firstByte)
+        throws IOException, JsonParseException
+    {
+        int c = (int) firstByte;
+        if (c < 0) { // if >= 0, is ascii and fine as is
+            int needed;
+            
+            // Ok; if we end here, we got multi-byte combination
+            if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
+                c &= 0x1F;
+                needed = 1;
+            } else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
+                c &= 0x0F;
+                needed = 2;
+            } else if ((c & 0xF8) == 0xF0) {
+                // 4 bytes; double-char with surrogates and all...
+                c &= 0x07;
+                needed = 3;
+            } else {
+                _reportInvalidInitial(c & 0xFF);
+                needed = 1; // never gets here
+            }
+
+            int d = nextByte();
+            if ((d & 0xC0) != 0x080) {
+                _reportInvalidOther(d & 0xFF);
+            }
+            c = (c << 6) | (d & 0x3F);
+            
+            if (needed > 1) { // needed == 1 means 2 bytes total
+                d = nextByte(); // 3rd byte
+                if ((d & 0xC0) != 0x080) {
+                    _reportInvalidOther(d & 0xFF);
+                }
+                c = (c << 6) | (d & 0x3F);
+                if (needed > 2) { // 4 bytes? (need surrogates)
+                    d = nextByte();
+                    if ((d & 0xC0) != 0x080) {
+                        _reportInvalidOther(d & 0xFF);
+                    }
+                    c = (c << 6) | (d & 0x3F);
+                }
+            }
+        }
+        return c;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods,UTF8 decoding
+    /**********************************************************
+     */
+
+    private int _decodeUtf8_2(int c)
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        int d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        return ((c & 0x1F) << 6) | (d & 0x3F);
+    }
+
+    private int _decodeUtf8_3(int c1)
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        c1 &= 0x0F;
+        int d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        int c = (c1 << 6) | (d & 0x3F);
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        c = (c << 6) | (d & 0x3F);
+        return c;
+    }
+
+    private int _decodeUtf8_3fast(int c1)
+        throws IOException, JsonParseException
+    {
+        c1 &= 0x0F;
+        int d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        int c = (c1 << 6) | (d & 0x3F);
+        d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        c = (c << 6) | (d & 0x3F);
+        return c;
+    }
+
+    /**
+     * @return Character value <b>minus 0x10000</c>; this so that caller
+     *    can readily expand it to actual surrogates
+     */
+    private int _decodeUtf8_4(int c)
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        int d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        c = ((c & 0x07) << 6) | (d & 0x3F);
+
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        c = (c << 6) | (d & 0x3F);
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+
+        /* note: won't change it to negative here, since caller
+         * already knows it'll need a surrogate
+         */
+        return ((c << 6) | (d & 0x3F)) - 0x10000;
+    }
+
+    private void _skipUtf8_2(int c)
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        c = (int) _inputBuffer[_inputPtr++];
+        if ((c & 0xC0) != 0x080) {
+            _reportInvalidOther(c & 0xFF, _inputPtr);
+        }
+    }
+
+    /* Alas, can't heavily optimize skipping, since we still have to
+     * do validity checks...
+     */
+    private void _skipUtf8_3(int c)
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        //c &= 0x0F;
+        c = (int) _inputBuffer[_inputPtr++];
+        if ((c & 0xC0) != 0x080) {
+            _reportInvalidOther(c & 0xFF, _inputPtr);
+        }
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        c = (int) _inputBuffer[_inputPtr++];
+        if ((c & 0xC0) != 0x080) {
+            _reportInvalidOther(c & 0xFF, _inputPtr);
+        }
+    }
+
+    private void _skipUtf8_4(int c)
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        int d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        d = (int) _inputBuffer[_inputPtr++];
+        if ((d & 0xC0) != 0x080) {
+            _reportInvalidOther(d & 0xFF, _inputPtr);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, input loading
+    /**********************************************************
+     */
+
+    /**
+     * We actually need to check the character value here
+     * (to see if we have \n following \r).
+     */
+    protected void _skipCR() throws IOException
+    {
+        if (_inputPtr < _inputEnd || loadMore()) {
+            if (_inputBuffer[_inputPtr] == BYTE_LF) {
+                ++_inputPtr;
+            }
+        }
+        ++_currInputRow;
+        _currInputRowStart = _inputPtr;
+    }
+
+    protected void _skipLF() throws IOException
+    {
+        ++_currInputRow;
+        _currInputRowStart = _inputPtr;
+    }
+
+    private int nextByte()
+        throws IOException, JsonParseException
+    {
+        if (_inputPtr >= _inputEnd) {
+            loadMoreGuaranteed();
+        }
+        return _inputBuffer[_inputPtr++] & 0xFF;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, error reporting
+    /**********************************************************
+     */
+
+    protected void _reportInvalidChar(int c)
+        throws JsonParseException
+    {
+        // Either invalid WS or illegal UTF-8 start char
+        if (c < INT_SPACE) {
+            _throwInvalidSpace(c);
+        }
+        _reportInvalidInitial(c);
+    }
+
+    protected void _reportInvalidInitial(int mask)
+        throws JsonParseException
+    {
+        _reportError("Invalid UTF-8 start byte 0x"+Integer.toHexString(mask));
+    }
+
+    protected void _reportInvalidOther(int mask)
+        throws JsonParseException
+    {
+        _reportError("Invalid UTF-8 middle byte 0x"+Integer.toHexString(mask));
+    }
+
+    protected void _reportInvalidOther(int mask, int ptr)
+        throws JsonParseException
+    {
+        _inputPtr = ptr;
+        _reportInvalidOther(mask);
+    }
+
+    public static int[] growArrayBy(int[] arr, int more)
+    {
+        if (arr == null) {
+            return new int[more];
+        }
+        int[] old = arr;
+        int len = arr.length;
+        arr = new int[len + more];
+        System.arraycopy(old, 0, arr, 0, len);
+        return arr;
+    }
+
+    /*
+    /**********************************************************
+    /* Binary access
+    /**********************************************************
+     */
+
+    /**
+     * Efficient handling for incremental parsing of base64-encoded
+     * textual content.
+     */
+    protected byte[] _decodeBase64(Base64Variant b64variant)
+        throws IOException, JsonParseException
+    {
+        ByteArrayBuilder builder = _getByteArrayBuilder();
+
+        //main_loop:
+        while (true) {
+            // first, we'll skip preceding white space, if any
+            int ch;
+            do {
+                if (_inputPtr >= _inputEnd) {
+                    loadMoreGuaranteed();
+                }
+                ch = (int) _inputBuffer[_inputPtr++] & 0xFF;
+            } while (ch <= INT_SPACE);
+            int bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) { // reached the end, fair and square?
+                if (ch == INT_QUOTE) {
+                    return builder.toByteArray();
+                }
+                bits = _decodeBase64Escape(b64variant, ch, 0);
+                if (bits < 0) { // white space to skip
+                    continue;
+                }
+            }
+            int decodedData = bits;
+            
+            // then second base64 char; can't get padding yet, nor ws
+            
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                bits = _decodeBase64Escape(b64variant, ch, 1);
+            }
+            decodedData = (decodedData << 6) | bits;
+            
+            // third base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+            bits = b64variant.decodeBase64Char(ch);
+
+            // First branch: can get padding (-> 1 byte)
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 4;
+                        builder.append(decodedData);
+                        return builder.toByteArray();
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 2);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    // Ok, must get padding
+                    if (_inputPtr >= _inputEnd) {
+                        loadMoreGuaranteed();
+                    }
+                    ch = _inputBuffer[_inputPtr++] & 0xFF;
+                    if (!b64variant.usesPaddingChar(ch)) {
+                        throw reportInvalidBase64Char(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
+                    }
+                    // Got 12 bits, only need 8, need to shift
+                    decodedData >>= 4;
+                    builder.append(decodedData);
+                    continue;
+                }
+            }
+            // Nope, 2 or 3 bytes
+            decodedData = (decodedData << 6) | bits;
+            // fourth and last base64 char; can be padding, but not ws
+            if (_inputPtr >= _inputEnd) {
+                loadMoreGuaranteed();
+            }
+            ch = _inputBuffer[_inputPtr++] & 0xFF;
+            bits = b64variant.decodeBase64Char(ch);
+            if (bits < 0) {
+                if (bits != Base64Variant.BASE64_VALUE_PADDING) {
+                    // as per [JACKSON-631], could also just be 'missing'  padding
+                    if (ch == '"' && !b64variant.usesPadding()) {
+                        decodedData >>= 2;
+                        builder.appendTwoBytes(decodedData);
+                        return builder.toByteArray();
+                    }
+                    bits = _decodeBase64Escape(b64variant, ch, 3);
+                }
+                if (bits == Base64Variant.BASE64_VALUE_PADDING) {
+                    /* With padding we only get 2 bytes; but we have
+                     * to shift it a bit so it is identical to triplet
+                     * case with partial output.
+                     * 3 chars gives 3x6 == 18 bits, of which 2 are
+                     * dummies, need to discard:
+                     */
+                    decodedData >>= 2;
+                    builder.appendTwoBytes(decodedData);
+                    continue;
+                }
+            }
+            // otherwise, our triplet is now complete
+            decodedData = (decodedData << 6) | bits;
+            builder.appendThreeBytes(decodedData);
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java
new file mode 100644
index 0000000..61eeb44
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java
@@ -0,0 +1,1911 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.*;
+
+/**
+ * {@link JsonGenerator} that outputs JSON content using a {@link java.io.Writer}
+ * which handles character encoding.
+ */
+public final class WriterBasedJsonGenerator
+    extends JsonGeneratorImpl
+{
+    final protected static int SHORT_WRITE = 32;
+
+    final protected static char[] HEX_CHARS = CharTypes.copyHexChars();
+    
+    /*
+    /**********************************************************
+    /* Output buffering
+    /**********************************************************
+     */
+
+    final protected Writer _writer;
+    
+    /**
+     * Intermediate buffer in which contents are buffered before
+     * being written using {@link #_writer}.
+     */
+    protected char[] _outputBuffer;
+
+    /**
+     * Pointer to the first buffered character to output
+     */
+    protected int _outputHead = 0;
+
+    /**
+     * Pointer to the position right beyond the last character to output
+     * (end marker; may point to position right beyond the end of the buffer)
+     */
+    protected int _outputTail = 0;
+
+    /**
+     * End marker of the output buffer; one past the last valid position
+     * within the buffer.
+     */
+    protected int _outputEnd;
+
+    /**
+     * Short (14 char) temporary buffer allocated if needed, for constructing
+     * escape sequences
+     */
+    protected char[] _entityBuffer;
+
+    /**
+     * When custom escapes are used, this member variable is used
+     * internally to hold a reference to currently used escape
+     */
+    protected SerializableString _currentEscape;
+    
+    
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public WriterBasedJsonGenerator(IOContext ctxt, int features,
+            ObjectCodec codec, Writer w)
+    {
+        super(ctxt, features, codec);
+        _writer = w;
+        _outputBuffer = ctxt.allocConcatBuffer();
+        _outputEnd = _outputBuffer.length;
+    }
+    
+    /*
+    /**********************************************************
+    /* Overridden configuration methods
+    /**********************************************************
+     */
+    
+    @Override
+    public Object getOutputTarget() {
+        return _writer;
+    }
+    
+    /*
+    /**********************************************************
+    /* Overridden methods
+    /**********************************************************
+     */
+
+    @Override
+    public void writeFieldName(String name)  throws IOException, JsonGenerationException
+    {
+        int status = _writeContext.writeFieldName(name);
+        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
+            _reportError("Can not write a field name, expecting a value");
+        }
+        _writeFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
+    }
+
+    @Override
+    public void writeFieldName(SerializableString name)
+        throws IOException, JsonGenerationException
+    {
+        // Object is a value, need to verify it's allowed
+        int status = _writeContext.writeFieldName(name.getValue());
+        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
+            _reportError("Can not write a field name, expecting a value");
+        }
+        _writeFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
+    }
+    
+    /*
+    /**********************************************************
+    /* Output method implementations, structural
+    /**********************************************************
+     */
+
+    @Override
+    public void writeStartArray() throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("start an array");
+        _writeContext = _writeContext.createChildArrayContext();
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeStartArray(this);
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '[';
+        }
+    }
+
+    @Override
+    public void writeEndArray() throws IOException, JsonGenerationException
+    {
+        if (!_writeContext.inArray()) {
+            _reportError("Current context not an ARRAY but "+_writeContext.getTypeDesc());
+        }
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeEndArray(this, _writeContext.getEntryCount());
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = ']';
+        }
+        _writeContext = _writeContext.getParent();
+    }
+
+    @Override
+    public void writeStartObject() throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("start an object");
+        _writeContext = _writeContext.createChildObjectContext();
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeStartObject(this);
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '{';
+        }
+    }
+
+    @Override
+    public void writeEndObject() throws IOException, JsonGenerationException
+    {
+        if (!_writeContext.inObject()) {
+            _reportError("Current context not an object but "+_writeContext.getTypeDesc());
+        }
+        if (_cfgPrettyPrinter != null) {
+            _cfgPrettyPrinter.writeEndObject(this, _writeContext.getEntryCount());
+        } else {
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '}';
+        }
+        _writeContext = _writeContext.getParent();
+    }
+
+    protected void _writeFieldName(String name, boolean commaBefore)
+        throws IOException, JsonGenerationException
+    {
+        if (_cfgPrettyPrinter != null) {
+            _writePPFieldName(name, commaBefore);
+            return;
+        }
+        // for fast+std case, need to output up to 2 chars, comma, dquote
+        if ((_outputTail + 1) >= _outputEnd) {
+            _flushBuffer();
+        }
+        if (commaBefore) {
+            _outputBuffer[_outputTail++] = ',';
+        }
+
+        /* To support [JACKSON-46], we'll do this:
+         * (Question: should quoting of spaces (etc) still be enabled?)
+         */
+        if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
+            _writeString(name);
+            return;
+        }
+
+        // we know there's room for at least one more char
+        _outputBuffer[_outputTail++] = '"';
+        // The beef:
+        _writeString(name);
+        // and closing quotes; need room for one more char:
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+    }
+
+    public void _writeFieldName(SerializableString name, boolean commaBefore)
+        throws IOException, JsonGenerationException
+    {
+        if (_cfgPrettyPrinter != null) {
+            _writePPFieldName(name, commaBefore);
+            return;
+        }
+        // for fast+std case, need to output up to 2 chars, comma, dquote
+        if ((_outputTail + 1) >= _outputEnd) {
+            _flushBuffer();
+        }
+        if (commaBefore) {
+            _outputBuffer[_outputTail++] = ',';
+        }
+        /* To support [JACKSON-46], we'll do this:
+         * (Question: should quoting of spaces (etc) still be enabled?)
+         */
+        final char[] quoted = name.asQuotedChars();
+        if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
+            writeRaw(quoted, 0, quoted.length);
+            return;
+        }
+        // we know there's room for at least one more char
+        _outputBuffer[_outputTail++] = '"';
+        // The beef:
+        final int qlen = quoted.length;
+        if ((_outputTail + qlen + 1) >= _outputEnd) {
+            writeRaw(quoted, 0, qlen);
+            // and closing quotes; need room for one more char:
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '"';
+        } else {
+            System.arraycopy(quoted, 0, _outputBuffer, _outputTail, qlen);
+            _outputTail += qlen;
+            _outputBuffer[_outputTail++] = '"';
+        }
+    }
+    
+    /**
+     * Specialized version of <code>_writeFieldName</code>, off-lined
+     * to keep the "fast path" as simple (and hopefully fast) as possible.
+     */
+    protected void _writePPFieldName(String name, boolean commaBefore)
+        throws IOException, JsonGenerationException
+    {
+        if (commaBefore) {
+            _cfgPrettyPrinter.writeObjectEntrySeparator(this);
+        } else {
+            _cfgPrettyPrinter.beforeObjectEntries(this);
+        }
+
+        if (isEnabled(Feature.QUOTE_FIELD_NAMES)) { // standard
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '"';
+            _writeString(name);
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '"';
+        } else { // non-standard, omit quotes
+            _writeString(name);
+        }
+    }
+
+    protected void _writePPFieldName(SerializableString name, boolean commaBefore)
+        throws IOException, JsonGenerationException
+    {
+        if (commaBefore) {
+            _cfgPrettyPrinter.writeObjectEntrySeparator(this);
+        } else {
+            _cfgPrettyPrinter.beforeObjectEntries(this);
+        }
+    
+        final char[] quoted = name.asQuotedChars();
+        if (isEnabled(Feature.QUOTE_FIELD_NAMES)) { // standard
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '"';
+            writeRaw(quoted, 0, quoted.length);
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '"';
+        } else { // non-standard, omit quotes
+            writeRaw(quoted, 0, quoted.length);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Output method implementations, textual
+    /**********************************************************
+     */
+
+    @Override
+    public void writeString(String text)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (text == null) {
+            _writeNull();
+            return;
+        }
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        _writeString(text);
+        // And finally, closing quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+    }
+
+    @Override
+    public void writeString(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        _writeString(text, offset, len);
+        // And finally, closing quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+    }
+
+    @Override
+    public void writeString(SerializableString sstr)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write text value");
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        // Note: copied from writeRaw:
+        char[] text = sstr.asQuotedChars();
+        final int len = text.length;
+        // Only worth buffering if it's a short write?
+        if (len < SHORT_WRITE) {
+            int room = _outputEnd - _outputTail;
+            if (len > room) {
+                _flushBuffer();
+            }
+            System.arraycopy(text, 0, _outputBuffer, _outputTail, len);
+            _outputTail += len;
+        } else {
+            // Otherwise, better just pass through:
+            _flushBuffer();
+            _writer.write(text, 0, len);
+        }
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+    }
+
+    @Override
+    public void writeRawUTF8String(byte[] text, int offset, int length)
+        throws IOException, JsonGenerationException
+    {
+        // could add support for buffering if we really want it...
+        _reportUnsupportedOperation();
+    }
+
+    @Override
+    public void writeUTF8String(byte[] text, int offset, int length)
+        throws IOException, JsonGenerationException
+    {
+        // could add support for buffering if we really want it...
+        _reportUnsupportedOperation();
+    }
+    
+    /*
+    /**********************************************************
+    /* Output method implementations, unprocessed ("raw")
+    /**********************************************************
+     */
+
+    @Override
+    public void writeRaw(String text)
+        throws IOException, JsonGenerationException
+    {
+        // Nothing to check, can just output as is
+        int len = text.length();
+        int room = _outputEnd - _outputTail;
+
+        if (room == 0) {
+            _flushBuffer();
+            room = _outputEnd - _outputTail;
+        }
+        // But would it nicely fit in? If yes, it's easy
+        if (room >= len) {
+            text.getChars(0, len, _outputBuffer, _outputTail);
+            _outputTail += len;
+        } else {
+            writeRawLong(text);
+        }
+    }
+
+    @Override
+    public void writeRaw(String text, int start, int len)
+        throws IOException, JsonGenerationException
+    {
+        // Nothing to check, can just output as is
+        int room = _outputEnd - _outputTail;
+
+        if (room < len) {
+            _flushBuffer();
+            room = _outputEnd - _outputTail;
+        }
+        // But would it nicely fit in? If yes, it's easy
+        if (room >= len) {
+            text.getChars(start, start+len, _outputBuffer, _outputTail);
+            _outputTail += len;
+        } else {            	
+            writeRawLong(text.substring(start, start+len));
+        }
+    }
+
+    // @since 2.1
+    @Override
+    public void writeRaw(SerializableString text) throws IOException, JsonGenerationException {
+        writeRaw(text.getValue());
+    }
+    
+    @Override
+    public void writeRaw(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        // Only worth buffering if it's a short write?
+        if (len < SHORT_WRITE) {
+            int room = _outputEnd - _outputTail;
+            if (len > room) {
+                _flushBuffer();
+            }
+            System.arraycopy(text, offset, _outputBuffer, _outputTail, len);
+            _outputTail += len;
+            return;
+        }
+        // Otherwise, better just pass through:
+        _flushBuffer();
+        _writer.write(text, offset, len);
+    }
+
+    @Override
+    public void writeRaw(char c)
+        throws IOException, JsonGenerationException
+    {
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = c;
+    }
+
+    private void writeRawLong(String text)
+        throws IOException, JsonGenerationException
+    {
+        int room = _outputEnd - _outputTail;
+        // If not, need to do it by looping
+        text.getChars(0, room, _outputBuffer, _outputTail);
+        _outputTail += room;
+        _flushBuffer();
+        int offset = room;
+        int len = text.length() - room;
+
+        while (len > _outputEnd) {
+            int amount = _outputEnd;
+            text.getChars(offset, offset+amount, _outputBuffer, 0);
+            _outputHead = 0;
+            _outputTail = amount;
+            _flushBuffer();
+            offset += amount;
+            len -= amount;
+        }
+        // And last piece (at most length of buffer)
+        text.getChars(offset, offset+len, _outputBuffer, 0);
+        _outputHead = 0;
+        _outputTail = len;
+    }
+
+    /*
+    /**********************************************************
+    /* Output method implementations, base64-encoded binary
+    /**********************************************************
+     */
+
+    @Override
+    public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write binary value");
+        // Starting quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        _writeBinary(b64variant, data, offset, offset+len);
+        // and closing quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+    }
+
+    @Override
+    public int writeBinary(Base64Variant b64variant,
+            InputStream data, int dataLength)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write binary value");
+        // Starting quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        byte[] encodingBuffer = _ioContext.allocBase64Buffer();
+        int bytes;
+        try {
+            if (dataLength < 0) { // length unknown
+                bytes = _writeBinary(b64variant, data, encodingBuffer);
+            } else {
+                int missing = _writeBinary(b64variant, data, encodingBuffer, dataLength);
+                if (missing > 0) {
+                    _reportError("Too few bytes available: missing "+missing+" bytes (out of "+dataLength+")");
+                }
+                bytes = dataLength;
+            }
+        } finally {
+            _ioContext.releaseBase64Buffer(encodingBuffer);
+        }
+        // and closing quotes
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        return bytes;
+    }
+    
+    /*
+    /**********************************************************
+    /* Output method implementations, primitive
+    /**********************************************************
+     */
+
+    @Override
+    public void writeNumber(short s)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedShort(s);
+            return;
+        }
+        // up to 5 digits and possible minus sign
+        if ((_outputTail + 6) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputTail = NumberOutput.outputInt(s, _outputBuffer, _outputTail);
+    }
+
+    private void _writeQuotedShort(short s) throws IOException {
+        if ((_outputTail + 8) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        _outputTail = NumberOutput.outputInt(s, _outputBuffer, _outputTail);
+        _outputBuffer[_outputTail++] = '"';
+    }    
+
+    @Override
+    public void writeNumber(int i)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedInt(i);
+            return;
+        }
+        // up to 10 digits and possible minus sign
+        if ((_outputTail + 11) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
+    }
+
+    private void _writeQuotedInt(int i) throws IOException {
+        if ((_outputTail + 13) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
+        _outputBuffer[_outputTail++] = '"';
+    }    
+
+    @Override
+    public void writeNumber(long l)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedLong(l);
+            return;
+        }
+        if ((_outputTail + 21) >= _outputEnd) {
+            // up to 20 digits, minus sign
+            _flushBuffer();
+        }
+        _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
+    }
+
+    private void _writeQuotedLong(long l) throws IOException {
+        if ((_outputTail + 23) >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
+        _outputBuffer[_outputTail++] = '"';
+    }
+
+    // !!! 05-Aug-2008, tatus: Any ways to optimize these?
+
+    @Override
+    public void writeNumber(BigInteger value)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (value == null) {
+            _writeNull();
+        } else if (_cfgNumbersAsStrings) {
+            _writeQuotedRaw(value);
+        } else {
+            writeRaw(value.toString());
+        }
+    }
+
+    
+    @Override
+    public void writeNumber(double d)
+        throws IOException, JsonGenerationException
+    {
+        if (_cfgNumbersAsStrings ||
+            // [JACKSON-139]
+            (((Double.isNaN(d) || Double.isInfinite(d))
+                && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
+            writeString(String.valueOf(d));
+            return;
+        }
+        // What is the max length for doubles? 40 chars?
+        _verifyValueWrite("write number");
+        writeRaw(String.valueOf(d));
+    }
+
+    @Override
+    public void writeNumber(float f)
+        throws IOException, JsonGenerationException
+    {
+        if (_cfgNumbersAsStrings ||
+            // [JACKSON-139]
+            (((Float.isNaN(f) || Float.isInfinite(f))
+                && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
+            writeString(String.valueOf(f));
+            return;
+        }
+        // What is the max length for floats?
+        _verifyValueWrite("write number");
+        writeRaw(String.valueOf(f));
+    }
+
+    @Override
+    public void writeNumber(BigDecimal value)
+        throws IOException, JsonGenerationException
+    {
+        // Don't really know max length for big decimal, no point checking
+        _verifyValueWrite("write number");
+        if (value == null) {
+            _writeNull();
+        } else if (_cfgNumbersAsStrings) {
+            _writeQuotedRaw(value);
+        } else {
+            writeRaw(value.toString());
+        }
+    }
+
+    @Override
+    public void writeNumber(String encodedValue)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write number");
+        if (_cfgNumbersAsStrings) {
+            _writeQuotedRaw(encodedValue);            
+        } else {
+            writeRaw(encodedValue);
+        }
+    }
+
+    private void _writeQuotedRaw(Object value) throws IOException
+    {
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+        writeRaw(value.toString());
+        if (_outputTail >= _outputEnd) {
+            _flushBuffer();
+        }
+        _outputBuffer[_outputTail++] = '"';
+    }
+    
+    @Override
+    public void writeBoolean(boolean state)
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write boolean value");
+        if ((_outputTail + 5) >= _outputEnd) {
+            _flushBuffer();
+        }
+        int ptr = _outputTail;
+        char[] buf = _outputBuffer;
+        if (state) {
+            buf[ptr] = 't';
+            buf[++ptr] = 'r';
+            buf[++ptr] = 'u';
+            buf[++ptr] = 'e';
+        } else {
+            buf[ptr] = 'f';
+            buf[++ptr] = 'a';
+            buf[++ptr] = 'l';
+            buf[++ptr] = 's';
+            buf[++ptr] = 'e';
+        }
+        _outputTail = ptr+1;
+    }
+
+    @Override
+    public void writeNull()
+        throws IOException, JsonGenerationException
+    {
+        _verifyValueWrite("write null value");
+        _writeNull();
+    }
+
+    /*
+    /**********************************************************
+    /* Implementations for other methods
+    /**********************************************************
+     */
+
+    @Override
+    protected void _verifyValueWrite(String typeMsg)
+        throws IOException, JsonGenerationException
+    {
+        int status = _writeContext.writeValue();
+        if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
+            _reportError("Can not "+typeMsg+", expecting field name");
+        }
+        if (_cfgPrettyPrinter == null) {
+            char c;
+            switch (status) {
+            case JsonWriteContext.STATUS_OK_AFTER_COMMA:
+                c = ',';
+                break;
+            case JsonWriteContext.STATUS_OK_AFTER_COLON:
+                c = ':';
+                break;
+            case JsonWriteContext.STATUS_OK_AFTER_SPACE: // root-value separator
+                if (_rootValueSeparator != null) {
+                    writeRaw(_rootValueSeparator.getValue());
+                }
+                return;
+            case JsonWriteContext.STATUS_OK_AS_IS:
+            default:
+                return;
+            }
+            if (_outputTail >= _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail] = c;
+            ++_outputTail;
+            return;
+        }
+        // Otherwise, pretty printer knows what to do...
+        _verifyPrettyValueWrite(typeMsg, status);
+    }
+
+    protected void _verifyPrettyValueWrite(String typeMsg, int status)
+        throws IOException, JsonGenerationException
+    {
+        // If we have a pretty printer, it knows what to do:
+        switch (status) {
+        case JsonWriteContext.STATUS_OK_AFTER_COMMA: // array
+            _cfgPrettyPrinter.writeArrayValueSeparator(this);
+            break;
+        case JsonWriteContext.STATUS_OK_AFTER_COLON:
+            _cfgPrettyPrinter.writeObjectFieldValueSeparator(this);
+            break;
+        case JsonWriteContext.STATUS_OK_AFTER_SPACE:
+            _cfgPrettyPrinter.writeRootValueSeparator(this);
+            break;
+        case JsonWriteContext.STATUS_OK_AS_IS:
+            // First entry, but of which context?
+            if (_writeContext.inArray()) {
+                _cfgPrettyPrinter.beforeArrayValues(this);
+            } else if (_writeContext.inObject()) {
+                _cfgPrettyPrinter.beforeObjectEntries(this);
+            }
+            break;
+        default:
+            _throwInternal();
+            break;
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Low-level output handling
+    /**********************************************************
+     */
+
+    @Override
+    public void flush()
+        throws IOException
+    {
+        _flushBuffer();
+        if (_writer != null) {
+            if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
+                _writer.flush();
+            }
+        }
+    }
+
+    @Override
+    public void close()
+        throws IOException
+    {
+        super.close();
+
+        /* 05-Dec-2008, tatu: To add [JACKSON-27], need to close open
+         *   scopes.
+         */
+        // First: let's see that we still have buffers...
+        if (_outputBuffer != null
+            && isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
+            while (true) {
+                JsonStreamContext ctxt = getOutputContext();
+                if (ctxt.inArray()) {
+                    writeEndArray();
+                } else if (ctxt.inObject()) {
+                    writeEndObject();
+                } else {
+                    break;
+                }
+            }
+        }
+        _flushBuffer();
+
+        /* 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
+         *   feature is enabled.
+         *   One downside: when using UTF8Writer, underlying buffer(s)
+         *   may not be properly recycled if we don't close the writer.
+         */
+        if (_writer != null) {
+            if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
+                _writer.close();
+            } else  if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
+                // If we can't close it, we should at least flush
+                _writer.flush();
+            }
+        }
+        // Internal buffer(s) generator has can now be released as well
+        _releaseBuffers();
+    }
+
+    @Override
+    protected void _releaseBuffers()
+    {
+        char[] buf = _outputBuffer;
+        if (buf != null) {
+            _outputBuffer = null;
+            _ioContext.releaseConcatBuffer(buf);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing; text, default
+    /**********************************************************
+     */
+
+    private void _writeString(String text)
+        throws IOException, JsonGenerationException
+    {
+        /* One check first: if String won't fit in the buffer, let's
+         * segment writes. No point in extending buffer to huge sizes
+         * (like if someone wants to include multi-megabyte base64
+         * encoded stuff or such)
+         */
+        final int len = text.length();
+        if (len > _outputEnd) { // Let's reserve space for entity at begin/end
+            _writeLongString(text);
+            return;
+        }
+
+        // Ok: we know String will fit in buffer ok
+        // But do we need to flush first?
+        if ((_outputTail + len) > _outputEnd) {
+            _flushBuffer();
+        }
+        text.getChars(0, len, _outputBuffer, _outputTail);
+
+        if (_characterEscapes != null) {
+            _writeStringCustom(len);
+        } else if (_maximumNonEscapedChar != 0) {
+            _writeStringASCII(len, _maximumNonEscapedChar);
+        } else {
+            _writeString2(len);
+        }
+    }
+
+    private void _writeString2(final int len)
+        throws IOException, JsonGenerationException
+    {
+        // And then we'll need to verify need for escaping etc:
+        int end = _outputTail + len;
+        final int[] escCodes = _outputEscapes;
+        final int escLen = escCodes.length;
+
+        output_loop:
+        while (_outputTail < end) {
+            // Fast loop for chars not needing escaping
+            escape_loop:
+            while (true) {
+                char c = _outputBuffer[_outputTail];
+                if (c < escLen && escCodes[c] != 0) {
+                    break escape_loop;
+                }
+                if (++_outputTail >= end) {
+                    break output_loop;
+                }
+            }
+
+            // Ok, bumped into something that needs escaping.
+            /* First things first: need to flush the buffer.
+             * Inlined, as we don't want to lose tail pointer
+             */
+            int flushLen = (_outputTail - _outputHead);
+            if (flushLen > 0) {
+                _writer.write(_outputBuffer, _outputHead, flushLen);
+            }
+            /* In any case, tail will be the new start, so hopefully
+             * we have room now.
+             */
+            char c = _outputBuffer[_outputTail++];
+            _prependOrWriteCharacterEscape(c, escCodes[c]);
+        }
+    }
+
+    /**
+     * Method called to write "long strings", strings whose length exceeds
+     * output buffer length.
+     */
+    private void _writeLongString(String text)
+        throws IOException, JsonGenerationException
+    {
+        // First things first: let's flush the buffer to get some more room
+        _flushBuffer();
+
+        // Then we can write 
+        final int textLen = text.length();
+        int offset = 0;
+        do {
+            int max = _outputEnd;
+            int segmentLen = ((offset + max) > textLen)
+                ? (textLen - offset) : max;
+            text.getChars(offset, offset+segmentLen, _outputBuffer, 0);
+            if (_characterEscapes != null) {
+                _writeSegmentCustom(segmentLen);
+            } else if (_maximumNonEscapedChar != 0) {
+                _writeSegmentASCII(segmentLen, _maximumNonEscapedChar);
+            } else {
+                _writeSegment(segmentLen);
+            }
+            offset += segmentLen;
+        } while (offset < textLen);
+    }
+
+    /**
+     * Method called to output textual context which has been copied
+     * to the output buffer prior to call. If any escaping is needed,
+     * it will also be handled by the method.
+     *<p>
+     * Note: when called, textual content to write is within output
+     * buffer, right after buffered content (if any). That's why only
+     * length of that text is passed, as buffer and offset are implied.
+     */
+    private void _writeSegment(int end)
+        throws IOException, JsonGenerationException
+    {
+        final int[] escCodes = _outputEscapes;
+        final int escLen = escCodes.length;
+    
+        int ptr = 0;
+        int start = ptr;
+
+        output_loop:
+        while (ptr < end) {
+            // Fast loop for chars not needing escaping
+            char c;
+            while (true) {
+                c = _outputBuffer[ptr];
+                if (c < escLen && escCodes[c] != 0) {
+                    break;
+                }
+                if (++ptr >= end) {
+                    break;
+                }
+            }
+
+            // Ok, bumped into something that needs escaping.
+            /* First things first: need to flush the buffer.
+             * Inlined, as we don't want to lose tail pointer
+             */
+            int flushLen = (ptr - start);
+            if (flushLen > 0) {
+                _writer.write(_outputBuffer, start, flushLen);
+                if (ptr >= end) {
+                    break output_loop;
+                }
+            }
+            ++ptr;
+            // So; either try to prepend (most likely), or write directly:
+            start = _prependOrWriteCharacterEscape(_outputBuffer, ptr, end, c, escCodes[c]);
+        }
+    }
+    
+    /**
+     * This method called when the string content is already in
+     * a char buffer, and need not be copied for processing.
+     */
+    private void _writeString(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        if (_characterEscapes != null) {
+            _writeStringCustom(text, offset, len);
+            return;
+        }
+        if (_maximumNonEscapedChar != 0) {
+            _writeStringASCII(text, offset, len, _maximumNonEscapedChar);
+            return;
+        }
+        
+        /* Let's just find longest spans of non-escapable
+         * content, and for each see if it makes sense
+         * to copy them, or write through
+         */
+        len += offset; // -> len marks the end from now on
+        final int[] escCodes = _outputEscapes;
+        final int escLen = escCodes.length;
+        while (offset < len) {
+            int start = offset;
+
+            while (true) {
+                char c = text[offset];
+                if (c < escLen && escCodes[c] != 0) {
+                    break;
+                }
+                if (++offset >= len) {
+                    break;
+                }
+            }
+
+            // Short span? Better just copy it to buffer first:
+            int newAmount = offset - start;
+            if (newAmount < SHORT_WRITE) {
+                // Note: let's reserve room for escaped char (up to 6 chars)
+                if ((_outputTail + newAmount) > _outputEnd) {
+                    _flushBuffer();
+                }
+                if (newAmount > 0) {
+                    System.arraycopy(text, start, _outputBuffer, _outputTail, newAmount);
+                    _outputTail += newAmount;
+                }
+            } else { // Nope: better just write through
+                _flushBuffer();
+                _writer.write(text, start, newAmount);
+            }
+            // Was this the end?
+            if (offset >= len) { // yup
+                break;
+            }
+            // Nope, need to escape the char.
+            char c = text[offset++];
+            _appendCharacterEscape(c, escCodes[c]);          
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, text segment
+    /* with additional escaping (ASCII or such)
+    /**********************************************************
+     */
+
+    /* Same as "_writeString2()", except needs additional escaping
+     * for subset of characters
+     */
+    private void _writeStringASCII(final int len, final int maxNonEscaped)
+        throws IOException, JsonGenerationException
+    {
+        // And then we'll need to verify need for escaping etc:
+        int end = _outputTail + len;
+        final int[] escCodes = _outputEscapes;
+        final int escLimit = Math.min(escCodes.length, maxNonEscaped+1);
+        int escCode = 0;
+        
+        output_loop:
+        while (_outputTail < end) {
+            char c;
+            // Fast loop for chars not needing escaping
+            escape_loop:
+            while (true) {
+                c = _outputBuffer[_outputTail];
+                if (c < escLimit) {
+                    escCode = escCodes[c];
+                    if (escCode != 0) {
+                        break escape_loop;
+                    }
+                } else if (c > maxNonEscaped) {
+                    escCode = CharacterEscapes.ESCAPE_STANDARD;
+                    break escape_loop;
+                }
+                if (++_outputTail >= end) {
+                    break output_loop;
+                }
+            }
+            int flushLen = (_outputTail - _outputHead);
+            if (flushLen > 0) {
+                _writer.write(_outputBuffer, _outputHead, flushLen);
+            }
+            ++_outputTail;
+            _prependOrWriteCharacterEscape(c, escCode);
+        }
+    }
+
+    private void _writeSegmentASCII(int end, final int maxNonEscaped)
+        throws IOException, JsonGenerationException
+    {
+        final int[] escCodes = _outputEscapes;
+        final int escLimit = Math.min(escCodes.length, maxNonEscaped+1);
+    
+        int ptr = 0;
+        int escCode = 0;
+        int start = ptr;
+    
+        output_loop:
+        while (ptr < end) {
+            // Fast loop for chars not needing escaping
+            char c;
+            while (true) {
+                c = _outputBuffer[ptr];
+                if (c < escLimit) {
+                    escCode = escCodes[c];
+                    if (escCode != 0) {
+                        break;
+                    }
+                } else if (c > maxNonEscaped) {
+                    escCode = CharacterEscapes.ESCAPE_STANDARD;
+                    break;
+                }
+                if (++ptr >= end) {
+                    break;
+                }
+            }
+            int flushLen = (ptr - start);
+            if (flushLen > 0) {
+                _writer.write(_outputBuffer, start, flushLen);
+                if (ptr >= end) {
+                    break output_loop;
+                }
+            }
+            ++ptr;
+            start = _prependOrWriteCharacterEscape(_outputBuffer, ptr, end, c, escCode);
+        }
+    }
+
+    private void _writeStringASCII(char[] text, int offset, int len,
+            final int maxNonEscaped)
+        throws IOException, JsonGenerationException
+    {
+        len += offset; // -> len marks the end from now on
+        final int[] escCodes = _outputEscapes;
+        final int escLimit = Math.min(escCodes.length, maxNonEscaped+1);
+
+        int escCode = 0;
+        
+        while (offset < len) {
+            int start = offset;
+            char c;
+            
+            while (true) {
+                c = text[offset];
+                if (c < escLimit) {
+                    escCode = escCodes[c];
+                    if (escCode != 0) {
+                        break;
+                    }
+                } else if (c > maxNonEscaped) {
+                    escCode = CharacterEscapes.ESCAPE_STANDARD;
+                    break;
+                }
+                if (++offset >= len) {
+                    break;
+                }
+            }
+
+            // Short span? Better just copy it to buffer first:
+            int newAmount = offset - start;
+            if (newAmount < SHORT_WRITE) {
+                // Note: let's reserve room for escaped char (up to 6 chars)
+                if ((_outputTail + newAmount) > _outputEnd) {
+                    _flushBuffer();
+                }
+                if (newAmount > 0) {
+                    System.arraycopy(text, start, _outputBuffer, _outputTail, newAmount);
+                    _outputTail += newAmount;
+                }
+            } else { // Nope: better just write through
+                _flushBuffer();
+                _writer.write(text, start, newAmount);
+            }
+            // Was this the end?
+            if (offset >= len) { // yup
+                break;
+            }
+            // Nope, need to escape the char.
+            ++offset;
+            _appendCharacterEscape(c, escCode);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, text segment
+    /* with custom escaping (possibly coupling with ASCII limits)
+    /**********************************************************
+     */
+
+    /* Same as "_writeString2()", except needs additional escaping
+     * for subset of characters
+     */
+    private void _writeStringCustom(final int len)
+        throws IOException, JsonGenerationException
+    {
+        // And then we'll need to verify need for escaping etc:
+        int end = _outputTail + len;
+        final int[] escCodes = _outputEscapes;
+        final int maxNonEscaped = (_maximumNonEscapedChar < 1) ? 0xFFFF : _maximumNonEscapedChar;
+        final int escLimit = Math.min(escCodes.length, maxNonEscaped+1);
+        int escCode = 0;
+        final CharacterEscapes customEscapes = _characterEscapes;
+
+        output_loop:
+        while (_outputTail < end) {
+            char c;
+            // Fast loop for chars not needing escaping
+            escape_loop:
+            while (true) {
+                c = _outputBuffer[_outputTail];
+                if (c < escLimit) {
+                    escCode = escCodes[c];
+                    if (escCode != 0) {
+                        break escape_loop;
+                    }
+                } else if (c > maxNonEscaped) {
+                    escCode = CharacterEscapes.ESCAPE_STANDARD;
+                    break escape_loop;
+                } else {
+                    if ((_currentEscape = customEscapes.getEscapeSequence(c)) != null) {
+                        escCode = CharacterEscapes.ESCAPE_CUSTOM;
+                        break escape_loop;
+                    }
+                }
+                if (++_outputTail >= end) {
+                    break output_loop;
+                }
+            }
+            int flushLen = (_outputTail - _outputHead);
+            if (flushLen > 0) {
+                _writer.write(_outputBuffer, _outputHead, flushLen);
+            }
+            ++_outputTail;
+            _prependOrWriteCharacterEscape(c, escCode);
+        }
+    }
+
+    private void _writeSegmentCustom(int end)
+        throws IOException, JsonGenerationException
+    {
+        final int[] escCodes = _outputEscapes;
+        final int maxNonEscaped = (_maximumNonEscapedChar < 1) ? 0xFFFF : _maximumNonEscapedChar;
+        final int escLimit = Math.min(escCodes.length, maxNonEscaped+1);
+        final CharacterEscapes customEscapes = _characterEscapes;
+    
+        int ptr = 0;
+        int escCode = 0;
+        int start = ptr;
+    
+        output_loop:
+        while (ptr < end) {
+            // Fast loop for chars not needing escaping
+            char c;
+            while (true) {
+                c = _outputBuffer[ptr];
+                if (c < escLimit) {
+                    escCode = escCodes[c];
+                    if (escCode != 0) {
+                        break;
+                    }
+                } else if (c > maxNonEscaped) {
+                    escCode = CharacterEscapes.ESCAPE_STANDARD;
+                    break;
+                } else {
+                    if ((_currentEscape = customEscapes.getEscapeSequence(c)) != null) {
+                        escCode = CharacterEscapes.ESCAPE_CUSTOM;
+                        break;
+                    }
+                }
+                if (++ptr >= end) {
+                    break;
+                }
+            }
+            int flushLen = (ptr - start);
+            if (flushLen > 0) {
+                _writer.write(_outputBuffer, start, flushLen);
+                if (ptr >= end) {
+                    break output_loop;
+                }
+            }
+            ++ptr;
+            start = _prependOrWriteCharacterEscape(_outputBuffer, ptr, end, c, escCode);
+        }
+    }
+
+    private void _writeStringCustom(char[] text, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        len += offset; // -> len marks the end from now on
+        final int[] escCodes = _outputEscapes;
+        final int maxNonEscaped = (_maximumNonEscapedChar < 1) ? 0xFFFF : _maximumNonEscapedChar;
+        final int escLimit = Math.min(escCodes.length, maxNonEscaped+1);
+        final CharacterEscapes customEscapes = _characterEscapes;
+
+        int escCode = 0;
+        
+        while (offset < len) {
+            int start = offset;
+            char c;
+            
+            while (true) {
+                c = text[offset];
+                if (c < escLimit) {
+                    escCode = escCodes[c];
+                    if (escCode != 0) {
+                        break;
+                    }
+                } else if (c > maxNonEscaped) {
+                    escCode = CharacterEscapes.ESCAPE_STANDARD;
+                    break;
+                } else {
+                    if ((_currentEscape = customEscapes.getEscapeSequence(c)) != null) {
+                        escCode = CharacterEscapes.ESCAPE_CUSTOM;
+                        break;
+                    }
+                }
+                if (++offset >= len) {
+                    break;
+                }
+            }
+    
+            // Short span? Better just copy it to buffer first:
+            int newAmount = offset - start;
+            if (newAmount < SHORT_WRITE) {
+                // Note: let's reserve room for escaped char (up to 6 chars)
+                if ((_outputTail + newAmount) > _outputEnd) {
+                    _flushBuffer();
+                }
+                if (newAmount > 0) {
+                    System.arraycopy(text, start, _outputBuffer, _outputTail, newAmount);
+                    _outputTail += newAmount;
+                }
+            } else { // Nope: better just write through
+                _flushBuffer();
+                _writer.write(text, start, newAmount);
+            }
+            // Was this the end?
+            if (offset >= len) { // yup
+                break;
+            }
+            // Nope, need to escape the char.
+            ++offset;
+            _appendCharacterEscape(c, escCode);
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing; binary
+    /**********************************************************
+     */
+    
+    protected void _writeBinary(Base64Variant b64variant, byte[] input, int inputPtr, final int inputEnd)
+        throws IOException, JsonGenerationException
+    {
+        // Encoding is by chunks of 3 input, 4 output chars, so:
+        int safeInputEnd = inputEnd - 3;
+        // Let's also reserve room for possible (and quoted) lf char each round
+        int safeOutputEnd = _outputEnd - 6;
+        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+
+        // Ok, first we loop through all full triplets of data:
+        while (inputPtr <= safeInputEnd) {
+            if (_outputTail > safeOutputEnd) { // need to flush
+                _flushBuffer();
+            }
+            // First, mash 3 bytes into lsb of 32-bit int
+            int b24 = ((int) input[inputPtr++]) << 8;
+            b24 |= ((int) input[inputPtr++]) & 0xFF;
+            b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF);
+            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
+            if (--chunksBeforeLF <= 0) {
+                // note: must quote in JSON value
+                _outputBuffer[_outputTail++] = '\\';
+                _outputBuffer[_outputTail++] = 'n';
+                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+            }
+        }
+
+        // And then we may have 1 or 2 leftover bytes to encode
+        int inputLeft = inputEnd - inputPtr; // 0, 1 or 2
+        if (inputLeft > 0) { // yes, but do we have room for output?
+            if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
+                _flushBuffer();
+            }
+            int b24 = ((int) input[inputPtr++]) << 16;
+            if (inputLeft == 2) {
+                b24 |= (((int) input[inputPtr++]) & 0xFF) << 8;
+            }
+            _outputTail = b64variant.encodeBase64Partial(b24, inputLeft, _outputBuffer, _outputTail);
+        }
+    }
+
+    // write-method called when length is definitely known
+    protected int _writeBinary(Base64Variant b64variant,
+            InputStream data, byte[] readBuffer, int bytesLeft)
+        throws IOException, JsonGenerationException
+    {
+        int inputPtr = 0;
+        int inputEnd = 0;
+        int lastFullOffset = -3;       
+        
+        // Let's also reserve room for possible (and quoted) lf char each round
+        int safeOutputEnd = _outputEnd - 6;
+        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+
+        while (bytesLeft > 2) { // main loop for full triplets
+            if (inputPtr > lastFullOffset) {
+                inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
+                inputPtr = 0;
+                if (inputEnd < 3) { // required to try to read to have at least 3 bytes
+                    break;
+                }
+                lastFullOffset = inputEnd-3;
+            }
+            if (_outputTail > safeOutputEnd) { // need to flush
+                _flushBuffer();
+            }
+            int b24 = ((int) readBuffer[inputPtr++]) << 8;
+            b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
+            b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
+            bytesLeft -= 3;
+            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
+            if (--chunksBeforeLF <= 0) {
+                _outputBuffer[_outputTail++] = '\\';
+                _outputBuffer[_outputTail++] = 'n';
+                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+            }
+        }
+        
+        // And then we may have 1 or 2 leftover bytes to encode
+        if (bytesLeft > 0) {
+            inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
+            inputPtr = 0;
+            if (inputEnd > 0) { // yes, but do we have room for output?
+                if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
+                    _flushBuffer();
+                }
+                int b24 = ((int) readBuffer[inputPtr++]) << 16;
+                int amount;
+                if (inputPtr < inputEnd) {
+                    b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
+                    amount = 2;
+                } else {
+                    amount = 1;
+                }
+                _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
+                bytesLeft -= amount;
+            }
+        }
+        return bytesLeft;
+    }
+    
+    // write method when length is unknown
+    protected int _writeBinary(Base64Variant b64variant,
+            InputStream data, byte[] readBuffer)
+        throws IOException, JsonGenerationException
+    {
+        int inputPtr = 0;
+        int inputEnd = 0;
+        int lastFullOffset = -3;
+        int bytesDone = 0;
+        
+        // Let's also reserve room for possible (and quoted) LF char each round
+        int safeOutputEnd = _outputEnd - 6;
+        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+
+        // Ok, first we loop through all full triplets of data:
+        while (true) {
+            if (inputPtr > lastFullOffset) { // need to load more
+                inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, readBuffer.length);
+                inputPtr = 0;
+                if (inputEnd < 3) { // required to try to read to have at least 3 bytes
+                    break;
+                }
+                lastFullOffset = inputEnd-3;
+            }
+            if (_outputTail > safeOutputEnd) { // need to flush
+                _flushBuffer();
+            }
+            // First, mash 3 bytes into lsb of 32-bit int
+            int b24 = ((int) readBuffer[inputPtr++]) << 8;
+            b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
+            b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
+            bytesDone += 3;
+            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
+            if (--chunksBeforeLF <= 0) {
+                _outputBuffer[_outputTail++] = '\\';
+                _outputBuffer[_outputTail++] = 'n';
+                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+            }
+        }
+
+        // And then we may have 1 or 2 leftover bytes to encode
+        if (inputPtr < inputEnd) { // yes, but do we have room for output?
+            if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
+                _flushBuffer();
+            }
+            int b24 = ((int) readBuffer[inputPtr++]) << 16;
+            int amount = 1;
+            if (inputPtr < inputEnd) {
+                b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
+                amount = 2;
+            }
+            bytesDone += amount;
+            _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
+        }
+        return bytesDone;
+    }
+    
+    private int _readMore(InputStream in,
+            byte[] readBuffer, int inputPtr, int inputEnd,
+            int maxRead) throws IOException
+    {
+        // anything to shift to front?
+        int i = 0;
+        while (inputPtr < inputEnd) {
+            readBuffer[i++]  = readBuffer[inputPtr++];
+        }
+        inputPtr = 0;
+        inputEnd = i;
+        maxRead = Math.min(maxRead, readBuffer.length);
+        
+        do {
+            int length = maxRead - inputEnd;
+            if (length == 0) {
+                break;
+            }
+            int count = in.read(readBuffer, inputEnd, length);            
+            if (count < 0) {
+                return inputEnd;
+            }
+            inputEnd += count;
+        } while (inputEnd < 3);
+        return inputEnd;
+    }
+    
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, other
+    /**********************************************************
+     */
+    
+    private void _writeNull() throws IOException
+    {
+        if ((_outputTail + 4) >= _outputEnd) {
+            _flushBuffer();
+        }
+        int ptr = _outputTail;
+        char[] buf = _outputBuffer;
+        buf[ptr] = 'n';
+        buf[++ptr] = 'u';
+        buf[++ptr] = 'l';
+        buf[++ptr] = 'l';
+        _outputTail = ptr+1;
+    }
+        
+    /*
+    /**********************************************************
+    /* Internal methods, low-level writing, escapes
+    /**********************************************************
+     */
+
+    /**
+     * Method called to try to either prepend character escape at front of
+     * given buffer; or if not possible, to write it out directly.
+     * Uses head and tail pointers (and updates as necessary)
+     */
+    private void _prependOrWriteCharacterEscape(char ch, int escCode)
+        throws IOException, JsonGenerationException
+    {
+        if (escCode >= 0) { // \\N (2 char)
+            if (_outputTail >= 2) { // fits, just prepend
+                int ptr = _outputTail - 2;
+                _outputHead = ptr;
+                _outputBuffer[ptr++] = '\\';
+                _outputBuffer[ptr] = (char) escCode;
+                return;
+            }
+            // won't fit, write
+            char[] buf = _entityBuffer;
+            if (buf == null) {
+                buf = _allocateEntityBuffer();
+            }
+            _outputHead = _outputTail;
+            buf[1] = (char) escCode;
+            _writer.write(buf, 0, 2);
+            return;
+        }
+        if (escCode != CharacterEscapes.ESCAPE_CUSTOM) { // std, \\uXXXX
+            if (_outputTail >= 6) { // fits, prepend to buffer
+                char[] buf = _outputBuffer;
+                int ptr = _outputTail - 6;
+                _outputHead = ptr;
+                buf[ptr] = '\\';
+                buf[++ptr] = 'u';
+                // We know it's a control char, so only the last 2 chars are non-0
+                if (ch > 0xFF) { // beyond 8 bytes
+                    int hi = (ch >> 8) & 0xFF;
+                    buf[++ptr] = HEX_CHARS[hi >> 4];
+                    buf[++ptr] = HEX_CHARS[hi & 0xF];
+                    ch &= 0xFF;
+                } else {
+                    buf[++ptr] = '0';
+                    buf[++ptr] = '0';
+                }
+                buf[++ptr] = HEX_CHARS[ch >> 4];
+                buf[++ptr] = HEX_CHARS[ch & 0xF];
+                return;
+            }
+            // won't fit, flush and write
+            char[] buf = _entityBuffer;
+            if (buf == null) {
+                buf = _allocateEntityBuffer();
+            }
+            _outputHead = _outputTail;
+            if (ch > 0xFF) { // beyond 8 bytes
+                int hi = (ch >> 8) & 0xFF;
+                int lo = ch & 0xFF;
+                buf[10] = HEX_CHARS[hi >> 4];
+                buf[11] = HEX_CHARS[hi & 0xF];
+                buf[12] = HEX_CHARS[lo >> 4];
+                buf[13] = HEX_CHARS[lo & 0xF];
+                _writer.write(buf, 8, 6);
+            } else { // We know it's a control char, so only the last 2 chars are non-0
+                buf[6] = HEX_CHARS[ch >> 4];
+                buf[7] = HEX_CHARS[ch & 0xF];
+                _writer.write(buf, 2, 6);
+            }
+            return;
+        }
+        String escape;
+
+        if (_currentEscape == null) {
+            escape = _characterEscapes.getEscapeSequence(ch).getValue();
+        } else {
+            escape = _currentEscape.getValue();
+            _currentEscape = null;
+        }
+        int len = escape.length();
+        if (_outputTail >= len) { // fits in, prepend
+            int ptr = _outputTail - len;
+            _outputHead = ptr;
+            escape.getChars(0, len, _outputBuffer, ptr);
+            return;
+        }
+        // won't fit, write separately
+        _outputHead = _outputTail;
+        _writer.write(escape);
+    }
+
+    /**
+     * Method called to try to either prepend character escape at front of
+     * given buffer; or if not possible, to write it out directly.
+     * 
+     * @return Pointer to start of prepended entity (if prepended); or 'ptr'
+     *   if not.
+     */
+    private int _prependOrWriteCharacterEscape(char[] buffer, int ptr, int end,
+            char ch, int escCode)
+        throws IOException, JsonGenerationException
+    {
+        if (escCode >= 0) { // \\N (2 char)
+            if (ptr > 1 && ptr < end) { // fits, just prepend
+                ptr -= 2;
+                buffer[ptr] = '\\';
+                buffer[ptr+1] = (char) escCode;
+            } else { // won't fit, write
+                char[] ent = _entityBuffer;
+                if (ent == null) {
+                    ent = _allocateEntityBuffer();
+                }
+                ent[1] = (char) escCode;
+                _writer.write(ent, 0, 2);
+            }
+            return ptr;
+        }
+        if (escCode != CharacterEscapes.ESCAPE_CUSTOM) { // std, \\uXXXX
+            if (ptr > 5 && ptr < end) { // fits, prepend to buffer
+                ptr -= 6;
+                buffer[ptr++] = '\\';
+                buffer[ptr++] = 'u';
+                // We know it's a control char, so only the last 2 chars are non-0
+                if (ch > 0xFF) { // beyond 8 bytes
+                    int hi = (ch >> 8) & 0xFF;
+                    buffer[ptr++] = HEX_CHARS[hi >> 4];
+                    buffer[ptr++] = HEX_CHARS[hi & 0xF];
+                    ch &= 0xFF;
+                } else {
+                    buffer[ptr++] = '0';
+                    buffer[ptr++] = '0';
+                }
+                buffer[ptr++] = HEX_CHARS[ch >> 4];
+                buffer[ptr] = HEX_CHARS[ch & 0xF];
+                ptr -= 5;
+            } else {
+                // won't fit, flush and write
+                char[] ent = _entityBuffer;
+                if (ent == null) {
+                    ent = _allocateEntityBuffer();
+                }
+                _outputHead = _outputTail;
+                if (ch > 0xFF) { // beyond 8 bytes
+                    int hi = (ch >> 8) & 0xFF;
+                    int lo = ch & 0xFF;
+                    ent[10] = HEX_CHARS[hi >> 4];
+                    ent[11] = HEX_CHARS[hi & 0xF];
+                    ent[12] = HEX_CHARS[lo >> 4];
+                    ent[13] = HEX_CHARS[lo & 0xF];
+                    _writer.write(ent, 8, 6);
+                } else { // We know it's a control char, so only the last 2 chars are non-0
+                    ent[6] = HEX_CHARS[ch >> 4];
+                    ent[7] = HEX_CHARS[ch & 0xF];
+                    _writer.write(ent, 2, 6);
+                }
+            }
+            return ptr;
+        }
+        String escape;
+        if (_currentEscape == null) {
+            escape = _characterEscapes.getEscapeSequence(ch).getValue();
+        } else {
+            escape = _currentEscape.getValue();
+            _currentEscape = null;
+        }
+        int len = escape.length();
+        if (ptr >= len && ptr < end) { // fits in, prepend
+            ptr -= len;
+            escape.getChars(0, len, buffer, ptr);
+        } else { // won't fit, write separately
+            _writer.write(escape);
+        }
+        return ptr;
+    }
+
+    /**
+     * Method called to append escape sequence for given character, at the
+     * end of standard output buffer; or if not possible, write out directly.
+     */
+    private void _appendCharacterEscape(char ch, int escCode)
+        throws IOException, JsonGenerationException
+    {
+        if (escCode >= 0) { // \\N (2 char)
+            if ((_outputTail + 2) > _outputEnd) {
+                _flushBuffer();
+            }
+            _outputBuffer[_outputTail++] = '\\';
+            _outputBuffer[_outputTail++] = (char) escCode;
+            return;
+        }
+        if (escCode != CharacterEscapes.ESCAPE_CUSTOM) { // std, \\uXXXX
+            if ((_outputTail + 2) > _outputEnd) {
+                _flushBuffer();
+            }
+            int ptr = _outputTail;
+            char[] buf = _outputBuffer;
+            buf[ptr++] = '\\';
+            buf[ptr++] = 'u';
+            // We know it's a control char, so only the last 2 chars are non-0
+            if (ch > 0xFF) { // beyond 8 bytes
+                int hi = (ch >> 8) & 0xFF;
+                buf[ptr++] = HEX_CHARS[hi >> 4];
+                buf[ptr++] = HEX_CHARS[hi & 0xF];
+                ch &= 0xFF;
+            } else {
+                buf[ptr++] = '0';
+                buf[ptr++] = '0';
+            }
+            buf[ptr++] = HEX_CHARS[ch >> 4];
+            buf[ptr] = HEX_CHARS[ch & 0xF];
+            _outputTail = ptr;
+            return;
+        }
+        String escape;
+        if (_currentEscape == null) {
+            escape = _characterEscapes.getEscapeSequence(ch).getValue();
+        } else {
+            escape = _currentEscape.getValue();
+            _currentEscape = null;
+        }
+        int len = escape.length();
+        if ((_outputTail + len) > _outputEnd) {
+            _flushBuffer();
+            if (len > _outputEnd) { // very very long escape; unlikely but theoretically possible
+                _writer.write(escape);
+                return;
+            }
+        }
+        escape.getChars(0, len, _outputBuffer, _outputTail);
+        _outputTail += len;
+    }
+    
+    private char[] _allocateEntityBuffer()
+    {
+        char[] buf = new char[14];
+        // first 2 chars, non-numeric escapes (like \n)
+        buf[0] = '\\';
+        // next 6; 8-bit escapes (control chars mostly)
+        buf[2] = '\\';
+        buf[3] = 'u';
+        buf[4] = '0';
+        buf[5] = '0';
+        // last 6, beyond 8 bits
+        buf[8] = '\\';
+        buf[9] = 'u';
+        _entityBuffer = buf;
+        return buf;
+    }
+    
+    protected void _flushBuffer() throws IOException
+    {
+        int len = _outputTail - _outputHead;
+        if (len > 0) {
+            int offset = _outputHead;
+            _outputTail = _outputHead = 0;
+            _writer.write(_outputBuffer, offset, len);
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/package-info.java b/src/main/java/com/fasterxml/jackson/core/json/package-info.java
new file mode 100644
index 0000000..7657a3f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * JSON-specific parser and generator implementation classes that
+ * Jackson defines and uses.
+ * Application code should not (need to) use contents of this package;
+ * nor are these implementations likely to be of use for sub-classing.
+ */
+package com.fasterxml.jackson.core.json;
diff --git a/src/main/java/com/fasterxml/jackson/core/package-info.java b/src/main/java/com/fasterxml/jackson/core/package-info.java
new file mode 100644
index 0000000..d30c4fb
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/package-info.java
@@ -0,0 +1,28 @@
+/**
+ * Main public API classes of the core streaming JSON
+ * processor: most importantly {@link com.fasterxml.jackson.core.JsonFactory}
+ * used for constructing
+ * JSON parser ({@link com.fasterxml.jackson.core.JsonParser})
+ * and generator
+ * ({@link com.fasterxml.jackson.core.JsonParser})
+ * instances.
+ * <p>
+ * Public API of the higher-level mapping interfaces ("Mapping API")
+ * is found from the "jackson-databind" bundle, except for following
+ * base interfaces that are defined here:
+ * <ul>
+ *<li>{@link com.fasterxml.jackson.core.TreeNode} is included
+ *within Streaming API to support integration of the Tree Model
+ *(which is based on <code>JsonNode</code>) with the basic
+ *parsers and generators (iff using mapping-supporting factory: which
+ *is part of Mapping API, not core)
+ *  </li>
+ *<li>{@link com.fasterxml.jackson.core.ObjectCodec} is included so that
+ *  reference to the object capable of serializing/deserializing
+ *  Objects to/from JSON (usually, <code>com.fasterxml.jackson.databind.ObjectMapper</code>)
+ *  can be exposed, without adding direct dependency to implementation.
+ *  </li>
+ *</ul>
+ */
+
+package com.fasterxml.jackson.core;
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java b/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java
new file mode 100644
index 0000000..8695cc3
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java
@@ -0,0 +1,1208 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.core.util.InternCache;
+
+/**
+ * A caching symbol table implementation used for canonicalizing JSON field
+ * names (as {@link Name}s which are constructed directly from a byte-based
+ * input source).
+ * Complications arise from trying to do efficient reuse and merging of
+ * symbol tables, to be able to make use of usually shared vocabulary
+ * of subsequent parsing runs.
+ *
+ * @author Tatu Saloranta
+ */
+public final class BytesToNameCanonicalizer
+{
+    protected static final int DEFAULT_TABLE_SIZE = 64;
+
+    /**
+     * Let's not expand symbol tables past some maximum size;
+     * this should protected against OOMEs caused by large documents
+     * with unique (~= random) names.
+     */
+    protected static final int MAX_TABLE_SIZE = 0x10000; // 64k entries == 256k mem
+    
+    /**
+     * Let's only share reasonably sized symbol tables. Max size set to 3/4 of 16k;
+     * this corresponds to 64k main hash index. This should allow for enough distinct
+     * names for almost any case.
+     */
+    final static int MAX_ENTRIES_FOR_REUSE = 6000;
+
+    /**
+     * Also: to thwart attacks based on hash collisions (which may or may not
+     * be cheap to calculate), we will need to detect "too long"
+     * collision chains. Let's start with static value of 255 entries
+     * for the longest legal chain.
+     *<p>
+     * Note: longest chain we have been able to produce without malicious
+     * intent has been 60 (with "com.fasterxml.jackson.core.main.TestWithTonsaSymbols");
+     * our setting should be reasonable here.
+     * 
+     * @since 2.1
+     */
+    final static int MAX_COLL_CHAIN_LENGTH = 255;
+
+    /**
+     * And to support reduce likelihood of accidental collisions causing
+     * exceptions, let's prevent reuse of tables with long collision
+     * overflow lists as well.
+     * 
+     * @since 2.1
+     */
+    final static int MAX_COLL_CHAIN_FOR_REUSE  = 63;
+
+    /**
+     * No point in trying to construct tiny tables, just need to resize
+     * soon.
+     */
+    final static int MIN_HASH_SIZE = 16;
+
+    /**
+     * We will also need to defin
+     */
+    final static int INITIAL_COLLISION_LEN = 32;
+
+    /**
+     * Bucket index is 8 bits, and value 0 is reserved to represent
+     * 'empty' status.
+     */
+    final static int LAST_VALID_BUCKET = 0xFE;
+    
+    /*
+    /**********************************************************
+    /* 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 BytesToNameCanonicalizer _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.
+     * 
+     * @since 2.1
+     */
+    final private int _hashSeed;
+    
+    /*
+    /**********************************************************
+    /* Main table state
+    /**********************************************************
+     */
+
+    /**
+     * Whether canonical symbol Strings are to be intern()ed before added
+     * to the table or not
+     */
+    protected final boolean _intern;
+    
+    // // // First, global information
+
+    /**
+     * Total number of Names in the symbol table;
+     * only used for child tables.
+     */
+    protected int _count;
+
+    /**
+     * We need to keep track of the longest collision list; this is needed
+     * both to indicate problems with attacks and to allow flushing for
+     * other cases.
+     * 
+     * @since 2.1
+     */
+    protected int _longestCollisionList;
+    
+    // // // Then information regarding primary hash array and its
+    // // // matching Name array
+
+    /**
+     * Mask used to truncate 32-bit hash value to current hash array
+     * size; essentially, hash array size - 1 (since hash array sizes
+     * are 2^N).
+     */
+    protected int _mainHashMask;
+
+    /**
+     * Array of 2^N size, which contains combination
+     * of 24-bits of hash (0 to indicate 'empty' slot),
+     * and 8-bit collision bucket index (0 to indicate empty
+     * collision bucket chain; otherwise subtract one from index)
+     */
+    protected int[] _mainHash;
+
+    /**
+     * Array that contains <code>Name</code> instances matching
+     * entries in <code>_mainHash</code>. Contains nulls for unused
+     * entries.
+     */
+    protected Name[] _mainNames;
+
+    // // // Then the collision/spill-over area info
+
+    /**
+     * Array of heads of collision bucket chains; size dynamically
+     */
+    protected Bucket[] _collList;
+
+    /**
+     * Total number of Names in collision buckets (included in
+     * <code>_count</code> along with primary entries)
+     */
+    protected int _collCount;
+
+    /**
+     * Index of the first unused collision bucket entry (== size of
+     * the used portion of collision list): less than
+     * or equal to 0xFF (255), since max number of entries is 255
+     * (8-bit, minus 0 used as 'empty' marker)
+     */
+    protected int _collEnd;
+
+    // // // Info regarding pending rehashing...
+
+    /**
+     * 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 _mainHashShared;
+
+    private boolean _mainNamesShared;
+
+    /**
+     * Flag that indicates whether underlying data structures for
+     * the collision list 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 when adding new collision entries.
+     */
+    private boolean _collListShared;
+
+    /*
+    /**********************************************************
+    /* Life-cycle: constructors
+    /**********************************************************
+     */
+
+    /**
+     * Constructor used for creating per-<code>JsonFactory</code> "root"
+     * symbol tables: ones used for merging and sharing common symbols
+     * 
+     * @param hashSize Initial 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 BytesToNameCanonicalizer(int hashSize, boolean intern, int seed)
+    {
+        _parent = null;
+        _hashSeed = seed;
+        _intern = intern;
+        // Sanity check: let's now allow hash sizes below certain minimum value
+        if (hashSize < MIN_HASH_SIZE) {
+            hashSize = 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 ((hashSize & (hashSize - 1)) != 0) { // only true if it's 2^N
+                int curr = MIN_HASH_SIZE;
+                while (curr < hashSize) {
+                    curr += curr;
+                }
+                hashSize = curr;
+            }
+        }
+        _tableInfo = new AtomicReference<TableInfo>(initTableInfo(hashSize));
+    }
+
+    /**
+     * Constructor used when creating a child instance
+     */
+    private BytesToNameCanonicalizer(BytesToNameCanonicalizer parent, boolean intern, int seed,
+            TableInfo state)
+    {
+        _parent = parent;
+        _hashSeed = seed;
+        _intern = intern;
+        _tableInfo = null; // not used by child tables
+
+        // Then copy shared state
+        _count = state.count;
+        _mainHashMask = state.mainHashMask;
+        _mainHash = state.mainHash;
+        _mainNames = state.mainNames;
+        _collList = state.collList;
+        _collCount = state.collCount;
+        _collEnd = state.collEnd;
+        _longestCollisionList = state.longestCollisionList;
+
+        // and then set other state to reflect sharing status
+        _needRehash = false;
+        _mainHashShared = true;
+        _mainNamesShared = true;
+        _collListShared = true;
+    }
+
+    /*
+        public TableInfo(int count, int mainHashMask, int[] mainHash, Name[] mainNames,
+                Bucket[] collList, int collCount, int collEnd, int longestCollisionList)
+     */
+    private TableInfo initTableInfo(int hashSize)
+    {
+        return new TableInfo(0, // count
+                hashSize - 1, // mainHashMask
+                new int[hashSize], // mainHash
+                new Name[hashSize], // mainNames
+                null, // collList
+                0, // collCount,
+                0, // collEnd
+                0 // longestCollisionList
+        );
+    }
+    
+    /*
+    /**********************************************************
+    /* Life-cycle: factory methods, merging
+    /**********************************************************
+     */
+    
+    /**
+     * Factory method to call to create a symbol table instance with a
+     * randomized seed value.
+     */
+    public static BytesToNameCanonicalizer 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 BytesToNameCanonicalizer createRoot(int hashSeed) {
+        return new BytesToNameCanonicalizer(DEFAULT_TABLE_SIZE, true, hashSeed);
+    }
+    
+    /**
+     * Factory method used to create actual symbol table instance to
+     * use for parsing.
+     * 
+     * @param intern Whether canonical symbol Strings should be interned
+     *   or not
+     */
+    public BytesToNameCanonicalizer makeChild(boolean canonicalize,
+        boolean intern)
+    {
+        return new BytesToNameCanonicalizer(this, intern, _hashSeed, _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.
+             */
+            _mainHashShared = true;
+            _mainNamesShared = true;
+            _collListShared = true;
+        }
+    }
+
+    private void mergeChild(TableInfo childState)
+    {
+        final int childCount = childState.count;
+        TableInfo currState = _tableInfo.get();
+        
+        // Only makes sense if child actually has more entries
+        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
+                || childState.longestCollisionList > MAX_COLL_CHAIN_FOR_REUSE) {
+            /* Should there be a way to get notified about this
+             * event, to log it or such? (as it's somewhat abnormal
+             * thing to happen)
+             */
+            // At any rate, need to clean up the tables
+            childState = initTableInfo(DEFAULT_TABLE_SIZE);
+        }
+        _tableInfo.compareAndSet(currState, childState);
+    }
+
+    /*
+    /**********************************************************
+    /* API, accessors
+    /**********************************************************
+     */
+
+    public int size()
+    {
+        if (_tableInfo != null) { // root table
+            return _tableInfo.get().count;
+        }
+        // nope, child table
+        return _count;
+    }
+
+    /**
+     * @since 2.1
+     */
+    public int bucketCount() { return _mainHash.length; }
+    
+    /**
+     * 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 !_mainHashShared;
+    }
+
+    /**
+     * @since 2.1
+     */
+    public int hashSeed() { return _hashSeed; }
+    
+    /**
+     * Method mostly needed by unit tests; calculates number of
+     * entries that are in collision list. Value can be at most
+     * ({@link #size} - 1), but should usually be much lower, ideally 0.
+     * 
+     * @since 2.1
+     */
+    public int collisionCount() {
+        return _collCount;
+    }
+
+    /**
+     * Method mostly needed by unit tests; calculates length of the
+     * longest collision chain. This should typically be a low number,
+     * but may be up to {@link #size} - 1 in the pathological case
+     * 
+     * @since 2.1
+     */
+    public int maxCollisionLength() {
+        return _longestCollisionList;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, accessing symbols:
+    /**********************************************************
+     */
+    
+    public static Name getEmptyName()
+    {
+        return Name1.getEmptyName();
+    }
+
+    /**
+     * Finds and returns name matching the specified symbol, if such
+     * name already exists in the table.
+     * If not, will return null.
+     *<p>
+     * Note: separate methods to optimize common case of
+     * short element/attribute names (4 or less ascii characters)
+     *
+     * @param firstQuad int32 containing first 4 bytes of the name;
+     *   if the whole name less than 4 bytes, padded with zero bytes
+     *   in front (zero MSBs, ie. right aligned)
+     *
+     * @return Name matching the symbol passed (or constructed for
+     *   it)
+     */
+    public Name findName(int firstQuad)
+    {
+        int hash = calcHash(firstQuad);
+        int ix = (hash & _mainHashMask);
+        int val = _mainHash[ix];
+        
+        /* High 24 bits of the value are low 24 bits of hash (low 8 bits
+         * are bucket index)... match?
+         */
+        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(firstQuad)) {
+                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, firstQuad, 0);
+            }
+        }
+        // Nope, no match whatsoever
+        return null;
+    }
+
+    /**
+     * Finds and returns name matching the specified symbol, if such
+     * name already exists in the table.
+     * If not, will return null.
+     *<p>
+     * Note: separate methods to optimize common case of relatively
+     * short element/attribute names (8 or less ascii characters)
+     *
+     * @param firstQuad int32 containing first 4 bytes of the name.
+     * @param secondQuad int32 containing bytes 5 through 8 of the
+     *   name; if less than 8 bytes, padded with up to 3 zero bytes
+     *   in front (zero MSBs, ie. right aligned)
+     *
+     * @return Name matching the symbol passed (or constructed for it)
+     */
+    public Name findName(int firstQuad, int secondQuad)
+    {
+        int hash = (secondQuad == 0) ? calcHash(firstQuad) : calcHash(firstQuad, secondQuad);
+        int ix = (hash & _mainHashMask);
+        int val = _mainHash[ix];
+        
+        /* High 24 bits of the value are low 24 bits of hash (low 8 bits
+         * are bucket index)... match?
+         */
+        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(firstQuad, secondQuad)) {
+                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, firstQuad, secondQuad);
+            }
+        }
+        // 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,
+     * adds to the table, and returns it.
+     *<p>
+     * Note: this is the general purpose method that can be called for
+     * names of any length. However, if name is less than 9 bytes long,
+     * it is preferable to call the version optimized for short
+     * names.
+     *
+     * @param quads Array of int32s, each of which contain 4 bytes of
+     *   encoded name
+     * @param qlen Number of int32s, starting from index 0, in quads
+     *   parameter
+     *
+     * @return Name matching the symbol passed (or constructed for it)
+     */
+    public Name findName(int[] quads, int qlen)
+    {
+        if (qlen < 3) { // another sanity check
+            return findName(quads[0], (qlen < 2) ? 0 : quads[1]);
+        }
+        int hash = calcHash(quads, qlen);
+        // (for rest of comments regarding logic, see method above)
+        int ix = (hash & _mainHashMask);
+        int val = _mainHash[ix];
+        if ((((val >> 8) ^ hash) << 8) == 0) {
+            Name name = _mainNames[ix];
+            if (name == null // main slot empty; no collision list then either
+                || name.equals(quads, qlen)) { // should be match, let's verify
+                return name;
+            }
+        } else if (val == 0) { // empty slot? no match
+            return null;
+        }
+        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, quads, qlen);
+            }
+        }
+        return null;
+    }
+
+    /*
+    /**********************************************************
+    /* API, mutators
+    /**********************************************************
+     */
+
+    public Name addName(String symbolStr, int q1, int q2)
+    {
+        if (_intern) {
+            symbolStr = InternCache.instance.intern(symbolStr);
+        }
+        int hash = (q2 == 0) ? calcHash(q1) : calcHash(q1, q2);
+        Name symbol = constructName(hash, symbolStr, q1, q2);
+        _addSymbol(hash, symbol);
+        return symbol;
+    }
+    
+    public Name addName(String symbolStr, int[] quads, int qlen)
+    {
+        if (_intern) {
+            symbolStr = InternCache.instance.intern(symbolStr);
+        }
+        int hash;
+        if (qlen < 3) {
+            hash = (qlen == 1) ? calcHash(quads[0]) : calcHash(quads[0], quads[1]);
+        } else {
+            hash = calcHash(quads, qlen);
+        }
+        Name symbol = constructName(hash, symbolStr, quads, qlen);
+        _addSymbol(hash, symbol);
+        return symbol;
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    /* 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 firstQuad)
+    {
+        int hash = firstQuad ^ _hashSeed;
+        hash += (hash >>> 15); // to xor hi- and low- 16-bits
+        hash ^= (hash >>> 9); // as well as lowest 2 bytes
+        return hash;
+    }
+
+    public int calcHash(int firstQuad, int secondQuad)
+    {
+        /* For two quads, let's change algorithm a bit, to spice
+         * things up (can do bit more processing anyway)
+         */
+        int hash = firstQuad;
+        hash ^= (hash >>> 15); // try mixing first and second byte pairs first
+        hash += (secondQuad * MULT); // then add second quad
+        hash ^= _hashSeed;
+        hash += (hash >>> 7); // and shuffle some more
+        return hash;
+    }
+
+    public int calcHash(int[] quads, int qlen)
+    {
+        // Note: may be called for qlen < 3; but has at least one int
+        if (qlen < 3) {
+            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 = quads[0] ^ _hashSeed;
+        hash += (hash >>> 9);
+        hash *= MULT;
+        hash += quads[1];
+        hash *= MULT2;
+        hash += (hash >>> 15);
+        hash ^= quads[2];
+        hash += (hash >>> 17);
+        
+        for (int i = 3; i < qlen; ++i) {
+            hash = (hash * MULT3) ^ quads[i];
+            // for longer entries, mess a bit in-between too
+            hash += (hash >>> 3);
+            hash ^= (hash << 7);
+        }
+        // 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;
+    }
+
+    // Method only used by unit tests
+    protected static 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;
+    }
+
+    /*
+    /**********************************************************
+    /* Standard methods
+    /**********************************************************
+     */
+
+    /*
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[BytesToNameCanonicalizer, size: ");
+        sb.append(_count);
+        sb.append('/');
+        sb.append(_mainHash.length);
+        sb.append(", ");
+        sb.append(_collCount);
+        sb.append(" coll; avg length: ");
+
+        // Average length: minimum of 1 for all (1 == primary hit);
+        // and then 1 per each traversal for collisions/buckets
+        //int maxDist = 1;
+        int pathCount = _count;
+        for (int i = 0; i < _collEnd; ++i) {
+            int spillLen = _collList[i].length();
+            for (int j = 1; j <= spillLen; ++j) {
+                pathCount += j;
+            }
+        }
+        double avgLength;
+
+        if (_count == 0) {
+            avgLength = 0.0;
+        } else {
+            avgLength = (double) pathCount / (double) _count;
+        }
+        // let's round up a bit (two 2 decimal places)
+        //avgLength -= (avgLength % 0.01);
+
+        sb.append(avgLength);
+        sb.append(']');
+        return sb.toString();
+    }
+    */
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    private void _addSymbol(int hash, Name symbol)
+    {
+        if (_mainHashShared) { // always have to modify main entry
+            unshareMain();
+        }
+        // First, do we need to rehash?
+        if (_needRehash) {
+            rehash();
+        }
+
+        ++_count;
+
+        /* Ok, enough about set up: now we need to find the slot to add
+         * symbol in:
+         */
+        int ix = (hash & _mainHashMask);
+        if (_mainNames[ix] == null) { // primary empty?
+            _mainHash[ix] = (hash << 8);
+            if (_mainNamesShared) {
+                unshareNames();
+            }
+            _mainNames[ix] = symbol;
+        } else { // nope, it's a collision, need to spill over
+            /* How about spill-over area... do we already know the bucket
+             * (is the case if it's not the first collision)
+             */
+            if (_collListShared) {
+                unshareCollision(); // also allocates if list was null
+            }
+            ++_collCount;
+            int entryValue = _mainHash[ix];
+            int bucket = entryValue & 0xFF;
+            if (bucket == 0) { // first spill over?
+                if (_collEnd <= LAST_VALID_BUCKET) { // yup, still unshared bucket
+                    bucket = _collEnd;
+                    ++_collEnd;
+                    // need to expand?
+                    if (bucket >= _collList.length) {
+                        expandCollision();
+                    }
+                } else { // nope, have to share... let's find shortest?
+                    bucket = findBestBucket();
+                }
+                // Need to mark the entry... and the spill index is 1-based
+                _mainHash[ix] = (entryValue & ~0xFF) | (bucket + 1);
+            } else {
+                --bucket; // 1-based index in value
+            }
+            
+            // And then just need to link the new bucket entry in
+            Bucket newB = new Bucket(symbol, _collList[bucket]);
+            _collList[bucket] = newB;
+            // but, be careful wrt attacks
+            _longestCollisionList = Math.max(newB.length(), _longestCollisionList);
+            if (_longestCollisionList > MAX_COLL_CHAIN_LENGTH) {
+                reportTooManyCollisions(MAX_COLL_CHAIN_LENGTH);
+            }
+        }
+
+        /* Ok. Now, do we need a rehash next time? Need to have at least
+         * 50% fill rate no matter what:
+         */
+        {
+            int hashSize = _mainHash.length;
+            if (_count > (hashSize >> 1)) {
+                int hashQuarter = (hashSize >> 2);
+                /* And either strictly above 75% (the usual) or
+                 * just 50%, and collision count >= 25% of total hash size
+                 */
+                if (_count > (hashSize - hashQuarter)) {
+                    _needRehash = true;
+                } else if (_collCount >= hashQuarter) {
+                    _needRehash = true;
+                }
+            }
+        }
+    }
+
+    private void rehash()
+    {
+        _needRehash = false;        
+        // Note: since we'll make copies, no need to unshare, can just mark as such:
+        _mainNamesShared = 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.
+         */
+        int[] oldMainHash = _mainHash;
+        int len = oldMainHash.length;
+        int newLen = len+len;
+
+        /* 13-Mar-2010, tatu: Let's guard against OOME that could be caused by
+         *    large documents with unique (or mostly so) names
+         */
+        if (newLen > MAX_TABLE_SIZE) {
+            nukeSymbols();
+            return;
+        }
+        
+        _mainHash = new int[newLen];
+        _mainHashMask = (newLen - 1);
+        Name[] oldNames = _mainNames;
+        _mainNames = new Name[newLen];
+        int symbolsSeen = 0; // let's do a sanity check
+        for (int i = 0; i < len; ++i) {
+            Name symbol = oldNames[i];
+            if (symbol != null) {
+                ++symbolsSeen;
+                int hash = symbol.hashCode();
+                int ix = (hash & _mainHashMask);
+                _mainNames[ix] = symbol;
+                _mainHash[ix] = hash << 8; // will clear spill index
+            }
+        }
+
+        /* And then the spill area. This may cause collisions, although
+         * not necessarily as many as there were earlier. Let's allocate
+         * same amount of space, however
+         */
+        int oldEnd = _collEnd;
+        if (oldEnd == 0) { // no prior collisions...
+            _longestCollisionList = 0;
+            return;
+        }
+
+        _collCount = 0;
+        _collEnd = 0;
+        _collListShared = false;
+
+        int maxColl = 0;
+        
+        Bucket[] oldBuckets = _collList;
+        _collList = new Bucket[oldBuckets.length];
+        for (int i = 0; i < oldEnd; ++i) {
+            for (Bucket curr = oldBuckets[i]; curr != null; curr = curr._next) {
+                ++symbolsSeen;
+                Name symbol = curr._name;
+                int hash = symbol.hashCode();
+                int ix = (hash & _mainHashMask);
+                int val = _mainHash[ix];
+                if (_mainNames[ix] == null) { // no primary entry?
+                    _mainHash[ix] = (hash << 8);
+                    _mainNames[ix] = symbol;
+                } else { // nope, it's a collision, need to spill over
+                    ++_collCount;
+                    int bucket = val & 0xFF;
+                    if (bucket == 0) { // first spill over?
+                        if (_collEnd <= LAST_VALID_BUCKET) { // yup, still unshared bucket
+                            bucket = _collEnd;
+                            ++_collEnd;
+                            // need to expand?
+                            if (bucket >= _collList.length) {
+                                expandCollision();
+                            }
+                        } else { // nope, have to share... let's find shortest?
+                            bucket = findBestBucket();
+                        }
+                        // Need to mark the entry... and the spill index is 1-based
+                        _mainHash[ix] = (val & ~0xFF) | (bucket + 1);
+                    } else {
+                        --bucket; // 1-based index in value
+                    }
+                    // And then just need to link the new bucket entry in
+                    Bucket newB = new Bucket(symbol, _collList[bucket]);
+                    _collList[bucket] = newB;
+                    maxColl = Math.max(maxColl, newB.length());
+                }
+            } // for (... buckets in the chain ...)
+        } // for (... list of bucket heads ... )
+
+        _longestCollisionList = maxColl;
+        
+        if (symbolsSeen != _count) { // sanity check
+            throw new RuntimeException("Internal error: count after rehash "+symbolsSeen+"; should be "+_count);
+        }
+    }
+
+    /**
+     * Helper method called to empty all shared symbols, but to leave
+     * arrays allocated
+     */
+    private void nukeSymbols()
+    {
+        _count = 0;
+        _longestCollisionList = 0;
+        Arrays.fill(_mainHash, 0);
+        Arrays.fill(_mainNames, null);
+        Arrays.fill(_collList, null);
+        _collCount = 0;
+        _collEnd = 0;
+    }
+    
+    /**
+     * Method called to find the best bucket to spill a Name over to:
+     * usually the first bucket that has only one entry, but in general
+     * first one of the buckets with least number of entries
+     */
+    private int findBestBucket()
+    {
+        Bucket[] buckets = _collList;
+        int bestCount = Integer.MAX_VALUE;
+        int bestIx = -1;
+
+        for (int i = 0, len = _collEnd; i < len; ++i) {
+            int count = buckets[i].length();
+            if (count < bestCount) {
+                if (count == 1) { // best possible
+                    return i;
+                }
+                bestCount = count;
+                bestIx = i;
+            }
+        }
+        return bestIx;
+    }
+
+    /**
+     * Method that needs to be called, if the main hash structure
+     * is (may be) shared. This happens every time something is added,
+     * even if addition is to the collision list (since collision list
+     * index comes from lowest 8 bits of the primary hash entry)
+     */
+    private void unshareMain()
+    {
+        int[] old = _mainHash;
+        int len = _mainHash.length;
+
+        _mainHash = new int[len];
+        System.arraycopy(old, 0, _mainHash, 0, len);
+        _mainHashShared = false;
+    }
+
+    private void unshareCollision()
+    {
+        Bucket[] old = _collList;
+        if (old == null) {
+            _collList = new Bucket[INITIAL_COLLISION_LEN];
+        } else {
+            int len = old.length;
+            _collList = new Bucket[len];
+            System.arraycopy(old, 0, _collList, 0, len);
+        }
+        _collListShared = false;
+    }
+
+    private void unshareNames()
+    {
+        Name[] old = _mainNames;
+        int len = old.length;
+        _mainNames = new Name[len];
+        System.arraycopy(old, 0, _mainNames, 0, len);
+        _mainNamesShared = false;
+    }
+
+    private void expandCollision()
+    {
+        Bucket[] old = _collList;
+        int len = old.length;
+        _collList = new Bucket[len+len];
+        System.arraycopy(old, 0, _collList, 0, len);
+    }
+
+
+    /*
+    /**********************************************************
+    /* Constructing name objects
+    /**********************************************************
+     */
+
+    private static Name constructName(int hash, String name, int q1, int q2)
+    {     
+        if (q2 == 0) { // one quad only?
+            return new Name1(name, hash, q1);
+        }
+        return new Name2(name, hash, q1, q2);
+    }
+
+    private static Name constructName(int hash, String name, int[] quads, int qlen)
+    {
+        if (qlen < 4) { // Need to check for 3 quad one, can do others too
+            switch (qlen) {
+            case 1:
+                return new Name1(name, hash, quads[0]);
+            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:
+            }
+        }
+        // Otherwise, need to copy the incoming buffer
+        int[] buf = new int[qlen];
+        for (int i = 0; i < qlen; ++i) {
+            buf[i] = quads[i];
+        }
+        return new NameN(name, hash, buf, qlen);
+    }
+
+    /*
+    /**********************************************************
+    /* Other helper methods
+    /**********************************************************
+     */
+    
+    /**
+     * @since 2.1
+     */
+    protected void reportTooManyCollisions(int maxLen)
+    {
+        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
+    /**********************************************************
+     */
+
+    /**
+     * 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 count;
+        public final int mainHashMask;
+        public final int[] mainHash;
+        public final Name[] mainNames;
+        public final Bucket[] collList;
+        public final int collCount;
+        public final int collEnd;
+        public final int longestCollisionList;
+
+        public TableInfo(int count, int mainHashMask, int[] mainHash, Name[] mainNames,
+                Bucket[] collList, int collCount, int collEnd, int longestCollisionList)
+        {
+            this.count = count;
+            this.mainHashMask = mainHashMask;
+            this.mainHash = mainHash;
+            this.mainNames = mainNames;
+            this.collList = collList;
+            this.collCount = collCount;
+            this.collEnd = collEnd;
+            this.longestCollisionList = longestCollisionList;
+        }
+
+        public TableInfo(BytesToNameCanonicalizer src)
+        {
+            count = src._count;
+            mainHashMask = src._mainHashMask;
+            mainHash = src._mainHash;
+            mainNames = src._mainNames;
+            collList = src._collList;
+            collCount = src._collCount;
+            collEnd = src._collEnd;
+            longestCollisionList = src._longestCollisionList;
+        }
+    
+    }
+    
+    /**
+     * 
+     */
+    final static class Bucket
+    {
+        protected final Name _name;
+        protected final Bucket _next;
+        private final int _length;
+
+        Bucket(Name name, Bucket next)
+        {
+            _name = name;
+            _next = next;
+            _length = (next == null) ? 1 : next._length+1;
+        }
+
+        public int length() { return _length; }
+
+        public Name find(int hash, int firstQuad, int secondQuad)
+        {
+            if (_name.hashCode() == hash) {
+                if (_name.equals(firstQuad, secondQuad)) {
+                    return _name;
+                }
+            }
+            for (Bucket curr = _next; curr != null; curr = curr._next) {
+                Name currName = curr._name;
+                if (currName.hashCode() == hash) {
+                    if (currName.equals(firstQuad, secondQuad)) {
+                        return currName;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public Name find(int hash, int[] quads, int qlen)
+        {
+            if (_name.hashCode() == hash) {
+                if (_name.equals(quads, qlen)) {
+                    return _name;
+                }
+            }
+            for (Bucket curr = _next; curr != null; curr = curr._next) {
+                Name currName = curr._name;
+                if (currName.hashCode() == hash) {
+                    if (currName.equals(quads, qlen)) {
+                        return currName;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java b/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java
new file mode 100644
index 0000000..63d9a81
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java
@@ -0,0 +1,730 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.util.Arrays;
+
+import com.fasterxml.jackson.core.util.InternCache;
+
+/**
+ * This class is a kind of specialized type-safe Map, from char array to
+ * String value. Specialization means that in addition to type-safety
+ * and specific access patterns (key char array, Value optionally interned
+ * String; values added on access if necessary), and that instances are
+ * meant to be used concurrently, but by using well-defined mechanisms
+ * to obtain such concurrently usable instances. Main use for the class
+ * is to store symbol table information for things like compilers and
+ * parsers; especially when number of symbols (keywords) is limited.
+ *<p>
+ * For optimal performance, usage pattern should be one where matches
+ * should be very common (especially after "warm-up"), and as with most hash-based
+ * maps/sets, that hash codes are uniformly distributed. Also, collisions
+ * are slightly more expensive than with HashMap or HashSet, since hash codes
+ * are not used in resolving collisions; that is, equals() comparison is
+ * done with all symbols in same bucket index.<br />
+ * Finally, rehashing is also more expensive, as hash codes are not
+ * stored; rehashing requires all entries' hash codes to be recalculated.
+ * Reason for not storing hash codes is reduced memory usage, hoping
+ * for better memory locality.
+ *<p>
+ * Usual usage pattern is to create a single "master" instance, and either
+ * use that instance in sequential fashion, or to create derived "child"
+ * instances, which after use, are asked to return possible symbol additions
+ * to master instance. In either case benefit is that symbol table gets
+ * initialized so that further uses are more efficient, as eventually all
+ * symbols needed will already be in symbol table. At that point no more
+ * Symbol String allocations are needed, nor changes to symbol table itself.
+ *<p>
+ * Note that while individual SymbolTable instances are NOT thread-safe
+ * (much like generic collection classes), concurrently used "child"
+ * instances can be freely used without synchronization. However, using
+ * master table concurrently with child instances can only be done if
+ * access to master instance is read-only (i.e. no modifications done).
+ */
+public final class CharsToNameCanonicalizer
+{
+    /* If we use "multiply-add" based hash algorithm, this is the multiplier
+     * we use.
+     */
+    public final static int HASH_MULT = 33;
+    
+    /**
+     * Default initial table size. Shouldn't be miniscule (as there's
+     * cost to both array realloc and rehashing), but let's keep
+     * it reasonably small nonetheless. For systems that properly 
+     * reuse factories it doesn't matter either way; but when
+     * recreating factories often, initial overhead may dominate.
+     */
+    protected static final int DEFAULT_TABLE_SIZE = 64;
+
+    /**
+     * Let's not expand symbol tables past some maximum size;
+     * this should protected against OOMEs caused by large documents
+     * with uniquer (~= random) names.
+     */
+    protected static final int MAX_TABLE_SIZE = 0x10000; // 64k entries == 256k mem
+
+    /**
+     * Let's only share reasonably sized symbol tables. Max size set to 3/4 of 16k;
+     * this corresponds to 64k main hash index. This should allow for enough distinct
+     * names for almost any case.
+     */
+    final static int MAX_ENTRIES_FOR_REUSE = 12000;
+
+    /**
+     * Also: to thwart attacks based on hash collisions (which may or may not
+     * be cheap to calculate), we will need to detect "too long"
+     * collision chains. Let's start with static value of 255 entries
+     * for the longest legal chain.
+     *<p>
+     * Note: longest chain we have been able to produce without malicious
+     * intent has been 38 (with "com.fasterxml.jackson.core.main.TestWithTonsaSymbols");
+     * our setting should be reasonable here.
+     * 
+     * @since 2.1
+     */
+    final static int MAX_COLL_CHAIN_LENGTH = 255;
+
+    /**
+     * And to support reduce likelihood of accidental collisons causing
+     * exceptions, let's prevent reuse of tables with long collision
+     * overflow lists as well.
+     * 
+     * @since 2.1
+     */
+    final static int MAX_COLL_CHAIN_FOR_REUSE  = 63;
+    
+    final static CharsToNameCanonicalizer sBootstrapSymbolTable;
+    static {
+        sBootstrapSymbolTable = new CharsToNameCanonicalizer();
+    }
+
+    /*
+    /**********************************************************
+    /* Configuration
+    /**********************************************************
+     */
+
+    /**
+     * Sharing of learnt symbols is done by optional linking of symbol
+     * table instances with their parents. When parent linkage is
+     * defined, and child instance is released (call to <code>release</code>),
+     * parent's shared tables may be updated from the child instance.
+     */
+    protected CharsToNameCanonicalizer _parent;
+
+    /**
+     * 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.
+     * 
+     * @since 2.1
+     */
+    final private int _hashSeed;
+    
+    /**
+     * Whether canonical symbol Strings are to be intern()ed before added
+     * to the table or not
+     */
+    final protected boolean _intern;
+
+    /**
+     * Whether any canonicalization should be attempted (whether using
+     * intern or not)
+     */
+    final protected boolean _canonicalize;
+    
+    /*
+    /**********************************************************
+    /* Actual symbol table data
+    /**********************************************************
+     */
+
+    /**
+     * Primary matching symbols; it's expected most match occur from
+     * here.
+     */
+    protected String[] _symbols;
+
+    /**
+     * Overflow buckets; if primary doesn't match, lookup is done
+     * from here.
+     *<p>
+     * Note: Number of buckets is half of number of symbol entries, on
+     * assumption there's less need for buckets.
+     */
+    protected Bucket[] _buckets;
+
+    /**
+     * Current size (number of entries); needed to know if and when
+     * rehash.
+     */
+    protected int _size;
+
+    /**
+     * Limit that indicates maximum size this instance can hold before
+     * it needs to be expanded and rehashed. Calculated using fill
+     * factor passed in to constructor.
+     */
+    protected int _sizeThreshold;
+
+    /**
+     * Mask used to get index from hash values; equal to
+     * <code>_buckets.length - 1</code>, when _buckets.length is
+     * a power of two.
+     */
+    protected int _indexMask;
+
+    /**
+     * We need to keep track of the longest collision list; this is needed
+     * both to indicate problems with attacks and to allow flushing for
+     * other cases.
+     * 
+     * @since 2.1
+     */
+    protected int _longestCollisionList;
+    
+    /*
+    /**********************************************************
+    /* State regarding shared arrays
+    /**********************************************************
+     */
+
+    /**
+     * Flag that indicates if any changes have been made to the data;
+     * used to both determine if bucket array needs to be copied when
+     * (first) change is made, and potentially if updated bucket list
+     * is to be resync'ed back to master instance.
+     */
+    protected boolean _dirty;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    /**
+     * Method called to create root canonicalizer for a {@link com.fasterxml.jackson.core.JsonFactory}
+     * instance. Root instance is never used directly; its main use is for
+     * storing and sharing underlying symbol arrays as needed.
+     */
+    public static CharsToNameCanonicalizer 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);
+    }
+    
+    protected static CharsToNameCanonicalizer createRoot(int hashSeed) {
+        return sBootstrapSymbolTable.makeOrphan(hashSeed);
+    }
+
+    /**
+     * Main method for constructing a master symbol table instance.
+     *
+     * @param initialSize Minimum initial size for bucket array; internally
+     *   will always use a power of two equal to or bigger than this value.
+     */
+    private CharsToNameCanonicalizer()
+    {
+        // these settings don't really matter for the bootstrap instance
+        _canonicalize = true;
+        _intern = true;
+        // And we'll also set flags so no copying of buckets is needed:
+        _dirty = true;
+        _hashSeed = 0;
+        _longestCollisionList = 0;
+        initTables(DEFAULT_TABLE_SIZE);
+    }
+
+    private void initTables(int initialSize)
+    {
+        _symbols = new String[initialSize];
+        _buckets = new Bucket[initialSize >> 1];
+        // Mask is easy to calc for powers of two.
+        _indexMask = initialSize - 1;
+        _size = 0;
+        _longestCollisionList = 0;
+        // Hard-coded fill factor is 75%
+        _sizeThreshold = _thresholdSize(initialSize);
+    }
+
+    private static int _thresholdSize(int hashAreaSize) {
+        return hashAreaSize - (hashAreaSize >> 2);
+    }
+    
+    /**
+     * Internal constructor used when creating child instances.
+     */
+    private CharsToNameCanonicalizer(CharsToNameCanonicalizer parent,
+            boolean canonicalize, boolean intern,
+            String[] symbols, Bucket[] buckets, int size,
+            int hashSeed, int longestColl)
+    {
+        _parent = parent;
+        _canonicalize = canonicalize;
+        _intern = intern;
+
+        _symbols = symbols;
+        _buckets = buckets;
+        _size = size;
+        _hashSeed = hashSeed;
+        // Hard-coded fill factor, 75%
+        int arrayLen = (symbols.length);
+        _sizeThreshold = _thresholdSize(arrayLen);
+        _indexMask =  (arrayLen - 1);
+        _longestCollisionList = longestColl;
+
+        // Need to make copies of arrays, if/when adding new entries
+        _dirty = false;
+    }
+
+    /**
+     * "Factory" method; will create a new child instance of this symbol
+     * table. It will be a copy-on-write instance, ie. it will only use
+     * read-only copy of parent's data, but when changes are needed, a
+     * copy will be created.
+     *<p>
+     * Note: while this method is synchronized, it is generally not
+     * safe to both use makeChild/mergeChild, AND to use instance
+     * actively. Instead, a separate 'root' instance should be used
+     * on which only makeChild/mergeChild are called, but instance itself
+     * is not used as a symbol table.
+     */
+    public CharsToNameCanonicalizer makeChild(final boolean canonicalize,
+            final boolean intern)
+    {
+        /* 24-Jul-2012, tatu: Trying to reduce scope of synchronization, assuming
+         *   that synchronizing construction is the (potentially) expensive part,
+         *   and not so much short copy-the-variables thing.
+         */
+        final String[] symbols;
+        final Bucket[] buckets;
+        final int size;
+        final int hashSeed;
+        final int longestCollisionList;
+        
+        synchronized (this) {
+            symbols = _symbols;
+            buckets = _buckets;
+            size = _size;
+            hashSeed = _hashSeed;
+            longestCollisionList = _longestCollisionList;
+        }
+        
+        return new CharsToNameCanonicalizer(this, canonicalize, intern,
+                symbols, buckets, size, hashSeed, longestCollisionList);
+    }
+
+    private CharsToNameCanonicalizer makeOrphan(int seed)
+    {
+        return new CharsToNameCanonicalizer(null, true, true,
+                _symbols, _buckets, _size, seed, _longestCollisionList);
+    }
+
+    /**
+     * Method that allows contents of child table to potentially be
+     * "merged in" with contents of this symbol table.
+     *<p>
+     * Note that caller has to make sure symbol table passed in is
+     * really a child or sibling of this symbol table.
+     */
+    private void mergeChild(CharsToNameCanonicalizer child)
+    {
+        /* 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 (child.size() > MAX_ENTRIES_FOR_REUSE
+                || child._longestCollisionList > MAX_COLL_CHAIN_FOR_REUSE) {
+            // Should there be a way to get notified about this event, to log it or such?
+            // (as it's somewhat abnormal thing to happen)
+            // At any rate, need to clean up the tables, then:
+            synchronized (this) {
+                initTables(DEFAULT_TABLE_SIZE);
+                // Dirty flag... well, let's just clear it. Shouldn't really matter for master tables
+                // (which this is, given something is merged to it)
+                _dirty = false;
+            }
+        } else {
+            // Otherwise, we'll merge changed stuff in, if there are  more entries (which
+            // may not be the case if one of siblings has added symbols first or such)
+            if (child.size() <= size()) { // nothing to add
+                return;
+            }
+            // Okie dokie, let's get the data in!
+            synchronized (this) {
+                _symbols = child._symbols;
+                _buckets = child._buckets;
+                _size = child._size;
+                _sizeThreshold = child._sizeThreshold;
+                _indexMask = child._indexMask;
+                _longestCollisionList = child._longestCollisionList;
+                // Dirty flag... well, let's just clear it. Shouldn't really matter for master tables
+                // (which this is, given something is merged to it)
+                _dirty = false;
+            }
+        }
+    }
+
+    public void release()
+    {
+        // If nothing has been added, nothing to do
+        if (!maybeDirty()) {
+            return;
+        }
+        if (_parent != null) {
+            _parent.mergeChild(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.
+             */
+            _dirty = false;
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, generic accessors:
+    /**********************************************************
+     */
+
+    public int size() { return _size; }
+
+    /**
+     * Method for checking number of primary hash buckets this symbol
+     * table uses.
+     * 
+     * @since 2.1
+     */
+    public int bucketCount() { 
+       return _symbols.length; }
+    
+    public boolean maybeDirty() { return _dirty; }
+
+    public int hashSeed() { return _hashSeed; }
+    
+    /**
+     * Method mostly needed by unit tests; calculates number of
+     * entries that are in collision list. Value can be at most
+     * ({@link #size} - 1), but should usually be much lower, ideally 0.
+     * 
+     * @since 2.1
+     */
+    public int collisionCount()
+    {
+        int count = 0;
+        
+        for (Bucket bucket : _buckets) {
+            if (bucket != null) {
+                count += bucket.length();
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Method mostly needed by unit tests; calculates length of the
+     * longest collision chain. This should typically be a low number,
+     * but may be up to {@link #size} - 1 in the pathological case
+     * 
+     * @since 2.1
+     */
+    public int maxCollisionLength()
+    {
+        return _longestCollisionList;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, accessing symbols:
+    /**********************************************************
+     */
+
+    public String findSymbol(char[] buffer, int start, int len, int h)
+    {
+        if (len < 1) { // empty Strings are simplest to handle up front
+            return "";
+        }
+        if (!_canonicalize) { // [JACKSON-259]
+            return new String(buffer, start, len);
+        }
+
+        /* Related to problems with sub-standard hashing (somewhat
+         * relevant for collision attacks too), let's try little
+         * bit of shuffling to improve hash codes.
+         * (note, however, that this can't help with full collisions)
+         */
+        int index = _hashToIndex(h);
+        String sym = _symbols[index];
+
+        // Optimal case; checking existing primary symbol for hash index:
+        if (sym != null) {
+            // Let's inline primary String equality checking:
+            if (sym.length() == len) {
+                int i = 0;
+                do {
+                    if (sym.charAt(i) != buffer[start+i]) {
+                        break;
+                    }
+                } while (++i < len);
+                // Optimal case; primary match found
+                if (i == len) {
+                    return sym;
+                }
+            }
+            // How about collision bucket?
+            Bucket b = _buckets[index >> 1];
+            if (b != null) {
+                sym = b.find(buffer, start, len);
+                if (sym != null) {
+                    return sym;
+                }
+            }
+        }
+
+        if (!_dirty) { //need to do copy-on-write?
+            copyArrays();
+            _dirty = true;
+        } else if (_size >= _sizeThreshold) { // Need to expand?
+           rehash();
+           /* Need to recalc hash; rare occurence (index mask has been
+            * recalculated as part of rehash)
+            */
+           index = _hashToIndex(calcHash(buffer, start, len));
+        }
+
+        String newSymbol = new String(buffer, start, len);
+        if (_intern) {
+            newSymbol = InternCache.instance.intern(newSymbol);
+        }
+        ++_size;
+        // Ok; do we need to add primary entry, or a bucket?
+        if (_symbols[index] == null) {
+            _symbols[index] = newSymbol;
+        } else {
+            int bix = (index >> 1);
+            Bucket newB = new Bucket(newSymbol, _buckets[bix]);
+            _buckets[bix] = newB;
+            _longestCollisionList = Math.max(newB.length(), _longestCollisionList);
+            if (_longestCollisionList > MAX_COLL_CHAIN_LENGTH) {
+                reportTooManyCollisions(MAX_COLL_CHAIN_LENGTH);
+            }
+        }
+
+        return newSymbol;
+    }
+
+    /**
+     * Helper method that takes in a "raw" hash value, shuffles it as necessary,
+     * 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
+        return (rawHash & _indexMask);
+    }
+    
+    /**
+     * Implementation of a hashing method for variable length
+     * Strings. Most of the time intention is that this calculation
+     * is done by caller during parsing, not here; however, sometimes
+     * it needs to be done for parsed "String" too.
+     *
+     * @param len Length of String; has to be at least 1 (caller guarantees
+     *   this pre-condition)
+     */
+    public int calcHash(char[] buffer, int start, int len)
+    {
+        int hash = _hashSeed;
+        for (int i = 0; i < len; ++i) {
+            hash = (hash * HASH_MULT) + (int) buffer[i];
+        }
+        // NOTE: shuffling, if any, is done in 'findSymbol()', not here:
+        return (hash == 0) ? 1 : hash;
+    }
+
+    public int calcHash(String key)
+    {
+        final int len = key.length();
+        
+        int hash = _hashSeed;
+        for (int i = 0; i < len; ++i) {
+            hash = (hash * HASH_MULT) + (int) key.charAt(i);
+        }
+        // NOTE: shuffling, if any, is done in 'findSymbol()', not here:
+        return (hash == 0) ? 1 : hash;
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    /**
+     * Method called when copy-on-write is needed; generally when first
+     * change is made to a derived symbol table.
+     */
+    private void copyArrays() {
+        String[] oldSyms = _symbols;
+        int size = oldSyms.length;
+        _symbols = new String[size];
+        System.arraycopy(oldSyms, 0, _symbols, 0, size);
+        Bucket[] oldBuckets = _buckets;
+        size = oldBuckets.length;
+        _buckets = new Bucket[size];
+        System.arraycopy(oldBuckets, 0, _buckets, 0, size);
+    }
+
+    /**
+     * Method called when size (number of entries) of symbol table grows
+     * so big that load factor is exceeded. Since size has to remain
+     * power of two, arrays will then always be doubled. Main work
+     * is really redistributing old entries into new String/Bucket
+     * entries.
+     */
+    private void rehash()
+    {
+        int size = _symbols.length;
+        int newSize = size + size;
+
+        /* 12-Mar-2010, tatu: Let's actually limit maximum size we are
+         *    prepared to use, to guard against OOME in case of unbounded
+         *    name sets (unique [non-repeating] names)
+         */
+        if (newSize > MAX_TABLE_SIZE) {
+            /* If this happens, there's no point in either growing or
+             * shrinking hash areas. Rather, it's better to just clean
+             * them up for reuse.
+             */
+            _size = 0;
+            Arrays.fill(_symbols, null);
+            Arrays.fill(_buckets, null);
+            _dirty = true;
+            return;
+        }
+        
+        String[] oldSyms = _symbols;
+        Bucket[] oldBuckets = _buckets;
+        _symbols = new String[newSize];
+        _buckets = new Bucket[newSize >> 1];
+        // Let's update index mask, threshold, now (needed for rehashing)
+        _indexMask = newSize - 1;
+        _sizeThreshold = _thresholdSize(newSize);
+        
+        int count = 0; // let's do sanity check
+
+        /* Need to do two loops, unfortunately, since spill-over area is
+         * only half the size:
+         */
+        int maxColl = 0;
+        for (int i = 0; i < size; ++i) {
+            String symbol = oldSyms[i];
+            if (symbol != null) {
+                ++count;
+                int index = _hashToIndex(calcHash(symbol));
+                if (_symbols[index] == null) {
+                    _symbols[index] = symbol;
+                } else {
+                    int bix = (index >> 1);
+                    Bucket newB = new Bucket(symbol, _buckets[bix]);
+                    _buckets[bix] = newB;
+                    maxColl = Math.max(maxColl, newB.length());
+                }
+            }
+        }
+
+        size >>= 1;
+        for (int i = 0; i < size; ++i) {
+            Bucket b = oldBuckets[i];
+            while (b != null) {
+                ++count;
+                String symbol = b.getSymbol();
+                int index = _hashToIndex(calcHash(symbol));
+                if (_symbols[index] == null) {
+                    _symbols[index] = symbol;
+                } else {
+                    int bix = (index >> 1);
+                    Bucket newB = new Bucket(symbol, _buckets[bix]);
+                    _buckets[bix] = newB;
+                    maxColl = Math.max(maxColl, newB.length());
+                }
+                b = b.getNext();
+            }
+        }
+        _longestCollisionList = maxColl;
+
+        if (count != _size) {
+            throw new Error("Internal error on SymbolTable.rehash(): had "+_size+" entries; now have "+count+".");
+        }
+    }
+
+    /**
+     * @since 2.1
+     */
+    protected void reportTooManyCollisions(int maxLen)
+    {
+        throw new IllegalStateException("Longest collision chain in symbol table (of size "+_size
+                +") now exceeds maximum, "+maxLen+" -- suspect a DoS attack based on hash collisions");
+    }
+    
+    /*
+    /**********************************************************
+    /* Bucket class
+    /**********************************************************
+     */
+
+    /**
+     * This class is a symbol table entry. Each entry acts as a node
+     * in a linked list.
+     */
+    static final class Bucket
+    {
+        private final String _symbol;
+        private final Bucket _next;
+        private final int _length;
+
+        public Bucket(String symbol, Bucket next) {
+            _symbol = symbol;
+            _next = next;
+            _length = (next == null) ? 1 : next._length+1;
+        }
+
+        public String getSymbol() { return _symbol; }
+        public Bucket getNext() { return _next; }
+        public int length() { return _length; }
+
+        public String find(char[] buf, int start, int len) {
+            String sym = _symbol;
+            Bucket b = _next;
+
+            while (true) { // Inlined equality comparison:
+                if (sym.length() == len) {
+                    int i = 0;
+                    do {
+                        if (sym.charAt(i) != buf[start+i]) {
+                            break;
+                        }
+                    } while (++i < len);
+                    if (i == len) {
+                        return sym;
+                    }
+                }
+                if (b == null) {
+                    break;
+                }
+                sym = b.getSymbol();
+                b = b.getNext();
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name.java b/src/main/java/com/fasterxml/jackson/core/sym/Name.java
new file mode 100644
index 0000000..d26f55d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name.java
@@ -0,0 +1,50 @@
+package com.fasterxml.jackson.core.sym;
+
+/**
+ * Base class for tokenized names (key strings in objects) that have
+ * been tokenized from byte-based input sources (like
+ * {@link java.io.InputStream}.
+ *
+ * @author Tatu Saloranta
+ */
+public abstract class Name
+{
+    protected final String _name;
+
+    protected final int _hashCode;
+
+    protected Name(String name, int hashCode) {
+        _name = name;
+        _hashCode = hashCode;
+    }
+
+    public String getName() { return _name; }
+
+    /*
+    /**********************************************************
+    /* Methods for package/core parser
+    /**********************************************************
+     */
+
+    public abstract boolean equals(int quad1);
+
+    public abstract boolean equals(int quad1, int quad2);
+
+    public abstract boolean equals(int[] quads, int qlen);
+
+    /*
+    /**********************************************************
+    /* Overridden standard methods
+    /**********************************************************
+     */
+
+    @Override public String toString() { return _name; }
+
+    @Override public final int hashCode() { return _hashCode; }
+
+    @Override public boolean equals(Object o)
+    {
+        // Canonical instances, can usually just do identity comparison
+        return (o == this);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name1.java b/src/main/java/com/fasterxml/jackson/core/sym/Name1.java
new file mode 100644
index 0000000..f10f033
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name1.java
@@ -0,0 +1,44 @@
+package com.fasterxml.jackson.core.sym;
+
+/**
+ * Specialized implementation of PName: can be used for short Strings
+ * that consists of at most 4 bytes. Usually this means short
+ * ascii-only names.
+ *<p>
+ * The reason for such specialized classes is mostly space efficiency;
+ * and to a lesser degree performance. Both are achieved for short
+ * Strings by avoiding another level of indirection (via quad arrays)
+ */
+public final class Name1
+    extends Name
+{
+    final static Name1 sEmptyName = new Name1("", 0, 0);
+
+    final int mQuad;
+
+    Name1(String name, int hash, int quad)
+    {
+        super(name, hash);
+        mQuad = quad;
+    }
+
+    static Name1 getEmptyName() { return sEmptyName; }
+
+    @Override
+    public boolean equals(int quad)
+    {
+        return (quad == mQuad);
+    }
+
+    @Override
+    public boolean equals(int quad1, int quad2)
+    {
+        return (quad1 == mQuad) && (quad2 == 0);
+    }
+
+    @Override
+    public boolean equals(int[] quads, int qlen)
+    {
+        return (qlen == 1 && quads[0] == mQuad);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name2.java b/src/main/java/com/fasterxml/jackson/core/sym/Name2.java
new file mode 100644
index 0000000..cc425fb
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name2.java
@@ -0,0 +1,40 @@
+package com.fasterxml.jackson.core.sym;
+
+/**
+ * Specialized implementation of PName: can be used for short Strings
+ * that consists of 5 to 8 bytes. Usually this means relatively short
+ * ascii-only names.
+ *<p>
+ * The reason for such specialized classes is mostly space efficiency;
+ * and to a lesser degree performance. Both are achieved for short
+ * Strings by avoiding another level of indirection (via quad arrays)
+ */
+public final class Name2
+    extends Name
+{
+    final int mQuad1;
+
+    final int mQuad2;
+
+    Name2(String name, int hash, int quad1, int quad2)
+    {
+        super(name, hash);
+        mQuad1 = quad1;
+        mQuad2 = quad2;
+    }
+
+    @Override
+    public boolean equals(int quad) { return false; }
+
+    @Override
+    public boolean equals(int quad1, int quad2)
+    {
+        return (quad1 == mQuad1) && (quad2 == mQuad2);
+    }
+
+    @Override
+    public boolean equals(int[] quads, int qlen)
+    {
+        return (qlen == 2 && quads[0] == mQuad1 && quads[1] == mQuad2);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/Name3.java b/src/main/java/com/fasterxml/jackson/core/sym/Name3.java
new file mode 100644
index 0000000..fe3a4d7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/Name3.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.core.sym;
+
+/**
+ * Specialized implementation of PName: can be used for short Strings
+ * that consists of 9 to 12 bytes. It's the longest special purpose
+ * implementaion; longer ones are expressed using {@link NameN}.
+ */
+public final class Name3
+    extends Name
+{
+    final int mQuad1;
+    final int mQuad2;
+    final int mQuad3;
+
+    Name3(String name, int hash, int q1, int q2, int q3)
+    {
+        super(name, hash);
+        mQuad1 = q1;
+        mQuad2 = q2;
+        mQuad3 = q3;
+    }
+
+    // Implies quad length == 1, never matches
+    @Override
+    public boolean equals(int quad) { return false; }
+
+    // Implies quad length == 2, never matches
+    @Override
+    public boolean equals(int quad1, int quad2) { return false; }
+
+    @Override
+    public boolean equals(int[] quads, int qlen)
+    {
+        return (qlen == 3)
+            && (quads[0] == mQuad1)
+            && (quads[1] == mQuad2)
+            && (quads[2] == mQuad3);
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/NameN.java b/src/main/java/com/fasterxml/jackson/core/sym/NameN.java
new file mode 100644
index 0000000..3040104
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/NameN.java
@@ -0,0 +1,68 @@
+package com.fasterxml.jackson.core.sym;
+
+/**
+ * Generic implementation of PName used for "long" names, where long
+ * means that its byte (UTF-8) representation is 13 bytes or more.
+ */
+public final class NameN
+    extends Name
+{
+    final int[] mQuads;
+    final int mQuadLen;
+
+    NameN(String name, int hash, int[] quads, int quadLen)
+    {
+        super(name, hash);
+        /* We have specialized implementations for shorter
+         * names, so let's not allow runt instances here
+         */
+        if (quadLen < 3) {
+            throw new IllegalArgumentException("Qlen must >= 3");
+        }
+        mQuads = quads;
+        mQuadLen = quadLen;
+    }
+
+    // Implies quad length == 1, never matches
+    @Override
+	public boolean equals(int quad) { return false; }
+
+    // Implies quad length == 2, never matches
+    @Override
+	public boolean equals(int quad1, int quad2) { return false; }
+
+    @Override
+	public boolean equals(int[] quads, int qlen)
+    {
+        if (qlen != mQuadLen) {
+            return false;
+        }
+
+        /* 26-Nov-2008, tatus: Strange, but it does look like
+         *   unrolling here is counter-productive, reducing
+         *   speed. Perhaps it prevents inlining by HotSpot or
+         *   something...
+         */
+        // Will always have >= 3 quads, can unroll
+        /*
+        if (quads[0] == mQuads[0]
+            && quads[1] == mQuads[1]
+            && quads[2] == mQuads[2]) {
+            for (int i = 3; i < qlen; ++i) {
+                if (quads[i] != mQuads[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        */
+
+        // or simpler way without unrolling:
+        for (int i = 0; i < qlen; ++i) {
+            if (quads[i] != mQuads[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/sym/package-info.java b/src/main/java/com/fasterxml/jackson/core/sym/package-info.java
new file mode 100644
index 0000000..b0ae032
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Internal implementation classes for efficient handling of
+ * of symbols in JSON (field names in Objects)
+ */
+package com.fasterxml.jackson.core.sym;
diff --git a/src/main/java/com/fasterxml/jackson/core/type/ResolvedType.java b/src/main/java/com/fasterxml/jackson/core/type/ResolvedType.java
new file mode 100644
index 0000000..e9931a0
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/type/ResolvedType.java
@@ -0,0 +1,122 @@
+package com.fasterxml.jackson.core.type;
+
+/**
+ * Type abstraction that represents Java type that has been resolved
+ * (i.e. has all generic information, if any, resolved to concrete
+ * types).
+ * Note that this is an intermediate type, and all concrete instances
+ * MUST be of type <code>JavaType</code> from "databind" bundle -- this
+ * abstraction is only needed so that types can be passed through
+ * {@link com.fasterxml.jackson.core.JsonParser#readValueAs} methods.
+ * 
+ * @since 2.0
+ */
+public abstract class ResolvedType
+{
+    /*
+    /**********************************************************
+    /* Public API, simple property accessors
+    /**********************************************************
+     */
+
+    /**
+     * Accessor for type-erased {@link Class} of resolved type.
+     */
+    public abstract Class<?> getRawClass();
+
+    public abstract boolean hasRawClass(Class<?> clz);
+
+    public abstract boolean isAbstract();
+    
+    public abstract boolean isConcrete();
+
+    public abstract boolean isThrowable();
+
+    public abstract boolean isArrayType();
+
+    public abstract boolean isEnumType();
+
+    public abstract boolean isInterface();
+
+    public abstract boolean isPrimitive();
+
+    public abstract boolean isFinal();
+
+    public abstract boolean isContainerType();
+
+    public abstract boolean isCollectionLikeType();
+
+    public abstract boolean isMapLikeType();
+
+    /*
+    /**********************************************************
+    /* Public API, type parameter access
+    /**********************************************************
+     */
+    
+    /**
+     * Method that can be used to find out if the type directly declares generic
+     * parameters (for its direct super-class and/or super-interfaces).
+     */
+    public abstract boolean hasGenericTypes();
+    
+    /**
+     * Method for accessing key type for this type, assuming type
+     * has such a concept (only Map types do)
+     */
+    public abstract ResolvedType getKeyType();
+
+    /**
+     * Method for accessing content type of this type, if type has
+     * such a thing: simple types do not, structured types do
+     * (like arrays, Collections and Maps)
+     */
+    public abstract ResolvedType getContentType();
+
+    /**
+     * Method for checking how many contained types this type
+     * has. Contained types are usually generic types, so that
+     * generic Maps have 2 contained types.
+     */
+    public abstract int containedTypeCount();
+
+    /**
+     * Method for accessing definitions of contained ("child")
+     * types.
+     * 
+     * @param index Index of contained type to return
+     * 
+     * @return Contained type at index, or null if no such type
+     *    exists (no exception thrown)
+     */
+    public abstract ResolvedType containedType(int index);
+    
+    /**
+     * Method for accessing name of type variable in indicated
+     * position. If no name is bound, will use placeholders (derived
+     * from 0-based index); if no type variable or argument exists
+     * with given index, null is returned.
+     * 
+     * @param index Index of contained type to return
+     * 
+     * @return Contained type at index, or null if no such type
+     *    exists (no exception thrown)
+     */
+    public abstract String containedTypeName(int index);
+
+    /*
+    /**********************************************************
+    /* Public API, other
+    /**********************************************************
+     */
+
+    /**
+     * Method that can be used to serialize type into form from which
+     * it can be fully deserialized from at a later point (using
+     * <code>TypeFactory</code> from mapper package).
+     * For simple types this is same as calling
+     * {@link Class#getName}, but for structured types it may additionally
+     * contain type information about contents.
+     */
+    public abstract String toCanonical();
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/type/TypeReference.java b/src/main/java/com/fasterxml/jackson/core/type/TypeReference.java
new file mode 100644
index 0000000..9138d11
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/type/TypeReference.java
@@ -0,0 +1,61 @@
+package com.fasterxml.jackson.core.type;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * This generic abstract class is used for obtaining full generics type information
+ * by sub-classing; it must be converted to {@link ResolvedType} implementation
+ * (implemented by <code>JavaType</code> from "databind" bundle) to be used.
+ * Class is based on ideas from
+ * <a href="http://gafter.blogspot.com/2006/12/super-type-tokens.html"
+ * >http://gafter.blogspot.com/2006/12/super-type-tokens.html</a>,
+ * Additional idea (from a suggestion made in comments of the article)
+ * is to require bogus implementation of <code>Comparable</code>
+ * (any such generic interface would do, as long as it forces a method
+ * with generic type to be implemented).
+ * to ensure that a Type argument is indeed given.
+ *<p>
+ * Usage is by sub-classing: here is one way to instantiate reference
+ * to generic type <code>List<Integer></code>:
+ *<pre>
+ *  TypeReference ref = new TypeReference<List<Integer>>() { };
+ *</pre>
+ * which can be passed to methods that accept TypeReference, or resolved
+ * using <code>TypeFactory</code> to obtain {@link ResolvedType}.
+ */
+public abstract class TypeReference<T>
+    implements Comparable<TypeReference<T>>
+{
+    protected final Type _type;
+    
+    protected TypeReference()
+    {
+        Type superClass = getClass().getGenericSuperclass();
+        if (superClass instanceof Class<?>) { // sanity check, should never happen
+            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
+        }
+        /* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
+         *   it is possible to make it fail?
+         *   But let's deal with specific
+         *   case when we know an actual use case, and thereby suitable
+         *   workarounds for valid case(s) and/or error to throw
+         *   on invalid one(s).
+         */
+        _type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
+    }
+
+    public Type getType() { return _type; }
+    
+    /**
+     * The only reason we define this method (and require implementation
+     * of <code>Comparable</code>) is to prevent constructing a
+     * reference without type information.
+     */
+    @Override
+    public int compareTo(TypeReference<T> o) {
+        // just need an implementation, not a good one... hence:
+        return 0;
+    }
+}
+
diff --git a/src/main/java/com/fasterxml/jackson/core/type/package-info.java b/src/main/java/com/fasterxml/jackson/core/type/package-info.java
new file mode 100644
index 0000000..4b28a37
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/type/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Contains classes needed for type introspection, mostly used by data binding
+ * functionality. Most of this functionality is needed to properly handled
+ * generic types, and to simplify and unify processing of things Jackson needs
+ * to determine how contained types (of {@link java.util.Collection} and
+ * {@link java.util.Map} classes) are to be handled.
+ */
+package com.fasterxml.jackson.core.type;
diff --git a/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java b/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java
new file mode 100644
index 0000000..3ebd295
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java
@@ -0,0 +1,117 @@
+package com.fasterxml.jackson.core.util;
+
+/**
+ * This is a small utility class, whose main functionality is to allow
+ * simple reuse of raw byte/char buffers. It is usually used through
+ * <code>ThreadLocal</code> member of the owning class pointing to
+ * instance of this class through a <code>SoftReference</code>. The
+ * end result is a low-overhead GC-cleanable recycling: hopefully
+ * ideal for use by stream readers.
+ */
+public class BufferRecycler
+{
+    public final static int DEFAULT_WRITE_CONCAT_BUFFER_LEN = 2000;
+    
+    public enum ByteBufferType {
+        READ_IO_BUFFER(4000)
+        /**
+         * Buffer used for temporarily storing encoded content; used
+         * for example by UTF-8 encoding writer
+         */
+        ,WRITE_ENCODING_BUFFER(4000)
+
+        /**
+         * Buffer used for temporarily concatenating output; used for
+         * example when requesting output as byte array.
+         */
+        ,WRITE_CONCAT_BUFFER(2000)
+        
+        /**
+         * Buffer used for concatenating binary data that is either being
+         * encoded as base64 output, or decoded from base64 input.
+         * 
+         * @since 2.1
+         */
+        ,BASE64_CODEC_BUFFER(2000)
+        ;
+            
+        protected final int size;
+
+        ByteBufferType(int size) { this.size = size; }
+    }
+
+    public enum CharBufferType {
+        TOKEN_BUFFER(2000) // Tokenizable input
+            ,CONCAT_BUFFER(2000) // concatenated output
+            ,TEXT_BUFFER(200) // Text content from input
+            ,NAME_COPY_BUFFER(200) // Temporary buffer for getting name characters
+            ;
+        
+        protected final int size;
+
+        CharBufferType(int size) { this.size = size; }
+    }
+
+    final protected byte[][] _byteBuffers = new byte[ByteBufferType.values().length][];
+    final protected char[][] _charBuffers = new char[CharBufferType.values().length][];
+
+    public BufferRecycler() { }
+
+    public final byte[] allocByteBuffer(ByteBufferType type)
+    {
+        int ix = type.ordinal();
+        byte[] buffer = _byteBuffers[ix];
+        if (buffer == null) {
+            buffer = balloc(type.size);
+        } else {
+            _byteBuffers[ix] = null;
+        }
+        return buffer;
+    }
+
+    public final void releaseByteBuffer(ByteBufferType type, byte[] buffer)
+    {
+        _byteBuffers[type.ordinal()] = buffer;
+    }
+
+    public final char[] allocCharBuffer(CharBufferType type)
+    {
+        return allocCharBuffer(type, 0);
+    }
+
+    public final char[] allocCharBuffer(CharBufferType type, int minSize)
+    {
+        if (type.size > minSize) {
+            minSize = type.size;
+        }
+        int ix = type.ordinal();
+        char[] buffer = _charBuffers[ix];
+        if (buffer == null || buffer.length < minSize) {
+            buffer = calloc(minSize);
+        } else {
+            _charBuffers[ix] = null;
+        }
+        return buffer;
+    }
+
+    public final void releaseCharBuffer(CharBufferType type, char[] buffer)
+    {
+        _charBuffers[type.ordinal()] = buffer;
+    }
+
+    /*
+    /**********************************************************
+    /* Actual allocations separated for easier debugging/profiling
+    /**********************************************************
+     */
+
+    private byte[] balloc(int size)
+    {
+        return new byte[size];
+    }
+
+    private char[] calloc(int size)
+    {
+        return new char[size];
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/ByteArrayBuilder.java b/src/main/java/com/fasterxml/jackson/core/util/ByteArrayBuilder.java
new file mode 100644
index 0000000..c5cff33
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/ByteArrayBuilder.java
@@ -0,0 +1,278 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta at iki.fi
+ */
+
+package com.fasterxml.jackson.core.util;
+
+import java.io.OutputStream;
+import java.util.*;
+
+/**
+ * Helper class that is similar to {@link java.io.ByteArrayOutputStream}
+ * in usage, but more geared to Jackson use cases internally.
+ * Specific changes include segment storage (no need to have linear
+ * backing buffer, can avoid reallocs, copying), as well API
+ * not based on {@link java.io.OutputStream}. In short, a very much
+ * specialized builder object.
+ *<p>
+ * Since version 1.5, also implements {@link OutputStream} to allow
+ * efficient aggregation of output content as a byte array, similar
+ * to how {@link java.io.ByteArrayOutputStream} works, but somewhat more
+ * efficiently for many use cases.
+ */
+public final class ByteArrayBuilder
+    extends OutputStream
+{
+    private final static byte[] NO_BYTES = new byte[0];
+    
+    /**
+     * Size of the first block we will allocate.
+     */
+    private final static int INITIAL_BLOCK_SIZE = 500;
+    
+    /**
+     * Maximum block size we will use for individual non-aggregated
+     * blocks. Let's limit to using 256k chunks.
+     */
+    private final static int MAX_BLOCK_SIZE = (1 << 18);
+    
+    final static int DEFAULT_BLOCK_ARRAY_SIZE = 40;
+
+    /**
+     * Optional buffer recycler instance that we can use for allocating
+     * the first block.
+     */
+    private final BufferRecycler _bufferRecycler;
+    
+    private final LinkedList<byte[]> _pastBlocks = new LinkedList<byte[]>();
+    
+    /**
+     * Number of bytes within byte arrays in {@link _pastBlocks}.
+     */
+    private int _pastLen;
+
+    private byte[] _currBlock;
+
+    private int _currBlockPtr;
+    
+    public ByteArrayBuilder() { this(null); }
+
+    public ByteArrayBuilder(BufferRecycler br) { this(br, INITIAL_BLOCK_SIZE); }
+
+    public ByteArrayBuilder(int firstBlockSize) { this(null, firstBlockSize); }
+
+    public ByteArrayBuilder(BufferRecycler br, int firstBlockSize)
+    {
+        _bufferRecycler = br;
+        if (br == null) {
+            _currBlock = new byte[firstBlockSize];
+        } else {
+            _currBlock = br.allocByteBuffer(BufferRecycler.ByteBufferType.WRITE_CONCAT_BUFFER);
+        }
+    }
+
+    public void reset()
+    {
+        _pastLen = 0;
+        _currBlockPtr = 0;
+
+        if (!_pastBlocks.isEmpty()) {
+            _pastBlocks.clear();
+        }
+    }
+
+    /**
+     * Clean up method to call to release all buffers this object may be
+     * using. After calling the method, no other accessors can be used (and
+     * attempt to do so may result in an exception)
+     */
+    public void release() {
+        reset();
+        if (_bufferRecycler != null && _currBlock != null) {
+            _bufferRecycler.releaseByteBuffer(BufferRecycler.ByteBufferType.WRITE_CONCAT_BUFFER, _currBlock);
+            _currBlock = null;
+        }
+    }
+
+    public void append(int i)
+    {
+        if (_currBlockPtr >= _currBlock.length) {
+            _allocMore();
+        }
+        _currBlock[_currBlockPtr++] = (byte) i;
+    }
+
+    public void appendTwoBytes(int b16)
+    {
+        if ((_currBlockPtr + 1) < _currBlock.length) {
+            _currBlock[_currBlockPtr++] = (byte) (b16 >> 8);
+            _currBlock[_currBlockPtr++] = (byte) b16;
+        } else {
+            append(b16 >> 8);
+            append(b16);
+        }
+    }
+
+    public void appendThreeBytes(int b24)
+    {
+        if ((_currBlockPtr + 2) < _currBlock.length) {
+            _currBlock[_currBlockPtr++] = (byte) (b24 >> 16);
+            _currBlock[_currBlockPtr++] = (byte) (b24 >> 8);
+            _currBlock[_currBlockPtr++] = (byte) b24;
+        } else {
+            append(b24 >> 16);
+            append(b24 >> 8);
+            append(b24);
+        }
+    }
+
+    /**
+     * Method called when results are finalized and we can get the
+     * full aggregated result buffer to return to the caller
+     */
+    public byte[] toByteArray()
+    {
+        int totalLen = _pastLen + _currBlockPtr;
+        
+        if (totalLen == 0) { // quick check: nothing aggregated?
+            return NO_BYTES;
+        }
+        
+        byte[] result = new byte[totalLen];
+        int offset = 0;
+
+        for (byte[] block : _pastBlocks) {
+            int len = block.length;
+            System.arraycopy(block, 0, result, offset, len);
+            offset += len;
+        }
+        System.arraycopy(_currBlock, 0, result, offset, _currBlockPtr);
+        offset += _currBlockPtr;
+        if (offset != totalLen) { // just a sanity check
+            throw new RuntimeException("Internal error: total len assumed to be "+totalLen+", copied "+offset+" bytes");
+        }
+        // Let's only reset if there's sizable use, otherwise will get reset later on
+        if (!_pastBlocks.isEmpty()) {
+            reset();
+        }
+        return result;
+    }
+
+    /*
+    /**********************************************************
+    /* Non-stream API (similar to TextBuffer), since 1.6
+    /**********************************************************
+     */
+
+    /**
+     * Method called when starting "manual" output: will clear out
+     * current state and return the first segment buffer to fill
+     */
+    public byte[] resetAndGetFirstSegment() {
+        reset();
+        return _currBlock;
+    }
+
+    /**
+     * Method called when the current segment buffer is full; will
+     * append to current contents, allocate a new segment buffer
+     * and return it
+     */
+    public byte[] finishCurrentSegment() {
+        _allocMore();
+        return _currBlock;
+    }
+
+    /**
+     * Method that will complete "manual" output process, coalesce
+     * content (if necessary) and return results as a contiguous buffer.
+     * 
+     * @param lastBlockLength Amount of content in the current segment
+     * buffer.
+     * 
+     * @return Coalesced contents
+     */
+    public byte[] completeAndCoalesce(int lastBlockLength)
+    {
+        _currBlockPtr = lastBlockLength;
+        return toByteArray();
+    }
+
+    public byte[] getCurrentSegment() {
+        return _currBlock;
+    }
+
+    public void setCurrentSegmentLength(int len) {
+        _currBlockPtr = len;
+    }
+
+    public int getCurrentSegmentLength() {
+        return _currBlockPtr;
+    }
+    
+    /*
+    /**********************************************************
+    /* OutputStream implementation
+    /**********************************************************
+     */
+    
+    @Override
+    public void write(byte[] b) {
+        write(b, 0, b.length);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len)
+    {
+        while (true) {
+            int max = _currBlock.length - _currBlockPtr;
+            int toCopy = Math.min(max, len);
+            if (toCopy > 0) {
+                System.arraycopy(b, off, _currBlock, _currBlockPtr, toCopy);
+                off += toCopy;
+                _currBlockPtr += toCopy;
+                len -= toCopy;
+            }
+            if (len <= 0) break;
+            _allocMore();
+        }
+    }
+
+    @Override
+    public void write(int b) {
+        append(b);
+    }
+
+    @Override public void close() { /* NOP */ }
+
+    @Override public void flush() { /* NOP */ }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+    
+    private void _allocMore()
+    {
+        _pastLen += _currBlock.length;
+
+        /* Let's allocate block that's half the total size, except
+         * never smaller than twice the initial block size.
+         * The idea is just to grow with reasonable rate, to optimize
+         * between minimal number of chunks and minimal amount of
+         * wasted space.
+         */
+        int newSize = Math.max((_pastLen >> 1), (INITIAL_BLOCK_SIZE + INITIAL_BLOCK_SIZE));
+        // plus not to exceed max we define...
+        if (newSize > MAX_BLOCK_SIZE) {
+            newSize = MAX_BLOCK_SIZE;
+        }
+        _pastBlocks.add(_currBlock);
+        _currBlock = new byte[newSize];
+        _currBlockPtr = 0;
+    }
+
+}
+
diff --git a/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java b/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java
new file mode 100644
index 0000000..a2018fb
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java
@@ -0,0 +1,389 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.*;
+import java.util.Arrays;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+/**
+ * Default {@link PrettyPrinter} implementation that uses 2-space
+ * indentation with platform-default linefeeds.
+ * Usually this class is not instantiated directly, but instead
+ * method {@link JsonGenerator#useDefaultPrettyPrinter} is
+ * used, which will use an instance of this class for operation.
+ */
+ at SuppressWarnings("serial")
+public class DefaultPrettyPrinter
+    implements PrettyPrinter, Instantiatable<DefaultPrettyPrinter>,
+        java.io.Serializable
+{
+    private static final long serialVersionUID = -5512586643324525213L;
+
+    /**
+     * Constant that specifies default "root-level" separator to use between
+     * root values: a single space character.
+     * 
+     * @since 2.1
+     */
+    public final static SerializedString DEFAULT_ROOT_VALUE_SEPARATOR = new SerializedString(" ");
+    
+    /**
+     * Interface that defines objects that can produce indentation used
+     * to separate object entries and array values. Indentation in this
+     * context just means insertion of white space, independent of whether
+     * linefeeds are output.
+     */
+    public interface Indenter
+    {
+        void writeIndentation(JsonGenerator jg, int level)
+            throws IOException, JsonGenerationException;
+
+        /**
+         * @return True if indenter is considered inline (does not add linefeeds),
+         *   false otherwise
+         */
+        boolean isInline();
+    }
+    
+    // // // Config, indentation
+
+    /**
+     * By default, let's use only spaces to separate array values.
+     */
+    protected Indenter _arrayIndenter = FixedSpaceIndenter.instance;
+
+    /**
+     * By default, let's use linefeed-adding indenter for separate
+     * object entries. We'll further configure indenter to use
+     * system-specific linefeeds, and 2 spaces per level (as opposed to,
+     * say, single tabs)
+     */
+    protected Indenter _objectIndenter = Lf2SpacesIndenter.instance;
+
+    /**
+     * String printed between root-level values, if any.
+     */
+    protected final SerializableString _rootSeparator;
+    
+    // // // Config, other white space configuration
+
+    /**
+     * By default we will add spaces around colons used to
+     * separate object fields and values.
+     * If disabled, will not use spaces around colon.
+     */
+    protected boolean _spacesInObjectEntries = true;
+
+    // // // State:
+
+    /**
+     * Number of open levels of nesting. Used to determine amount of
+     * indentation to use.
+     */
+    protected transient int _nesting = 0;
+
+    /*
+    /**********************************************************
+    /* Life-cycle (construct, configure)
+    /**********************************************************
+    */
+
+    public DefaultPrettyPrinter() {
+        this(DEFAULT_ROOT_VALUE_SEPARATOR);
+    }
+
+    /**
+     * Constructor that specifies separator String to use between root values;
+     * if null, no separator is printed.
+     *<p>
+     * Note: simply constructs a {@link SerializedString} out of parameter,
+     * calls {@link #DefaultPrettyPrinter(SerializableString)}
+     * 
+     * @param rootSeparator
+     * 
+     * @since 2.1
+     */
+    public DefaultPrettyPrinter(String rootSeparator) {
+        this((rootSeparator == null) ? null : new SerializedString(rootSeparator));
+    }
+
+    /**
+     * Constructor that specifies separator String to use between root values;
+     * if null, no separator is printed.
+     * 
+     * @param rootSeparator
+     * 
+     * @since 2.1
+     */
+    public DefaultPrettyPrinter(SerializableString rootSeparator) {
+        _rootSeparator = rootSeparator;
+    }
+    
+    public DefaultPrettyPrinter(DefaultPrettyPrinter base) {
+        this(base, base._rootSeparator);
+    }
+
+    public DefaultPrettyPrinter(DefaultPrettyPrinter base,
+            SerializableString rootSeparator)
+    {
+        _arrayIndenter = base._arrayIndenter;
+        _objectIndenter = base._objectIndenter;
+        _spacesInObjectEntries = base._spacesInObjectEntries;
+        _nesting = base._nesting;
+
+        _rootSeparator = rootSeparator;
+    }
+
+    public DefaultPrettyPrinter withRootSeparator(SerializableString rootSeparator)
+    {
+        if (_rootSeparator == rootSeparator ||
+                (rootSeparator != null && rootSeparator.equals(_rootSeparator))) {
+            return this;
+        }
+        return new DefaultPrettyPrinter(this, rootSeparator);
+    }
+    
+    public void indentArraysWith(Indenter i)
+    {
+        _arrayIndenter = (i == null) ? NopIndenter.instance : i;
+    }
+
+    public void indentObjectsWith(Indenter i)
+    {
+        _objectIndenter = (i == null) ? NopIndenter.instance : i;
+    }
+
+    public void spacesInObjectEntries(boolean b) { _spacesInObjectEntries = b; }
+
+    /*
+    /**********************************************************
+    /* Instantiatable impl
+    /**********************************************************
+     */
+    
+    @Override
+    public DefaultPrettyPrinter createInstance() {
+        return new DefaultPrettyPrinter(this);
+    }
+    
+    /*
+    /**********************************************************
+    /* PrettyPrinter impl
+    /**********************************************************
+     */
+
+    @Override
+    public void writeRootValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        if (_rootSeparator != null) {
+            jg.writeRaw(_rootSeparator);
+        }
+    }
+
+    @Override
+    public void writeStartObject(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw('{');
+        if (!_objectIndenter.isInline()) {
+            ++_nesting;
+        }
+    }
+
+    @Override
+    public void beforeObjectEntries(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        _objectIndenter.writeIndentation(jg, _nesting);
+    }
+
+    /**
+     * Method called after an object field has been output, but
+     * before the value is output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * colon to separate the two. Pretty-printer is
+     * to output a colon as well, but can surround that with other
+     * (white-space) decoration.
+     */
+    @Override
+    public void writeObjectFieldValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        if (_spacesInObjectEntries) {
+            jg.writeRaw(" : ");
+        } else {
+            jg.writeRaw(':');
+        }
+    }
+
+    /**
+     * Method called after an object entry (field:value) has been completely
+     * output, and before another value is to be output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * comma to separate the two. Pretty-printer is
+     * to output a comma as well, but can surround that with other
+     * (white-space) decoration.
+     */
+    @Override
+    public void writeObjectEntrySeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw(',');
+        _objectIndenter.writeIndentation(jg, _nesting);
+    }
+
+    @Override
+    public void writeEndObject(JsonGenerator jg, int nrOfEntries)
+        throws IOException, JsonGenerationException
+    {
+        if (!_objectIndenter.isInline()) {
+            --_nesting;
+        }
+        if (nrOfEntries > 0) {
+            _objectIndenter.writeIndentation(jg, _nesting);
+        } else {
+            jg.writeRaw(' ');
+        }
+        jg.writeRaw('}');
+    }
+
+    @Override
+    public void writeStartArray(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        if (!_arrayIndenter.isInline()) {
+            ++_nesting;
+        }
+        jg.writeRaw('[');
+    }
+
+    @Override
+    public void beforeArrayValues(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        _arrayIndenter.writeIndentation(jg, _nesting);
+    }
+
+    /**
+     * Method called after an array value has been completely
+     * output, and before another value is to be output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * comma to separate the two. Pretty-printer is
+     * to output a comma as well, but can surround that with other
+     * (white-space) decoration.
+     */
+    @Override
+    public void writeArrayValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw(',');
+        _arrayIndenter.writeIndentation(jg, _nesting);
+    }
+
+    @Override
+    public void writeEndArray(JsonGenerator jg, int nrOfValues)
+        throws IOException, JsonGenerationException
+    {
+        if (!_arrayIndenter.isInline()) {
+            --_nesting;
+        }
+        if (nrOfValues > 0) {
+            _arrayIndenter.writeIndentation(jg, _nesting);
+        } else {
+            jg.writeRaw(' ');
+        }
+        jg.writeRaw(']');
+    }
+
+    /*
+    /**********************************************************
+    /* Helper classes
+    /**********************************************************
+     */
+
+    /**
+     * Dummy implementation that adds no indentation whatsoever
+     */
+    public static class NopIndenter
+        implements Indenter, java.io.Serializable
+    {
+        public static final NopIndenter instance = new NopIndenter();
+
+        @Override
+        public void writeIndentation(JsonGenerator jg, int level)
+            throws IOException, JsonGenerationException
+        { }
+
+        @Override
+        public boolean isInline() { return true; }
+    }
+
+    /**
+     * This is a very simple indenter that only every adds a
+     * single space for indentation. It is used as the default
+     * indenter for array values.
+     */
+    public static class FixedSpaceIndenter
+        extends NopIndenter
+    {
+        public static final FixedSpaceIndenter instance = new FixedSpaceIndenter();
+
+        @Override
+        public void writeIndentation(JsonGenerator jg, int level)
+            throws IOException, JsonGenerationException
+        {
+            jg.writeRaw(' ');
+        }
+
+        @Override
+        public boolean isInline() { return true; }
+    }
+
+    /**
+     * Default linefeed-based indenter uses system-specific linefeeds and
+     * 2 spaces for indentation per level.
+     */
+    public static class Lf2SpacesIndenter
+        extends NopIndenter
+    {
+        public static final Lf2SpacesIndenter instance = new Lf2SpacesIndenter();
+
+        private final static String SYS_LF;
+        static {
+            String lf = null;
+            try {
+                lf = System.getProperty("line.separator");
+            } catch (Throwable t) { } // access exception?
+            SYS_LF = (lf == null) ? "\n" : lf;
+        }
+
+        final static int SPACE_COUNT = 64;
+        final static char[] SPACES = new char[SPACE_COUNT];
+        static {
+            Arrays.fill(SPACES, ' ');
+        }
+
+        @Override
+        public boolean isInline() { return false; }
+
+        @Override
+        public void writeIndentation(JsonGenerator jg, int level)
+            throws IOException, JsonGenerationException
+        {
+            jg.writeRaw(SYS_LF);
+            if (level > 0) { // should we err on negative values (as there's some flaw?)
+                level += level; // 2 spaces per level
+                while (level > SPACE_COUNT) { // should never happen but...
+                    jg.writeRaw(SPACES, 0, SPACE_COUNT); 
+                    level -= SPACES.length;
+                }
+                jg.writeRaw(SPACES, 0, level);
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/Instantiatable.java b/src/main/java/com/fasterxml/jackson/core/util/Instantiatable.java
new file mode 100644
index 0000000..997293c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/Instantiatable.java
@@ -0,0 +1,24 @@
+package com.fasterxml.jackson.core.util;
+
+/**
+ * Add-on interface used to indicate things that may be "blueprint" objects
+ * which can not be used as is, but are used for creating usable per-process
+ * (serialization, deserialization) instances, using
+ * {@link #createInstance} method.
+ *<p>
+ * Note that some implementations may choose to implement {@link #createInstance}
+ * by simply returning 'this': this is acceptable if instances are stateless.
+ * 
+ * @see DefaultPrettyPrinter
+ * 
+ * @since 2.1
+ */
+public interface Instantiatable<T>
+{
+    /**
+     * Method called to ensure that we have a non-blueprint object to use;
+     * it is either this object (if stateless), or a newly created object
+     * with separate state.
+     */
+    T createInstance();
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/InternCache.java b/src/main/java/com/fasterxml/jackson/core/util/InternCache.java
new file mode 100644
index 0000000..50711e1
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/InternCache.java
@@ -0,0 +1,53 @@
+package com.fasterxml.jackson.core.util;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+/**
+ * Singleton class that adds a simple first-level cache in front of
+ * regular String.intern() functionality. This is done as a minor
+ * performance optimization, to avoid calling native intern() method
+ * in cases where same String is being interned multiple times.
+ *<p>
+ * Note: that this class extends {@link LinkedHashMap} is an implementation
+ * detail -- no code should ever directly call Map methods.
+ */
+ at SuppressWarnings("serial")
+public final class InternCache
+    extends LinkedHashMap<String,String>
+{
+    /**
+     * Size to use is somewhat arbitrary, so let's choose something that's
+     * neither too small (low hit ratio) nor too large (waste of memory).
+     *<p>
+     * 11-Jul-2012, tatu: Also, consider the nasty case of String hashCode()
+     *    collisions; size needs to be small enough to survive linear list
+     *    lookup... so let's go down a notch (from 192 to 100)
+     */
+    private final static int MAX_ENTRIES = 100;
+
+    public final static InternCache instance = new InternCache();
+
+    private InternCache() {
+        super(MAX_ENTRIES, 0.8f, true);
+    }
+
+    @Override
+    protected boolean removeEldestEntry(Map.Entry<String,String> eldest)
+    {
+        return size() > MAX_ENTRIES;
+    }
+
+    public synchronized String intern(String input)
+    {
+        String result = get(input);
+        if (result == null) {
+            result = input.intern();
+            put(result, result);
+        }
+        return result;
+    }
+
+
+}
+
diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java
new file mode 100644
index 0000000..81ca045
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java
@@ -0,0 +1,414 @@
+package com.fasterxml.jackson.core.util;
+
+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.io.CharacterEscapes;
+
+public class JsonGeneratorDelegate extends JsonGenerator
+{
+    /**
+     * Delegate object that method calls are delegated to.
+     */
+    protected JsonGenerator delegate;
+
+    /*
+    /**********************************************************
+    /* Construction, initialization
+    /**********************************************************
+     */
+    
+    public JsonGeneratorDelegate(JsonGenerator d) {
+        delegate = d;
+    }   
+
+    @Override
+    public ObjectCodec getCodec() {
+        return delegate.getCodec();
+    }
+
+    @Override
+    public JsonGenerator setCodec(ObjectCodec oc) {
+        delegate.setCodec(oc);
+        return this;
+    }
+    
+    @Override
+    public void setSchema(FormatSchema schema) {
+        delegate.setSchema(schema);
+    }
+
+    @Override
+    public FormatSchema getSchema() {
+        return delegate.getSchema();
+    }
+    
+    @Override
+    public boolean canUseSchema(FormatSchema schema) {
+        return delegate.canUseSchema(schema);
+    }
+
+    @Override
+    public Version version() {
+        return delegate.version();
+    }
+
+    @Override
+    public Object getOutputTarget() {
+        return delegate.getOutputTarget();
+    }
+
+    @Override
+    public JsonGenerator setRootValueSeparator(SerializableString sep) {
+        delegate.setRootValueSeparator(sep);
+        return this;
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, configuration
+    /**********************************************************
+     */
+
+    @Override
+    public JsonGenerator enable(Feature f) {
+        delegate.enable(f);
+        return this;
+    }
+    
+    @Override
+    public JsonGenerator disable(Feature f) {
+        delegate.disable(f);
+        return this;
+    }
+
+    @Override
+    public boolean isEnabled(Feature f) {
+        return delegate.isEnabled(f);
+    }
+
+    // final, can't override (and no need to)
+    //public final JsonGenerator configure(Feature f, boolean state)
+
+    /*
+    /**********************************************************
+    /* Configuring generator
+    /**********************************************************
+      */
+
+    @Override
+    public JsonGenerator setPrettyPrinter(PrettyPrinter pp) {
+        delegate.setPrettyPrinter(pp);
+        return this;
+    }
+
+    @Override
+    public PrettyPrinter getPrettyPrinter() {
+        return delegate.getPrettyPrinter();
+    }
+    
+    @Override
+    public JsonGenerator useDefaultPrettyPrinter() {
+        delegate.useDefaultPrettyPrinter();
+        return this;
+    }
+
+    @Override
+    public JsonGenerator setHighestNonEscapedChar(int charCode) {
+        delegate.setHighestNonEscapedChar(charCode);
+        return this;
+    }
+
+    @Override
+    public int getHighestEscapedChar() {
+        return delegate.getHighestEscapedChar();
+    }
+
+    @Override
+    public CharacterEscapes getCharacterEscapes() {
+        return delegate.getCharacterEscapes();
+    }
+
+    @Override
+    public JsonGenerator setCharacterEscapes(CharacterEscapes esc) {
+        delegate.setCharacterEscapes(esc);
+        return this;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, structural
+    /**********************************************************
+     */
+
+    @Override
+    public void writeStartArray() throws IOException, JsonGenerationException {
+         delegate.writeStartArray();
+    }
+
+
+    @Override
+    public void writeEndArray() throws IOException, JsonGenerationException {
+        delegate.writeEndArray();
+    }
+
+    @Override
+    public void writeStartObject() throws IOException, JsonGenerationException {
+        delegate.writeStartObject();
+    }
+    
+    @Override
+    public void writeEndObject() throws IOException, JsonGenerationException {
+        delegate.writeEndObject();
+    }
+
+    @Override
+    public void writeFieldName(String name)
+        throws IOException, JsonGenerationException
+    {
+        delegate.writeFieldName(name);
+    }
+
+    @Override
+    public void writeFieldName(SerializableString name)
+        throws IOException, JsonGenerationException
+    {
+        delegate.writeFieldName(name);
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, write methods, text/String values
+    /**********************************************************
+     */
+
+    @Override
+    public void writeString(String text) throws IOException,JsonGenerationException {
+        delegate.writeString(text);
+    }
+
+    @Override
+    public void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+        delegate.writeString(text, offset, len);
+    }
+
+    @Override
+    public void writeString(SerializableString text) throws IOException, JsonGenerationException {
+        delegate.writeString(text);
+    }
+
+    @Override
+    public void writeRawUTF8String(byte[] text, int offset, int length)
+        throws IOException, JsonGenerationException
+    {
+        delegate.writeRawUTF8String(text, offset, length);
+    }
+
+    @Override
+    public void writeUTF8String(byte[] text, int offset, int length)
+        throws IOException, JsonGenerationException
+    {
+        delegate.writeUTF8String(text, offset, length);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, binary/raw content
+    /**********************************************************
+     */
+
+    @Override
+    public void writeRaw(String text) throws IOException, JsonGenerationException {
+        delegate.writeRaw(text);
+    }
+
+    @Override
+    public void writeRaw(String text, int offset, int len) throws IOException, JsonGenerationException {
+        delegate.writeRaw(text, offset, len);
+    }
+
+    @Override
+    public void writeRaw(SerializableString raw)
+        throws IOException, JsonGenerationException {
+        delegate.writeRaw(raw);
+    }
+    
+    @Override
+    public void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+        delegate.writeRaw(text, offset, len);
+    }
+
+    @Override
+    public void writeRaw(char c) throws IOException, JsonGenerationException {
+        delegate.writeRaw(c);
+    }
+
+    @Override
+    public void writeRawValue(String text) throws IOException, JsonGenerationException {
+        delegate.writeRawValue(text);
+    }
+
+    @Override
+    public void writeRawValue(String text, int offset, int len) throws IOException, JsonGenerationException {
+         delegate.writeRawValue(text, offset, len);
+    }
+
+    @Override
+    public void writeRawValue(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+         delegate.writeRawValue(text, offset, len);
+    }
+
+    @Override
+    public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len)
+        throws IOException, JsonGenerationException
+    {
+        delegate.writeBinary(b64variant, data, offset, len);
+    }
+
+    @Override
+    public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength)
+        throws IOException, JsonGenerationException {
+        return delegate.writeBinary(b64variant, data, dataLength);
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, write methods, other value types
+    /**********************************************************
+     */
+
+    @Override
+    public void writeNumber(short v) throws IOException, JsonGenerationException {
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(int v) throws IOException, JsonGenerationException {
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(long v) throws IOException, JsonGenerationException {
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(BigInteger v) throws IOException,
+            JsonGenerationException {
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(double v) throws IOException,
+            JsonGenerationException {
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(float v) throws IOException,
+            JsonGenerationException {
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(BigDecimal v) throws IOException,
+            JsonGenerationException {
+        delegate.writeNumber(v);
+    }
+
+    @Override
+    public void writeNumber(String encodedValue) throws IOException, JsonGenerationException, UnsupportedOperationException {
+        delegate.writeNumber(encodedValue);
+    }
+
+    @Override
+    public void writeBoolean(boolean state) throws IOException, JsonGenerationException {
+        delegate.writeBoolean(state);
+    }
+    
+    @Override
+    public void writeNull() throws IOException, JsonGenerationException {
+        delegate.writeNull();
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, write methods, serializing Java objects
+    /**********************************************************
+     */
+    
+    @Override
+    public void writeObject(Object pojo) throws IOException,JsonProcessingException {
+        delegate.writeObject(pojo);
+    }
+    
+    @Override
+    public void writeTree(TreeNode rootNode) throws IOException, JsonProcessingException {
+        delegate.writeTree(rootNode);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, convenience field write methods
+    /**********************************************************
+     */
+
+    // // These are fine, just delegate to other methods...
+
+    /*
+    /**********************************************************
+    /* Public API, copy-through methods
+    /**********************************************************
+     */
+
+    @Override
+    public void copyCurrentEvent(JsonParser jp) throws IOException, JsonProcessingException {
+        delegate.copyCurrentEvent(jp);
+    }
+
+    @Override
+    public void copyCurrentStructure(JsonParser jp) throws IOException, JsonProcessingException {
+        delegate.copyCurrentStructure(jp);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, context access
+    /**********************************************************
+     */
+
+    @Override
+    public JsonStreamContext getOutputContext() {
+        return delegate.getOutputContext();
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, buffer handling
+    /**********************************************************
+     */
+    
+    @Override
+    public void flush() throws IOException {
+        delegate.flush();
+    }
+    
+    @Override
+    public void close() throws IOException {
+        delegate.close();
+    }
+
+    /*
+    /**********************************************************
+    /* Closeable implementation
+    /**********************************************************
+     */
+    
+    @Override
+    public boolean isClosed() {
+        return delegate.isClosed();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java b/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java
new file mode 100644
index 0000000..7e2f80a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java
@@ -0,0 +1,350 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Helper class that implements
+ * <a href="http://en.wikipedia.org/wiki/Delegation_pattern">delegation pattern</a> for {@link JsonParser},
+ * to allow for simple overridability of basic parsing functionality.
+ * The idea is that any functionality to be modified can be simply
+ * overridden; and anything else will be delegated by default.
+ */
+public class JsonParserDelegate extends JsonParser
+{
+    /**
+     * Delegate object that method calls are delegated to.
+     */
+    protected JsonParser delegate;
+
+    public JsonParserDelegate(JsonParser d) {
+        delegate = d;
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, configuration
+    /**********************************************************
+     */
+
+    @Override
+    public void setCodec(ObjectCodec c) {
+        delegate.setCodec(c);
+    }
+
+    @Override
+    public ObjectCodec getCodec() {
+        return delegate.getCodec();
+    }
+
+    @Override
+    public JsonParser enable(Feature f) {
+        delegate.enable(f);
+        return this;
+    }
+
+    @Override
+    public JsonParser disable(Feature f) {
+        delegate.disable(f);
+        return this;
+    }
+ 
+    @Override
+    public boolean isEnabled(Feature f) {
+        return delegate.isEnabled(f);
+    }
+
+    @Override
+    public FormatSchema getSchema() {
+        return delegate.getSchema();
+    }
+    
+    @Override
+    public void setSchema(FormatSchema schema) {
+        delegate.setSchema(schema);
+    }
+
+    @Override
+    public boolean canUseSchema(FormatSchema schema) {
+        return delegate.canUseSchema(schema);
+    }
+
+    @Override
+    public boolean requiresCustomCodec() {
+        return delegate.requiresCustomCodec();
+    }
+    
+    @Override
+    public Version version() {
+        return delegate.version();
+    }
+
+    @Override
+    public Object getInputSource() {
+        return delegate.getInputSource();
+    }
+    
+    /*
+    /**********************************************************
+    /* Closeable impl
+    /**********************************************************
+     */
+
+    @Override
+    public void close() throws IOException {
+        delegate.close();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return delegate.isClosed();
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, token accessors
+    /**********************************************************
+     */
+
+    @Override
+    public JsonToken getCurrentToken() {
+        return delegate.getCurrentToken();
+    }
+
+    @Override
+    public boolean hasCurrentToken() {
+        return delegate.hasCurrentToken();
+    }
+
+    @Override
+    public String getCurrentName() throws IOException, JsonParseException {
+        return delegate.getCurrentName();
+    }
+
+    @Override
+    public JsonLocation getCurrentLocation() {
+        return delegate.getCurrentLocation();
+    }
+
+    @Override
+    public JsonStreamContext getParsingContext() {
+        return delegate.getParsingContext();
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, token state overrides
+    /**********************************************************
+     */
+    
+    @Override
+    public void clearCurrentToken() {
+        delegate.clearCurrentToken();        
+    }
+
+    @Override
+    public JsonToken getLastClearedToken() {
+        return delegate.getLastClearedToken();
+    }
+    
+    @Override
+    public void overrideCurrentName(String name) {
+        delegate.overrideCurrentName(name);
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, text
+    /**********************************************************
+     */
+
+    @Override
+    public String getText() throws IOException, JsonParseException {
+        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();
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, numeric
+    /**********************************************************
+     */
+    
+    @Override
+    public BigInteger getBigIntegerValue() throws IOException,JsonParseException {
+        return delegate.getBigIntegerValue();
+    }
+
+    @Override
+    public boolean getBooleanValue() throws IOException, JsonParseException {
+        return delegate.getBooleanValue();
+    }
+    
+    @Override
+    public byte getByteValue() throws IOException, JsonParseException {
+        return delegate.getByteValue();
+    }
+
+    @Override
+    public short getShortValue() throws IOException, JsonParseException {
+        return delegate.getShortValue();
+    }
+
+    @Override
+    public BigDecimal getDecimalValue() throws IOException, JsonParseException {
+        return delegate.getDecimalValue();
+    }
+
+    @Override
+    public double getDoubleValue() throws IOException, JsonParseException {
+        return delegate.getDoubleValue();
+    }
+
+    @Override
+    public float getFloatValue() throws IOException, JsonParseException {
+        return delegate.getFloatValue();
+    }
+
+    @Override
+    public int getIntValue() throws IOException, JsonParseException {
+        return delegate.getIntValue();
+    }
+
+    @Override
+    public long getLongValue() throws IOException, JsonParseException {
+        return delegate.getLongValue();
+    }
+
+    @Override
+    public NumberType getNumberType() throws IOException, JsonParseException {
+        return delegate.getNumberType();
+    }
+
+    @Override
+    public Number getNumberValue() throws IOException, JsonParseException {
+        return delegate.getNumberValue();
+    }
+
+    /*
+    /**********************************************************
+    /* Public API, access to token information, coercion/conversion
+    /**********************************************************
+     */
+    
+    @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);
+    }
+    
+    /*
+    /**********************************************************
+    /* Public API, access to token values, other
+    /**********************************************************
+     */
+
+    @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 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 JsonParser skipChildren() throws IOException, JsonParseException {
+        delegate.skipChildren();
+        // NOTE: must NOT delegate this method to delegate, needs to be self-reference for chaining
+        return this;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonParserSequence.java b/src/main/java/com/fasterxml/jackson/core/util/JsonParserSequence.java
new file mode 100644
index 0000000..b2f188a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonParserSequence.java
@@ -0,0 +1,147 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.IOException;
+import java.util.*;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Helper class that can be used to sequence multiple physical
+ * {@link JsonParser}s to create a single logical sequence of
+ * tokens, as a single {@link JsonParser}.
+ *<p>
+ * Fairly simple use of {@link JsonParserDelegate}: only need
+ * to override {@link #nextToken} to handle transition
+ */
+public class JsonParserSequence extends JsonParserDelegate
+{
+    /**
+     * Parsers other than the first one (which is initially assigned
+     * as delegate)
+     */
+    protected final JsonParser[] _parsers;
+    
+    /**
+     * Index of the next parser in {@link #_parsers}.
+     */
+    protected int _nextParser;
+    
+    /*
+     *******************************************************
+     * Construction
+     *******************************************************
+     */
+
+    protected JsonParserSequence(JsonParser[] parsers)
+    {
+        super(parsers[0]);
+        _parsers = parsers;
+        _nextParser = 1;
+    }
+
+    /**
+     * Method that will construct a parser (possibly a sequence) that
+     * contains all given sub-parsers.
+     * All parsers given are checked to see if they are sequences: and
+     * if so, they will be "flattened", that is, contained parsers are
+     * directly added in a new sequence instead of adding sequences
+     * within sequences. This is done to minimize delegation depth,
+     * ideally only having just a single level of delegation.
+     */
+    public static JsonParserSequence createFlattened(JsonParser first, JsonParser second)
+    {
+        if (!(first instanceof JsonParserSequence || second instanceof JsonParserSequence)) {
+            // simple:
+            return new JsonParserSequence(new JsonParser[] { first, second });
+        }
+        ArrayList<JsonParser> p = new ArrayList<JsonParser>();
+        if (first instanceof JsonParserSequence) {
+            ((JsonParserSequence) first).addFlattenedActiveParsers(p);
+        } else {
+            p.add(first);
+        }
+        if (second instanceof JsonParserSequence) {
+            ((JsonParserSequence) second).addFlattenedActiveParsers(p);
+        } else {
+            p.add(second);
+        }
+        return new JsonParserSequence(p.toArray(new JsonParser[p.size()]));
+    }
+
+    protected void addFlattenedActiveParsers(List<JsonParser> result)
+    {
+        for (int i = _nextParser-1, len = _parsers.length; i < len; ++i) {
+            JsonParser p = _parsers[i];
+            if (p instanceof JsonParserSequence) {
+                ((JsonParserSequence) p).addFlattenedActiveParsers(result);
+            } else {
+                result.add(p);
+            }
+        }
+    }
+    
+    /*
+     *******************************************************
+     * Overridden methods, needed: cases where default
+     * delegation does not work
+     *******************************************************
+     */
+    
+    @Override
+    public void close() throws IOException
+    {
+        do {
+            delegate.close();
+        } while (switchToNext());
+    }
+
+    @Override
+    public JsonToken nextToken() throws IOException, JsonParseException
+    {
+        JsonToken t = delegate.nextToken();
+        if (t != null) return t;
+        while (switchToNext()) {
+            t = delegate.nextToken();
+            if (t != null) return t;
+        }
+        return null;
+    }
+
+    /*
+    /*******************************************************
+    /* Additional extended API
+    /*******************************************************
+     */
+
+    /**
+     * Method that is most useful for debugging or testing;
+     * returns actual number of underlying parsers sequence
+     * was constructed with (nor just ones remaining active)
+     */
+    public int containedParsersCount() {
+        return _parsers.length;
+    }
+    
+    /*
+    /*******************************************************
+    /* Helper methods
+    /*******************************************************
+     */
+
+    /**
+     * Method that will switch active parser from the current one
+     * to next parser in sequence, if there is another parser left,
+     * making this the new delegate. Old delegate is returned if
+     * switch succeeds.
+     * 
+     * @return True if switch succeeded; false otherwise
+     */
+    protected boolean switchToNext()
+    {
+        if (_nextParser >= _parsers.length) {
+            return false;
+        }
+        delegate = _parsers[_nextParser++];
+        return true;
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java b/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java
new file mode 100644
index 0000000..ab65ffe
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java
@@ -0,0 +1,153 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.PrettyPrinter;
+
+/**
+ * {@link PrettyPrinter} implementation that adds no indentation,
+ * just implements everything necessary for value output to work
+ * as expected, and provide simpler extension points to allow
+ * for creating simple custom implementations that add specific
+ * decoration or overrides. Since behavior then is very similar
+ * to using no pretty printer at all, usually sub-classes are used.
+ *<p>
+ * Beyond purely minimal implementation, there is limited amount of
+ * configurability which may be useful for actual use: for example,
+ * it is possible to redefine separator used between root-level
+ * values (default is single space; can be changed to line-feed).
+ *<p>
+ * Note: does NOT implement {@link Instantiatable} since this is
+ * a stateless implementation; that is, a single instance can be
+ * shared between threads.
+ */
+public class MinimalPrettyPrinter
+    implements PrettyPrinter, java.io.Serializable
+{
+    private static final long serialVersionUID = -562765100295218442L;
+
+    /**
+     * Default String used for separating root values is single space.
+     */
+    public final static String DEFAULT_ROOT_VALUE_SEPARATOR = " ";
+    
+    protected String _rootValueSeparator = DEFAULT_ROOT_VALUE_SEPARATOR;
+
+    /*
+    /**********************************************************
+    /* Life-cycle, construction, configuration
+    /**********************************************************
+     */
+    
+    public MinimalPrettyPrinter() {
+        this(DEFAULT_ROOT_VALUE_SEPARATOR);
+    }
+
+    public MinimalPrettyPrinter(String rootValueSeparator) {
+        _rootValueSeparator = rootValueSeparator;
+    }
+    
+    public void setRootValueSeparator(String sep) {
+        _rootValueSeparator = sep;
+    }
+    
+    /*
+    /**********************************************************
+    /* PrettyPrinter impl
+    /**********************************************************
+     */
+
+    @Override
+    public void writeRootValueSeparator(JsonGenerator jg) throws IOException, JsonGenerationException
+    {
+        if (_rootValueSeparator != null) {
+            jg.writeRaw(_rootValueSeparator);    
+        }
+    }
+    
+    @Override
+    public void writeStartObject(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw('{');
+    }
+    
+    @Override
+    public void beforeObjectEntries(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        // nothing special, since no indentation is added
+    }
+
+    /**
+     * Method called after an object field has been output, but
+     * before the value is output.
+     *<p>
+     * Default handling will just output a single
+     * colon to separate the two, without additional spaces.
+     */
+    @Override
+    public void writeObjectFieldValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw(':');
+    }
+    
+    /**
+     * Method called after an object entry (field:value) has been completely
+     * output, and before another value is to be output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * comma to separate the two.
+     */
+    @Override
+    public void writeObjectEntrySeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw(',');
+    }
+
+    @Override
+    public void writeEndObject(JsonGenerator jg, int nrOfEntries)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw('}');
+    }
+    
+    @Override
+    public void writeStartArray(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw('[');
+    }
+    
+    @Override
+    public void beforeArrayValues(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        // nothing special, since no indentation is added
+    }
+
+    /**
+     * Method called after an array value has been completely
+     * output, and before another value is to be output.
+     *<p>
+     * Default handling (without pretty-printing) will output a single
+     * comma to separate values.
+     */
+    @Override
+    public void writeArrayValueSeparator(JsonGenerator jg)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw(',');
+    }
+    
+    @Override
+    public void writeEndArray(JsonGenerator jg, int nrOfValues)
+        throws IOException, JsonGenerationException
+    {
+        jg.writeRaw(']');
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java b/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
new file mode 100644
index 0000000..eebe9ac
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
@@ -0,0 +1,718 @@
+package com.fasterxml.jackson.core.util;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+
+import com.fasterxml.jackson.core.io.NumberInput;
+
+/**
+ * TextBuffer is a class similar to {@link StringBuffer}, with
+ * following differences:
+ *<ul>
+ *  <li>TextBuffer uses segments character arrays, to avoid having
+ *     to do additional array copies when array is not big enough.
+ *     This means that only reallocating that is necessary is done only once:
+ *     if and when caller
+ *     wants to access contents in a linear array (char[], String).
+ *    </li>
+*  <li>TextBuffer can also be initialized in "shared mode", in which
+*     it will just act as a wrapper to a single char array managed
+*     by another object (like parser that owns it)
+ *    </li>
+ *  <li>TextBuffer is not synchronized.
+ *    </li>
+ * </ul>
+ */
+public final class TextBuffer
+{
+    final static char[] NO_CHARS = new char[0];
+
+    /**
+     * Let's start with sizable but not huge buffer, will grow as necessary
+     */
+    final static int MIN_SEGMENT_LEN = 1000;
+    
+    /**
+     * Let's limit maximum segment length to something sensible
+     * like 256k
+     */
+    final static int MAX_SEGMENT_LEN = 0x40000;
+    
+    /*
+    /**********************************************************
+    /* Configuration:
+    /**********************************************************
+     */
+
+    private final BufferRecycler _allocator;
+
+    /*
+    /**********************************************************
+    /* Shared input buffers
+    /**********************************************************
+     */
+
+    /**
+     * Shared input buffer; stored here in case some input can be returned
+     * as is, without being copied to collector's own buffers. Note that
+     * this is read-only for this Object.
+     */
+    private char[] _inputBuffer;
+
+    /**
+     * Character offset of first char in input buffer; -1 to indicate
+     * that input buffer currently does not contain any useful char data
+     */
+    private int _inputStart;
+
+    private int _inputLen;
+
+    /*
+    /**********************************************************
+    /* Aggregation segments (when not using input buf)
+    /**********************************************************
+     */
+
+    /**
+     * List of segments prior to currently active segment.
+     */
+    private ArrayList<char[]> _segments;
+
+    /**
+     * Flag that indicates whether _seqments is non-empty
+     */
+    private boolean _hasSegments = false;
+
+    // // // Currently used segment; not (yet) contained in _seqments
+
+    /**
+     * Amount of characters in segments in {@link _segments}
+     */
+    private int _segmentSize;
+
+    private char[] _currentSegment;
+
+    /**
+     * Number of characters in currently active (last) segment
+     */
+    private int _currentSize;
+
+    /*
+    /**********************************************************
+    /* Caching of results
+    /**********************************************************
+     */
+
+    /**
+     * String that will be constructed when the whole contents are
+     * needed; will be temporarily stored in case asked for again.
+     */
+    private String _resultString;
+
+    private char[] _resultArray;
+
+    /*
+    /**********************************************************
+    /* Life-cycle
+    /**********************************************************
+     */
+
+    public TextBuffer(BufferRecycler allocator)
+    {
+        _allocator = allocator;
+    }
+
+    /**
+     * Method called to indicate that the underlying buffers should now
+     * be recycled if they haven't yet been recycled. Although caller
+     * can still use this text buffer, it is not advisable to call this
+     * method if that is likely, since next time a buffer is needed,
+     * buffers need to reallocated.
+     * Note: calling this method automatically also clears contents
+     * of the buffer.
+     */
+    public void releaseBuffers()
+    {
+        if (_allocator == null) {
+            resetWithEmpty();
+        } else {
+            if (_currentSegment != null) {
+                // First, let's get rid of all but the largest char array
+                resetWithEmpty();
+                // And then return that array
+                char[] buf = _currentSegment;
+                _currentSegment = null;
+                _allocator.releaseCharBuffer(BufferRecycler.CharBufferType.TEXT_BUFFER, buf);
+            }
+        }
+    }
+
+    /**
+     * Method called to clear out any content text buffer may have, and
+     * initializes buffer to use non-shared data.
+     */
+    public void resetWithEmpty()
+    {
+        _inputStart = -1; // indicates shared buffer not used
+        _currentSize = 0;
+        _inputLen = 0;
+
+        _inputBuffer = null;
+        _resultString = null;
+        _resultArray = null;
+
+        // And then reset internal input buffers, if necessary:
+        if (_hasSegments) {
+            clearSegments();
+        }
+    }
+
+    /**
+     * Method called to initialize the buffer with a shared copy of data;
+     * this means that buffer will just have pointers to actual data. It
+     * also means that if anything is to be appended to the buffer, it
+     * will first have to unshare it (make a local copy).
+     */
+    public void resetWithShared(char[] buf, int start, int len)
+    {
+        // First, let's clear intermediate values, if any:
+        _resultString = null;
+        _resultArray = null;
+
+        // Then let's mark things we need about input buffer
+        _inputBuffer = buf;
+        _inputStart = start;
+        _inputLen = len;
+
+        // And then reset internal input buffers, if necessary:
+        if (_hasSegments) {
+            clearSegments();
+        }
+    }
+
+    public void resetWithCopy(char[] buf, int start, int len)
+    {
+        _inputBuffer = null;
+        _inputStart = -1; // indicates shared buffer not used
+        _inputLen = 0;
+
+        _resultString = null;
+        _resultArray = null;
+
+        // And then reset internal input buffers, if necessary:
+        if (_hasSegments) {
+            clearSegments();
+        } else if (_currentSegment == null) {
+            _currentSegment = findBuffer(len);
+        }
+        _currentSize = _segmentSize = 0;
+        append(buf, start, len);
+    }
+
+    public void resetWithString(String value)
+    {
+        _inputBuffer = null;
+        _inputStart = -1;
+        _inputLen = 0;
+
+        _resultString = value;
+        _resultArray = null;
+
+        if (_hasSegments) {
+            clearSegments();
+        }
+        _currentSize = 0;
+        
+    }
+    
+    /**
+     * Helper method used to find a buffer to use, ideally one
+     * recycled earlier.
+     */
+    private char[] findBuffer(int needed)
+    {
+        if (_allocator != null) {
+            return _allocator.allocCharBuffer(BufferRecycler.CharBufferType.TEXT_BUFFER, needed);
+        }
+        return new char[Math.max(needed, MIN_SEGMENT_LEN)];
+    }
+
+    private void clearSegments()
+    {
+        _hasSegments = false;
+        /* Let's start using _last_ segment from list; for one, it's
+         * the biggest one, and it's also most likely to be cached
+         */
+        /* 28-Aug-2009, tatu: Actually, the current segment should
+         *   be the biggest one, already
+         */
+        //_currentSegment = _segments.get(_segments.size() - 1);
+        _segments.clear();
+        _currentSize = _segmentSize = 0;
+    }
+
+    /*
+    /**********************************************************
+    /* Accessors for implementing public interface
+    /**********************************************************
+     */
+
+    /**
+     * @return Number of characters currently stored by this collector
+     */
+    public int size() {
+        if (_inputStart >= 0) { // shared copy from input buf
+            return _inputLen;
+        }
+        if (_resultArray != null) {
+            return _resultArray.length;
+        }
+        if (_resultString != null) {
+            return _resultString.length();
+        }
+        // local segmented buffers
+        return _segmentSize + _currentSize;
+    }
+
+    public int getTextOffset()
+    {
+        /* Only shared input buffer can have non-zero offset; buffer
+         * segments start at 0, and if we have to create a combo buffer,
+         * that too will start from beginning of the buffer
+         */
+        return (_inputStart >= 0) ? _inputStart : 0;
+    }
+
+    /**
+     * Method that can be used to check whether textual contents can
+     * be efficiently accessed using {@link #getTextBuffer}.
+     */
+    public boolean hasTextAsCharacters()
+    {
+        // if we have array in some form, sure
+        if (_inputStart >= 0 || _resultArray != null) {
+            return true;
+        }
+        // not if we have String as value
+        if (_resultString != null) {
+            return false;
+        }
+        return true;
+    }
+    
+    public char[] getTextBuffer()
+    {
+        // Are we just using shared input buffer?
+        if (_inputStart >= 0) {
+            return _inputBuffer;
+        }
+        if (_resultArray != null) {
+            return _resultArray;
+        }
+        if (_resultString != null) {
+            return (_resultArray = _resultString.toCharArray());
+        }
+        // Nope; but does it fit in just one segment?
+        if (!_hasSegments) {
+            return _currentSegment;
+        }
+        // Nope, need to have/create a non-segmented array and return it
+        return contentsAsArray();
+    }
+
+    /*
+    /**********************************************************
+    /* Other accessors:
+    /**********************************************************
+     */
+
+    public String contentsAsString()
+    {
+        if (_resultString == null) {
+            // Has array been requested? Can make a shortcut, if so:
+            if (_resultArray != null) {
+                _resultString = new String(_resultArray);
+            } else {
+                // Do we use shared array?
+                if (_inputStart >= 0) {
+                    if (_inputLen < 1) {
+                        return (_resultString = "");
+                    }
+                    _resultString = new String(_inputBuffer, _inputStart, _inputLen);
+                } else { // nope... need to copy
+                    // But first, let's see if we have just one buffer
+                    int segLen = _segmentSize;
+                    int currLen = _currentSize;
+                    
+                    if (segLen == 0) { // yup
+                        _resultString = (currLen == 0) ? "" : new String(_currentSegment, 0, currLen);
+                    } else { // no, need to combine
+                        StringBuilder sb = new StringBuilder(segLen + currLen);
+                        // First stored segments
+                        if (_segments != null) {
+                            for (int i = 0, len = _segments.size(); i < len; ++i) {
+                                char[] curr = _segments.get(i);
+                                sb.append(curr, 0, curr.length);
+                            }
+                        }
+                        // And finally, current segment:
+                        sb.append(_currentSegment, 0, _currentSize);
+                        _resultString = sb.toString();
+                    }
+                }
+            }
+        }
+        return _resultString;
+    }
+ 
+    public char[] contentsAsArray()
+    {
+        char[] result = _resultArray;
+        if (result == null) {
+            _resultArray = result = buildResultArray();
+        }
+        return result;
+    }
+
+    /**
+     * Convenience method for converting contents of the buffer
+     * into a {@link BigDecimal}.
+     */
+    public BigDecimal contentsAsDecimal()
+        throws NumberFormatException
+    {
+        // Already got a pre-cut array?
+        if (_resultArray != null) {
+            return new BigDecimal(_resultArray);
+        }
+        // Or a shared buffer?
+        if (_inputStart >= 0) {
+            return new BigDecimal(_inputBuffer, _inputStart, _inputLen);
+        }
+        // Or if not, just a single buffer (the usual case)
+        if (_segmentSize == 0) {
+            return new BigDecimal(_currentSegment, 0, _currentSize);
+        }
+        // If not, let's just get it aggregated...
+        return new BigDecimal(contentsAsArray());
+    }
+
+    /**
+     * Convenience method for converting contents of the buffer
+     * into a Double value.
+     */
+    public double contentsAsDouble()
+        throws NumberFormatException
+    {
+        return NumberInput.parseDouble(contentsAsString());
+    }
+
+    /*
+    /**********************************************************
+    /* Public mutators:
+    /**********************************************************
+     */
+
+    /**
+     * Method called to make sure that buffer is not using shared input
+     * buffer; if it is, it will copy such contents to private buffer.
+     */
+    public void ensureNotShared() {
+        if (_inputStart >= 0) {
+            unshare(16);
+        }
+    }
+
+    public void append(char c) {
+        // Using shared buffer so far?
+        if (_inputStart >= 0) {
+            unshare(16);
+        }
+        _resultString = null;
+        _resultArray = null;
+        // Room in current segment?
+        char[] curr = _currentSegment;
+        if (_currentSize >= curr.length) {
+            expand(1);
+            curr = _currentSegment;
+        }
+        curr[_currentSize++] = c;
+    }
+
+    public void append(char[] c, int start, int len)
+    {
+        // Can't append to shared buf (sanity check)
+        if (_inputStart >= 0) {
+            unshare(len);
+        }
+        _resultString = null;
+        _resultArray = null;
+
+        // Room in current segment?
+        char[] curr = _currentSegment;
+        int max = curr.length - _currentSize;
+            
+        if (max >= len) {
+            System.arraycopy(c, start, curr, _currentSize, len);
+            _currentSize += len;
+            return;
+        }
+        // No room for all, need to copy part(s):
+        if (max > 0) {
+            System.arraycopy(c, start, curr, _currentSize, max);
+            start += max;
+            len -= max;
+        }
+        /* And then allocate new segment; we are guaranteed to now
+         * have enough room in segment.
+         */
+        // Except, as per [Issue-24], not for HUGE appends... so:
+        do {
+            expand(len);
+            int amount = Math.min(_currentSegment.length, len);
+            System.arraycopy(c, start, _currentSegment, 0, amount);
+            _currentSize += amount;
+            start += amount;
+            len -= amount;
+        } while (len > 0);
+    }
+
+    public void append(String str, int offset, int len)
+    {
+        // Can't append to shared buf (sanity check)
+        if (_inputStart >= 0) {
+            unshare(len);
+        }
+        _resultString = null;
+        _resultArray = null;
+
+        // Room in current segment?
+        char[] curr = _currentSegment;
+        int max = curr.length - _currentSize;
+        if (max >= len) {
+            str.getChars(offset, offset+len, curr, _currentSize);
+            _currentSize += len;
+            return;
+        }
+        // No room for all, need to copy part(s):
+        if (max > 0) {
+            str.getChars(offset, offset+max, curr, _currentSize);
+            len -= max;
+            offset += max;
+        }
+        /* And then allocate new segment; we are guaranteed to now
+         * have enough room in segment.
+         */
+        // Except, as per [Issue-24], not for HUGE appends... so:
+        do {
+            expand(len);
+            int amount = Math.min(_currentSegment.length, len);
+            str.getChars(offset, offset+amount, _currentSegment, 0);
+            _currentSize += amount;
+            offset += amount;
+            len -= amount;
+        } while (len > 0);
+    }
+
+    /*
+    /**********************************************************
+    /* Raw access, for high-performance use:
+    /**********************************************************
+     */
+
+    public char[] getCurrentSegment()
+    {
+        /* Since the intention of the caller is to directly add stuff into
+         * buffers, we should NOT have anything in shared buffer... ie. may
+         * need to unshare contents.
+         */
+        if (_inputStart >= 0) {
+            unshare(1);
+        } else {
+            char[] curr = _currentSegment;
+            if (curr == null) {
+                _currentSegment = findBuffer(0);
+            } else if (_currentSize >= curr.length) {
+                // Plus, we better have room for at least one more char
+                expand(1);
+            }
+        }
+        return _currentSegment;
+    }
+
+    public char[] emptyAndGetCurrentSegment()
+    {
+        // inlined 'resetWithEmpty()'
+        _inputStart = -1; // indicates shared buffer not used
+        _currentSize = 0;
+        _inputLen = 0;
+
+        _inputBuffer = null;
+        _resultString = null;
+        _resultArray = null;
+
+        // And then reset internal input buffers, if necessary:
+        if (_hasSegments) {
+            clearSegments();
+        }
+        char[] curr = _currentSegment;
+        if (curr == null) {
+            _currentSegment = curr = findBuffer(0);
+        }
+        return curr;
+    }
+
+    public int getCurrentSegmentSize() {
+        return _currentSize;
+    }
+
+    public void setCurrentLength(int len) {
+        _currentSize = len;
+    }
+
+    public char[] finishCurrentSegment()
+    {
+        if (_segments == null) {
+            _segments = new ArrayList<char[]>();
+        }
+        _hasSegments = true;
+        _segments.add(_currentSegment);
+        int oldLen = _currentSegment.length;
+        _segmentSize += oldLen;
+        // Let's grow segments by 50%
+        int newLen = Math.min(oldLen + (oldLen >> 1), MAX_SEGMENT_LEN);
+        char[] curr = _charArray(newLen);
+        _currentSize = 0;
+        _currentSegment = curr;
+        return curr;
+    }
+
+    /**
+     * Method called to expand size of the current segment, to
+     * accomodate for more contiguous content. Usually only
+     * used when parsing tokens like names.
+     */
+    public char[] expandCurrentSegment()
+    {
+        char[] curr = _currentSegment;
+        // Let's grow by 50%
+        int len = curr.length;
+        // Must grow by at least 1 char, no matter what
+        int newLen = (len == MAX_SEGMENT_LEN) ?
+            (MAX_SEGMENT_LEN + 1) : Math.min(MAX_SEGMENT_LEN, len + (len >> 1));
+        _currentSegment = _charArray(newLen);
+        System.arraycopy(curr, 0, _currentSegment, 0, len);
+        return _currentSegment;
+    }
+
+    /*
+    /**********************************************************
+    /* Standard methods:
+    /**********************************************************
+     */
+
+    /**
+     * Note: calling this method may not be as efficient as calling
+     * {@link #contentsAsString}, since it's not guaranteed that resulting
+     * String is cached.
+     */
+    @Override
+    public String toString() {
+         return contentsAsString();
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods:
+    /**********************************************************
+     */
+
+    /**
+     * Method called if/when we need to append content when we have been
+     * initialized to use shared buffer.
+     */
+    private void unshare(int needExtra)
+    {
+        int sharedLen = _inputLen;
+        _inputLen = 0;
+        char[] inputBuf = _inputBuffer;
+        _inputBuffer = null;
+        int start = _inputStart;
+        _inputStart = -1;
+
+        // Is buffer big enough, or do we need to reallocate?
+        int needed = sharedLen+needExtra;
+        if (_currentSegment == null || needed > _currentSegment.length) {
+            _currentSegment = findBuffer(needed);
+        }
+        if (sharedLen > 0) {
+            System.arraycopy(inputBuf, start, _currentSegment, 0, sharedLen);
+        }
+        _segmentSize = 0;
+        _currentSize = sharedLen;
+    }
+
+    /**
+     * Method called when current segment is full, to allocate new
+     * segment.
+     */
+    private void expand(int minNewSegmentSize)
+    {
+        // First, let's move current segment to segment list:
+        if (_segments == null) {
+            _segments = new ArrayList<char[]>();
+        }
+        char[] curr = _currentSegment;
+        _hasSegments = true;
+        _segments.add(curr);
+        _segmentSize += curr.length;
+        int oldLen = curr.length;
+        // Let's grow segments by 50% minimum
+        int sizeAddition = oldLen >> 1;
+        if (sizeAddition < minNewSegmentSize) {
+            sizeAddition = minNewSegmentSize;
+        }
+        curr = _charArray(Math.min(MAX_SEGMENT_LEN, oldLen + sizeAddition));
+        _currentSize = 0;
+        _currentSegment = curr;
+    }
+
+    private char[] buildResultArray()
+    {
+        if (_resultString != null) { // Can take a shortcut...
+            return _resultString.toCharArray();
+        }
+        char[] result;
+        
+        // Do we use shared array?
+        if (_inputStart >= 0) {
+            if (_inputLen < 1) {
+                return NO_CHARS;
+            }
+            result = _charArray(_inputLen);
+            System.arraycopy(_inputBuffer, _inputStart, result, 0,
+                             _inputLen);
+        } else { // nope 
+            int size = size();
+            if (size < 1) {
+                return NO_CHARS;
+            }
+            int offset = 0;
+            result = _charArray(size);
+            if (_segments != null) {
+                for (int i = 0, len = _segments.size(); i < len; ++i) {
+                    char[] curr = (char[]) _segments.get(i);
+                    int currLen = curr.length;
+                    System.arraycopy(curr, 0, result, offset, currLen);
+                    offset += currLen;
+                }
+            }
+            System.arraycopy(_currentSegment, 0, result, offset, _currentSize);
+        }
+        return result;
+    }
+
+    private char[] _charArray(int len) {
+        return new char[len];
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java b/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java
new file mode 100644
index 0000000..acb0b28
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java
@@ -0,0 +1,270 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.*;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+
+/**
+ * Functionality for supporting exposing of component {@link Version}s.
+ * Also contains other misc methods that have no other place to live in.
+ *<p>
+ * Note that this class can be used in two roles: first, as a static
+ * utility class for loading purposes, and second, as a singleton
+ * loader of per-module version information.
+ *<p>
+ * Note that method for accessing version information changed between versions
+ * 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.
+ */
+public class VersionUtil
+{
+    /**
+     * @deprecated Since 2.2, use of version file is deprecated, and generated
+     *    class should be used instead.
+     */
+    @Deprecated
+    public final static String VERSION_FILE = "VERSION.txt";
+    public final static String PACKAGE_VERSION_CLASS_NAME = "PackageVersion";
+//    public final static String PACKAGE_VERSION_FIELD = "VERSION";
+
+    private final static Pattern VERSION_SEPARATOR = Pattern.compile("[-_./;:]");
+
+    private final Version _version;
+
+    /*
+    /**********************************************************
+    /* Instance life-cycle, accesso
+    /**********************************************************
+     */
+    
+    protected VersionUtil()
+    {
+        Version v = null;
+        try {
+            /* Class we pass only matters for resource-loading: can't use this Class
+             * (as it's just being loaded at this point), nor anything that depends on it.
+             */
+            v = VersionUtil.versionFor(getClass());
+        } catch (Exception e) { // not good to dump to stderr; but that's all we have at this low level
+            System.err.println("ERROR: Failed to load Version information for bundle (via "+getClass().getName()+").");
+        }
+        if (v == null) {
+            v = Version.unknownVersion();
+        }
+        _version = v;
+    }
+
+    public Version version() { return _version; }
+    
+    /*
+    /**********************************************************
+    /* Static load methods
+    /**********************************************************
+     */
+    
+    /**
+     * Helper method that will try to load version information for specified
+     * class. Implementation is as follows:
+     *
+     * 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.
+     */
+    public static Version versionFor(Class<?> cls)
+    {
+        Version packageVersion = packageVersionFor(cls);
+        if (packageVersion != null) {
+            return packageVersion;
+        }
+
+        final InputStream in = cls.getResourceAsStream(VERSION_FILE);
+
+        if (in == null)
+            return Version.unknownVersion();
+
+        try {
+            InputStreamReader reader = new InputStreamReader(in, "UTF-8");
+            try {
+                return doReadVersion(reader);
+            } finally {
+                try {
+                    reader.close();
+                } catch (IOException ignored) {
+                }
+            }
+        } catch (UnsupportedEncodingException e) {
+            return Version.unknownVersion();
+        } finally {
+            try {
+                in.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * Loads version information by introspecting a class named
+     * "PackageVersion" in the same package as the given class.
+     *
+     * 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)
+    {
+        Class<?> versionInfoClass = null;
+        try {
+            Package p = cls.getPackage();
+            String versionInfoClassName =
+                new StringBuilder(p.getName())
+                    .append(".")
+                    .append(PACKAGE_VERSION_CLASS_NAME)
+                    .toString();
+            versionInfoClass = Class.forName(versionInfoClassName, true, cls.getClassLoader());
+        } catch (Exception e) { // ok to be missing (not good, acceptable)
+            return null;
+        }
+        if (versionInfoClass == null) {
+            return null;
+        }
+        // However, if class exists, it better work correctly, no swallowing exceptions
+        Object v;
+        try {
+            v = versionInfoClass.newInstance();
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Failed to instantiate "+versionInfoClass.getName()
+                    +" to find version information, problem: "+e.getMessage(), e);
+        }
+        if (!(v instanceof Versioned)) {
+            throw new IllegalArgumentException("Bad version class "+versionInfoClass.getName()
+        			+": does not implement "+Versioned.class.getName());
+        }
+        return ((Versioned) v).version();
+    }
+
+    private static Version doReadVersion(final Reader reader)
+    {
+        String version = null, group = null, artifact = null;
+
+        final BufferedReader br = new BufferedReader(reader);
+        try {
+            version = br.readLine();
+            if (version != null) {
+                group = br.readLine();
+                if (group != null)
+                    artifact = br.readLine();
+            }
+        } catch (IOException ignored) {
+        } finally {
+            try {
+                br.close();
+            } catch (IOException ignored) {
+            }
+        }
+
+        // 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);
+    }
+
+    /**
+     * Will attempt to load the maven version for the given groupId and
+     * artifactId.  Maven puts a pom.properties file in
+     * META-INF/maven/groupId/artifactId, containing the groupId,
+     * artifactId and version of the library.
+     *
+     * @param classLoader the ClassLoader to load the pom.properties file from
+     * @param groupId the groupId of the library
+     * @param artifactId the artifactId of the library
+     * @return The version
+     */
+    public static Version mavenVersionFor(ClassLoader classLoader, String groupId, String artifactId) {
+        InputStream pomPoperties = classLoader.getResourceAsStream("META-INF/maven/" + groupId.replaceAll("\\.", "/")
+                + "/" + artifactId + "/pom.properties");
+        if (pomPoperties != null) {
+            try {
+                Properties props = new Properties();
+                props.load(pomPoperties);
+                String versionStr = props.getProperty("version");
+                String pomPropertiesArtifactId = props.getProperty("artifactId");
+                String pomPropertiesGroupId = props.getProperty("groupId");
+                return parseVersion(versionStr, pomPropertiesGroupId, pomPropertiesArtifactId);
+            } catch (IOException e) {
+                // Ignore
+            } finally {
+                try {
+                    pomPoperties.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return Version.unknownVersion();
+    }
+
+    /**
+     * Use variant that takes three arguments instead
+     * 
+     * @deprecated
+     */
+    @Deprecated
+    public static Version parseVersion(String versionStr) {
+        return parseVersion(versionStr, null, null);
+    }
+
+    public static Version parseVersion(String versionStr, String groupId, String artifactId)
+    {
+        if (versionStr == null) {
+            return null;
+        }
+        versionStr = versionStr.trim();
+        if (versionStr.length() == 0) {
+            return null;
+        }
+        String[] parts = VERSION_SEPARATOR.split(versionStr);
+        int major = parseVersionPart(parts[0]);
+        int minor = (parts.length > 1) ? parseVersionPart(parts[1]) : 0;
+        int patch = (parts.length > 2) ? parseVersionPart(parts[2]) : 0;
+        String snapshot = (parts.length > 3) ? parts[3] : null;
+
+        return new Version(major, minor, patch, snapshot,
+                groupId, artifactId);
+    }
+
+    protected static int parseVersionPart(String partStr)
+    {
+        partStr = partStr.toString();
+        int len = partStr.length();
+        int number = 0;
+        for (int i = 0; i < len; ++i) {
+            char c = partStr.charAt(i);
+            if (c > '9' || c < '0') break;
+            number = (number * 10) + (c - '0');
+        }
+        return number;
+    }
+
+    /*
+    /**********************************************************
+    /* Orphan utility methods
+    /**********************************************************
+     */
+
+    public final static void throwInternal() {
+        throw new RuntimeException("Internal error: this code path should never get executed");
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/package-info.java b/src/main/java/com/fasterxml/jackson/core/util/package-info.java
new file mode 100644
index 0000000..9ad785f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Utility classes used by Jackson Core functionality.
+ */
+package com.fasterxml.jackson.core.util;
diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..f5f45d2
--- /dev/null
+++ b/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,8 @@
+This copy of Jackson JSON processor streaming parser/generator is licensed under the
+Apache (Software) License, version 2.0 ("the License").
+See the License for details about distribution rights, and the
+specific rights regarding derivate works.
+
+You may obtain a copy of the License at:
+
+http://www.apache.org/licenses/LICENSE-2.0
diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE
new file mode 100644
index 0000000..4c976b7
--- /dev/null
+++ b/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,20 @@
+# Jackson JSON processor
+
+Jackson is a high-performance, Free/Open Source JSON processing library.
+It was originally written by Tatu Saloranta (tatu.saloranta at iki.fi), and has
+been in development since 2007.
+It is currently developed by a community of developers, as well as supported
+commercially by FasterXML.com.
+
+## Licensing
+
+Jackson core and extension components may licensed under different licenses.
+To find the details that apply to this artifact see the accompanying LICENSE file.
+For more information, including possible other licensing options, contact
+FasterXML.com (http://fasterxml.com).
+
+## Credits
+
+A list of contributors may be found from CREDITS file, which is included
+in some artifacts (usually source distributions); but is always available
+from the source code management (SCM) system project uses.
diff --git a/src/main/resources/META-INF/services/com.fasterxml.jackson.core.JsonFactory b/src/main/resources/META-INF/services/com.fasterxml.jackson.core.JsonFactory
new file mode 100644
index 0000000..239a78a
--- /dev/null
+++ b/src/main/resources/META-INF/services/com.fasterxml.jackson.core.JsonFactory
@@ -0,0 +1 @@
+com.fasterxml.jackson.core.JsonFactory
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..253cdd1
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/DECORATION/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/DECORATION/1.1.0 http://maven.apache.org/xsd/decoration-1.1.0.xsd"
+  name="${project.name}">
+
+  <skin>
+    <groupId>org.apache.maven.skins</groupId>
+    <artifactId>maven-fluido-skin</artifactId>
+    <version>1.1</version>
+  </skin>
+
+  <custom>
+    <fluidoSkin>
+      <topBarEnabled>true</topBarEnabled>
+      <sideBarEnabled>false</sideBarEnabled>
+      <twitter>
+        <user>cowtowncoder</user>
+        <showUser>true</showUser>
+        <showFollowers>true</showFollowers>
+      </twitter>
+    </fluidoSkin>
+  </custom>
+
+  <publishDate format="dd MMMM yyyy" position="left" />
+  <version position="left" />
+
+  <body>
+    <menu name="User guide">
+      <!-- TODO -->
+    </menu>
+
+    <menu ref="reports" inherit="bottom" />
+  </body>
+</project>
diff --git a/src/test/java/com/fasterxml/jackson/core/TestJDKSerializability.java b/src/test/java/com/fasterxml/jackson/core/TestJDKSerializability.java
new file mode 100644
index 0000000..54b00f3
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/TestJDKSerializability.java
@@ -0,0 +1,98 @@
+package com.fasterxml.jackson.core;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.test.BaseTest;
+
+/**
+ * Unit tests for [Issue#31] (https://github.com/FasterXML/jackson-core/issues/31)
+ */
+public class TestJDKSerializability extends BaseTest
+{
+    public void testJsonFactorySerializable() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        String origJson = "{\"simple\":[1,true,{}]}";
+        assertEquals(origJson, _copyJson(f, origJson, false));
+
+        // Ok: freeze dry factory, thaw, and try to use again:
+        byte[] frozen = jdkSerialize(f);
+        JsonFactory f2 = jdkDeserialize(frozen);
+        assertNotNull(f2);
+        assertEquals(origJson, _copyJson(f2, origJson, false));
+
+        // Let's also try byte-based variant, for fun...
+        assertEquals(origJson, _copyJson(f2, origJson, true));
+    }
+
+    public void testBase64Variant() throws Exception
+    {
+        Base64Variant orig = Base64Variants.PEM;
+        byte[] stuff = jdkSerialize(orig);
+        Base64Variant back = jdkDeserialize(stuff);
+        assertSame(orig, back);
+    }
+
+    public void testPrettyPrinter() throws Exception
+    {
+        PrettyPrinter p = new DefaultPrettyPrinter();
+        byte[] stuff = jdkSerialize(p);
+        PrettyPrinter back = jdkDeserialize(stuff);
+        // what should we test?
+        assertNotNull(back);
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+    
+    protected byte[] jdkSerialize(Object o) throws IOException
+    {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream(1000);
+        ObjectOutputStream obOut = new ObjectOutputStream(bytes);
+        obOut.writeObject(o);
+        obOut.close();
+        return bytes.toByteArray();
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T jdkDeserialize(byte[] raw) throws IOException
+    {
+        ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(raw));
+        try {
+            return (T) objIn.readObject();
+        } catch (ClassNotFoundException e) {
+            fail("Missing class: "+e.getMessage());
+            return null;
+        } finally {
+            objIn.close();
+        }
+    }
+    
+    protected String _copyJson(JsonFactory f, String json, boolean useBytes) throws IOException
+    {
+        if (useBytes) {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            JsonGenerator jg = f.createGenerator(bytes);
+            _copyJson(f, json, jg);
+            return bytes.toString("UTF-8");
+        }
+        StringWriter sw = new StringWriter();
+        JsonGenerator jg = f.createGenerator(sw);
+        _copyJson(f, json, jg);
+        return sw.toString();
+    }
+        
+    protected void _copyJson(JsonFactory f, String json, JsonGenerator jg) throws IOException
+    {
+        JsonParser jp = f.createParser(json);
+        while (jp.nextToken() != null) {
+            jg.copyCurrentEvent(jp);
+        }
+        jp.close();
+        jg.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/TestJsonFactory.java b/src/test/java/com/fasterxml/jackson/core/TestJsonFactory.java
new file mode 100644
index 0000000..9825685
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/TestJsonFactory.java
@@ -0,0 +1,28 @@
+package com.fasterxml.jackson.core;
+
+import com.fasterxml.jackson.test.BaseTest;
+
+public class TestJsonFactory extends BaseTest
+{
+    // #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/TestVersions.java b/src/test/java/com/fasterxml/jackson/core/TestVersions.java
new file mode 100644
index 0000000..0620d17
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/TestVersions.java
@@ -0,0 +1,40 @@
+package com.fasterxml.jackson.core;
+
+import com.fasterxml.jackson.core.json.*;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+
+/**
+ * Tests to verify [JACKSON-278]
+ */
+public class TestVersions extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testCoreVersions() throws Exception
+    {
+        assertVersion(new JsonFactory().version());
+        JsonParser jp = new ReaderBasedJsonParser(getIOContext(), 0, null, null,
+                CharsToNameCanonicalizer.createRoot());
+        assertVersion(jp.version());
+        jp.close();
+        JsonGenerator jgen = new WriterBasedJsonGenerator(getIOContext(), 0, null, null);
+        assertVersion(jgen.version());
+        jgen.close();
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+    
+    private void assertVersion(Version v)
+    {
+    	assertEquals(PackageVersion.VERSION, v);
+    }
+
+    private IOContext getIOContext() {
+        return new IOContext(new BufferRecycler(), null, false);
+    }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/core/format/TestJsonFormatDetection.java b/src/test/java/com/fasterxml/jackson/core/format/TestJsonFormatDetection.java
new file mode 100644
index 0000000..63ad9e1
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/format/TestJsonFormatDetection.java
@@ -0,0 +1,92 @@
+package com.fasterxml.jackson.core.format;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.format.DataFormatDetector;
+import com.fasterxml.jackson.core.format.DataFormatMatcher;
+import com.fasterxml.jackson.core.format.MatchStrength;
+
+public class TestJsonFormatDetection extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testSimpleValidArray() throws Exception
+    {
+        JsonFactory jsonF = new JsonFactory();
+        DataFormatDetector detector = new DataFormatDetector(jsonF);
+        final String ARRAY_JSON = "[ 1, 2 ]";
+        DataFormatMatcher matcher = detector.findFormat(new ByteArrayInputStream(ARRAY_JSON.getBytes("UTF-8")));
+        // should have match
+        assertTrue(matcher.hasMatch());
+        assertEquals("JSON", matcher.getMatchedFormatName());
+        assertSame(jsonF, matcher.getMatch());
+        // no "certain" match with JSON, but solid:
+        assertEquals(MatchStrength.SOLID_MATCH, matcher.getMatchStrength());
+        // and thus:
+        JsonParser jp = matcher.createParserWithMatch();
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+        jp.close();
+    }
+
+    public void testSimpleValidObject() throws Exception
+    {
+        JsonFactory jsonF = new JsonFactory();
+        DataFormatDetector detector = new DataFormatDetector(jsonF);
+        final String JSON = "{  \"field\" : true }";
+        DataFormatMatcher matcher = detector.findFormat(new ByteArrayInputStream(JSON.getBytes("UTF-8")));
+        // should have match
+        assertTrue(matcher.hasMatch());
+        assertEquals("JSON", matcher.getMatchedFormatName());
+        assertSame(jsonF, matcher.getMatch());
+        // no "certain" match with JSON, but solid:
+        assertEquals(MatchStrength.SOLID_MATCH, matcher.getMatchStrength());
+        // and thus:
+        JsonParser jp = matcher.createParserWithMatch();
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("field", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        assertNull(jp.nextToken());
+        jp.close();
+    }
+
+    /**
+     * While JSON String is not a strong match alone, it should
+     * be detected unless some better match is available
+     */
+    public void testSimpleValidString() throws Exception
+    {
+        JsonFactory jsonF = new JsonFactory();
+        DataFormatDetector detector = new DataFormatDetector(jsonF);
+        final String JSON = "\"JSON!\"";
+        DataFormatMatcher matcher = detector.findFormat(new ByteArrayInputStream(JSON.getBytes("UTF-8")));
+        // should have match
+        assertTrue(matcher.hasMatch());
+        assertEquals("JSON", matcher.getMatchedFormatName());
+        assertSame(jsonF, matcher.getMatch());
+        assertEquals(MatchStrength.WEAK_MATCH, matcher.getMatchStrength());
+        JsonParser jp = matcher.createParserWithMatch();
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("JSON!", jp.getText());
+        assertNull(jp.nextToken());
+        jp.close();
+    }
+    
+    public void testSimpleInvalid() throws Exception
+    {
+        DataFormatDetector detector = new DataFormatDetector(new JsonFactory());
+        final String NON_JSON = "<root />";
+        DataFormatMatcher matcher = detector.findFormat(new ByteArrayInputStream(NON_JSON.getBytes("UTF-8")));
+        // should not have match
+        assertFalse(matcher.hasMatch());
+        // and thus:
+        assertEquals(MatchStrength.INCONCLUSIVE, matcher.getMatchStrength());
+        // also:
+        assertNull(matcher.createParserWithMatch());
+    }
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/io/TestIOContext.java b/src/test/java/com/fasterxml/jackson/core/io/TestIOContext.java
new file mode 100644
index 0000000..341ec3c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/io/TestIOContext.java
@@ -0,0 +1,93 @@
+package com.fasterxml.jackson.core.io;
+
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+
+public class TestIOContext
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testAllocations() throws Exception
+    {
+        IOContext ctxt = new IOContext(new BufferRecycler(), "N/A", true);
+
+        /* I/O Read buffer */
+
+        // First succeeds:
+        assertNotNull(ctxt.allocReadIOBuffer());
+        // second fails
+        try {
+            ctxt.allocReadIOBuffer();
+        } catch (IllegalStateException e) {
+            verifyException(e, "second time");
+        }
+        // Also: can't succeed with different buffer
+        try {
+            ctxt.releaseReadIOBuffer(new byte[1]);
+        } catch (IllegalArgumentException e) {
+            verifyException(e, "not owned");
+        }
+        // but call with null is a NOP for convenience
+        ctxt.releaseReadIOBuffer(null);
+
+        /* I/O Write buffer */
+
+        assertNotNull(ctxt.allocWriteEncodingBuffer());
+        try {
+            ctxt.allocWriteEncodingBuffer();
+        } catch (IllegalStateException e) {
+            verifyException(e, "second time");
+        }
+        try {
+            ctxt.releaseWriteEncodingBuffer(new byte[1]);
+        } catch (IllegalArgumentException e) {
+            verifyException(e, "not owned");
+        }
+        ctxt.releaseWriteEncodingBuffer(null);
+
+        /* Token (read) buffer */
+
+        assertNotNull(ctxt.allocTokenBuffer());
+        try {
+            ctxt.allocTokenBuffer();
+        } catch (IllegalStateException e) {
+            verifyException(e, "second time");
+        }
+        try {
+            ctxt.releaseTokenBuffer(new char[1]);
+        } catch (IllegalArgumentException e) {
+            verifyException(e, "not owned");
+        }
+        ctxt.releaseTokenBuffer(null);
+
+        /* Concat (write?) buffer */
+
+        assertNotNull(ctxt.allocConcatBuffer());
+        try {
+            ctxt.allocConcatBuffer();
+        } catch (IllegalStateException e) {
+            verifyException(e, "second time");
+        }
+        try {
+            ctxt.releaseConcatBuffer(new char[1]);
+        } catch (IllegalArgumentException e) {
+            verifyException(e, "not owned");
+        }
+        ctxt.releaseConcatBuffer(null);
+
+        /* NameCopy (write?) buffer */
+
+        assertNotNull(ctxt.allocNameCopyBuffer(100));
+        try {
+            ctxt.allocNameCopyBuffer(100);
+        } catch (IllegalStateException e) {
+            verifyException(e, "second time");
+        }
+        try {
+            ctxt.releaseNameCopyBuffer(new char[1]);
+        } catch (IllegalArgumentException e) {
+            verifyException(e, "not owned");
+        }
+        ctxt.releaseNameCopyBuffer(null);
+    }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/core/io/TestJDKSerializable.java b/src/test/java/com/fasterxml/jackson/core/io/TestJDKSerializable.java
new file mode 100644
index 0000000..1d7eab0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/io/TestJDKSerializable.java
@@ -0,0 +1,30 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestJDKSerializable
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testLocationSerializability() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        JsonParser jp = jf.createParser("  { }");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        JsonLocation loc = jp.getCurrentLocation();
+
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(bytes);
+        out.writeObject(loc);
+        out.close();
+        byte[] stuff = bytes.toByteArray();
+        
+        ObjectInputStream obIn = new ObjectInputStream(new ByteArrayInputStream(stuff));
+        JsonLocation loc2 = (JsonLocation) obIn.readObject();
+        assertNotNull(loc2);
+        
+        assertEquals(loc.getLineNr(), loc2.getLineNr());
+        assertEquals(loc.getColumnNr(), loc2.getColumnNr());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/io/TestJsonStringEncoder.java b/src/test/java/com/fasterxml/jackson/core/io/TestJsonStringEncoder.java
new file mode 100644
index 0000000..0fcd074
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/io/TestJsonStringEncoder.java
@@ -0,0 +1,105 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.StringWriter;
+import java.util.Random;
+
+import static org.junit.Assert.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.JsonStringEncoder;
+
+public class TestJsonStringEncoder
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testQuoteAsString() throws Exception
+    {
+        JsonStringEncoder encoder = new JsonStringEncoder();
+        char[] result = encoder.quoteAsString("foobar");
+        assertArrayEquals("foobar".toCharArray(), result);
+        result = encoder.quoteAsString("\"x\"");
+        assertArrayEquals("\\\"x\\\"".toCharArray(), result);
+    }
+
+    // For [JACKSON-853]
+    public void testQuoteLongAsString() throws Exception
+    {
+        JsonStringEncoder encoder = new JsonStringEncoder();
+        StringBuilder sb = new StringBuilder();
+        StringBuilder sb2 = new StringBuilder();
+        for (int i = 0; i < 1111; ++i) {
+            sb.append('"');
+            sb2.append("\\\"");
+        }
+        String input = sb.toString();
+        String exp = sb2.toString();
+        char[] result = encoder.quoteAsString(input);
+        assertEquals(2*input.length(), result.length);
+        assertEquals(exp, new String(result));
+        
+    }
+    
+    public void testQuoteAsUTF8() throws Exception
+    {
+        // In this case, let's actually use existing JsonGenerator to produce expected values
+        JsonFactory f = new JsonFactory();
+        JsonStringEncoder encoder = new JsonStringEncoder();
+        int[] lengths = new int[] {
+            5, 19, 200, 7000, 21000, 37000
+        };
+        for (int length : lengths) {
+            String str = generateRandom(length);
+            StringWriter sw = new StringWriter(length*2);
+            JsonGenerator jgen = f.createGenerator(sw);
+            jgen.writeString(str);
+            jgen.close();
+            String encoded = sw.toString();
+            // ok, except need to remove surrounding quotes
+            encoded = encoded.substring(1, encoded.length() - 1);
+            byte[] expected = encoded.getBytes("UTF-8");
+            byte[] actual = encoder.quoteAsUTF8(str);
+            assertArrayEquals(expected, actual);
+        }
+    }
+
+    public void testEncodeAsUTF8() throws Exception
+    {
+        JsonStringEncoder encoder = new JsonStringEncoder();
+        String[] strings = new String[] {
+                "a", "foobar", "p\u00f6ll\u00f6", "\"foo\"",
+                generateRandom(200),
+                generateRandom(5000),
+                generateRandom(39000)
+        };
+        for (String str : strings) {
+            assertArrayEquals(str.getBytes("UTF-8"), encoder.encodeAsUTF8(str));
+        }
+    }
+
+    // [JACKSON-884]
+    public void testCtrlChars() throws Exception
+    {
+        char[] input = new char[] { 0, 1, 2, 3, 4 };
+        char[] quoted = JsonStringEncoder.getInstance().quoteAsString(new String(input));
+        assertEquals("\\u0000\\u0001\\u0002\\u0003\\u0004", new String(quoted));
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    
+    private String generateRandom(int length)
+    {
+        StringBuilder sb = new StringBuilder(length);
+        Random rnd = new Random(length);
+        for (int i = 0; i < length; ++i) {
+            // let's limit it not to include surrogate pairs:
+            char ch = (char) rnd.nextInt(0xCFFF);
+            sb.append(ch);
+        }
+        return sb.toString();
+    }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/core/io/TestMergedStream.java b/src/test/java/com/fasterxml/jackson/core/io/TestMergedStream.java
new file mode 100644
index 0000000..c0bc390
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/io/TestMergedStream.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.io.MergedStream;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+
+public class TestMergedStream
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testSimple() throws Exception
+    {
+        BufferRecycler rec = new BufferRecycler();
+        IOContext ctxt = new IOContext(rec, null, false);
+        // bit complicated; must use recyclable buffer...
+        byte[] first = ctxt.allocReadIOBuffer();
+        System.arraycopy("ABCDE".getBytes("UTF-8"), 0, first, 99, 5);
+        byte[] second = "FGHIJ".getBytes("UTF-8");
+
+        assertNull(ctxt.getSourceReference());
+        assertFalse(ctxt.isResourceManaged());
+        ctxt.setEncoding(JsonEncoding.UTF8);
+        MergedStream ms = new MergedStream(ctxt, new ByteArrayInputStream(second),
+                                           first, 99, 99+5);
+        // Ok, first, should have 5 bytes from first buffer:
+        assertEquals(5, ms.available());
+        // not supported when there's buffered stuff...
+        assertFalse(ms.markSupported());
+        // so this won't work, but shouldn't throw exception
+        ms.mark(1);
+        assertEquals((byte) 'A', ms.read());
+        assertEquals(3, ms.skip(3));
+        byte[] buffer = new byte[5];
+        /* Ok, now, code is allowed to return anywhere between 1 and 3,
+         * but we now it will return 1...
+         */
+        assertEquals(1, ms.read(buffer, 1, 3));
+        assertEquals((byte) 'E', buffer[1]);
+        // So let's read bit more
+        assertEquals(3, ms.read(buffer, 0, 3));
+        assertEquals((byte) 'F', buffer[0]);
+        assertEquals((byte) 'G', buffer[1]);
+        assertEquals((byte) 'H', buffer[2]);
+        assertEquals(2, ms.available());
+        // And then skip the reset
+        assertEquals(2, ms.skip(200));
+
+        ms.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/io/TestUTF8Writer.java b/src/test/java/com/fasterxml/jackson/core/io/TestUTF8Writer.java
new file mode 100644
index 0000000..22630f5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/io/TestUTF8Writer.java
@@ -0,0 +1,60 @@
+package com.fasterxml.jackson.core.io;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.io.UTF8Writer;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+
+public class TestUTF8Writer
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testSimple() 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 = "AB\u00A0\u1AE9\uFFFC";
+        char[] ch = str.toCharArray();
+
+        // Let's write 3 times, using different methods
+        w.write(str);
+
+        w.append(ch[0]);
+        w.write(ch[1]);
+        w.write(ch, 2, 3);
+
+        w.write(str, 0, str.length());
+        w.close();
+
+        // and thus should have 3 times contents
+        byte[] data = out.toByteArray();
+        assertEquals(3*10, data.length);
+        String act = out.toString("UTF-8");
+        assertEquals(15, act.length());
+
+        assertEquals(3 * str.length(), act.length());
+        assertEquals(str+str+str, act);
+    }
+
+    public void testFlushAfterClose() throws Exception
+    {
+        BufferRecycler rec = new BufferRecycler();
+        IOContext ctxt = new IOContext(rec, null, false);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        UTF8Writer w = new UTF8Writer(ctxt, out);
+        
+        w.write('X');
+        
+        w.close();
+        assertEquals(1, out.size());
+
+        // and this ought to be fine...
+        w.flush();
+        // as well as some more...
+        w.close();
+        w.flush();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestBase64Codec.java b/src/test/java/com/fasterxml/jackson/core/json/TestBase64Codec.java
new file mode 100644
index 0000000..9589569
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestBase64Codec.java
@@ -0,0 +1,59 @@
+package com.fasterxml.jackson.core.json;
+
+import org.junit.Assert;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestBase64Codec
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testProps()
+    {
+        Base64Variant std = Base64Variants.MIME;
+        // let's verify basic props of std cocec
+        assertEquals("MIME", std.getName());
+        assertEquals("MIME", std.toString());
+        assertTrue(std.usesPadding());
+        assertFalse(std.usesPaddingChar('X'));
+        assertEquals('=', std.getPaddingChar());
+        assertTrue(std.usesPaddingChar('='));
+        assertEquals((byte) '=', std.getPaddingByte());
+        assertEquals(76, std.getMaxLineLength());
+    }
+
+    public void testCharEncoding() throws Exception
+    {
+        Base64Variant std = Base64Variants.MIME;
+        assertEquals(Base64Variant.BASE64_VALUE_INVALID, std.decodeBase64Char('?'));
+        assertEquals(Base64Variant.BASE64_VALUE_INVALID, std.decodeBase64Char((int) '?'));
+        assertEquals(Base64Variant.BASE64_VALUE_INVALID, std.decodeBase64Char((byte) '?'));
+
+        assertEquals(0, std.decodeBase64Char('A'));
+        assertEquals(1, std.decodeBase64Char((int) 'B'));
+        assertEquals(2, std.decodeBase64Char((byte)'C'));
+
+        assertEquals('/', std.encodeBase64BitsAsChar(63));
+        assertEquals((byte) 'b', std.encodeBase64BitsAsByte(27));
+
+        String EXP_STR = "HwdJ";
+        int TRIPLET = 0x1F0749;
+        StringBuilder sb = new StringBuilder();
+        std.encodeBase64Chunk(sb, TRIPLET);
+        assertEquals(EXP_STR, sb.toString());
+
+        byte[] exp = EXP_STR.getBytes("UTF-8");
+        byte[] act = new byte[exp.length];
+        std.encodeBase64Chunk(TRIPLET, act, 0);
+        Assert.assertArrayEquals(exp, act);
+    }
+
+    @SuppressWarnings("unused")
+    public void testErrors() throws Exception
+    {
+        try {
+            Base64Variant b = new Base64Variant("foobar", "xyz", false, '!', 24);
+        } catch (IllegalArgumentException iae) {
+            verifyException(iae, "length must be exactly");
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestBase64Generation.java b/src/test/java/com/fasterxml/jackson/core/json/TestBase64Generation.java
new file mode 100644
index 0000000..972023a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestBase64Generation.java
@@ -0,0 +1,124 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestBase64Generation
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    static class ThrottledInputStream extends FilterInputStream
+    {
+        protected final int _maxBytes;
+
+        public ThrottledInputStream(byte[] data, int maxBytes)
+        {
+            this(new ByteArrayInputStream(data), maxBytes);
+        }
+        
+        public ThrottledInputStream(InputStream in, int maxBytes)
+        {
+            super(in);
+            _maxBytes = maxBytes;
+        }
+
+        @Override
+        public int read(byte[] buf) throws IOException {
+            return read(buf, 0, buf.length);
+        }
+        
+        @Override
+        public int read(byte[] buf, int offset, int len) throws IOException {
+            return in.read(buf, offset, Math.min(_maxBytes, len));
+        }
+        
+    }
+    
+    /*
+    /**********************************************************
+    /* Test methods
+    /**********************************************************
+     */
+
+    public void testStreamingWrites() throws Exception
+    {
+        final JsonFactory f = new JsonFactory();
+        _testStreamingWrites(f, true);
+        _testStreamingWrites(f, false);
+    }
+
+    // For [#55]
+    public void testIssue55() throws Exception
+    {
+        final JsonFactory f = new JsonFactory();
+
+        // First,  byte-backed:
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+        JsonGenerator gen = f.createGenerator(bytes);
+        ByteArrayInputStream data = new ByteArrayInputStream(new byte[2000]);
+        gen.writeBinary(data, 1999);       
+        gen.close();
+
+        final int EXP_LEN = 2670;
+        
+        assertEquals(EXP_LEN, bytes.size());
+
+        // Then char-backed
+        StringWriter sw = new StringWriter();
+        
+        gen = f.createGenerator(sw);
+        data = new ByteArrayInputStream(new byte[2000]);
+        gen.writeBinary(data, 1999);       
+        gen.close();
+        
+        assertEquals(EXP_LEN, sw.toString().length());
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private final static Base64Variant[] VARIANTS = {
+            Base64Variants.MIME,
+            Base64Variants.MIME_NO_LINEFEEDS,
+            Base64Variants.MODIFIED_FOR_URL,
+            Base64Variants.PEM
+    };
+
+    private final static String TEXT = "Some content so that we can test encoding of base64 data; must"
+            +" be long enough include a line wrap or two...";
+    private final static String TEXT4 = TEXT + TEXT + TEXT + TEXT;
+
+    private void _testStreamingWrites(JsonFactory jf, boolean useBytes) throws Exception
+    {
+        final byte[] INPUT = TEXT4.getBytes("UTF-8");
+        for (Base64Variant variant : VARIANTS) {
+            final String EXP_OUTPUT = "[" + quote(variant.encode(INPUT))+"]";
+            for (boolean passLength : new boolean[] { true, false }) {
+                for (int chunkSize : new int[] { 1, 2, 3, 4, 7, 11, 29, 5000 }) {
+//System.err.println(""+variant+", length "+passLength+", chunk "+chunkSize);
+                    
+                    JsonGenerator jgen;
+                    
+                    final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+                    if (useBytes) {
+                        jgen = jf.createGenerator(bytes);
+                    } else {
+                        jgen = jf.createGenerator(new OutputStreamWriter(bytes, "UTF-8"));
+                    }
+                    jgen.writeStartArray();
+                    int length = passLength ? INPUT.length : -1;
+                    InputStream data = new ThrottledInputStream(INPUT, chunkSize);
+                    jgen.writeBinary(variant, data, length);
+                    jgen.writeEndArray();
+                    jgen.close();
+                    String JSON = bytes.toString("UTF-8");
+                    assertEquals(EXP_OUTPUT, JSON);
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestBase64Parsing.java b/src/test/java/com/fasterxml/jackson/core/json/TestBase64Parsing.java
new file mode 100644
index 0000000..4b06a49
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestBase64Parsing.java
@@ -0,0 +1,150 @@
+package com.fasterxml.jackson.core.json;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestBase64Parsing
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testBase64UsingInputStream() throws Exception
+    {
+        _testBase64Text(true);
+    }
+
+    public void testBase64UsingReader() throws Exception
+    {
+        _testBase64Text(false);
+    }
+
+    // [Issue-15] (streaming binary reads)
+    public void testStreaming() throws IOException
+    {
+        _testStreaming(false);
+        _testStreaming(true);
+    }
+
+    /*
+    /**********************************************************
+    /* Test helper methods
+    /**********************************************************
+     */
+    
+    // Test for [JACKSON-631]
+    public void _testBase64Text(boolean useBytes) throws Exception
+    {
+        // let's actually iterate over sets of encoding modes, lengths
+        
+        final int[] LENS = { 1, 2, 3, 4, 7, 9, 32, 33, 34, 35 };
+        final Base64Variant[] VARIANTS = {
+                Base64Variants.MIME,
+                Base64Variants.MIME_NO_LINEFEEDS,
+                Base64Variants.MODIFIED_FOR_URL,
+                Base64Variants.PEM
+        };
+
+        JsonFactory jsonFactory = new JsonFactory();
+        final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        StringWriter chars = null;
+        for (int len : LENS) {
+            byte[] input = new byte[len];
+            for (int i = 0; i < input.length; ++i) {
+                input[i] = (byte) i;
+            }
+            for (Base64Variant variant : VARIANTS) {
+                JsonGenerator jgen;
+                if (useBytes) {
+                    bytes.reset();
+                    jgen = jsonFactory.createGenerator(bytes, JsonEncoding.UTF8);
+                } else {
+                    chars = new StringWriter();
+                    jgen = jsonFactory.createGenerator(chars);
+                }
+                jgen.writeBinary(variant, input, 0, input.length);
+                jgen.close();
+                JsonParser jp;
+                if (useBytes) {
+                    jp = jsonFactory.createParser(bytes.toByteArray());
+                } else {
+                    jp = jsonFactory.createParser(chars.toString());
+                }
+                assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+                byte[] data = null;
+                try {
+                    data = jp.getBinaryValue(variant);
+                } catch (Exception e) {
+                    IOException ioException = new IOException("Failed (variant "+variant+", data length "+len+"): "+e.getMessage());
+                    ioException.initCause(e);
+                    throw ioException;
+                }
+                assertNotNull(data);
+                assertArrayEquals(data, input);
+                assertNull(jp.nextToken());
+                jp.close();
+            }
+        }
+    }
+
+    private byte[] _generateData(int size)
+    {
+        byte[] result = new byte[size];
+        for (int i = 0; i < size; ++i) {
+            result[i] = (byte) (i % 255);
+        }
+        return result;
+    }
+
+    private void _testStreaming(boolean useBytes) throws IOException
+    {
+        final int[] SIZES = new int[] {
+            1, 2, 3, 4, 5, 6,
+            7, 8, 12,
+            100, 350, 1900, 6000, 19000, 65000,
+            139000
+        };
+
+        JsonFactory jsonFactory = new JsonFactory();
+        final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        StringWriter chars = null;
+
+        for (int size : SIZES) {
+            byte[] data = _generateData(size);
+            JsonGenerator g;
+            if (useBytes) {
+                bytes.reset();
+                g = jsonFactory.createGenerator(bytes, JsonEncoding.UTF8);
+            } else {
+                chars = new StringWriter();
+                g = jsonFactory.createGenerator(chars);
+            }
+
+            g.writeStartObject();
+            g.writeFieldName("b");
+            g.writeBinary(data);
+            g.writeEndObject();
+            g.close();
+
+            // and verify
+            JsonParser p;
+            if (useBytes) {
+                p = jsonFactory.createParser(bytes.toByteArray());
+            } else {
+                p = jsonFactory.createParser(chars.toString());
+            }
+            assertToken(JsonToken.START_OBJECT, p.nextToken());
+    
+            assertToken(JsonToken.FIELD_NAME, p.nextToken());
+            assertEquals("b", p.getCurrentName());
+            assertToken(JsonToken.VALUE_STRING, p.nextToken());
+            ByteArrayOutputStream result = new ByteArrayOutputStream(size);
+            int gotten = p.readBinaryValue(result);
+            assertEquals(size, gotten);
+            assertArrayEquals(data, result.toByteArray());
+            assertToken(JsonToken.END_OBJECT, p.nextToken());
+            assertNull(p.nextToken());
+            p.close();
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestCustomEscaping.java b/src/test/java/com/fasterxml/jackson/core/json/TestCustomEscaping.java
new file mode 100644
index 0000000..4ab4440
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestCustomEscaping.java
@@ -0,0 +1,176 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.CharacterEscapes;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+public class TestCustomEscaping extends com.fasterxml.jackson.test.BaseTest
+{
+    final static int TWO_BYTE_ESCAPED = 0x111;
+    final static int THREE_BYTE_ESCAPED = 0x1111;
+
+    final static SerializedString TWO_BYTE_ESCAPED_STRING = new SerializedString("&111;");
+    final static SerializedString THREE_BYTE_ESCAPED_STRING = new SerializedString("&1111;");
+    
+    /*
+    /********************************************************
+    /* Helper types
+    /********************************************************
+     */
+
+    /**
+     * Trivial simple custom escape definition set.
+     */
+    @SuppressWarnings("serial")
+    static class MyEscapes extends CharacterEscapes
+    {
+        
+        private final int[] _asciiEscapes;
+
+        public MyEscapes() {
+            _asciiEscapes = standardAsciiEscapesForJSON();
+            _asciiEscapes['a'] = 'A'; // to basically give us "\A"
+            _asciiEscapes['b'] = CharacterEscapes.ESCAPE_STANDARD; // too force "\u0062"
+            _asciiEscapes['d'] = CharacterEscapes.ESCAPE_CUSTOM;
+        }
+        
+        @Override
+        public int[] getEscapeCodesForAscii() {
+            return _asciiEscapes;
+        }
+
+        @Override
+        public SerializableString getEscapeSequence(int ch)
+        {
+            if (ch == 'd') {
+                return new SerializedString("[D]");
+            }
+            if (ch == TWO_BYTE_ESCAPED) {
+                return TWO_BYTE_ESCAPED_STRING;
+            }
+            if (ch == THREE_BYTE_ESCAPED) {
+                return THREE_BYTE_ESCAPED_STRING;
+            }
+            return null;
+        }
+    }
+    
+    /*
+    /********************************************************
+    /* Unit tests
+    /********************************************************
+     */
+
+    /**
+     * Test to ensure that it is possible to force escaping
+     * of non-ASCII characters.
+     * Related to [JACKSON-102]
+     */
+    public void testAboveAsciiEscapeWithReader() throws Exception
+    {
+        _testEscapeAboveAscii(false); // reader
+    }
+
+    public void testAboveAsciiEscapeWithUTF8Stream() throws Exception
+    {
+        _testEscapeAboveAscii(true); // stream (utf-8)
+    }
+
+    // // // Tests for [JACKSON-106]
+    
+    public void testEscapeCustomWithReader() throws Exception
+    {
+        _testEscapeCustom(false); // reader
+    }
+
+    public void testEscapeCustomWithUTF8Stream() throws Exception
+    {
+        _testEscapeCustom(true); // stream (utf-8)
+    }
+    
+    /*
+    /********************************************************
+    /* Secondary test methods
+    /********************************************************
+     */
+
+    @SuppressWarnings("resource")
+    private void _testEscapeAboveAscii(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        final String VALUE = "chars: [\u00A0]/[\u1234]";
+        final String KEY = "fun:\u0088:\u3456";
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        JsonGenerator jgen;
+
+        // First: output normally; should not add escaping
+        if (useStream) {
+            jgen = f.createGenerator(bytes, JsonEncoding.UTF8);
+        } else {
+            jgen = f.createGenerator(new OutputStreamWriter(bytes, "UTF-8"));
+        }
+        jgen.writeStartArray();
+        jgen.writeString(VALUE);
+        jgen.writeEndArray();
+        jgen.close();
+        String json = bytes.toString("UTF-8");
+        
+        assertEquals("["+quote(VALUE)+"]", json);
+
+        // And then with forced ASCII; first, values
+
+        bytes = new ByteArrayOutputStream();
+        if (useStream) {
+            jgen = f.createGenerator(bytes, JsonEncoding.UTF8);
+        } else {
+            jgen = f.createGenerator(new OutputStreamWriter(bytes, "UTF-8"));
+        }
+        jgen.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
+        jgen.writeStartArray();
+        jgen.writeString(VALUE);
+        jgen.writeEndArray();
+        jgen.close();
+        json = bytes.toString("UTF-8");
+        assertEquals("["+quote("chars: [\\u00A0]/[\\u1234]")+"]", json);
+
+        // and then keys
+        bytes = new ByteArrayOutputStream();
+        if (useStream) {
+            jgen = f.createGenerator(bytes, JsonEncoding.UTF8);
+        } else {
+            jgen = f.createGenerator(new OutputStreamWriter(bytes, "UTF-8"));
+        }
+        jgen.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
+        jgen.writeStartObject();
+        jgen.writeFieldName(KEY);
+        jgen.writeBoolean(true);
+        jgen.writeEndObject();
+        jgen.close();
+        json = bytes.toString("UTF-8");
+        assertEquals("{"+quote("fun:\\u0088:\\u3456")+":true}", json);
+    }
+
+    private void _testEscapeCustom(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory().setCharacterEscapes(new MyEscapes());
+        final String STR_IN = "[abcd/"+((char) TWO_BYTE_ESCAPED)+"/"+((char) THREE_BYTE_ESCAPED)+"]";
+        final String STR_OUT = "[\\A\\u0062c[D]/"+TWO_BYTE_ESCAPED_STRING+"/"+THREE_BYTE_ESCAPED_STRING+"]";
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        JsonGenerator jgen;
+        
+        // First: output normally; should not add escaping
+        if (useStream) {
+            jgen = f.createGenerator(bytes, JsonEncoding.UTF8);
+        } else {
+            jgen = f.createGenerator(new OutputStreamWriter(bytes, "UTF-8"));
+        }
+        jgen.writeStartObject();
+        jgen.writeStringField(STR_IN, STR_IN);
+        jgen.writeEndObject();
+        jgen.close();
+        String json = bytes.toString("UTF-8");
+        assertEquals("{"+quote(STR_OUT)+":"+quote(STR_OUT)+"}", json);
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestDecorators.java b/src/test/java/com/fasterxml/jackson/core/json/TestDecorators.java
new file mode 100644
index 0000000..6c94f8d
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestDecorators.java
@@ -0,0 +1,115 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.io.InputDecorator;
+import com.fasterxml.jackson.core.io.OutputDecorator;
+
+/**
+ * Unit tests to verify that input and output decorators work as
+ * expected
+ * 
+ * @since 1.8
+ */
+ at SuppressWarnings("serial")
+public class TestDecorators extends com.fasterxml.jackson.test.BaseTest
+{
+    /*
+    /**********************************************************
+    /* Helper classes
+    /**********************************************************
+     */
+
+    static class SimpleInputDecorator extends InputDecorator
+    {
+        @Override
+        public InputStream decorate(IOContext ctxt, InputStream in)
+            throws IOException
+        {
+            return new ByteArrayInputStream("123".getBytes("UTF-8"));
+        }
+
+        @Override
+        public InputStream decorate(IOContext ctxt, byte[] src, int offset, int length)
+            throws IOException
+        {
+            return new ByteArrayInputStream("456".getBytes("UTF-8"));
+        }
+
+        @Override
+        public Reader decorate(IOContext ctxt, Reader src) {
+            return new StringReader("789");
+        }
+    }
+
+    static class SimpleOutputDecorator extends OutputDecorator
+    {
+        @Override
+        public OutputStream decorate(IOContext ctxt, OutputStream out) throws IOException
+        {
+            out.write("123".getBytes("UTF-8"));
+            out.flush();
+            return new ByteArrayOutputStream();
+        }
+
+        @Override
+        public Writer decorate(IOContext ctxt, Writer w) throws IOException
+        {
+            w.write("567");
+            w.flush();
+            return new StringWriter();
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Unit tests
+    /**********************************************************
+     */
+
+    public void testInputDecoration() throws IOException
+    {
+        JsonFactory f = new JsonFactory();
+        f.setInputDecorator(new SimpleInputDecorator());
+        JsonParser jp;
+        // first test with Reader
+        jp = f.createParser(new StringReader("{ }"));
+        // should be overridden;
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(789, jp.getIntValue());
+        jp.close();
+
+        // similarly with InputStream
+        jp = f.createParser(new ByteArrayInputStream("[ ]".getBytes("UTF-8")));
+        // should be overridden;
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(123, jp.getIntValue());
+        jp.close();
+
+        // and with raw bytes
+        jp = f.createParser("[ ]".getBytes("UTF-8"));
+        // should be overridden;
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(456, jp.getIntValue());
+        jp.close();
+    }
+
+    public void testOutputDecoration() throws IOException
+    {
+        JsonFactory f = new JsonFactory();
+        f.setOutputDecorator(new SimpleOutputDecorator());
+        JsonGenerator jg;
+
+        StringWriter sw = new StringWriter();
+        jg = f.createGenerator(sw);
+        jg.close();
+        assertEquals("567", sw.toString());
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        jg = f.createGenerator(out, JsonEncoding.UTF8);
+        jg.close();
+        assertEquals("123", out.toString("UTF-8"));
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestJsonParser.java b/src/test/java/com/fasterxml/jackson/core/json/TestJsonParser.java
new file mode 100644
index 0000000..4a76e24
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestJsonParser.java
@@ -0,0 +1,543 @@
+package com.fasterxml.jackson.core.json;
+
+import com.fasterxml.jackson.core.*;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestJsonParser
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testConfig() throws Exception
+    {
+        JsonParser jp = createParserUsingReader("[ ]");
+        jp.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+        assertTrue(jp.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+        jp.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+        assertFalse(jp.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+
+        jp.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
+        assertTrue(jp.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+        jp.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
+        assertFalse(jp.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+    }
+
+    public void testInterningWithStreams() throws Exception
+    {
+        _testIntern(true, true, "a");
+        _testIntern(true, false, "b");
+    }
+
+    public void testInterningWithReaders() throws Exception
+    {
+        _testIntern(false, true, "c");
+        _testIntern(false, false, "d");
+    }
+    
+    private void _testIntern(boolean useStream, boolean enableIntern, String expName) throws IOException
+    {
+        JsonFactory f = new JsonFactory();
+        f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, enableIntern);
+        assertEquals(enableIntern, f.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
+        final String JSON = "{ \""+expName+"\" : 1}";
+        JsonParser jp = useStream ?
+            createParserUsingStream(f, JSON, "UTF-8") : createParserUsingReader(f, JSON);
+            
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        // needs to be same of cours
+        String actName = jp.getCurrentName();
+        assertEquals(expName, actName);
+        if (enableIntern) {
+            assertSame(expName, actName);
+        } else {
+            assertNotSame(expName, actName);
+        }
+        jp.close();
+    }
+
+    /**
+     * This basic unit test verifies that example given in the Json
+     * specification (RFC-4627 or later) is properly parsed at
+     * high-level, without verifying values.
+     */
+    public void testSpecExampleSkipping()
+        throws Exception
+    {
+        doTestSpec(false);
+    }
+
+    /**
+     * Unit test that verifies that the spec example JSON is completely
+     * parsed, and proper values are given for contents of all
+     * events/tokens.
+     */
+    public void testSpecExampleFully()
+        throws Exception
+    {
+        doTestSpec(true);
+    }
+
+    /**
+     * Unit test that verifies that 3 basic keywords (null, true, false)
+     * are properly parsed in various contexts.
+     */
+    public void testKeywords()
+        throws Exception
+    {
+        final String DOC = "{\n"
+            +"\"key1\" : null,\n"
+            +"\"key2\" : true,\n"
+            +"\"key3\" : false,\n"
+            +"\"key4\" : [ false, null, true ]\n"
+            +"}"
+            ;
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+
+        JsonStreamContext ctxt = jp.getParsingContext();
+        assertTrue(ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertFalse(ctxt.inObject());
+        assertEquals(0, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        /* Before advancing to content, we should have following
+         * default state...
+         */
+        assertFalse(jp.hasCurrentToken());
+        assertNull(jp.getText());
+        assertNull(jp.getTextCharacters());
+        assertEquals(0, jp.getTextLength());
+        // not sure if this is defined but:
+        assertEquals(0, jp.getTextOffset());
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+
+        assertTrue(jp.hasCurrentToken());
+        JsonLocation loc = jp.getTokenLocation();
+        assertNotNull(loc);
+        assertEquals(1, loc.getLineNr());
+        assertEquals(1, loc.getColumnNr());
+
+        ctxt = jp.getParsingContext();
+        assertFalse(ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertTrue(ctxt.inObject());
+        assertEquals(0, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key1");
+        assertEquals(2, jp.getTokenLocation().getLineNr());
+
+        ctxt = jp.getParsingContext();
+        assertFalse(ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertTrue(ctxt.inObject());
+        assertEquals(1, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+
+        ctxt = jp.getParsingContext();
+        assertEquals(1, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key2");
+        ctxt = jp.getParsingContext();
+        assertEquals(2, ctxt.getEntryCount());
+        assertEquals(1, ctxt.getCurrentIndex());
+
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key3");
+        assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key4");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+        assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+    }
+
+    public void testInvalidKeywordsStream() throws Exception {
+        _testInvalidKeywords(true);
+    }
+    
+    public void testInvalidKeywordsReader() throws Exception {
+        _testInvalidKeywords(false);
+    }
+
+    private void _testInvalidKeywords(boolean useStream) throws Exception
+    {
+        doTestInvalidKeyword1(useStream, "nul");
+        doTestInvalidKeyword1(useStream, "Null");
+        doTestInvalidKeyword1(useStream, "nulla");
+        doTestInvalidKeyword1(useStream, "fal");
+        doTestInvalidKeyword3(useStream, "False");
+        doTestInvalidKeyword1(useStream, "fals0");
+        doTestInvalidKeyword1(useStream, "falsett0");
+        doTestInvalidKeyword1(useStream, "tr");
+        doTestInvalidKeyword1(useStream, "truE");
+        doTestInvalidKeyword1(useStream, "treu");
+        doTestInvalidKeyword1(useStream, "trueenough");
+    }
+
+    public void testSkipping()
+        throws Exception
+    {
+        String DOC =
+            "[ 1, 3, [ true, null ], 3, { \"a\":\"b\" }, [ [ ] ], { } ]";
+            ;
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+
+        // First, skipping of the whole thing
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        jp.skipChildren();
+        assertEquals(JsonToken.END_ARRAY, jp.getCurrentToken());
+        JsonToken t = jp.nextToken();
+        if (t != null) {
+            fail("Expected null at end of doc, got "+t);
+        }
+        jp.close();
+
+        // Then individual ones
+        jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        jp.skipChildren();
+        // shouldn't move
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.getCurrentToken());
+        assertEquals(1, jp.getIntValue());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        // then skip array
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        jp.skipChildren();
+        assertToken(JsonToken.END_ARRAY, jp.getCurrentToken());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        jp.skipChildren();
+        assertToken(JsonToken.END_OBJECT, jp.getCurrentToken());
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        jp.skipChildren();
+        assertToken(JsonToken.END_ARRAY, jp.getCurrentToken());
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        jp.skipChildren();
+        assertToken(JsonToken.END_OBJECT, jp.getCurrentToken());
+
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+
+        jp.close();
+    }
+
+    public void testNameEscaping() throws IOException
+    {
+        _testNameEscaping(false);
+        _testNameEscaping(true);
+    }
+
+    private void _testNameEscaping(boolean useStream) throws IOException
+    {
+        final Map<String,String> NAME_MAP = new LinkedHashMap<String,String>();
+        NAME_MAP.put("", "");
+        NAME_MAP.put("\\\"funny\\\"", "\"funny\"");
+        NAME_MAP.put("\\\\", "\\");
+        NAME_MAP.put("\\r", "\r");
+        NAME_MAP.put("\\n", "\n");
+        NAME_MAP.put("\\t", "\t");
+        NAME_MAP.put("\\r\\n", "\r\n");
+        NAME_MAP.put("\\\"\\\"", "\"\"");
+        NAME_MAP.put("Line\\nfeed", "Line\nfeed");
+        NAME_MAP.put("Yet even longer \\\"name\\\"!", "Yet even longer \"name\"!");
+
+        JsonFactory jf = new JsonFactory();
+        int entry = 0;
+        for (Map.Entry<String,String> en : NAME_MAP.entrySet()) {
+            ++entry;
+            String input = en.getKey();
+            String expResult = en.getValue();
+            final String DOC = "{ \""+input+"\":null}";
+            JsonParser jp = useStream ?
+                jf.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+                : jf.createParser(new StringReader(DOC));
+
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+            // first, sanity check (field name == getText()
+            String act = jp.getCurrentName();
+            assertEquals(act, getAndVerifyText(jp));
+            if (!expResult.equals(act)) {
+                String msg = "Failed for name #"+entry+"/"+NAME_MAP.size();
+                if (expResult.length() != act.length()) {
+                    fail(msg+": exp length "+expResult.length()+", actual "+act.length());
+                }
+                assertEquals(msg, expResult, act);
+            }
+            assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+            assertToken(JsonToken.END_OBJECT, jp.nextToken());
+            jp.close();
+        }
+    }
+    
+    /**
+     * Unit test that verifies that long text segments are handled
+     * correctly; mostly to stress-test underlying segment-based
+     * text buffer(s).
+     */
+    public void testLongText() throws Exception
+    {
+        final int LEN = 96000;
+        StringBuilder sb = new StringBuilder(LEN + 100);
+        Random r = new Random(99);
+        while (sb.length() < LEN) {
+            sb.append(r.nextInt());
+            sb.append(" xyz foo");
+            if (r.nextBoolean()) {
+                sb.append(" and \"bar\"");
+            } else if (r.nextBoolean()) {
+                sb.append(" [whatever].... ");
+            } else {
+                // Let's try some more 'exotic' chars
+                sb.append(" UTF-8-fu: try this {\u00E2/\u0BF8/\uA123!} (look funny?)");
+            }
+            if (r.nextBoolean()) {
+                if (r.nextBoolean()) {
+                    sb.append('\n');
+                } else if (r.nextBoolean()) {
+                    sb.append('\r');
+                } else {
+                    sb.append("\r\n");
+                }
+            }
+        }
+        final String VALUE = sb.toString();
+
+        JsonFactory jf = new JsonFactory();
+        
+        // Let's use real generator to get json done right
+        StringWriter sw = new StringWriter(LEN + (LEN >> 2));
+        JsonGenerator jg = jf.createGenerator(sw);
+        jg.writeStartObject();
+        jg.writeFieldName("doc");
+        jg.writeString(VALUE);
+        jg.writeEndObject();
+        jg.close();
+        
+        final String DOC = sw.toString();
+
+        for (int type = 0; type < 3; ++type) {
+            JsonParser jp;
+
+            switch (type) {
+            default:
+                jp = jf.createParser(DOC.getBytes("UTF-8"));
+                break;
+            case 1:
+                jp = jf.createParser(DOC);
+                break;
+            case 2: // NEW: let's also exercise UTF-32...
+                jp = jf.createParser(encodeInUTF32BE(DOC));
+                break;
+            }
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+            assertEquals("doc", jp.getCurrentName());
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            
+            String act = getAndVerifyText(jp);
+            if (act.length() != VALUE.length()) {
+                fail("Expected length "+VALUE.length()+", got "+act.length());
+            }
+            if (!act.equals(VALUE)) {
+                fail("Long text differs");
+            }
+
+            // should still know the field name
+            assertEquals("doc", jp.getCurrentName());
+            assertToken(JsonToken.END_OBJECT, jp.nextToken());
+            assertNull(jp.nextToken());
+            jp.close();
+        }
+    }
+
+    /**
+     * Simple unit test that verifies that passing in a byte array
+     * as source works as expected.
+     */
+    public void testBytesAsSource() throws Exception
+    {
+        String JSON = "[ 1, 2, 3, 4 ]";
+        byte[] b = JSON.getBytes("UTF-8");
+        int offset = 50;
+        int len = b.length;
+        byte[] src = new byte[offset + len + offset];
+
+        System.arraycopy(b, 0, src, offset, len);
+
+        JsonFactory jf = new JsonFactory();
+        JsonParser jp = jf.createParser(src, offset, len);
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(1, jp.getIntValue());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(2, jp.getIntValue());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(3, jp.getIntValue());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(4, jp.getIntValue());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+
+        jp.close();
+    }
+
+    // [JACKSON-632]
+    public void testUtf8BOMHandling() throws Exception
+    {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        // first, write BOM:
+        bytes.write(0xEF);
+        bytes.write(0xBB);
+        bytes.write(0xBF);
+        bytes.write("[ 1 ]".getBytes("UTF-8"));
+        JsonFactory jf = new JsonFactory();
+        JsonParser jp = jf.createParser(bytes.toByteArray());
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        // should also have skipped first 3 bytes of BOM; but do we have offset available?
+        /*
+        JsonLocation loc = jp.getTokenLocation();
+        assertEquals(3, loc.getByteOffset());
+        assertEquals(-1, loc.getCharOffset());
+        */
+    }
+
+
+    // [Issue#48]
+    public void testSpacesInURL() throws Exception
+    {
+        File f = File.createTempFile("pre fix&stuff", ".txt");
+        BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
+        w.write("{ }");
+        w.close();
+        URL url = f.toURI().toURL();
+
+        JsonFactory jf = new JsonFactory();
+        JsonParser jp = jf.createParser(url);
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        jp.close();
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private void doTestSpec(boolean verify)
+        throws IOException
+    {
+        // First, using a StringReader:
+        doTestSpecIndividual(null, verify);
+
+        // Then with streams using supported encodings:
+        doTestSpecIndividual("UTF-8", verify);
+        doTestSpecIndividual("UTF-16BE", verify);
+        doTestSpecIndividual("UTF-16LE", verify);
+
+        /* Hmmh. UTF-32 is harder only because JDK doesn't come with
+         * a codec for it. Can't test it yet using this method
+         */
+        doTestSpecIndividual("UTF-32", verify);
+    }
+
+    private void doTestSpecIndividual(String enc, boolean verify)
+        throws IOException
+    {
+        String doc = SAMPLE_DOC_JSON_SPEC;
+        JsonParser jp;
+
+        if (enc == null) {
+            jp = createParserUsingReader(doc);
+        } else {
+            jp = createParserUsingStream(doc, enc);
+        }
+        verifyJsonSpecSampleDoc(jp, verify);
+        jp.close();
+    }
+
+    private void doTestInvalidKeyword1(boolean useStream, String value)
+        throws IOException
+    {
+        final String doc = "{ \"key1\" : "+value+" }";
+        JsonParser jp = useStream ? createParserUsingStream(doc, "UTF-8")
+                : createParserUsingReader(doc);
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        /* 24-Nov-2008, tatu: Note that depending on parser impl, we may
+         *   get the exception early or late...
+         */
+        try {
+            assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+            jp.nextToken();
+            fail("Expected an exception for malformed value keyword");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "Unrecognized token");
+            verifyException(jex, value);
+        } finally {
+            jp.close();
+        }
+
+        // Try as root-level value as well:
+        jp = useStream ? createParserUsingStream(value, "UTF-8")
+                : createParserUsingReader(value);
+        try {
+            jp.nextToken();
+            fail("Expected an exception for malformed value keyword");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "Unrecognized token");
+            verifyException(jex, value);
+        } finally {
+            jp.close();
+        }
+    }
+
+    private void doTestInvalidKeyword3(boolean useStream, String value)
+        throws IOException
+    {
+        final String doc = "{ \"key1\" : "+value+" }";
+        JsonParser jp = useStream ? createParserUsingStream(doc, "UTF-8")
+                : this.createParserUsingReader(doc);
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        /* 24-Nov-2008, tatu: Note that depending on parser impl, we may
+         *   get the exception early or late...
+         */
+        try {
+            assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+            jp.nextToken();
+            fail("Expected an exception for malformed value keyword");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "expected a valid value");
+        } finally {
+            jp.close();
+        }
+    }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestJsonParserBinary.java b/src/test/java/com/fasterxml/jackson/core/json/TestJsonParserBinary.java
new file mode 100644
index 0000000..7fe0716
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestJsonParserBinary.java
@@ -0,0 +1,153 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for verifying that accessing base64 encoded content works ok.
+ */
+public class TestJsonParserBinary
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    /*
+    /**********************************************************************
+    /* Unit tests
+    /**********************************************************************
+     */
+
+    public void testSimple()
+        throws IOException
+    {
+        // let's test reader (char) based first, then stream (byte)
+        _testSimple(false);
+        _testSimple(true);
+    }
+
+    public void testInArray()
+        throws IOException
+    {
+        // let's test reader (char) based first, then stream (byte)
+        _testInArray(false);
+        _testInArray(true);
+    }
+
+    public void testWithEscaped() throws IOException
+    {
+        // let's test reader (char) based first, then stream (byte)
+        _testEscaped(false);
+        _testEscaped(true);
+    }
+    
+    /*
+    /**********************************************************************
+    /* Actual test methods
+    /**********************************************************************
+     */
+
+    private void _testSimple(boolean useStream)
+        throws IOException
+    {
+        /* The usual sample input string, from Thomas Hobbes's "Leviathan"
+         * (via Wikipedia)
+         */
+        final String RESULT = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
+        final byte[] RESULT_BYTES = RESULT.getBytes("US-ASCII");
+
+        // And here's what should produce it...
+        final String INPUT_STR = 
+ "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"
++"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"
++"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu"
++"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo"
++"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="
+            ;
+
+        final String DOC = "\""+INPUT_STR+"\"";
+        JsonParser jp = _getParser(DOC, useStream);
+
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        byte[] data = jp.getBinaryValue();
+        assertNotNull(data);
+        assertArrayEquals(RESULT_BYTES, data);
+    }
+
+    private void _testInArray(boolean useStream)
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+
+        final int entryCount = 7;
+
+        StringWriter sw = new StringWriter();
+        JsonGenerator jg = jf.createGenerator(sw);
+        jg.writeStartArray();
+
+        byte[][] entries = new byte[entryCount][];
+        for (int i = 0; i < entryCount; ++i) {
+            byte[] b = new byte[200 + i * 100];
+            for (int x = 0; x < b.length; ++x) {
+                b[x] = (byte) (i + x);
+            }
+            entries[i] = b;
+            jg.writeBinary(b);
+        }
+
+        jg.writeEndArray();
+        jg.close();
+
+        JsonParser jp = _getParser(sw.toString(), useStream);
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        for (int i = 0; i < entryCount; ++i) {
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            byte[] b = jp.getBinaryValue();
+            assertArrayEquals(entries[i], b);
+        }
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+    }
+
+    private void _testEscaped(boolean useStream) throws IOException
+    {
+        // Input: "Test!" -> "VGVzdCE="
+
+        // First, try with embedded linefeed half-way through:
+
+        String DOC = quote("VGVz\\ndCE="); // note: must double-quote to get linefeed
+        JsonParser jp = _getParser(DOC, useStream);
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        byte[] b = jp.getBinaryValue();
+        assertEquals("Test!", new String(b, "US-ASCII"));
+        assertNull(jp.nextToken());
+        jp.close();
+
+        // and then with escaped chars
+//        DOC = quote("V\\u0047V\\u007AdCE="); // note: must escape backslash...
+        DOC = quote("V\\u0047V\\u007AdCE="); // note: must escape backslash...
+        jp = _getParser(DOC, useStream);
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        b = jp.getBinaryValue();
+        assertEquals("Test!", new String(b, "US-ASCII"));
+        assertNull(jp.nextToken());
+        jp.close();
+    }
+    
+    /*
+    /**********************************************************************
+    /* Other helper methods
+    /**********************************************************************
+     */
+    
+    private JsonParser _getParser(String doc, boolean useStream)
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        if (useStream) {
+            return jf.createParser(doc.getBytes("UTF-8"));
+        }
+        return jf.createParser(new StringReader(doc));
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestNextXxx.java b/src/test/java/com/fasterxml/jackson/core/json/TestNextXxx.java
new file mode 100644
index 0000000..68fc9f5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestNextXxx.java
@@ -0,0 +1,179 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.SerializableString;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+public class TestNextXxx
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    /*
+    /********************************************************
+    /* Wrappers to test InputStream vs Reader
+    /********************************************************
+     */
+    
+    // [JACKSON-653]
+    public void testIsNextTokenName() throws Exception
+    {
+        _testIsNextTokenName1(false);
+        _testIsNextTokenName1(true);
+        _testIsNextTokenName2(false);
+        _testIsNextTokenName2(true);
+    }
+
+    // [Issue#34]
+    public void testIssue34() throws Exception
+    {
+        _testIssue34(false);
+        _testIssue34(true);
+    }
+
+    // [Issue#38] with nextFieldName
+    public void testIssue38() throws Exception
+    {
+        _testIssue38(false);
+        _testIssue38(true);
+    }
+    
+    /*
+    /********************************************************
+    /* Actual test code
+    /********************************************************
+     */
+
+    private void _testIsNextTokenName1(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));
+        SerializedString NAME = new SerializedString("name");
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.START_OBJECT, jp.getCurrentToken());
+        assertTrue(jp.nextFieldName(NAME));
+        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
+        assertEquals(NAME.getValue(), jp.getCurrentName());
+        assertEquals(NAME.getValue(), jp.getText());
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.getCurrentToken());
+        assertEquals(123, jp.getIntValue());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
+        assertEquals("name2", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
+        assertEquals("x", jp.getCurrentName());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.VALUE_STRING, jp.getCurrentToken());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.END_OBJECT, jp.getCurrentToken());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertNull(jp.getCurrentToken());
+
+        jp.close();
+    }
+
+    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));
+        SerializableString NAME = new SerializedString("name");
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.START_OBJECT, jp.getCurrentToken());
+        assertTrue(jp.nextFieldName(NAME));
+        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
+        assertEquals(NAME.getValue(), jp.getCurrentName());
+        assertEquals(NAME.getValue(), jp.getText());
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.getCurrentToken());
+        assertEquals(123, jp.getIntValue());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
+        assertEquals("name2", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.FIELD_NAME, jp.getCurrentToken());
+        assertEquals("x", jp.getCurrentName());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.VALUE_STRING, jp.getCurrentToken());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertToken(JsonToken.END_OBJECT, jp.getCurrentToken());
+
+        assertFalse(jp.nextFieldName(NAME));
+        assertNull(jp.getCurrentToken());
+
+        jp.close();
+    }
+
+    private void _testIssue34(boolean useStream) throws Exception
+    {
+        final int TESTROUNDS = 223;
+        final String DOC_PART = "{ \"fieldName\": 1 }";
+        
+        // build the big document to trigger issue
+        StringBuilder sb = new StringBuilder(2000);
+        for (int i = 0; i < TESTROUNDS; ++i) {
+            sb.append(DOC_PART);
+        }
+        final String DOC = sb.toString();
+        
+        SerializableString fieldName = new SerializedString("fieldName");
+        JsonFactory jf = new JsonFactory();
+        JsonParser parser = useStream ?
+            jf.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : jf.createParser(new StringReader(DOC));
+
+        for (int i = 0; i < TESTROUNDS - 1; i++) {
+            assertEquals(JsonToken.START_OBJECT, parser.nextToken());
+
+            // These will succeed
+            assertTrue(parser.nextFieldName(fieldName));
+
+            parser.nextLongValue(-1);
+            assertEquals(JsonToken.END_OBJECT, parser.nextToken());
+        }
+
+        assertEquals(JsonToken.START_OBJECT, parser.nextToken());
+
+        // This will fail
+        assertTrue(parser.nextFieldName(fieldName));
+        parser.close();
+    }
+
+    private void _testIssue38(boolean useStream) throws Exception
+    {
+        final String DOC = "{\"field\" :\"value\"}";
+        SerializableString fieldName = new SerializedString("field");
+        JsonFactory jf = new JsonFactory();
+        JsonParser parser = useStream ?
+            jf.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+            : jf.createParser(new StringReader(DOC));
+        assertEquals(JsonToken.START_OBJECT, parser.nextToken());
+        assertTrue(parser.nextFieldName(fieldName));
+        assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
+        assertEquals("value", parser.getText());
+        assertEquals(JsonToken.END_OBJECT, parser.nextToken());
+        assertNull(parser.nextToken());
+        parser.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestParserNonStandard.java b/src/test/java/com/fasterxml/jackson/core/json/TestParserNonStandard.java
new file mode 100644
index 0000000..01b21fb
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestParserNonStandard.java
@@ -0,0 +1,466 @@
+package com.fasterxml.jackson.core.json;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestParserNonStandard
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    // // // And then tests to verify [JACKSON-69]:
+
+    public void testSimpleUnquoted() throws Exception
+    {
+        _testSimpleUnquoted(false);
+        _testSimpleUnquoted(true);
+    }
+
+    public void testLargeUnquoted() throws Exception
+    {
+        _testLargeUnquoted(false);
+        _testLargeUnquoted(true);
+    }
+
+    public void testSingleQuotesDefault() throws Exception
+    {
+        _testSingleQuotesDefault(false);
+        _testSingleQuotesDefault(true);
+    }
+
+    public void testSingleQuotesEnabled() throws Exception
+    {
+        _testSingleQuotesEnabled(false);
+        _testSingleQuotesEnabled(true);
+        _testSingleQuotesEscaped(false);
+        _testSingleQuotesEscaped(true);
+    }
+
+    // Test for [JACKSON-267], allowing '@' as name char, for unquoted names
+    public void testNonStandardNameChars() throws Exception
+    {
+        _testNonStandardNameChars(false);
+        _testNonStandardNameChars(true);
+    }
+    
+    // Test for [JACKSON-300]
+    public void testNonStandardAnyCharQuoting() throws Exception
+    {
+        _testNonStandarBackslashQuoting(false);
+        _testNonStandarBackslashQuoting(true);
+    }
+
+    // Test for [JACKSON-358]
+    public void testLeadingZeroesUTF8() throws Exception {
+        _testLeadingZeroes(true, false);
+        _testLeadingZeroes(true, true);
+    }
+
+    public void testLeadingZeroesReader() throws Exception {
+        _testLeadingZeroes(false, false);
+        _testLeadingZeroes(false, true);
+    }
+
+    // [JACKSON-142]: allow NaN
+    public void testAllowNaN() throws Exception {
+        _testAllowNaN(false);
+        _testAllowNaN(true);
+    }
+
+    // [JACKSON-142]: allow +Inf/-Inf
+    public void testAllowInfinity() throws Exception {
+        _testAllowInf(false);
+        _testAllowInf(true);
+    }
+    
+    /*
+    /****************************************************************
+    /* Secondary test methods
+    /****************************************************************
+     */
+
+    private void _testLargeUnquoted(boolean useStream) throws Exception
+    {
+        StringBuilder sb = new StringBuilder(5000);
+        sb.append("[\n");
+        //final int REPS = 2000;
+        final int REPS = 1050;
+        for (int i = 0; i < REPS; ++i) {
+            if (i > 0) {
+                sb.append(',');
+                if ((i & 7) == 0) {
+                    sb.append('\n');
+                }
+            }
+            sb.append("{");
+            sb.append("abc").append(i&127).append(':');
+            sb.append((i & 1) != 0);
+            sb.append("}\n");
+        }
+        sb.append("]");
+        String JSON = sb.toString();
+        JsonFactory f = new JsonFactory();
+        f.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
+        JsonParser jp = useStream ?
+            createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON)
+            ;
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        for (int i = 0; i < REPS; ++i) {
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+            assertEquals("abc"+(i&127), jp.getCurrentName());
+            assertToken(((i&1) != 0) ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE, jp.nextToken());
+            assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        }
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+    }
+
+    
+    private void _testSimpleUnquoted(boolean useStream) throws Exception
+    {
+        final String JSON = "{ a : 1, _foo:true, $:\"money!\", \" \":null }";
+        JsonFactory f = new JsonFactory();
+        f.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
+        JsonParser jp = useStream ?
+            createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON)
+            ;
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("a", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("_foo", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("$", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("money!", jp.getText());
+
+        // and then regular quoted one should still work too:
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(" ", jp.getCurrentName());
+
+        assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        jp.close();
+    }
+
+    /**
+     * Test to verify that the default parser settings do not
+     * accept single-quotes for String values (field names,
+     * textual values)
+     */
+    private void _testSingleQuotesDefault(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        // First, let's see that by default they are not allowed
+        String JSON = "[ 'text' ]";
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            jp.nextToken();
+            fail("Expected exception");
+        } catch (JsonParseException e) {
+            verifyException(e, "Unexpected character ('''");
+        } finally {
+            jp.close();
+        }
+
+        JSON = "{ 'a':1 }";
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON);
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        try {
+            jp.nextToken();
+            fail("Expected exception");
+        } catch (JsonParseException e) {
+            verifyException(e, "Unexpected character ('''");
+        } finally {
+            jp.close();
+        }
+    }
+
+    /**
+     * Test to verify [JACKSON-173], optional handling of
+     * single quotes, to allow handling invalid (but, alas, common)
+     * JSON.
+     */
+    private void _testSingleQuotesEnabled(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        f.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+
+        String JSON = "{ 'a' : 1, \"foobar\": 'b', '_abcde1234':'d', '\"' : '\"\"', '':'' }";
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON);
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("a", jp.getText());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals("1", jp.getText());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("foobar", jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("b", jp.getText());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("_abcde1234", jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("d", jp.getText());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("\"", jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        //assertEquals("\"\"", jp.getText());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("", jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("", jp.getText());
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        jp.close();
+    }
+
+    // test to verify that we implicitly allow escaping of apostrophe [JACKSON-548]
+    private void _testSingleQuotesEscaped(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        f.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+
+        String JSON = "[ '16\\'' ]";
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON);
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("16'", jp.getText());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+    }
+    
+    private void _testNonStandardNameChars(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        f.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
+        String JSON = "{ @type : \"mytype\", #color : 123, *error* : true, "
+            +" hyphen-ated : \"yes\", me+my : null"
+            +"}";
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+                : createParserUsingReader(f, JSON);
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("@type", jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("mytype", jp.getText());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("#color", jp.getText());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(123, jp.getIntValue());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("*error*", jp.getText());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("hyphen-ated", jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("yes", jp.getText());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("me+my", jp.getText());
+        assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+    
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        jp.close();
+    }
+
+    private void _testNonStandarBackslashQuoting(boolean useStream) throws Exception
+    {
+        // first: verify that we get an exception
+        JsonFactory f = new JsonFactory();
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER));
+        final String JSON = quote("\\'");
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")                
+                : createParserUsingReader(f, JSON);
+        try {      
+            jp.nextToken();
+            jp.getText();
+            fail("Should have thrown an exception for doc <"+JSON+">");
+        } catch (JsonParseException e) {
+            verifyException(e, "unrecognized character escape");
+        } finally {
+            jp.close();
+        }
+        // and then verify it's ok...
+        f.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
+        assertTrue(f.isEnabled(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER));
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")                
+                : createParserUsingReader(f, JSON);
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("'", jp.getText());
+        jp.close();
+    }
+
+    private void _testLeadingZeroes(boolean useStream, boolean appendSpace) throws Exception
+    {
+        // first: verify that we get an exception
+        JsonFactory f = new JsonFactory();
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS));
+        String JSON = "00003";
+        if (appendSpace) {
+            JSON += " ";
+        }
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")                
+                : createParserUsingReader(f, JSON);
+        try {      
+            jp.nextToken();
+            jp.getText();
+            fail("Should have thrown an exception for doc <"+JSON+">");
+        } catch (JsonParseException e) {
+            verifyException(e, "invalid numeric value");
+        } finally {
+            jp.close();
+        }
+        
+        // and then verify it's ok when enabled
+        f.configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true);
+        assertTrue(f.isEnabled(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS));
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")                
+                : createParserUsingReader(f, JSON);
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(3, jp.getIntValue());
+        assertEquals("3", jp.getText());
+        jp.close();
+    
+        // Plus, also: verify that leading zero magnitude is ok:
+        JSON = "0"+Integer.MAX_VALUE;
+        if (appendSpace) {
+            JSON += " ";
+        }
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8") : createParserUsingReader(f, JSON);
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(String.valueOf(Integer.MAX_VALUE), jp.getText());
+        assertEquals(Integer.MAX_VALUE, jp.getIntValue());
+        Number nr = jp.getNumberValue();
+        assertSame(Integer.class, nr.getClass());
+        jp.close();
+    }
+
+    private void _testAllowNaN(boolean useStream) throws Exception
+    {
+        final String JSON = "[ NaN]";
+        JsonFactory f = new JsonFactory();
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS));
+
+        // without enabling, should get an exception
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON);
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            jp.nextToken();
+            fail("Expected exception");
+        } catch (Exception e) {
+            verifyException(e, "non-standard");
+        } finally {
+            jp.close();
+        }
+
+        // we can enable it dynamically (impl detail)
+        f.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+                : createParserUsingReader(f, JSON);
+        
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        double d = jp.getDoubleValue();
+        assertTrue(Double.isNaN(d));
+        assertEquals("NaN", jp.getText());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+
+        // finally, should also work with skipping
+        f.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+                : createParserUsingReader(f, JSON);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+    }
+
+    private void _testAllowInf(boolean useStream) throws Exception
+    {
+        final String JSON = "[ -INF, +INF, +Infinity,-Infinity ]";
+        JsonFactory f = new JsonFactory();
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS));
+
+        // without enabling, should get an exception
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON);
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            jp.nextToken();
+            fail("Expected exception");
+        } catch (Exception e) {
+            verifyException(e, "Non-standard token '-INF'");
+        } finally {
+            jp.close();
+        }
+
+        f.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+                : createParserUsingReader(f, JSON);
+        
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        double d = jp.getDoubleValue();
+        assertEquals("-INF", jp.getText());
+        assertTrue(Double.isInfinite(d));
+        assertTrue(d == Double.NEGATIVE_INFINITY);
+
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        d = jp.getDoubleValue();
+        assertEquals("+INF", jp.getText());
+        assertTrue(Double.isInfinite(d));
+        assertTrue(d == Double.POSITIVE_INFINITY);
+
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        d = jp.getDoubleValue();
+        assertEquals("+Infinity", jp.getText());
+        assertTrue(Double.isInfinite(d));
+        assertTrue(d == Double.POSITIVE_INFINITY);
+        
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        d = jp.getDoubleValue();
+        assertEquals("-Infinity", jp.getText());
+        assertTrue(Double.isInfinite(d));
+        assertTrue(d == Double.NEGATIVE_INFINITY);
+
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+
+        // finally, should also work with skipping
+        f.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
+        jp = useStream ? createParserUsingStream(f, JSON, "UTF-8")
+                : createParserUsingReader(f, JSON);
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        
+        jp.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestParserOverrides.java b/src/test/java/com/fasterxml/jackson/core/json/TestParserOverrides.java
new file mode 100644
index 0000000..a8f1a88
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestParserOverrides.java
@@ -0,0 +1,102 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+
+public class TestParserOverrides extends com.fasterxml.jackson.test.BaseTest
+{
+    /*
+    /**********************************************************
+    /* Wrappers, to test stream and reader-based parsers
+    /**********************************************************
+     */
+
+    public void testTokenAccess() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        _testTokenAccess(jf, false);
+        _testTokenAccess(jf, true);
+    }
+    
+    public void testCurrentName() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+
+        
+        _testCurrentName(jf, false);
+
+        
+        _testCurrentName(jf, true);
+    }
+
+    /*
+    /**********************************************************
+    /* Actual test methods
+    /**********************************************************
+     */
+    
+    public void _testTokenAccess(JsonFactory jf, boolean useStream) throws Exception
+    {
+        final String DOC = "[ ]";
+        JsonParser jp = useStream ?
+                jf.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+                : jf.createParser(DOC);
+        assertNull(jp.getCurrentToken());
+        jp.clearCurrentToken();
+        assertNull(jp.getCurrentToken());
+        assertNull(jp.getEmbeddedObject());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.START_ARRAY, jp.getCurrentToken());
+        jp.clearCurrentToken();
+        assertNull(jp.getCurrentToken());
+        // Also: no codec defined by default
+        try {
+            jp.readValueAsTree();
+            fail("Should get exception without codec");
+        } catch (IllegalStateException e) {
+            verifyException(e, "No ObjectCodec defined");
+        }
+        jp.close();
+    }
+    
+    private void _testCurrentName(JsonFactory jf, boolean useStream) throws Exception
+    {
+        final String DOC = "{\"first\":{\"second\":3, \"third\":false}}";
+        JsonParser jp = useStream ?
+                jf.createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8")))
+                : jf.createParser(new StringReader(DOC));
+        assertNull(jp.getCurrentToken());
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("first", jp.getCurrentName());
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals("first", jp.getCurrentName()); // still the same...
+        jp.overrideCurrentName("foobar");
+        assertEquals("foobar", jp.getCurrentName()); // but not any more!
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("second", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals("second", jp.getCurrentName());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("third", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+        assertEquals("third", jp.getCurrentName());
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        // should retain overrides, too
+        assertEquals("foobar", jp.getCurrentName());
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        jp.clearCurrentToken();
+        assertNull(jp.getCurrentToken());
+        jp.close();
+    }
+    
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Generator.java b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Generator.java
new file mode 100644
index 0000000..21dba10
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Generator.java
@@ -0,0 +1,29 @@
+package com.fasterxml.jackson.core.json;
+
+import java.io.ByteArrayOutputStream;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.json.UTF8JsonGenerator;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+import com.fasterxml.jackson.test.BaseTest;
+
+public class TestUtf8Generator
+    extends BaseTest
+{
+    public void testUtf8Issue462() throws Exception
+    {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        IOContext ioc = new IOContext(new BufferRecycler(), bytes, true);
+        JsonGenerator gen = new UTF8JsonGenerator(ioc, 0, null, bytes);
+        String str = "Natuurlijk is alles gelukt en weer een tevreden klant\uD83D\uDE04";
+        int length = 4000 - 38;
+
+        for (int i = 1; i <= length; ++i) {
+            gen.writeNumber(1);
+        }
+        gen.writeString(str);
+        gen.flush();
+        gen.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Parser.java b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Parser.java
new file mode 100644
index 0000000..7a3d3c3
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/json/TestUtf8Parser.java
@@ -0,0 +1,194 @@
+package com.fasterxml.jackson.core.json;
+
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.io.*;
+import java.util.Random;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestUtf8Parser
+    extends BaseTest
+{
+    final static String[] UTF8_2BYTE_STRINGS = new String[] {
+        /* This may look funny, but UTF8 scanner has fairly
+         * elaborate decoding machinery, and it is indeed
+         * necessary to try out various combinations...
+         */
+        "b", "A\u00D8", "abc", "c3p0",
+        "12345", "......", "Long\u00FAer",
+        "Latin1-fully-\u00BE-develop\u00A8d",
+        "Some very long name, ridiculously long actually to see that buffer expansion works: \u00BF?"
+    };
+
+    final static String[] UTF8_3BYTE_STRINGS = new String[] {
+        "\uC823?", "A\u400F", "1\u1234?",
+        "Ab123\u4034",
+        "Even-longer:\uC023"
+    };
+
+    public void testEmptyName()
+        throws Exception
+    {
+        final String DOC = "{ \"\" : \"\" }";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("", jp.getText());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        jp.close();
+    }
+
+    public void testUtf8Name2Bytes()
+        throws Exception
+    {
+        final String[] NAMES = UTF8_2BYTE_STRINGS;
+
+        for (int i = 0; i < NAMES.length; ++i) {
+            String NAME = NAMES[i];
+            String DOC = "{ \""+NAME+"\" : 0 }";
+            JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            
+            assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+            assertEquals(NAME, jp.getCurrentName());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            // should retain name during value entry, too
+            assertEquals(NAME, jp.getCurrentName());
+            
+            assertToken(JsonToken.END_OBJECT, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    public void testUtf8Name3Bytes() throws Exception
+    {
+        final String[] NAMES = UTF8_3BYTE_STRINGS;
+
+        for (int i = 0; i < NAMES.length; ++i) {
+            String NAME = NAMES[i];
+            String DOC = "{ \""+NAME+"\" : true }";
+
+            JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            
+            assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+            assertEquals(NAME, jp.getCurrentName());
+            assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+            assertEquals(NAME, jp.getCurrentName());
+            
+            assertToken(JsonToken.END_OBJECT, jp.nextToken());
+            
+            jp.close();
+        }
+    }
+
+    // How about tests for Surrogate-Pairs?
+
+    public void testUtf8StringTrivial() throws Exception
+    {
+        String[] VALUES = UTF8_2BYTE_STRINGS;
+        for (int i = 0; i < VALUES.length; ++i) {
+            String VALUE = VALUES[i];
+            String DOC = "[ \""+VALUE+"\" ]";
+            JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            String act = getAndVerifyText(jp);
+            if (act.length() != VALUE.length()) {
+                fail("Failed for value #"+(i+1)+"/"+VALUES.length+": length was "+act.length()+", should be "+VALUE.length());
+            }
+            assertEquals(VALUE, act);
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+            jp.close();
+        }
+
+        VALUES = UTF8_3BYTE_STRINGS;
+        for (int i = 0; i < VALUES.length; ++i) {
+            String VALUE = VALUES[i];
+            String DOC = "[ \""+VALUE+"\" ]";
+            JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(VALUE, getAndVerifyText(jp));
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    public void testUtf8StringValue() throws Exception
+    {
+        Random r = new Random(13);
+        //int LEN = 72000;
+        int LEN = 720;
+        StringBuilder sb = new StringBuilder(LEN + 20);
+        while (sb.length() < LEN) {
+            int c;
+            if (r.nextBoolean()) { // ascii
+                c = 32 + (r.nextInt() & 0x3F);
+                if (c == '"' || c == '\\') {
+                    c = ' ';
+                }
+            } else if (r.nextBoolean()) { // 2-byte
+                c = 160 + (r.nextInt() & 0x3FF);
+            } else if (r.nextBoolean()) { // 3-byte (non-surrogate)
+                c = 8000 + (r.nextInt() & 0x7FFF);
+            } else { // surrogates (2 chars)
+                int value = r.nextInt() & 0x3FFFF; // 20-bit, ~ 1 million
+                sb.append((char) (0xD800 + (value >> 10)));
+                c = (0xDC00 + (value & 0x3FF));
+
+            }
+            sb.append((char) c);
+        }
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream(LEN);
+        OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8");
+        out.write("[\"");
+        String VALUE = sb.toString();
+        out.write(VALUE);
+        out.write("\"]");
+        out.close();
+
+        byte[] data = bout.toByteArray();
+
+        JsonParser jp = new JsonFactory().createParser(new ByteArrayInputStream(data));
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        String act = jp.getText();
+
+        assertEquals(VALUE.length(), act.length());
+        assertEquals(VALUE, act);
+        jp.close();
+    }
+
+    // [JACKSON-889]
+	public void testNextFieldName() throws IOException
+	{
+		JsonFactory f = new JsonFactory();
+		SerializedString id = new SerializedString("id");
+
+		ByteArrayOutputStream os = new ByteArrayOutputStream();
+		os.write('{');
+		for (int i = 0; i < 3994; i++) {
+			os.write(' ');
+		}
+		os.write("\"id\":2".getBytes("UTF-8"));
+		os.write('}');
+
+		JsonParser parser = f.createParser(new ByteArrayInputStream(os.toByteArray()));
+		assertEquals(parser.nextToken(), JsonToken.START_OBJECT);
+		assertTrue(parser.nextFieldName(id));
+		assertEquals(parser.nextToken(), JsonToken.VALUE_NUMBER_INT);
+		assertEquals(parser.nextToken(), JsonToken.END_OBJECT);
+		parser.close();
+	}
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestArrayParsing.java b/src/test/java/com/fasterxml/jackson/core/main/TestArrayParsing.java
new file mode 100644
index 0000000..32fa6fb
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestArrayParsing.java
@@ -0,0 +1,73 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Set of additional unit for verifying array parsing, specifically
+ * edge cases.
+ */
+public class TestArrayParsing
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testValidEmpty()
+        throws Exception
+    {
+        final String DOC = "[   \n  ]";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+        jp.close();
+    }
+
+    public void testInvalidEmptyMissingClose()
+        throws Exception
+    {
+        final String DOC = "[ ";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected a parsing error for missing array close marker");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "expected close marker for ARRAY");
+        }
+    }
+
+    public void testInvalidMissingFieldName()
+        throws Exception
+    {
+        final String DOC = "[  : 3 ] ";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected a parsing error for odd character");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "Unexpected character");
+        }
+    }
+
+    public void testInvalidExtraComma()
+        throws Exception
+    {
+        final String DOC = "[ 24, ] ";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(24, jp.getIntValue());
+
+        try {
+            jp.nextToken();
+            fail("Expected a parsing error for missing array close marker");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "expected a value");
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestCharEscaping.java b/src/test/java/com/fasterxml/jackson/core/main/TestCharEscaping.java
new file mode 100644
index 0000000..d5ab95d
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestCharEscaping.java
@@ -0,0 +1,146 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.CharacterEscapes;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestCharEscaping
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    // for [JACKSON-627]
+    @SuppressWarnings("serial")
+    private final static CharacterEscapes ESC_627 = new CharacterEscapes() {
+        final int[] ascii = CharacterEscapes.standardAsciiEscapesForJSON();
+        {
+          ascii['<'] = CharacterEscapes.ESCAPE_STANDARD;
+          ascii['>'] = CharacterEscapes.ESCAPE_STANDARD;
+        }
+
+        @Override
+        public int[] getEscapeCodesForAscii() {
+          return ascii;
+        }
+
+        @Override
+        public SerializableString getEscapeSequence(int ch) {
+          throw new UnsupportedOperationException("Not implemented for test");
+        }
+      };
+    /*
+    /**********************************************************
+    /* Unit tests
+    /**********************************************************
+      */
+
+    public void testMissingEscaping()
+        throws Exception
+    {
+        // Invalid: control chars, including lf, must be escaped
+        final String DOC = "["
+            +"\"Linefeed: \n.\""
+            +"]";
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            // This may or may not trigger exception
+            JsonToken t = jp.nextToken();
+            assertToken(JsonToken.VALUE_STRING, t);
+            // and if not, should get it here:
+            jp.getText();
+            fail("Expected an exception for un-escaped linefeed in string value");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "has to be escaped");
+        }
+    }
+
+    public void testSimpleEscaping()
+        throws Exception
+    {
+        String DOC = "["
+            +"\"LF=\\n\""
+            +"]";
+
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("LF=\n", jp.getText());
+        jp.close();
+
+
+        /* Note: must split Strings, so that javac won't try to handle
+         * escape and inline null char
+         */
+        DOC = "[\"NULL:\\u0000!\"]";
+
+        jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("NULL:\0!", jp.getText());
+
+        // Then just a single char escaping
+        jp = createParserUsingReader("[\"\\u0123\"]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("\u0123", jp.getText());
+
+        // And then double sequence
+        jp = createParserUsingReader("[\"\\u0041\\u0043\"]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("AC", jp.getText());
+    }
+
+    public void testInvalid()
+        throws Exception
+    {
+        // 2-char sequences not allowed:
+        String DOC = "[\"\\u41=A\"]";
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            jp.nextToken();
+            jp.getText();
+            fail("Expected an exception for unclosed ARRAY");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "for character escape");
+        }
+    }
+
+    /**
+     * Test to verify that decoder does not allow 8-digit escapes
+     * (non-BMP characters must be escaped using two 4-digit sequences)
+     */
+    public void test8DigitSequence()
+        throws Exception
+    {
+        String DOC = "[\"\\u00411234\"]";
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("A1234", jp.getText());
+    }
+
+    // for [JACKSON-627]
+    public void testWriteLongCustomEscapes() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        jf.setCharacterEscapes(ESC_627); // must set to trigger bug
+        StringBuilder longString = new StringBuilder();
+        while (longString.length() < 2000) {
+          longString.append("\u65e5\u672c\u8a9e");
+        }
+
+        StringWriter writer = new StringWriter();
+        // must call #createGenerator(Writer), #createGenerator(OutputStream) doesn't trigger bug
+        JsonGenerator jgen = jf.createGenerator(writer);
+        jgen.setHighestNonEscapedChar(127); // must set to trigger bug
+        jgen.writeString(longString.toString());
+      }      
+
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestComments.java b/src/test/java/com/fasterxml/jackson/core/main/TestComments.java
new file mode 100644
index 0000000..3321f6c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestComments.java
@@ -0,0 +1,115 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Unit tests for verifying that support for (non-standard) comments
+ * works as expected.
+ */
+public class TestComments
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    final static String DOC_WITH_SLASHSTAR_COMMENT =
+        "[ /* comment:\n ends here */ 1 /* one more ok to have \"unquoted\"  */ ]"
+        ;
+
+    final static String DOC_WITH_SLASHSLASH_COMMENT =
+        "[ // comment...\n 1 \r  // one more, not array: []   \n ]"
+        ;
+
+    /*
+    /**********************************************************
+    /* Test method wrappers
+    /**********************************************************
+     */
+    
+    /**
+     * Unit test for verifying that by default comments are not
+     * recognized.
+     */
+    public void testDefaultSettings()
+        throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        assertFalse(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+        JsonParser jp = jf.createParser(new StringReader("[ 1 ]"));
+        assertFalse(jp.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+    }
+
+    public void testCommentsDisabled()
+        throws Exception
+    {
+        _testDisabled(DOC_WITH_SLASHSTAR_COMMENT, false);
+        _testDisabled(DOC_WITH_SLASHSLASH_COMMENT, false);
+        _testDisabled(DOC_WITH_SLASHSTAR_COMMENT, true);
+        _testDisabled(DOC_WITH_SLASHSLASH_COMMENT, true);
+    }
+
+    public void testCommentsEnabled()
+        throws Exception
+    {
+        _testEnabled(DOC_WITH_SLASHSTAR_COMMENT, false);
+        _testEnabled(DOC_WITH_SLASHSLASH_COMMENT, false);
+        _testEnabled(DOC_WITH_SLASHSTAR_COMMENT, true);
+        _testEnabled(DOC_WITH_SLASHSLASH_COMMENT, true);
+    }
+
+    // for [JACKSON-779]
+    public void testCommentsWithUTF8() throws Exception
+    {
+        final String JSON = "/* \u00a9 2099 Yoyodyne Inc. */\n [ \"bar? \u00a9\" ]\n";
+        _testWithUTF8Chars(JSON, false);
+        _testWithUTF8Chars(JSON, true);
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private void _testWithUTF8Chars(String doc, boolean useStream) throws IOException
+    {
+        // should basically just stream through
+        JsonParser jp = _createParser(doc, useStream, true);
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+        jp.close();
+    }
+    
+    private void _testDisabled(String doc, boolean useStream) throws IOException
+    {
+        JsonParser jp = _createParser(doc, useStream, false);
+        try {
+            jp.nextToken();
+            fail("Expected exception for unrecognized comment");
+        } catch (JsonParseException je) {
+            // Should have something denoting that user may want to enable 'ALLOW_COMMENTS'
+            verifyException(je, "ALLOW_COMMENTS");
+        }
+    }
+
+    private void _testEnabled(String doc, boolean useStream)
+        throws IOException
+    {
+        JsonParser jp = _createParser(doc, useStream, true);
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(1, jp.getIntValue());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+    }
+
+    private JsonParser _createParser(String doc, boolean useStream, boolean enabled)
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        jf.configure(JsonParser.Feature.ALLOW_COMMENTS, enabled);
+        JsonParser jp = useStream ?
+            jf.createParser(doc.getBytes("UTF-8"))
+            : jf.createParser(doc);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        return jp;
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestExceptions.java b/src/test/java/com/fasterxml/jackson/core/main/TestExceptions.java
new file mode 100644
index 0000000..253c4b2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestExceptions.java
@@ -0,0 +1,17 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+public class TestExceptions extends BaseTest
+{
+    // For [Issue#10]
+    public void testOriginalMesssage()
+    {
+        JsonProcessingException exc = new JsonParseException("Foobar", JsonLocation.NA);
+        String msg = exc.getMessage();
+        String orig = exc.getOriginalMessage();
+        assertEquals("Foobar", orig);
+        assertTrue(msg.length() > orig.length());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorArray.java b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorArray.java
new file mode 100644
index 0000000..10dd461
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorArray.java
@@ -0,0 +1,109 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.*;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests for verifying that the Array write methods
+ * of {@link JsonGenerator} work as expected.
+ */
+public class TestGeneratorArray
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testEmptyArrayWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+
+        JsonStreamContext ctxt = gen.getOutputContext();
+        assertTrue(ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertFalse(ctxt.inObject());
+        assertEquals(0, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        gen.writeStartArray();
+
+        ctxt = gen.getOutputContext();
+        assertFalse(ctxt.inRoot());
+        assertTrue(ctxt.inArray());
+        assertFalse(ctxt.inObject());
+        assertEquals(0, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        gen.writeEndArray();
+
+        ctxt = gen.getOutputContext();
+        assertTrue("Should be in root, was "+ctxt.getTypeDesc(), ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertFalse(ctxt.inObject());
+        assertEquals(1, ctxt.getEntryCount());
+        // Index won't yet move
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+
+        // Ok, then array with nested empty array
+        sw = new StringWriter();
+        gen = new JsonFactory().createGenerator(sw);
+        gen.writeStartArray();
+        gen.writeStartArray();
+        gen.writeEndArray();
+        gen.writeEndArray();
+        gen.close();
+        docStr = sw.toString();
+        jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    public void testInvalidArrayWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.writeStartArray();
+        // Mismatch:
+        try {
+            gen.writeEndObject();
+            fail("Expected an exception for mismatched array/object write");
+        } catch (JsonGenerationException e) {
+            verifyException(e, "Current context not an object");
+        }
+    }
+
+    public void testSimpleArrayWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.writeStartArray();
+        gen.writeNumber(13);
+        gen.writeBoolean(true);
+        gen.writeString("foobar");
+        gen.writeEndArray();
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(13, jp.getIntValue());
+        assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("foobar", jp.getText());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorClosing.java b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorClosing.java
new file mode 100644
index 0000000..67a5249
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorClosing.java
@@ -0,0 +1,235 @@
+package com.fasterxml.jackson.core.main;
+
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests that verify aspect of closing a
+ * {@link JsonGenerator} instance. This includes both closing
+ * of physical resources (target), and logical content
+ * (json content tree)
+ *<p>
+ * Specifically, features
+ * <code>JsonGenerator.Feature#AUTO_CLOSE_TARGET</code>
+ * and
+ * <code>JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT</code>
+ * are tested.
+ */
+public class TestGeneratorClosing
+    extends BaseTest
+{
+    /*
+    /**********************************************************
+    /* Helper classes
+    /**********************************************************
+     */
+
+    final static class MyWriter extends StringWriter
+    {
+        boolean mIsClosed = false;
+
+        public MyWriter() { }
+
+        @Override
+        public void close() throws IOException {
+            mIsClosed = true;
+            super.close();
+        }
+        public boolean isClosed() { return mIsClosed; }
+    }
+
+    final static class MyStream extends ByteArrayOutputStream
+    {
+        boolean mIsClosed = false;
+
+        public MyStream() { }
+
+        @Override
+        public void close() throws IOException {
+            mIsClosed = true;
+            super.close();
+        }
+        public boolean isClosed() { return mIsClosed; }
+    }
+
+    static class MyBytes extends ByteArrayOutputStream
+    {
+        public int flushed = 0;
+
+        @Override
+        public void flush() throws IOException
+        {
+            ++flushed;
+            super.flush();
+        }
+    }
+
+    static class MyChars extends StringWriter
+    {
+        public int flushed = 0;
+
+        @Override
+        public void flush()
+        {
+            ++flushed;
+            super.flush();
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* Unit tests
+    /**********************************************************
+     */
+    
+    /**
+     * This unit test checks the default behaviour; with no auto-close, no
+     * automatic closing should occur, nor explicit one unless specific
+     * forcing method is used.
+     */
+    public void testNoAutoCloseGenerator()
+        throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+
+        // Check the default settings
+        assertTrue(f.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET));
+        // then change
+        f.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
+        assertFalse(f.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET));
+        MyWriter output = new MyWriter();
+        JsonGenerator jg = f.createGenerator(output);
+
+        // shouldn't be closed to begin with...
+        assertFalse(output.isClosed());
+        jg.writeNumber(39);
+        // regular close won't close it either:
+        jg.close();
+        assertFalse(output.isClosed());
+    }
+
+    public void testCloseGenerator()
+        throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        f.enable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
+        MyWriter output = new MyWriter();
+        JsonGenerator jg = f.createGenerator(output);
+
+        // shouldn't be closed to begin with...
+        assertFalse(output.isClosed());
+        jg.writeNumber(39);
+        // but close() should now close the writer
+        jg.close();
+        assertTrue(output.isClosed());
+    }
+
+    public void testNoAutoCloseOutputStream()
+        throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        f.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
+        MyStream output = new MyStream();
+        JsonGenerator jg = f.createGenerator(output, JsonEncoding.UTF8);
+
+        assertFalse(output.isClosed());
+        jg.writeNumber(39);
+        jg.close();
+        assertFalse(output.isClosed());
+    }
+
+    public void testAutoCloseArraysAndObjects()
+        throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        // let's verify default setting, first:
+        assertTrue(f.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT));
+        StringWriter sw = new StringWriter();
+
+        // First, test arrays:
+        JsonGenerator jg = f.createGenerator(sw);
+        jg.writeStartArray();
+        jg.close();
+        assertEquals("[]", sw.toString());
+
+        // Then objects
+        sw = new StringWriter();
+        jg = f.createGenerator(sw);
+        jg.writeStartObject();
+        jg.close();
+        assertEquals("{}", sw.toString());
+    }
+
+    public void testNoAutoCloseArraysAndObjects()
+        throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        f.disable(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT);
+        StringWriter sw = new StringWriter();
+        JsonGenerator jg = f.createGenerator(sw);
+        jg.writeStartArray();
+        jg.close();
+        // shouldn't close
+        assertEquals("[", sw.toString());
+
+        // Then objects
+        sw = new StringWriter();
+        jg = f.createGenerator(sw);
+        jg.writeStartObject();
+        jg.close();
+        assertEquals("{", sw.toString());
+    }
+
+    // [JACKSON-401]
+    public void testAutoFlushOrNot() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        assertTrue(f.isEnabled(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM));
+        MyChars sw = new MyChars();
+        JsonGenerator jg = f.createGenerator(sw);
+        jg.writeStartArray();
+        jg.writeEndArray();
+        assertEquals(0, sw.flushed);
+        jg.flush();
+        assertEquals(1, sw.flushed);
+        jg.close();
+        
+        // ditto with stream
+        MyBytes bytes = new MyBytes();
+        jg = f.createGenerator(bytes, JsonEncoding.UTF8);
+        jg.writeStartArray();
+        jg.writeEndArray();
+        assertEquals(0, bytes.flushed);
+        jg.flush();
+        assertEquals(1, bytes.flushed);
+        assertEquals(2, bytes.toByteArray().length);
+        jg.close();
+
+        // then disable and we should not see flushing again...
+        f.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM);
+        // first with a Writer
+        sw = new MyChars();
+        jg = f.createGenerator(sw);
+        jg.writeStartArray();
+        jg.writeEndArray();
+        assertEquals(0, sw.flushed);
+        jg.flush();
+        assertEquals(0, sw.flushed);
+        jg.close();
+        assertEquals("[]", sw.toString());
+
+        // and then with OutputStream
+        bytes = new MyBytes();
+        jg = f.createGenerator(bytes, JsonEncoding.UTF8);
+        jg.writeStartArray();
+        jg.writeEndArray();
+        assertEquals(0, bytes.flushed);
+        jg.flush();
+        assertEquals(0, bytes.flushed);
+        jg.close();
+        assertEquals(2, bytes.toByteArray().length);
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorCopy.java b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorCopy.java
new file mode 100644
index 0000000..90d351d
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorCopy.java
@@ -0,0 +1,82 @@
+package com.fasterxml.jackson.core.main;
+
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests for verifying that copy-through methods
+ * of {@link JsonGenerator} work as expected.
+ */
+public class TestGeneratorCopy
+    extends BaseTest
+{
+    public void testCopyRootTokens()
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        final String DOC = "\"text\\non two lines\" true false 2.0";
+        JsonParser jp = jf.createParser(new StringReader(DOC));
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = jf.createGenerator(sw);
+
+        JsonToken t;
+
+        while ((t = jp.nextToken()) != null) {
+            gen.copyCurrentEvent(jp);
+            // should not change parser state:
+            assertToken(t, jp.getCurrentToken());
+        }
+        jp.close();
+        gen.close();
+
+        assertEquals("\"text\\non two lines\" true false 2.0", sw.toString());
+    }
+
+    public void testCopyArrayTokens()
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        final String DOC = "123 [ 1, null, [ false ] ]";
+        JsonParser jp = jf.createParser(new StringReader(DOC));
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = jf.createGenerator(sw);
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        gen.copyCurrentEvent(jp);
+        // should not change parser state:
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.getCurrentToken());
+        assertEquals(123, jp.getIntValue());
+
+        // And then let's copy the array
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        gen.copyCurrentStructure(jp);
+        // which will advance parser to matching close Array
+        assertToken(JsonToken.END_ARRAY, jp.getCurrentToken());
+        jp.close();
+        gen.close();
+
+        assertEquals("123 [1,null,[false]]", sw.toString());
+    }
+
+    public void testCopyObjectTokens()
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        final String DOC = "{ \"a\":1, \"b\":[{ \"c\" : null }] }";
+        JsonParser jp = jf.createParser(new StringReader(DOC));
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = jf.createGenerator(sw);
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        gen.copyCurrentStructure(jp);
+        // which will advance parser to matching end Object
+        assertToken(JsonToken.END_OBJECT, jp.getCurrentToken());
+        jp.close();
+        gen.close();
+
+        assertEquals("{\"a\":1,\"b\":[{\"c\":null}]}", sw.toString());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorMisc.java b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorMisc.java
new file mode 100644
index 0000000..7420c04
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorMisc.java
@@ -0,0 +1,300 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.concurrent.atomic.*;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Set of basic unit tests for verifying basic generator
+ * features.
+ */
+public class TestGeneratorMisc
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    /*
+    /**********************************************************
+    /* Tests for closing, status
+    /**********************************************************
+     */
+
+    public void testIsClosed()
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        for (int i = 0; i < 2; ++i) {
+            boolean stream = ((i & 1) == 0);
+            JsonGenerator jg = stream ?
+                jf.createGenerator(new StringWriter())
+                : jf.createGenerator(new ByteArrayOutputStream(), JsonEncoding.UTF8)
+                ;
+            assertFalse(jg.isClosed());
+            jg.writeStartArray();
+            jg.writeNumber(-1);
+            jg.writeEndArray();
+            assertFalse(jg.isClosed());
+            jg.close();
+            assertTrue(jg.isClosed());
+            jg.close();
+            assertTrue(jg.isClosed());
+        }
+    }
+
+    // Also, "very simple" objects are supported even without Codec:
+    public void testSimpleWriteObject() throws IOException
+    {
+        // note: NOT mapping factory, for this test
+        JsonFactory jf = new JsonFactory();
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = jf.createGenerator(sw);
+        gen.writeStartArray();
+
+        // simple wrappers first
+        gen.writeObject(1);
+        gen.writeObject((short) -2);
+        gen.writeObject((long) 3);
+        gen.writeObject((byte) -4);
+        gen.writeObject(0.25);
+        gen.writeObject(-0.125f);
+        gen.writeObject(Boolean.TRUE);
+        gen.close();
+        String act = sw.toString().trim();
+        assertEquals("[1,-2,3,-4,0.25,-0.125,true]", act);
+        
+        // then other basic types
+        sw = new StringWriter();
+        gen = jf.createGenerator(sw);
+        gen.writeStartArray();
+        gen.writeObject(BigInteger.valueOf(1234));
+        gen.writeObject(new BigDecimal(0.5));
+        gen.writeEndArray();
+        gen.close();
+        act = sw.toString().trim();
+        assertEquals("[1234,0.5]", act);
+
+        // then Atomic types
+        sw = new StringWriter();
+        gen = jf.createGenerator(sw);
+        gen.writeStartArray();
+        gen.writeObject(new AtomicBoolean(false));
+        gen.writeObject(new AtomicInteger(13));
+        gen.writeObject(new AtomicLong(-127L));
+        gen.writeEndArray();
+        gen.close();
+        act = sw.toString().trim();
+        assertEquals("[false,13,-127]", act);
+    }
+    
+    /*
+    /**********************************************************
+    /* Tests for raw output
+    /**********************************************************
+     */
+
+    public void testRaw() throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = jf.createGenerator(sw);
+        gen.writeStartArray();
+        gen.writeRaw("-123, true");
+        gen.writeRaw(", \"x\"  ");
+        gen.writeEndArray();
+        gen.close();
+
+                
+        JsonParser jp = createParserUsingReader(sw.toString());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(-123, jp.getIntValue());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("x", jp.getText());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+    }
+
+    public void testRawValue() throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = jf.createGenerator(sw);
+        gen.writeStartArray();
+        gen.writeRawValue("7");
+        gen.writeRawValue("[ null ]");
+        gen.writeRawValue("false");
+        gen.writeEndArray();
+        gen.close();
+
+        JsonParser jp = createParserUsingReader(sw.toString());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(7, jp.getIntValue());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+    }
+    
+    /*
+    /**********************************************************
+    /* Tests for binary data
+    /**********************************************************
+     */
+
+    /**
+     * This is really inadequate test, all in all, but should serve
+     * as some kind of sanity check. Reader-side should more thoroughly
+     * test things, as it does need writers to construct the data first.
+     */
+    public void testBinaryWrite() throws Exception
+    {
+        _testBinaryWrite(false);
+        _testBinaryWrite(true);
+    }
+
+    private void _testBinaryWrite(boolean useCharBased) throws Exception
+    {
+        /* The usual sample input string, from Thomas Hobbes's "Leviathan"
+         * (via Wikipedia)
+         */
+        final String INPUT = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
+        final byte[] INPUT_BYTES = INPUT.getBytes("US-ASCII");
+        // as per MIME variant, result minus lfs =
+        final String OUTPUT =
+ "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"
++"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"
++"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu"
++"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo"
++"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="
+            ;
+
+        /* Let's only test the standard base64 variant; but write
+         * values in root, array and object contexts.
+         */
+        Base64Variant b64v = Base64Variants.getDefaultVariant();
+        JsonFactory jf = new JsonFactory();
+
+        for (int i = 0; i < 3; ++i) {
+            JsonGenerator gen;
+            ByteArrayOutputStream bout = new ByteArrayOutputStream(200);
+            if (useCharBased) {
+                gen = jf.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
+            } else {
+                gen = jf.createGenerator(bout, JsonEncoding.UTF8);
+            }
+
+            switch (i) {
+            case 0: // root
+                gen.writeBinary(b64v, INPUT_BYTES, 0, INPUT_BYTES.length);
+                break;
+            case 1: // array
+                gen.writeStartArray();
+                gen.writeBinary(b64v, INPUT_BYTES, 0, INPUT_BYTES.length);
+                gen.writeEndArray();
+                break;
+            default: // object
+                gen.writeStartObject();
+                gen.writeFieldName("field");
+                gen.writeBinary(b64v, INPUT_BYTES, 0, INPUT_BYTES.length);
+                gen.writeEndObject();
+                break;
+            }
+            gen.close();
+
+            JsonParser jp = jf.createParser(new ByteArrayInputStream(bout.toByteArray()));
+            
+            // Need to skip other events before binary data:
+            switch (i) {
+            case 0:
+                break;
+            case 1:
+                assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+                break;
+            default:
+                assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+                assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+                break;
+            }
+            assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+            String actualValue = jp.getText();
+            jp.close();
+            assertEquals(OUTPUT, actualValue);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Tests for object writing
+    /**********************************************************
+     */
+
+    /**
+     * Unit test that tries to trigger buffer-boundary conditions
+     */
+    public void testLongerObjects() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        for (int i = 0; i < 2; ++i) {
+            boolean useChars = (i == 0);
+            JsonGenerator jgen;
+            ByteArrayOutputStream bout = new ByteArrayOutputStream(200);
+            if (useChars) {
+                jgen = jf.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
+            } else {
+                jgen = jf.createGenerator(bout, JsonEncoding.UTF8);
+            }
+
+            jgen.writeStartObject();
+
+            for (int rounds = 0; rounds < 1500; ++rounds) {
+                for (int letter = 'a'; letter <= 'z'; ++letter) {
+                    for (int index = 0; index < 20; ++index) {
+                        String name;
+                        if (letter > 'f') {
+                            name = "X"+letter+index;
+                        } else if (letter > 'p') {
+                            name = ""+letter+index;
+                        } else {
+                            name = "__"+index+letter;
+                        }
+                        jgen.writeFieldName(name);
+                        jgen.writeNumber(index-1);
+                    }
+                    jgen.writeRaw('\n');
+                }
+            }
+            jgen.writeEndObject();
+            jgen.close();
+
+            byte[] json = bout.toByteArray();
+            JsonParser jp = jf.createParser(json);
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            for (int rounds = 0; rounds < 1500; ++rounds) {
+            for (int letter = 'a'; letter <= 'z'; ++letter) {
+                for (int index = 0; index < 20; ++index) {
+                    assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+                    String name;
+                    if (letter > 'f') {
+                        name = "X"+letter+index;
+                    } else if (letter > 'p') {
+                        name = ""+letter+index;
+                    } else {
+                        name = "__"+index+letter;
+                    }
+                    assertEquals(name, jp.getCurrentName());
+                    assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+                    assertEquals(index-1, jp.getIntValue());
+                }
+            }
+            }
+            assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorObject.java b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorObject.java
new file mode 100644
index 0000000..82ed963
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorObject.java
@@ -0,0 +1,208 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.io.*;
+import java.math.BigDecimal;
+
+/**
+ * Set of basic unit tests for verifying that the Object write methods
+ * of {@link JsonGenerator} work as expected.
+ */
+public class TestGeneratorObject
+    extends BaseTest
+{
+    public void testEmptyObjectWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+
+        JsonStreamContext ctxt = gen.getOutputContext();
+        assertTrue(ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertFalse(ctxt.inObject());
+        assertEquals(0, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        gen.writeStartObject();
+
+        ctxt = gen.getOutputContext();
+        assertFalse(ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertTrue(ctxt.inObject());
+        assertEquals(0, ctxt.getEntryCount());
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        gen.writeEndObject();
+
+        ctxt = gen.getOutputContext();
+        assertTrue(ctxt.inRoot());
+        assertFalse(ctxt.inArray());
+        assertFalse(ctxt.inObject());
+        assertEquals(1, ctxt.getEntryCount());
+        // Index won't yet move
+        assertEquals(0, ctxt.getCurrentIndex());
+
+        gen.close();
+
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+    }
+
+    public void testInvalidObjectWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.writeStartObject();
+        // Mismatch:
+        try {
+            gen.writeEndArray();
+            fail("Expected an exception for mismatched array/object write");
+        } catch (JsonGenerationException e) {
+            verifyException(e, "Current context not an array");
+        }
+    }
+
+    public void testSimpleObjectWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.writeStartObject();
+        gen.writeFieldName("first");
+        gen.writeNumber(-901);
+        gen.writeFieldName("sec");
+        gen.writeBoolean(false);
+        gen.writeFieldName("3rd!"); // json field names are just strings, not ids with restrictions
+        gen.writeString("yee-haw");
+        gen.writeEndObject();
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("first", jp.getText());
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(-901, jp.getIntValue());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("sec", jp.getText());
+        assertEquals(JsonToken.VALUE_FALSE, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("3rd!", jp.getText());
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("yee-haw", jp.getText());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    /**
+     * Methods to test functionality added for [JACKSON-26]
+     */
+    public void testConvenienceMethods()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.writeStartObject();
+
+        final BigDecimal dec = new BigDecimal("0.1");
+        final String TEXT = "\"some\nString!\"";
+
+        gen.writeNullField("null");
+        gen.writeBooleanField("bt", true);
+        gen.writeBooleanField("bf", false);
+        gen.writeNumberField("int", -1289);
+        gen.writeNumberField("dec", dec);
+
+        gen.writeObjectFieldStart("ob");
+        gen.writeStringField("str", TEXT);
+        gen.writeEndObject();
+
+        gen.writeArrayFieldStart("arr");
+        gen.writeEndArray();
+
+        gen.writeEndObject();
+        gen.close();
+
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("null", jp.getText());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("bt", jp.getText());
+        assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("bf", jp.getText());
+        assertEquals(JsonToken.VALUE_FALSE, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("int", jp.getText());
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("dec", jp.getText());
+        assertEquals(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("ob", jp.getText());
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("str", jp.getText());
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals(TEXT, getAndVerifyText(jp));
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("arr", jp.getText());
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    /**
+     * Tests to cover [JACKSON-164]
+     */
+    public void testConvenienceMethodsWithNulls()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.writeStartObject();
+
+        gen.writeStringField("str", null);
+        gen.writeNumberField("num", null);
+        gen.writeObjectField("obj", null);
+
+        gen.writeEndObject();
+        gen.close();
+
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("str", jp.getCurrentName());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("num", jp.getCurrentName());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("obj", jp.getCurrentName());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorWithSerializedString.java b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorWithSerializedString.java
new file mode 100644
index 0000000..eea3cd7
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestGeneratorWithSerializedString.java
@@ -0,0 +1,95 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+public class TestGeneratorWithSerializedString
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    final static String NAME_WITH_QUOTES = "\"name\"";
+    final static String NAME_WITH_LATIN1 = "P\u00f6ll\u00f6";
+
+    private final SerializedString quotedName = new SerializedString(NAME_WITH_QUOTES);
+    private final SerializedString latin1Name = new SerializedString(NAME_WITH_LATIN1);
+    
+    public void testSimple() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+
+        // First using char-backed generator
+        StringWriter sw = new StringWriter();
+        JsonGenerator jgen = jf.createGenerator(sw);
+        _writeSimple(jgen);
+        jgen.close();
+        String json = sw.toString();
+        _verifySimple(jf.createParser(json));
+
+        // then using UTF-8
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        jgen = jf.createGenerator(out, JsonEncoding.UTF8);
+        _writeSimple(jgen);
+        jgen.close();
+        byte[] jsonB = out.toByteArray();
+        _verifySimple(jf.createParser(jsonB));
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+    
+    private void _writeSimple(JsonGenerator jgen) throws Exception
+    {
+        // Let's just write array of 2 objects
+        jgen.writeStartArray();
+
+        jgen.writeStartObject();
+        jgen.writeFieldName(quotedName);
+        jgen.writeString("a");
+        jgen.writeFieldName(latin1Name);
+        jgen.writeString("b");
+        jgen.writeEndObject();
+
+        jgen.writeStartObject();
+        jgen.writeFieldName(latin1Name);
+        jgen.writeString("c");
+        jgen.writeFieldName(quotedName);
+        jgen.writeString("d");
+        jgen.writeEndObject();
+        
+        jgen.writeEndArray();
+    }
+
+    private void _verifySimple(JsonParser jp) throws Exception
+    {
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(NAME_WITH_QUOTES, jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("a", jp.getText());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(NAME_WITH_LATIN1, jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("b", jp.getText());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(NAME_WITH_LATIN1, jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("c", jp.getText());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(NAME_WITH_QUOTES, jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("d", jp.getText());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestJsonFactory.java b/src/test/java/com/fasterxml/jackson/core/main/TestJsonFactory.java
new file mode 100644
index 0000000..34f3b0b
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestJsonFactory.java
@@ -0,0 +1,63 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestJsonFactory
+    extends com.fasterxml.jackson.test.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();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestJsonGenerator.java b/src/test/java/com/fasterxml/jackson/core/main/TestJsonGenerator.java
new file mode 100644
index 0000000..46ae85b
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestJsonGenerator.java
@@ -0,0 +1,228 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.*;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests for verifying that the basic generator
+ * functionality works as expected.
+ */
+public class TestJsonGenerator
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    // // // 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) {
+                for (int strIx = 0; strIx < inputStrings.length; ++strIx) {
+                    String input = inputStrings[strIx];
+                    JsonGenerator gen;
+                    ByteArrayOutputStream bout = new ByteArrayOutputStream();
+                    if (useReader != 0) {
+                        gen = jf.createGenerator(new OutputStreamWriter(bout, "UTF-8"));
+                    } else {
+                        gen = jf.createGenerator(bout, JsonEncoding.UTF8);
+                    }
+                    if (writeString > 0) {
+                        gen.writeString(input);
+                    } else {
+                        int len = input.length();
+                        char[] buffer = new char[len + 20];
+                        // Let's use non-zero base offset too...
+                        input.getChars(0, len, buffer, strIx);
+                        gen.writeString(buffer, strIx, len);
+                    }
+                    gen.flush();
+                    gen.close();
+                    JsonParser jp = jf.createParser(new ByteArrayInputStream(bout.toByteArray()));
+                
+                    JsonToken t = jp.nextToken();
+                    assertNotNull("Document \""+bout.toString("UTF-8")+"\" yielded no tokens", t);
+                    assertEquals(JsonToken.VALUE_STRING, t);
+                    assertEquals(input, jp.getText());
+                    assertEquals(null, jp.nextToken());
+                    jp.close();
+                }
+            }
+        }
+    }
+
+    public void testIntWrite()
+        throws Exception
+    {
+        doTestIntWrite(false);
+        doTestIntWrite(true);
+    }
+
+    public void testLongWrite()
+        throws Exception
+    {
+        doTestLongWrite(false);
+        doTestLongWrite(true);
+    }
+
+    public void testBooleanWrite()
+        throws Exception
+    {
+        for (int i = 0; i < 4; ++i) {
+            boolean state = (i & 1) == 0;
+            boolean pad = (i & 2) == 0;
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            gen.writeBoolean(state);
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            String exp = Boolean.valueOf(state).toString();
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE, t);
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    public void testNullWrite()
+        throws Exception
+    {
+        for (int i = 0; i < 2; ++i) {
+            boolean pad = (i & 1) == 0;
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            gen.writeNull();
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            String exp = "null";
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(JsonToken.VALUE_NULL, t);
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+    
+    // // Then root-level output testing
+
+     public void testRootIntsWrite()
+         throws Exception
+     {
+         StringWriter sw = new StringWriter();
+         JsonGenerator gen = new JsonFactory().createGenerator(sw);
+         gen.writeNumber(1);
+         gen.writeNumber(2);
+         gen.writeNumber(-13);
+         gen.close();
+
+         String docStr = sw.toString();
+
+         JsonParser jp = createParserUsingReader(docStr);
+         assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+         assertEquals(1, jp.getIntValue());
+         assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+         assertEquals(2, jp.getIntValue());
+         assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+         assertEquals(-13, jp.getIntValue());
+         jp.close();
+     }
+    
+    // Convenience methods
+    
+    public void testFieldValueWrites()
+         throws Exception
+     {
+         StringWriter sw = new StringWriter();
+         JsonGenerator gen = new JsonFactory().createGenerator(sw);
+         gen.writeStartObject();
+         gen.writeNumberField("long", 3L);
+         gen.writeNumberField("double", 0.25);
+         gen.writeNumberField("float", -0.25f);
+         gen.writeEndObject();
+         gen.close();
+
+         assertEquals("{\"long\":3,\"double\":0.25,\"float\":-0.25}", sw.toString().trim());
+     }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+    
+    private void doTestIntWrite(boolean pad)
+        throws Exception
+    {
+        int[] VALUES = new int[] {
+            0, 1, -9, 32, -32, 57, 13240, -9999, Integer.MAX_VALUE, Integer.MAX_VALUE
+        };
+        for (int i = 0; i < VALUES.length; ++i) {
+            int VALUE = VALUES[i];
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            gen.writeNumber(VALUE);
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            assertNotNull("Document \""+docStr+"\" yielded no tokens", t);
+            // Number are always available as lexical representation too
+            String exp = ""+VALUE;
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(JsonToken.VALUE_NUMBER_INT, t);
+            assertEquals(VALUE, jp.getIntValue());
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    private void doTestLongWrite(boolean pad)
+        throws Exception
+    {
+        long[] VALUES = new long[] {
+            0L, 1L, -1L, -12005002294L, Long.MIN_VALUE, Long.MAX_VALUE
+        };
+        for (int i = 0; i < VALUES.length; ++i) {
+            long VALUE = VALUES[i];
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            gen.writeNumber(VALUE);
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            assertNotNull("Document \""+docStr+"\" yielded no tokens", t);
+            String exp = ""+VALUE;
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(JsonToken.VALUE_NUMBER_INT, t);
+            assertEquals(VALUE, jp.getLongValue());
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestJsonGeneratorFeatures.java b/src/test/java/com/fasterxml/jackson/core/main/TestJsonGeneratorFeatures.java
new file mode 100644
index 0000000..69e256b
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestJsonGeneratorFeatures.java
@@ -0,0 +1,131 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Set of basic unit tests for verifying that the basic generator
+ * functionality works as expected.
+ */
+public class TestJsonGeneratorFeatures
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testConfigDefaults() throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        JsonGenerator jg = jf.createGenerator(new StringWriter());
+        assertFalse(jg.isEnabled(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS));
+    }
+
+    public void testFieldNameQuoting() throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        // by default, quoting should be enabled
+        _testFieldNameQuoting(jf, true);
+        // can disable it
+        jf.disable(JsonGenerator.Feature.QUOTE_FIELD_NAMES);
+        _testFieldNameQuoting(jf, false);
+        // and (re)enable:
+        jf.enable(JsonGenerator.Feature.QUOTE_FIELD_NAMES);
+        _testFieldNameQuoting(jf, true);
+    }
+
+    public void testNonNumericQuoting()
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        // by default, quoting should be enabled
+        _testNonNumericQuoting(jf, true);
+        // can disable it
+        jf.disable(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS);
+        _testNonNumericQuoting(jf, false);
+        // and (re)enable:
+        jf.enable(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS);
+        _testNonNumericQuoting(jf, true);
+    }
+
+    /**
+     * Testing for [JACKSON-176], ability to force serializing numbers
+     * as JSON Strings.
+     */
+    public void testNumbersAsJSONStrings() throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        // by default should output numbers as-is:
+        assertEquals("[1,2,1.25,2.25,3001,0.5,-1]", _writeNumbers(jf));        
+
+        // but if overridden, quotes as Strings
+        jf.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, true);
+        assertEquals("[\"1\",\"2\",\"1.25\",\"2.25\",\"3001\",\"0.5\",\"-1\"]",
+                     _writeNumbers(jf));
+    }
+
+    private String _writeNumbers(JsonFactory jf) throws IOException
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator jg = jf.createGenerator(sw);
+    
+        jg.writeStartArray();
+        jg.writeNumber(1);
+        jg.writeNumber(2L);
+        jg.writeNumber(1.25);
+        jg.writeNumber(2.25f);
+        jg.writeNumber(BigInteger.valueOf(3001));
+        jg.writeNumber(BigDecimal.valueOf(0.5));
+        jg.writeNumber("-1");
+        jg.writeEndArray();
+        jg.close();
+
+        return sw.toString();
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private void _testFieldNameQuoting(JsonFactory jf, boolean quoted)
+        throws IOException
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator jg = jf.createGenerator(sw);
+        jg.writeStartObject();
+        jg.writeFieldName("foo");
+        jg.writeNumber(1);
+        jg.writeEndObject();
+        jg.close();
+
+        String result = sw.toString();
+        if (quoted) {
+            assertEquals("{\"foo\":1}", result);
+        } else {
+            assertEquals("{foo:1}", result);
+        }
+    }
+    private void _testNonNumericQuoting(JsonFactory jf, boolean quoted)
+        throws IOException
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator jg = jf.createGenerator(sw);
+        jg.writeStartObject();
+        jg.writeFieldName("double");
+        jg.writeNumber(Double.NaN);
+        jg.writeEndObject();
+        jg.writeStartObject();
+        jg.writeFieldName("float");
+        jg.writeNumber(Float.NaN);
+        jg.writeEndObject();
+        jg.close();
+	
+        String result = sw.toString();
+        if (quoted) {
+            assertEquals("{\"double\":\"NaN\"} {\"float\":\"NaN\"}", result);
+        } else {
+            assertEquals("{\"double\":NaN} {\"float\":NaN}", result);
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestNumberParsing.java b/src/test/java/com/fasterxml/jackson/core/main/TestNumberParsing.java
new file mode 100644
index 0000000..cc79263
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestNumberParsing.java
@@ -0,0 +1,83 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.io.NumberInput;
+
+/**
+ * Set of basic unit tests for verifying that the low-level number
+ * handling methods work as expected.
+ */
+public class TestNumberParsing
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testIntParsing() throws Exception
+    {
+        char[] testChars = "123456789".toCharArray();
+
+        assertEquals(3, NumberInput.parseInt(testChars, 2, 1));
+        assertEquals(123, NumberInput.parseInt(testChars, 0, 3));
+        assertEquals(2345, NumberInput.parseInt(testChars, 1, 4));
+        assertEquals(9, NumberInput.parseInt(testChars, 8, 1));
+        assertEquals(456789, NumberInput.parseInt(testChars, 3, 6));
+        assertEquals(23456, NumberInput.parseInt(testChars, 1, 5));
+        assertEquals(123456789, NumberInput.parseInt(testChars, 0, 9));
+
+        testChars = "32".toCharArray();
+        assertEquals(32, NumberInput.parseInt(testChars, 0, 2));
+        testChars = "189".toCharArray();
+        assertEquals(189, NumberInput.parseInt(testChars, 0, 3));
+
+        testChars = "10".toCharArray();
+        assertEquals(10, NumberInput.parseInt(testChars, 0, 2));
+        assertEquals(0, NumberInput.parseInt(testChars, 1, 1));
+    }
+
+    public void testIntParsingWithStrings() throws Exception
+    {
+        assertEquals(3, NumberInput.parseInt("3"));
+        assertEquals(0, NumberInput.parseInt("0"));
+        assertEquals(-3, NumberInput.parseInt("-3"));
+        assertEquals(27, NumberInput.parseInt("27"));
+        assertEquals(-31, NumberInput.parseInt("-31"));
+        assertEquals(271, NumberInput.parseInt("271"));
+        assertEquals(-131, NumberInput.parseInt("-131"));
+        assertEquals(2709, NumberInput.parseInt("2709"));
+        assertEquals(-9999, NumberInput.parseInt("-9999"));
+        assertEquals(Integer.MIN_VALUE, NumberInput.parseInt(""+Integer.MIN_VALUE));
+        assertEquals(Integer.MAX_VALUE, NumberInput.parseInt(""+Integer.MAX_VALUE));
+    }
+    
+    public void testLongParsing() throws Exception
+    {
+        char[] testChars = "123456789012345678".toCharArray();
+
+        assertEquals(123456789012345678L, NumberInput.parseLong(testChars, 0, testChars.length));
+    }
+
+    // Unit test for [JACKSON-491]
+    public void testLongBoundsChecks() throws Exception
+    {
+        String minLong = String.valueOf(Long.MIN_VALUE).substring(1);
+        String maxLong = String.valueOf(Long.MAX_VALUE);
+        final String VALUE_491 = "1323372036854775807"; // is within range (JACKSON-491)
+        final String OVERFLOW =  "9999999999999999999"; // and this one is clearly out
+
+        assertTrue(NumberInput.inLongRange(minLong, true));
+        assertTrue(NumberInput.inLongRange(maxLong, false));
+        assertTrue(NumberInput.inLongRange(VALUE_491, true));
+        assertTrue(NumberInput.inLongRange(VALUE_491, false));
+        assertFalse(NumberInput.inLongRange(OVERFLOW, false));
+        assertFalse(NumberInput.inLongRange(OVERFLOW, true));
+
+        char[] cbuf = minLong.toCharArray();
+        assertTrue(NumberInput.inLongRange(cbuf, 0, cbuf.length, true));
+        cbuf = maxLong.toCharArray();
+        assertTrue(NumberInput.inLongRange(cbuf, 0, cbuf.length, false));
+        cbuf = VALUE_491.toCharArray();
+        assertTrue(NumberInput.inLongRange(cbuf, 0, cbuf.length, true));
+        assertTrue(NumberInput.inLongRange(cbuf, 0, cbuf.length, false));
+        cbuf = OVERFLOW.toCharArray();
+        assertFalse(NumberInput.inLongRange(cbuf, 0, cbuf.length, true));
+        assertFalse(NumberInput.inLongRange(cbuf, 0, cbuf.length, false));
+    }
+    
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestNumericValues.java b/src/test/java/com/fasterxml/jackson/core/main/TestNumericValues.java
new file mode 100644
index 0000000..9db299f
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestNumericValues.java
@@ -0,0 +1,375 @@
+package com.fasterxml.jackson.core.main;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestNumericValues
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testSimpleInt()
+        throws Exception
+    {
+        int EXP_I = 1234;
+
+        JsonParser jp = createParserUsingReader("[ "+EXP_I+" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(JsonParser.NumberType.INT, jp.getNumberType());
+        assertEquals(""+EXP_I, jp.getText());
+
+        assertEquals(EXP_I, jp.getIntValue());
+        assertEquals((long) EXP_I, jp.getLongValue());
+        assertEquals((double) EXP_I, jp.getDoubleValue());
+        assertEquals(BigDecimal.valueOf((long) EXP_I), jp.getDecimalValue());
+    }
+
+    public void testIntRange()
+        throws Exception
+    {
+        // let's test with readers and streams, separate code paths:
+        for (int i = 0; i < 2; ++i) {
+            String input = "[ "+Integer.MAX_VALUE+","+Integer.MIN_VALUE+" ]";
+            JsonParser jp;
+            if (i == 0) {
+                jp = createParserUsingReader(input);                
+            } else {
+                jp = this.createParserUsingStream(input, "UTF-8");
+            }
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.INT, jp.getNumberType());
+            assertEquals(Integer.MAX_VALUE, jp.getIntValue());
+    
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.INT, jp.getNumberType());
+            assertEquals(Integer.MIN_VALUE, jp.getIntValue());
+        }
+    }
+    
+    public void testInvalidIntAccess()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("[ \"abc\" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        try {
+            jp.getIntValue();
+            fail("Expected error trying to call getIntValue on non-numeric value");
+        } catch (JsonParseException e) {
+            verifyException(e, "can not use numeric value accessors");
+        }
+    }
+
+    public void testSimpleLong()
+        throws Exception
+    {
+        long EXP_L = 12345678907L;
+
+        JsonParser jp = createParserUsingReader("[ "+EXP_L+" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        // beyond int, should be long
+        assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+        assertEquals(""+EXP_L, jp.getText());
+
+        assertEquals(EXP_L, jp.getLongValue());
+        // Should get an exception if trying to convert to int 
+        try {
+            jp.getIntValue();
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "out of range");
+        }
+        assertEquals((double) EXP_L, jp.getDoubleValue());
+        assertEquals(BigDecimal.valueOf((long) EXP_L), jp.getDecimalValue());
+    }
+
+    public void testLongRange()
+        throws Exception
+    {
+        for (int i = 0; i < 2; ++i) {
+            long belowMinInt = -1L + Integer.MIN_VALUE;
+            long aboveMaxInt = 1L + Integer.MAX_VALUE;
+            String input = "[ "+Long.MAX_VALUE+","+Long.MIN_VALUE+","+aboveMaxInt+", "+belowMinInt+" ]";
+            JsonParser jp;
+            if (i == 0) {
+                jp = createParserUsingReader(input);                
+            } else {
+                jp = this.createParserUsingStream(input, "UTF-8");
+            }
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+            assertEquals(Long.MAX_VALUE, jp.getLongValue());
+        
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+            assertEquals(Long.MIN_VALUE, jp.getLongValue());
+
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+            assertEquals(aboveMaxInt, jp.getLongValue());
+
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+            assertEquals(belowMinInt, jp.getLongValue());
+
+            
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());        
+            jp.close();
+        }
+    }
+
+    public void testBigDecimalRange()
+        throws Exception
+    {
+        for (int i = 0; i < 2; ++i) {
+            // let's test first values outside of Long range
+            BigInteger small = new BigDecimal(Long.MIN_VALUE).toBigInteger();
+            small = small.subtract(BigInteger.ONE);
+            BigInteger big = new BigDecimal(Long.MAX_VALUE).toBigInteger();
+            big = big.add(BigInteger.ONE);
+            String input = "[ "+small+"  ,  "+big+"]";
+            JsonParser jp;
+            if (i == 0) {
+                jp = createParserUsingReader(input);                
+            } else {
+                jp = this.createParserUsingStream(input, "UTF-8");
+            }
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.BIG_INTEGER, jp.getNumberType());
+            assertEquals(small, jp.getBigIntegerValue());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(JsonParser.NumberType.BIG_INTEGER, jp.getNumberType());
+            assertEquals(big, jp.getBigIntegerValue());
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());        
+            jp.close();
+        }
+}
+    
+    public void testSimpleDouble()
+        throws Exception
+    {
+        final String[] INPUTS = new String[] {
+            "1234.00", "2.1101567E-16", "1.0e5", "2.5e+5", "9e4", "-12e-3", "0.25"
+        };
+        for (int input = 0; input < 2; ++input) {
+            for (int i = 0; i < INPUTS.length; ++i) {
+
+                /* Testing double is more difficult, given the rounding
+                 * errors and such. But let's try anyways.
+                 */
+                String STR = INPUTS[i];
+                double EXP_D = Double.parseDouble(STR);
+                String DOC = "["+STR+"]";
+                
+                JsonParser jp;
+                
+                if (input == 0) {
+                    jp = createParserUsingStream(DOC, "UTF-8");
+                } else {
+                    jp = createParserUsingReader(DOC);
+                }
+                assertToken(JsonToken.START_ARRAY, jp.nextToken());
+                assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+                assertEquals(STR, jp.getText());
+                assertEquals(EXP_D, jp.getDoubleValue());
+                assertToken(JsonToken.END_ARRAY, jp.nextToken());
+                jp.close();
+            }
+        }
+    }
+
+    public void testNumbers() throws Exception
+    {
+        final String DOC = "[ -13, 8100200300, 13.5, 0.00010, -2.033 ]";
+
+        for (int input = 0; input < 2; ++input) {
+            JsonParser jp;
+
+            if (input == 0) {
+                jp = createParserUsingStream(DOC, "UTF-8");
+            } else {
+                jp = createParserUsingReader(DOC);
+            }
+
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(-13, jp.getIntValue());
+            assertEquals(-13L, jp.getLongValue());
+            assertEquals(-13., jp.getDoubleValue());
+            assertEquals("-13", jp.getText());
+            
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(8100200300L, jp.getLongValue());
+            // Should get exception for overflow:
+            try {
+                /*int x =*/ jp.getIntValue();
+                fail("Expected an exception for overflow");
+            } catch (Exception e) {
+                verifyException(e, "out of range of int");
+            }
+            assertEquals(8100200300., jp.getDoubleValue());
+            assertEquals("8100200300", jp.getText());
+            
+            assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+            assertEquals(13, jp.getIntValue());
+            assertEquals(13L, jp.getLongValue());
+            assertEquals(13.5, jp.getDoubleValue());
+            assertEquals("13.5", jp.getText());
+            
+            assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+            assertEquals(0, jp.getIntValue());
+            assertEquals(0L, jp.getLongValue());
+            assertEquals(0.00010, jp.getDoubleValue());
+            assertEquals("0.00010", jp.getText());
+            
+            assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+            assertEquals(-2, jp.getIntValue());
+            assertEquals(-2L, jp.getLongValue());
+            assertEquals(-2.033, jp.getDoubleValue());
+            assertEquals("-2.033", jp.getText());
+            
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        }
+    }
+
+    @SuppressWarnings("resource")
+    public void testLongOverflow() throws Exception
+    {
+        BigInteger below = BigInteger.valueOf(Long.MIN_VALUE);
+        below = below.subtract(BigInteger.ONE);
+        BigInteger above = BigInteger.valueOf(Long.MAX_VALUE);
+        above = above.add(BigInteger.ONE);
+
+        String DOC_BELOW = below.toString() + " ";
+        String DOC_ABOVE = below.toString() + " ";
+        for (int input = 0; input < 2; ++input) {
+            JsonParser jp;
+
+            if (input == 0) {
+                jp = createParserUsingStream(DOC_BELOW, "UTF-8");
+            } else {
+                jp = createParserUsingReader(DOC_BELOW);
+            }
+            jp.nextToken();
+            try {
+                long x = jp.getLongValue();
+                fail("Expected an exception for underflow (input "+jp.getText()+"): instead, got long value: "+x);
+            } catch (JsonParseException e) {
+                verifyException(e, "out of range of long");
+            }
+            jp.close();
+
+            if (input == 0) {
+                jp = createParserUsingStream(DOC_ABOVE, "UTF-8");
+            } else {
+                jp = createParserUsingReader(DOC_ABOVE);
+            }
+            jp.nextToken();
+            try {
+                long x = jp.getLongValue();
+                fail("Expected an exception for underflow (input "+jp.getText()+"): instead, got long value: "+x);
+            } catch (JsonParseException e) {
+                verifyException(e, "out of range of long");
+            }
+            jp.close();
+            
+        }
+    }
+    
+    /**
+     * Method that tries to test that number parsing works in cases where
+     * input is split between buffer boundaries.
+     */
+    public void testParsingOfLongerSequences()
+        throws Exception
+    {
+        double[] values = new double[] { 0.01, -10.5, 2.1e9, 4.0e-8 };
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < values.length; ++i) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append(values[i]);
+        }
+        String segment = sb.toString();
+
+        int COUNT = 1000;
+        sb = new StringBuilder(COUNT * segment.length() + 20);
+        sb.append("[");
+        for (int i = 0; i < COUNT; ++i) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append(segment);
+            sb.append('\n');
+            // let's add somewhat arbitray number of spaces
+            int x = (i & 3);
+            if (i > 300) {
+                x += i % 5;
+            }
+            while (--x > 0) {
+                sb.append(' ');
+            }
+        }
+        sb.append("]");
+        String DOC = sb.toString();
+
+        for (int input = 0; input < 2; ++input) {
+            JsonParser jp;
+
+            if (input == 0) {
+                jp = createParserUsingStream(DOC, "UTF-8");
+            } else {
+                jp = createParserUsingReader(DOC);
+            }
+
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            for (int i = 0; i < COUNT; ++i) {
+                for (double d : values) {
+                    assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+                    assertEquals(d, jp.getDoubleValue());
+                }
+            }
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* New tests for 1.3 features
+    /**********************************************************
+     */
+
+    public void testSimpleBoolean()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("[ true ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(true, jp.getBooleanValue());
+    }
+
+    public void testInvalidBooleanAccess()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("[ \"abc\" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        try {
+            jp.getBooleanValue();
+            fail("Expected error trying to call getBooleanValue on non-boolean value");
+        } catch (JsonParseException e) {
+            verifyException(e, "not of boolean type");
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestParserClosing.java b/src/test/java/com/fasterxml/jackson/core/main/TestParserClosing.java
new file mode 100644
index 0000000..0124406
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestParserClosing.java
@@ -0,0 +1,167 @@
+package com.fasterxml.jackson.core.main;
+
+import static org.junit.Assert.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests that verify that the closing (or not) of
+ * the underlying source occurs as expected and specified
+ * by documentation.
+ */
+public class TestParserClosing
+    extends BaseTest
+{
+    /**
+     * This unit test checks the default behaviour; with no auto-close, no
+     * automatic closing should occur, nor explicit one unless specific
+     * forcing method is used.
+     */
+    public void testNoAutoCloseReader()
+        throws Exception
+    {
+        final String DOC = "[ 1 ]";
+
+        JsonFactory f = new JsonFactory();
+
+        // Check the default settings
+        assertTrue(f.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+        // then change
+        f.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+        assertFalse(f.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+        MyReader input = new MyReader(DOC);
+        JsonParser jp = f.createParser(input);
+
+        // shouldn't be closed to begin with...
+        assertFalse(input.isClosed());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+        // normally would be closed now
+        assertFalse(input.isClosed());
+        // regular close won't close it either:
+        jp.close();
+        assertFalse(input.isClosed());
+
+    }
+
+    public void testAutoCloseReader() throws Exception
+    {
+        final String DOC = "[ 1 ]";
+
+        JsonFactory f = new JsonFactory();
+        f.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+        assertTrue(f.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+        MyReader input = new MyReader(DOC);
+        JsonParser jp = f.createParser(input);
+        assertFalse(input.isClosed());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        // but can close half-way through
+        jp.close();
+        assertTrue(input.isClosed());
+
+        // And then let's test implicit close at the end too:
+        input = new MyReader(DOC);
+        jp = f.createParser(input);
+        assertFalse(input.isClosed());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+        assertTrue(input.isClosed());
+    }
+
+    public void testNoAutoCloseInputStream()
+        throws Exception
+    {
+        final String DOC = "[ 1 ]";
+        JsonFactory f = new JsonFactory();
+
+        f.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+        MyStream input = new MyStream(DOC.getBytes("UTF-8"));
+        JsonParser jp = f.createParser(input);
+
+        // shouldn't be closed to begin with...
+        assertFalse(input.isClosed());
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+        // normally would be closed now
+        assertFalse(input.isClosed());
+        // regular close won't close it either:
+        jp.close();
+        assertFalse(input.isClosed());
+    }
+
+    // [JACKSON-287]
+    public void testReleaseContentBytes() throws Exception
+    {
+        byte[] input = "[1]foobar".getBytes("UTF-8");
+        JsonParser jp = new JsonFactory().createParser(input);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        // theoretically could have only read subset; but current impl is more greedy
+        assertEquals(6, jp.releaseBuffered(out));
+        assertArrayEquals("foobar".getBytes("UTF-8"), out.toByteArray());
+    }
+
+    public void testReleaseContentChars() throws Exception
+    {
+        JsonParser jp = new JsonFactory().createParser("[true]xyz");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        StringWriter sw = new StringWriter();
+        // theoretically could have only read subset; but current impl is more greedy
+        assertEquals(3, jp.releaseBuffered(sw));
+        assertEquals("xyz", sw.toString());
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper classes
+    /**********************************************************
+     */
+
+    final static class MyReader extends StringReader
+    {
+        boolean mIsClosed = false;
+
+        public MyReader(String contents) {
+            super(contents);
+        }
+
+        @Override
+        public void close() {
+            mIsClosed = true;
+            super.close();
+        }
+
+        public boolean isClosed() { return mIsClosed; }
+    }
+
+    final static class MyStream extends ByteArrayInputStream
+    {
+        boolean mIsClosed = false;
+
+        public MyStream(byte[] data) {
+            super(data);
+        }
+
+        @Override
+        public void close() throws IOException {
+            mIsClosed = true;
+            super.close();
+        }
+
+        public boolean isClosed() { return mIsClosed; }
+    }
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestParserFeatures.java b/src/test/java/com/fasterxml/jackson/core/main/TestParserFeatures.java
new file mode 100644
index 0000000..0421b80
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestParserFeatures.java
@@ -0,0 +1,107 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Unit tests for verifying that additional <code>JsonParser.Feature</code>
+ * settings work as expected.
+ */
+public class TestParserFeatures
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testDefaultSettings()
+    {
+        JsonFactory f = new JsonFactory();
+        assertTrue(f.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES));
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_SINGLE_QUOTES));
+        assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS));
+    }
+
+    public void testQuotesRequired() throws Exception
+    {
+        _testQuotesRequired(false);
+        _testQuotesRequired(true);
+    }
+
+
+    // // Tests for [JACKSON-208], unquoted tabs:
+
+    public void testTabsDefault() throws Exception
+    {
+        _testTabsDefault(false);
+        _testTabsDefault(true);
+    }
+
+    public void testTabsEnabled() throws Exception
+    {
+        _testTabsEnabled(false);
+        _testTabsEnabled(true);
+    }
+    
+    /*
+    /****************************************************************
+    /* Secondary test methods
+    /****************************************************************
+     */
+
+    private void _testQuotesRequired(boolean useStream) throws Exception
+    {
+        final String JSON = "{ test : 3 }";
+        final String EXP_ERROR_FRAGMENT = "was expecting double-quote to start";
+        JsonFactory f = new JsonFactory();
+        JsonParser jp = useStream ?
+            createParserUsingStream(f, JSON, "UTF-8")
+            : createParserUsingReader(f, JSON)
+            ;
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        try {
+            jp.nextToken();
+        } catch (JsonParseException je) {
+            verifyException(je, EXP_ERROR_FRAGMENT);
+        } finally {
+            jp.close();
+        }
+    }
+
+    // // // Tests for [JACKSON-208]
+
+    private void _testTabsDefault(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        // First, let's see that by default unquoted tabs are illegal
+        String JSON = "[\"tab:\t\"]";
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8") : createParserUsingReader(f, JSON);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            jp.nextToken();
+            jp.getText();
+            fail("Expected exception");
+        } catch (JsonParseException e) {
+            verifyException(e, "Illegal unquoted character");
+        } finally {
+            jp.close();
+        }
+    }
+
+    private void _testTabsEnabled(boolean useStream) throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        f.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
+
+        String FIELD = "a\tb";
+        String VALUE = "\t";
+        String JSON = "{ "+quote(FIELD)+" : "+quote(VALUE)+"}";
+        JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8") : createParserUsingReader(f, JSON);
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(FIELD, jp.getText());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals(VALUE, jp.getText());
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+        jp.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestParserLinefeeds.java b/src/test/java/com/fasterxml/jackson/core/main/TestParserLinefeeds.java
new file mode 100644
index 0000000..2ac4fec
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestParserLinefeeds.java
@@ -0,0 +1,70 @@
+package com.fasterxml.jackson.core.main;
+
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.io.IOException;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestParserLinefeeds
+    extends BaseTest
+{
+    public void testCR() throws Exception
+    {
+        _testLinefeeds("\r", true);
+        _testLinefeeds("\r", false);
+    }
+
+    public void testLF() throws Exception
+    {
+        _testLinefeeds("\n", true);
+        _testLinefeeds("\n", false);
+    }
+
+    public void testCRLF() throws Exception
+    {
+        _testLinefeeds("\r\n", true);
+        _testLinefeeds("\r\n", false);
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private void _testLinefeeds(String lf, boolean useStream)
+        throws IOException
+    {
+        String DOC = "[1, at 2, at -178@]";
+        DOC = DOC.replaceAll("@", lf);
+
+        JsonParser jp = useStream ?
+            createParserUsingStream(DOC, "UTF-8")
+            : createParserUsingReader(DOC);
+            
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(1, jp.getCurrentLocation().getLineNr());
+        
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(1, jp.getIntValue());
+        assertEquals(1, jp.getCurrentLocation().getLineNr());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(2, jp.getIntValue());
+        assertEquals(2, jp.getCurrentLocation().getLineNr());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(-178, jp.getIntValue());
+        assertEquals(3, jp.getCurrentLocation().getLineNr());
+        
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(4, jp.getCurrentLocation().getLineNr());
+
+        jp.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestParserWithObjects.java b/src/test/java/com/fasterxml/jackson/core/main/TestParserWithObjects.java
new file mode 100644
index 0000000..764eccf
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestParserWithObjects.java
@@ -0,0 +1,170 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Unit tests for verifying that object mapping functionality can
+ * be accessed using JsonParser.
+ */
+public class TestParserWithObjects
+    extends com.fasterxml.jackson.test.BaseTest
+{
+
+    /*
+    /**********************************************************
+    /* Test for simple traversal with data mapping
+    /**********************************************************
+     */
+
+    public void testNextValue() throws IOException
+    {
+        // Let's test both byte-backed and Reader-based one
+        _testNextValueBasic(false);
+        _testNextValueBasic(true);
+    }
+
+    // [JACKSON-395]
+    public void testNextValueNested() throws IOException
+    {
+        // Let's test both byte-backed and Reader-based one
+        _testNextValueNested(false);
+        _testNextValueNested(true);
+    }
+
+    public void testIsClosed()
+        throws IOException
+    {
+        for (int i = 0; i < 4; ++i) {
+            String JSON = "[ 1, 2, 3 ]";
+            boolean stream = ((i & 1) == 0);
+            JsonParser jp = stream ?
+                createParserUsingStream(JSON, "UTF-8")
+                : createParserUsingReader(JSON);
+            boolean partial = ((i & 2) == 0);
+
+            assertFalse(jp.isClosed());
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertFalse(jp.isClosed());
+
+            if (partial) {
+                jp.close();
+                assertTrue(jp.isClosed());
+            } else {
+                assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+                assertToken(JsonToken.END_ARRAY, jp.nextToken());
+                assertNull(jp.nextToken());
+                assertTrue(jp.isClosed());
+            }
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Supporting methods
+    /**********************************************************
+     */
+
+    private void  _testNextValueBasic(boolean useStream) throws IOException
+    {
+        // first array, no change to default
+        JsonParser jp = _getParser("[ 1, 2, 3, 4 ]", useStream);
+        assertToken(JsonToken.START_ARRAY, jp.nextValue());
+        for (int i = 1; i <= 4; ++i) {
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextValue());
+            assertEquals(i, jp.getIntValue());
+        }
+        assertToken(JsonToken.END_ARRAY, jp.nextValue());
+        assertNull(jp.nextValue());
+        jp.close();
+
+        // then Object, is different
+        jp = _getParser("{ \"3\" :3, \"4\": 4, \"5\" : 5 }", useStream);
+        assertToken(JsonToken.START_OBJECT, jp.nextValue());
+        for (int i = 3; i <= 5; ++i) {
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextValue());
+            assertEquals(String.valueOf(i), jp.getCurrentName());
+            assertEquals(i, jp.getIntValue());
+        }
+        assertToken(JsonToken.END_OBJECT, jp.nextValue());
+        assertNull(jp.nextValue());
+        jp.close();
+
+        // and then mixed...
+        jp = _getParser("[ true, [ ], { \"a\" : 3 } ]", useStream);
+
+        assertToken(JsonToken.START_ARRAY, jp.nextValue());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextValue());
+        assertToken(JsonToken.START_ARRAY, jp.nextValue());
+        assertToken(JsonToken.END_ARRAY, jp.nextValue());
+
+        assertToken(JsonToken.START_OBJECT, jp.nextValue());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextValue());
+        assertEquals("a", jp.getCurrentName());
+        assertToken(JsonToken.END_OBJECT, jp.nextValue());
+        assertToken(JsonToken.END_ARRAY, jp.nextValue());
+
+        assertNull(jp.nextValue());
+        jp.close();
+    }
+
+    // [JACKSON-395]
+    private void  _testNextValueNested(boolean useStream) throws IOException
+    {
+        // first array, no change to default
+        JsonParser jp;
+    
+        // then object with sub-objects...
+        jp = _getParser("{\"a\": { \"b\" : true, \"c\": false }, \"d\": 3 }", useStream);
+
+        assertToken(JsonToken.START_OBJECT, jp.nextValue());
+        assertNull(jp.getCurrentName());
+        assertToken(JsonToken.START_OBJECT, jp.nextValue());
+        assertEquals("a", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextValue());
+        assertEquals("b", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_FALSE, jp.nextValue());
+        assertEquals("c", jp.getCurrentName());
+        assertToken(JsonToken.END_OBJECT, jp.nextValue());
+        // ideally we should match closing marker with field, too:
+        assertEquals("a", jp.getCurrentName());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextValue());
+        assertEquals("d", jp.getCurrentName());
+        assertToken(JsonToken.END_OBJECT, jp.nextValue());
+        assertNull(jp.getCurrentName());
+        assertNull(jp.nextValue());
+        jp.close();
+
+        // and arrays
+        jp = _getParser("{\"a\": [ false ] }", useStream);
+
+        assertToken(JsonToken.START_OBJECT, jp.nextValue());
+        assertNull(jp.getCurrentName());
+        assertToken(JsonToken.START_ARRAY, jp.nextValue());
+        assertEquals("a", jp.getCurrentName());
+        assertToken(JsonToken.VALUE_FALSE, jp.nextValue());
+        assertNull(jp.getCurrentName());
+        assertToken(JsonToken.END_ARRAY, jp.nextValue());
+        // ideally we should match closing marker with field, too:
+        assertEquals("a", jp.getCurrentName());
+        assertToken(JsonToken.END_OBJECT, jp.nextValue());
+        assertNull(jp.getCurrentName());
+        assertNull(jp.nextValue());
+        jp.close();
+    }
+
+    private JsonParser _getParser(String doc, boolean useStream)
+        throws IOException
+    {
+        JsonFactory jf = new JsonFactory();
+        if (useStream) {
+            return jf.createParser(doc.getBytes("UTF-8"));
+        }
+        return jf.createParser(new StringReader(doc));
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestPrettyPrinter.java b/src/test/java/com/fasterxml/jackson/core/main/TestPrettyPrinter.java
new file mode 100644
index 0000000..539ede6
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestPrettyPrinter.java
@@ -0,0 +1,224 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.core.util.MinimalPrettyPrinter;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests for verifying that indenting
+ * option of generator works correctly
+ */
+ at SuppressWarnings("serial")
+public class TestPrettyPrinter
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    static class CountPrinter extends MinimalPrettyPrinter
+    {
+        @Override
+        public void writeEndObject(JsonGenerator jg, int nrOfEntries)
+                throws IOException, JsonGenerationException
+        {
+            jg.writeRaw("("+nrOfEntries+")}");
+        }
+
+        @Override
+        public void writeEndArray(JsonGenerator jg, int nrOfValues)
+            throws IOException, JsonGenerationException
+        {
+            jg.writeRaw("("+nrOfValues+")]");
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Test methods
+    /**********************************************************
+     */
+    
+    public void testObjectCount() throws Exception
+    {
+        final String EXP = "{\"x\":{\"a\":1,\"b\":2(2)}(1)}";
+        final JsonFactory jf = new JsonFactory();
+
+        for (int i = 0; i < 2; ++i) {
+            boolean useBytes = (i > 0);
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = useBytes ? jf.createGenerator(bytes)
+                    : jf.createGenerator(sw);
+            gen.setPrettyPrinter(new CountPrinter());
+            gen.writeStartObject();
+            gen.writeFieldName("x");
+            gen.writeStartObject();
+            gen.writeNumberField("a", 1);
+            gen.writeNumberField("b", 2);
+            gen.writeEndObject();
+            gen.writeEndObject();
+            gen.close();
+
+            String json = useBytes ? bytes.toString("UTF-8") : sw.toString();
+            assertEquals(EXP, json);
+        }
+    }
+
+    public void testArrayCount() throws Exception
+    {
+        final String EXP = "[6,[1,2,9(3)](2)]";
+        
+        final JsonFactory jf = new JsonFactory();
+
+        for (int i = 0; i < 2; ++i) {
+            boolean useBytes = (i > 0);
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = useBytes ? jf.createGenerator(bytes)
+                    : jf.createGenerator(sw);
+            gen.setPrettyPrinter(new CountPrinter());
+            gen.writeStartArray();
+            gen.writeNumber(6);
+            gen.writeStartArray();
+            gen.writeNumber(1);
+            gen.writeNumber(2);
+            gen.writeNumber(9);
+            gen.writeEndArray();
+            gen.writeEndArray();
+            gen.close();
+
+            String json = useBytes ? bytes.toString("UTF-8") : sw.toString();
+            assertEquals(EXP, json);
+        }
+    }
+    
+    public void testSimpleDocWithDefault() throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.useDefaultPrettyPrinter();
+        _verifyPrettyPrinter(gen, sw);
+    }
+
+    public void testSimpleDocWithMinimal() throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        // first with standard minimal
+        gen.setPrettyPrinter(new MinimalPrettyPrinter());
+        String docStr = _verifyPrettyPrinter(gen, sw);
+        // which should have no linefeeds, tabs
+        assertEquals(-1, docStr.indexOf('\n'));
+        assertEquals(-1, docStr.indexOf('\t'));
+
+        // And then with slightly customized variant
+        gen = new JsonFactory().createGenerator(sw);
+        gen.setPrettyPrinter(new MinimalPrettyPrinter() {
+            @Override
+            // use TAB between array values
+            public void beforeArrayValues(JsonGenerator jg) throws IOException, JsonGenerationException
+            {
+                jg.writeRaw("\t");
+            }
+        });
+        docStr = _verifyPrettyPrinter(gen, sw);
+        assertEquals(-1, docStr.indexOf('\n'));
+        assertTrue(docStr.indexOf('\t') >= 0);
+    }
+
+    // [Issue#26]
+    public void testCustomRootSeparatorWithPP() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        // first, no pretty-printing (will still separate root values with a space!)
+        assertEquals("{} {} []", _generateRoot(jf, null));
+        // First with default pretty printer, default configs:
+        assertEquals("{ } { } [ ]", _generateRoot(jf, new DefaultPrettyPrinter()));
+        // then custom:
+        assertEquals("{ }|{ }|[ ]", _generateRoot(jf, new DefaultPrettyPrinter("|")));
+    }
+
+    // Alternative solution for [Issue#26]
+    public void testCustomRootSeparatorWithFactory() throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        jf.setRootValueSeparator("##");
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = jf.createGenerator(sw);
+        gen.writeNumber(13);
+        gen.writeBoolean(false);
+        gen.writeNull();
+        gen.close();
+        assertEquals("13##false##null", sw.toString());
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private String _verifyPrettyPrinter(JsonGenerator gen, StringWriter sw) throws Exception
+    {    
+        gen.writeStartArray();
+        gen.writeNumber(3);
+        gen.writeString("abc");
+
+        gen.writeStartArray();
+        gen.writeBoolean(true);
+        gen.writeEndArray();
+
+        gen.writeStartObject();
+        gen.writeFieldName("f");
+        gen.writeNull();
+        gen.writeFieldName("f2");
+        gen.writeNull();
+        gen.writeEndObject();
+
+        gen.writeEndArray();
+        gen.close();
+
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(3, jp.getIntValue());
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("abc", jp.getText());
+
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("f", jp.getText());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("f2", jp.getText());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+
+        jp.close();
+
+        return docStr;
+    }
+
+    protected String _generateRoot(JsonFactory jf, PrettyPrinter pp) throws IOException
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createGenerator(sw);
+        gen.setPrettyPrinter(pp);
+        gen.writeStartObject();
+        gen.writeEndObject();
+        gen.writeStartObject();
+        gen.writeEndObject();
+        gen.writeStartArray();
+        gen.writeEndArray();
+        gen.close();
+        return sw.toString();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestRawStringWriting.java b/src/test/java/com/fasterxml/jackson/core/main/TestRawStringWriting.java
new file mode 100644
index 0000000..6adf819
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestRawStringWriting.java
@@ -0,0 +1,132 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+import java.util.*;
+
+import static org.junit.Assert.*;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * @since 1.7
+ */
+public class TestRawStringWriting extends com.fasterxml.jackson.test.BaseTest
+{
+    /**
+     * Unit test for "JsonGenerator.writeRawUTF8String()"
+     */
+    public void testUtf8RawStrings() throws Exception
+    {
+        // Let's create set of Strings to output; no ctrl chars as we do raw
+        List<byte[]> strings = generateStrings(new Random(28), 750000, false);
+        ByteArrayOutputStream out = new ByteArrayOutputStream(16000);
+        JsonFactory jf = new JsonFactory();
+        JsonGenerator jgen = jf.createGenerator(out, JsonEncoding.UTF8);
+        jgen.writeStartArray();
+        for (byte[] str : strings) {
+            jgen.writeRawUTF8String(str, 0, str.length);
+        }
+        jgen.writeEndArray();
+        jgen.close();
+        byte[] json = out.toByteArray();
+        
+        // Ok: let's verify that stuff was written out ok
+        JsonParser jp = jf.createParser(json);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        for (byte[] inputBytes : strings) {
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            String string = jp.getText();
+            byte[] outputBytes = string.getBytes("UTF-8");
+            assertEquals(inputBytes.length, outputBytes.length);
+            assertArrayEquals(inputBytes, outputBytes);
+        }
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+    }
+
+    /**
+     * Unit test for "JsonGenerator.writeUTF8String()", which needs
+     * to handle escaping properly
+     */
+    public void testUtf8StringsWithEscaping() throws Exception
+    {
+        // Let's create set of Strings to output; do include control chars too:
+        List<byte[]> strings = generateStrings(new Random(28), 720000, true);
+        ByteArrayOutputStream out = new ByteArrayOutputStream(16000);
+        JsonFactory jf = new JsonFactory();
+        JsonGenerator jgen = jf.createGenerator(out, JsonEncoding.UTF8);
+        jgen.writeStartArray();
+
+        for (byte[] str : strings) {
+            jgen.writeUTF8String(str, 0, str.length);
+            jgen.writeRaw('\n');
+        }
+        jgen.writeEndArray();
+        jgen.close();
+        byte[] json = out.toByteArray();
+        
+        // Ok: let's verify that stuff was written out ok
+        JsonParser jp = jf.createParser(json);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        for (byte[] inputBytes : strings) {
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            String string = jp.getText();
+
+            byte[] outputBytes = string.getBytes("UTF-8");
+            assertEquals(inputBytes.length, outputBytes.length);
+            assertArrayEquals(inputBytes, outputBytes);
+        }
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+    }
+    
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+    
+    private List<byte[]> generateStrings(Random rnd, int totalLength, boolean includeCtrlChars)
+        throws IOException
+    {
+        ArrayList<byte[]> strings = new ArrayList<byte[]>();
+        do {
+            int len = 2;
+            int bits = rnd.nextInt(13);
+            while (--bits >= 0) {
+                len += len;
+            }
+            len = 1 + ((len + len) / 3);
+            String str = generateString(rnd, len, includeCtrlChars);
+            byte[] bytes = str.getBytes("UTF-8");
+            strings.add(bytes);
+            totalLength -= bytes.length;
+        } while (totalLength > 0);
+        return strings;
+    }
+        
+    private String generateString(Random rnd, int length, boolean includeCtrlChars)
+    {
+        StringBuilder sb = new StringBuilder(length);
+        do {
+            int i;
+            switch (rnd.nextInt(3)) {
+            case 0: // 3 byte one
+                i = 2048 + rnd.nextInt(16383);
+                break;
+            case 1: // 2 byte
+                i = 128 + rnd.nextInt(1024);
+                break;
+            default: // ASCII
+                i = rnd.nextInt(192);
+                if (!includeCtrlChars) {
+                    i += 32;
+                    // but also need to avoid backslash, double-quote
+                    if (i == '\\' || i == '"') {
+                        i = '@'; // just arbitrary choice
+                    }
+                }
+            }
+            sb.append((char) i);
+        } while (sb.length() < length);
+        return sb.toString();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestScopeMatching.java b/src/test/java/com/fasterxml/jackson/core/main/TestScopeMatching.java
new file mode 100644
index 0000000..397ec97
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestScopeMatching.java
@@ -0,0 +1,140 @@
+package com.fasterxml.jackson.core.main;
+
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+/**
+ * Set of basic unit tests for verifying that Array/Object scopes
+ * are properly matched.
+ */
+public class TestScopeMatching
+    extends BaseTest
+{
+    public void testUnclosedArray()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("[ 1, 2");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected an exception for unclosed ARRAY");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "expected close marker for ARRAY");
+        }
+    }
+
+    public void testUnclosedObject()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("{ \"key\" : 3  ");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected an exception for unclosed OBJECT");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "expected close marker for OBJECT");
+        }
+    }
+
+    public void testEOFInName()
+        throws Exception
+    {
+        final String JSON = "{ \"abcd";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp = (i == 0) ? createParserUsingReader(JSON)
+                : createParserUsingStream(JSON, "UTF-8");
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            try {
+                jp.nextToken();
+                fail("Expected an exception for EOF");
+            } catch (JsonParseException jpe) {
+                verifyException(jpe, "Unexpected end-of-input");
+            }
+            jp.close();
+        }
+    }
+
+    public void testWeirdToken()
+        throws Exception
+    {
+        final String JSON = "[ nil ]";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp = (i == 0) ? createParserUsingReader(JSON)
+                : createParserUsingStream(JSON, "UTF-8");
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            try {
+                jp.nextToken();
+                fail("Expected an exception for weird token");
+            } catch (JsonParseException jpe) {
+                verifyException(jpe, "Unrecognized token");
+            }
+            jp.close();
+        }
+    }
+
+    public void testMismatchArrayToObject()
+        throws Exception
+    {
+        final String JSON = "[ 1, 2 }";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp = (i == 0) ? createParserUsingReader(JSON)
+                : createParserUsingStream(JSON, "UTF-8");
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            try {
+                jp.nextToken();
+                fail("Expected an exception for incorrectly closed ARRAY");
+            } catch (JsonParseException jpe) {
+                verifyException(jpe, "Unexpected close marker '}': expected ']'");
+            }
+            jp.close();
+        }
+    }
+
+    public void testMismatchObjectToArray()
+        throws Exception
+    {
+        final String JSON = "{ ]";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp = (i == 0) ? createParserUsingReader(JSON)
+                : createParserUsingStream(JSON, "UTF-8");
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            
+            try {
+                jp.nextToken();
+                fail("Expected an exception for incorrectly closed OBJECT");
+            } catch (JsonParseException jpe) {
+                verifyException(jpe, "Unexpected close marker ']': expected '}'");
+            }
+            jp.close();
+        }
+    }
+
+    public void testMisssingColon()
+        throws Exception
+    {
+        final String JSON = "{ \"a\" \"b\" }";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp = (i == 0) ? createParserUsingReader(JSON)
+                : createParserUsingStream(JSON, "UTF-8");
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            try {
+                // can be either here, or with next one...
+                assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+                jp.nextToken();
+                fail("Expected an exception for missing semicolon");
+            } catch (JsonParseException jpe) {
+                verifyException(jpe, "was expecting a colon");
+            }
+            jp.close();
+        }
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestStringGeneration.java b/src/test/java/com/fasterxml/jackson/core/main/TestStringGeneration.java
new file mode 100644
index 0000000..c1274de
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestStringGeneration.java
@@ -0,0 +1,225 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.*;
+
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.util.Random;
+
+/**
+ * Set of basic unit tests for verifying that the string
+ * generation, including character escaping, works as expected.
+ */
+public class TestStringGeneration
+    extends BaseTest
+{
+    final static String[] SAMPLES = new String[] {
+        "\"test\"",
+        "\n", "\\n", "\r\n", "a\\b", "tab:\nok?",
+        "a\tb\tc\n\fdef\t \tg\"\"\"h\"\\ijklmn\b",
+        "\"\"\"", "\\r)'\"",
+        "Longer text & other stuff:\twith some\r\n\r\n random linefeeds etc added in to cause some \"special\" handling \\\\ to occur...\n"
+    };
+    
+    public void testBasicEscaping()
+        throws Exception
+    {
+        doTestBasicEscaping(false);
+        doTestBasicEscaping(true);
+    }
+
+    public void testLongerRandomSingleChunk()
+        throws Exception
+    {
+        /* Let's first generate 100k of pseudo-random characters, favoring
+         * 7-bit ascii range
+         */
+        for (int round = 0; round < 80; ++round) {
+            String content = generateRandom(75000+round);
+            doTestLongerRandom(content, false);
+            doTestLongerRandom(content, true);
+        }
+    }
+
+    public void testLongerRandomMultiChunk()
+        throws Exception
+    {
+        /* Let's first generate 100k of pseudo-random characters, favoring
+         * 7-bit ascii range
+         */
+        for (int round = 0; round < 70; ++round) {
+            String content = generateRandom(73000+round);
+            doTestLongerRandomMulti(content, false, round);
+            doTestLongerRandomMulti(content, true, round);
+        }
+    }
+
+    /*
+    /**********************************************************
+    /* Internal methods
+    /**********************************************************
+     */
+
+    private String generateRandom(int len)
+    {
+        StringBuilder sb = new StringBuilder(len+1000); // pad for surrogates
+        Random r = new Random(len);
+        for (int i = 0; i < len; ++i) {
+            if (r.nextBoolean()) { // non-ascii
+                int value = r.nextInt() & 0xFFFF;
+                // Otherwise easy, except that need to ensure that
+                // surrogates are properly paired: and, also
+                // their values do not exceed 0x10FFFF
+                if (value >= 0xD800 && value <= 0xDFFF) {
+                    // Let's discard first value, then, and produce valid pair
+                    int fullValue = (r.nextInt() & 0xFFFFF);
+                    sb.append((char) (0xD800 + (fullValue >> 10)));
+                    value = 0xDC00 + (fullValue & 0x3FF);
+                }
+                sb.append((char) value);
+            } else { // ascii
+                sb.append((char) (r.nextInt() & 0x7F));
+            }
+        }
+        return sb.toString();
+    }   
+
+    private void doTestBasicEscaping(boolean charArray)
+        throws Exception
+    {
+        for (int i = 0; i < SAMPLES.length; ++i) {
+            String VALUE = SAMPLES[i];
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createGenerator(sw);
+            gen.writeStartArray();
+            if (charArray) {
+                char[] buf = new char[VALUE.length() + i];
+                VALUE.getChars(0, VALUE.length(), buf, i);
+                gen.writeString(buf, i, VALUE.length());
+            } else {
+                gen.writeString(VALUE);
+            }
+            gen.writeEndArray();
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+            JsonToken t = jp.nextToken();
+            assertEquals(JsonToken.VALUE_STRING, t);
+            assertEquals(VALUE, jp.getText());
+            assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    private void doTestLongerRandom(String text, boolean charArray)
+        throws Exception
+    {
+        ByteArrayOutputStream bow = new ByteArrayOutputStream(text.length());
+        JsonGenerator gen = new JsonFactory().createGenerator(bow, JsonEncoding.UTF8);
+
+        gen.writeStartArray();
+        if (charArray) {
+            char[] buf = new char[text.length()];
+            text.getChars(0, text.length(), buf, 0);
+            gen.writeString(buf, 0, text.length());
+        } else {
+            gen.writeString(text);
+        }
+        gen.writeEndArray();
+        gen.close();
+        byte[] docData = bow.toByteArray();
+        JsonParser jp = new JsonFactory().createParser(new ByteArrayInputStream(docData));
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        JsonToken t = jp.nextToken();
+        assertEquals(JsonToken.VALUE_STRING, t);
+        String act = jp.getText();
+        if (!text.equals(act)) {
+            if (text.length() != act.length()) {
+                fail("Expected string length "+text.length()+", actual "+act.length());
+            }
+            int i = 0;
+            for (int len = text.length(); i < len; ++i) {
+                if (text.charAt(i) != act.charAt(i)) {
+                    break;
+                }
+            }
+            fail("Strings differ at position #"+i+" (len "+text.length()+"): expected char 0x"+Integer.toHexString(text.charAt(i))+", actual 0x"+Integer.toHexString(act.charAt(i)));
+        }
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    private void doTestLongerRandomMulti(String text, boolean charArray, int round)
+        throws Exception
+    {
+        ByteArrayOutputStream bow = new ByteArrayOutputStream(text.length());
+        JsonGenerator gen = new JsonFactory().createGenerator(bow, JsonEncoding.UTF8);
+        gen.writeStartArray();
+
+        gen.writeString(text);
+        gen.writeEndArray();
+        gen.close();
+        
+        gen = new JsonFactory().createGenerator(bow, JsonEncoding.UTF8);
+        gen.writeStartArray();
+        gen.writeStartArray();
+
+        Random rnd = new Random(text.length());
+        int offset = 0;
+
+        while (offset < text.length()) {
+            int shift = 1 + ((rnd.nextInt() & 0xFFFFF) % 12); // 1 - 12
+            int len = (1 << shift) + shift; // up to 4k
+            if ((offset + len) >= text.length()) {
+                len = text.length() - offset;
+            } else {
+            	// Need to avoid splitting surrogates though
+            	char c = text.charAt(offset+len-1);
+            	if (c >= 0xD800 && c < 0xDC00) {
+            		++len;
+            	}
+            }
+            if (charArray) {
+                char[] buf = new char[len];
+                text.getChars(offset, offset+len, buf, 0);
+                gen.writeString(buf, 0, len);
+            } else {
+                gen.writeString(text.substring(offset, offset+len));
+            }
+            offset += len;
+        }
+
+        gen.writeEndArray();
+        gen.close();
+        byte[] docData = bow.toByteArray();
+        JsonParser jp = new JsonFactory().createParser(new ByteArrayInputStream(docData));
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+
+        offset = 0;
+        while (jp.nextToken() == JsonToken.VALUE_STRING) {
+            // Let's verify, piece by piece
+            String act = jp.getText();
+            String exp = text.substring(offset, offset+act.length());
+            if (act.length() != exp.length()) {
+                fail("String segment ["+offset+" - "+(offset+act.length())+"[ differs; exp length "+exp+", actual "+act);                
+            }
+            if (!act.equals(exp)) {
+                int i = 0;
+                while (act.charAt(i) == exp.charAt(i)) {
+                    ++i;
+                }
+                fail("String segment ["+offset+" - "+(offset+act.length())+"[ different at offset #"+i
+                        +"; exp char 0x"+Integer.toHexString(exp.charAt(i))
+                        +", actual 0x"+Integer.toHexString(act.charAt(i)));
+            }
+            offset += act.length();
+        }
+        assertEquals(JsonToken.END_ARRAY, jp.getCurrentToken());
+        jp.close();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestUnicode.java b/src/test/java/com/fasterxml/jackson/core/main/TestUnicode.java
new file mode 100644
index 0000000..e150a01
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestUnicode.java
@@ -0,0 +1,38 @@
+package com.fasterxml.jackson.core.main;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestUnicode extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testSurrogates() throws Exception
+    {
+        JsonFactory f = new JsonFactory();
+        _testSurrogates(f, true);
+        _testSurrogates(f, false);
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private void _testSurrogates(JsonFactory f, boolean checkText) throws IOException
+    {
+        byte[] json = "{\"text\":\"\uD83D\uDE03\"}".getBytes("UTF-8");
+        // first
+        JsonParser jp = f.createParser(json);
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        if (checkText) {
+            assertEquals("text", jp.getText());
+        }
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        if (checkText) {
+            assertEquals("\uD83D\uDE03", jp.getText());
+        }
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestValueConversions.java b/src/test/java/com/fasterxml/jackson/core/main/TestValueConversions.java
new file mode 100644
index 0000000..d40cdff
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestValueConversions.java
@@ -0,0 +1,189 @@
+package com.fasterxml.jackson.core.main;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+
+/**
+ * @since 1.6
+ */
+public class TestValueConversions
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testAsInt() throws Exception
+    {
+        final String input = "[ 1, -3, 4.98, true, false, null, \"-17\", \"foo\" ]";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp;
+            if (i == 0) {
+                jp = createParserUsingReader(input);                
+            } else {
+                jp = this.createParserUsingStream(input, "UTF-8");
+            }
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertEquals(0, jp.getValueAsLong());
+            assertEquals(9, jp.getValueAsLong(9));
+
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(1, jp.getValueAsLong());
+            assertEquals(1, jp.getValueAsLong(-99));
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(-3, jp.getValueAsLong());
+            assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+            assertEquals(4, jp.getValueAsLong());
+            assertEquals(4, jp.getValueAsLong(99));
+            assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+            assertEquals(1, jp.getValueAsLong());
+            assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+            assertEquals(0, jp.getValueAsLong());
+            assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+            assertEquals(0, jp.getValueAsLong());
+            assertEquals(0, jp.getValueAsLong(27));
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(-17, jp.getValueAsLong());
+            assertEquals(-17, jp.getValueAsLong(3));
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(0, jp.getValueAsLong());
+            assertEquals(9, jp.getValueAsLong(9));
+            
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+            assertEquals(0, jp.getValueAsLong());
+            assertEquals(9, jp.getValueAsLong(9));
+
+            jp.close();
+        }     
+    }
+
+    /**
+     * @since 1.7
+     */
+    public void testAsBoolean() throws Exception
+    {
+        final String input = "[ true, false, null, 1, 0, \"true\", \"false\", \"foo\" ]";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp;
+            if (i == 0) {
+                jp = createParserUsingReader(input);                
+            } else {
+                jp = this.createParserUsingStream(input, "UTF-8");
+            }
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertEquals(false, jp.getValueAsBoolean());
+            assertEquals(true, jp.getValueAsBoolean(true));
+
+            assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+            assertEquals(true, jp.getValueAsBoolean());
+            assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+            assertEquals(false, jp.getValueAsBoolean());
+            assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+            assertEquals(false, jp.getValueAsBoolean());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(1, jp.getIntValue());
+            assertEquals(true, jp.getValueAsBoolean());
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(0, jp.getIntValue());
+            assertEquals(false, jp.getValueAsBoolean());
+
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(true, jp.getValueAsBoolean());
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(false, jp.getValueAsBoolean());
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(false, jp.getValueAsBoolean());
+            
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+            assertEquals(false, jp.getValueAsBoolean());
+            assertEquals(true, jp.getValueAsBoolean(true));
+
+            jp.close();
+        }     
+    }
+    
+    public void testAsLong() throws Exception
+    {
+        final String input = "[ 1, -3, 4.98, true, false, null, \"-17\", \"foo\" ]";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp;
+            if (i == 0) {
+                jp = createParserUsingReader(input);                
+            } else {
+                jp = this.createParserUsingStream(input, "UTF-8");
+            }
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertEquals(0L, jp.getValueAsLong());
+            assertEquals(9L, jp.getValueAsLong(9L));
+
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(1L, jp.getValueAsLong());
+            assertEquals(1L, jp.getValueAsLong(-99L));
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(-3L, jp.getValueAsLong());
+            assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+            assertEquals(4L, jp.getValueAsLong());
+            assertEquals(4L, jp.getValueAsLong(99L));
+            assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+            assertEquals(1L, jp.getValueAsLong());
+            assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+            assertEquals(0L, jp.getValueAsLong());
+            assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+            assertEquals(0L, jp.getValueAsLong());
+            assertEquals(0L, jp.getValueAsLong(27L));
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(-17L, jp.getValueAsLong());
+            assertEquals(-17L, jp.getValueAsLong(3L));
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(0L, jp.getValueAsLong());
+            assertEquals(9L, jp.getValueAsLong(9L));
+            
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+            assertEquals(0L, jp.getValueAsLong());
+            assertEquals(9L, jp.getValueAsLong(9L));
+
+            jp.close();
+        }     
+    }
+
+    public void testAsDouble() throws Exception
+    {
+        final String input = "[ 1, -3, 4.98, true, false, null, \"-17.25\", \"foo\" ]";
+        for (int i = 0; i < 2; ++i) {
+            JsonParser jp;
+            if (i == 0) {
+                jp = createParserUsingReader(input);                
+            } else {
+                jp = this.createParserUsingStream(input, "UTF-8");
+            }
+            assertToken(JsonToken.START_ARRAY, jp.nextToken());
+            assertEquals(0.0, jp.getValueAsDouble());
+            assertEquals(9.0, jp.getValueAsDouble(9.0));
+
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(1., jp.getValueAsDouble());
+            assertEquals(1., jp.getValueAsDouble(-99.0));
+            assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+            assertEquals(-3., jp.getValueAsDouble());
+            assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+            assertEquals(4.98, jp.getValueAsDouble());
+            assertEquals(4.98, jp.getValueAsDouble(12.5));
+            assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+            assertEquals(1.0, jp.getValueAsDouble());
+            assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+            assertEquals(0.0, jp.getValueAsDouble());
+            assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+            assertEquals(0.0, jp.getValueAsDouble());
+            assertEquals(0.0, jp.getValueAsDouble(27.8));
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(-17.25, jp.getValueAsDouble());
+            assertEquals(-17.25, jp.getValueAsDouble(1.9));
+            assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+            assertEquals(0.0, jp.getValueAsDouble());
+            assertEquals(1.25, jp.getValueAsDouble(1.25));
+            
+            assertToken(JsonToken.END_ARRAY, jp.nextToken());
+            assertEquals(0.0, jp.getValueAsDouble());
+            assertEquals(7.5, jp.getValueAsDouble(7.5));
+
+            jp.close();
+        }     
+    }
+    
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/main/TestWithTonsaSymbols.java b/src/test/java/com/fasterxml/jackson/core/main/TestWithTonsaSymbols.java
new file mode 100644
index 0000000..6fd30e4
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/main/TestWithTonsaSymbols.java
@@ -0,0 +1,82 @@
+package com.fasterxml.jackson.core.main;
+
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.test.BaseTest;
+
+import java.io.*;
+
+/**
+ * Some unit tests to try to exercise part of parser code that
+ * deals with symbol (table) management.
+ */
+public class TestWithTonsaSymbols
+    extends BaseTest
+{
+    /**
+     * How many fields to generate? Since maximum symbol table
+     * size is defined as 6000 (above which table gets cleared,
+     * assuming the name vocabulary is unbounded), let's do something
+     * just slightly below it.
+     */
+    final static int FIELD_COUNT = 5000;
+
+    public void testStreamReaderParser() throws Exception
+    {
+        _testWith(true);
+    }
+
+    public void testReaderParser() throws Exception
+    {
+        _testWith(false);
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private void _testWith(boolean useStream)
+        throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        String doc = buildDoc(FIELD_COUNT);
+
+        /* And let's do this multiple times: just so that symbol table
+         * state is different between runs.
+         */
+        for (int x = 0; x < 3; ++x) {
+            JsonParser jp = useStream ?
+                jf.createParser(new ByteArrayInputStream(doc.getBytes("UTF-8")))
+                : jf.createParser(new StringReader(doc));
+            assertToken(JsonToken.START_OBJECT, jp.nextToken());
+            for (int i = 0; i < FIELD_COUNT; ++i) {
+                assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+                assertEquals(fieldNameFor(i), jp.getCurrentName());
+                assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+                assertEquals(i, jp.getIntValue());
+            }
+            assertToken(JsonToken.END_OBJECT, jp.nextToken());
+            jp.close();
+        }
+    }
+        
+    private String buildDoc(int len)
+    {
+        StringBuilder sb = new StringBuilder(len * 12);
+        sb.append('{');
+        for (int i = 0; i < len; ++i) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append('"');
+            fieldNameFor(sb, i);
+            sb.append('"');
+            sb.append(':');
+            sb.append(i);
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestByteBasedSymbols.java b/src/test/java/com/fasterxml/jackson/core/sym/TestByteBasedSymbols.java
new file mode 100644
index 0000000..b2cc82c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/sym/TestByteBasedSymbols.java
@@ -0,0 +1,139 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.io.*;
+
+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...).
+ */
+public class TestByteBasedSymbols
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    final static String[] FIELD_NAMES = new String[] {
+        "a", "b", "c", "x", "y", "b13", "abcdefg", "a123",
+        "a0", "b0", "c0", "d0", "e0", "f0", "g0", "h0",
+        "x2", "aa", "ba", "ab", "b31", "___x", "aX", "xxx",
+        "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2",
+        "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3",
+        "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1",
+    };
+
+    /**
+     * This unit test checks that [JACKSON-5] is fixed; if not, a
+     * symbol table corruption should result in odd problems.
+     */
+    public void testSharedSymbols()
+        throws Exception
+    {
+        // MUST share a single json factory
+        JsonFactory jf = new JsonFactory();
+
+        /* First things first: parse a dummy doc to populate
+         * shared symbol table with some stuff
+         */
+        String DOC0 = "{ \"a\" : 1, \"x\" : [ ] }";
+        JsonParser jp0 = createParser(jf, DOC0);
+
+        /* Important: don't close, don't traverse past end.
+         * This is needed to create partial still-in-use symbol
+         * table...
+         */
+        while (jp0.nextToken() != JsonToken.START_ARRAY) { }
+
+        String doc1 = createDoc(FIELD_NAMES, true);
+        String doc2 = createDoc(FIELD_NAMES, false);
+
+        // Let's run it twice... shouldn't matter
+        for (int x = 0; x < 2; ++x) {
+            JsonParser jp1 = createParser(jf, doc1);
+            JsonParser jp2 = createParser(jf, doc2);
+
+            assertToken(JsonToken.START_OBJECT, jp1.nextToken());
+            assertToken(JsonToken.START_OBJECT, jp2.nextToken());
+            
+            int len = FIELD_NAMES.length;
+            for (int i = 0; i < len; ++i) {
+                assertToken(JsonToken.FIELD_NAME, jp1.nextToken());
+                assertToken(JsonToken.FIELD_NAME, jp2.nextToken());
+                assertEquals(FIELD_NAMES[i], jp1.getCurrentName());
+                assertEquals(FIELD_NAMES[len-(i+1)], jp2.getCurrentName());
+                assertToken(JsonToken.VALUE_NUMBER_INT, jp1.nextToken());
+                assertToken(JsonToken.VALUE_NUMBER_INT, jp2.nextToken());
+                assertEquals(i, jp1.getIntValue());
+                assertEquals(i, jp2.getIntValue());
+            }
+            
+            assertToken(JsonToken.END_OBJECT, jp1.nextToken());
+            assertToken(JsonToken.END_OBJECT, jp2.nextToken());
+            
+            jp1.close();
+            jp2.close();
+        }
+    }
+
+    public void testAuxMethods()
+        throws Exception
+    {
+        final int A_BYTES = 0x41414141; // "AAAA"
+        final int B_BYTES = 0x42424242; // "BBBB"
+
+        BytesToNameCanonicalizer nc = BytesToNameCanonicalizer.createRoot()
+                .makeChild(true, true);
+        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());
+        nc.addName("AAAABBBB", new int[] { A_BYTES, B_BYTES }, 2);
+        Name n2 = nc.findName(A_BYTES, B_BYTES);
+        assertEquals("AAAABBBB", n2.getName());
+        assertNotNull(n2);
+
+        /* and let's then just exercise this method so it gets covered;
+         * it's only used for debugging.
+         */
+        assertNotNull(nc.toString());
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    protected JsonParser createParser(JsonFactory jf, String input)
+        throws IOException, JsonParseException
+    {
+        byte[] data = input.getBytes("UTF-8");
+        InputStream is = new ByteArrayInputStream(data);
+        return jf.createParser(is);
+    }
+
+    private String createDoc(String[] fieldNames, boolean add)
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{ ");
+
+        int len = fieldNames.length;
+        for (int i = 0; i < len; ++i) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append('"');
+            sb.append(add ? fieldNames[i] : fieldNames[len - (i+1)]);
+            sb.append("\" : ");
+            sb.append(i);
+        }
+        sb.append(" }");
+        return sb.toString();
+    }
+}
+
+
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestJsonParserSymbols.java b/src/test/java/com/fasterxml/jackson/core/sym/TestJsonParserSymbols.java
new file mode 100644
index 0000000..5a5bba2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/sym/TestJsonParserSymbols.java
@@ -0,0 +1,105 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.json.ReaderBasedJsonParser;
+import com.fasterxml.jackson.core.json.UTF8StreamJsonParser;
+
+/**
+ * Unit tests for verifying that {@link JsonParser} instances properly
+ * merge back symbols to the root symbol table
+ */
+ at SuppressWarnings("serial")
+public class TestJsonParserSymbols
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    /**
+     * To peek into state of "root" symbol tables (parent of all symbol
+     * tables for parsers constructed by this factory) we need to
+     * add some methods.
+     */
+    final static class MyJsonFactory extends JsonFactory
+    {
+        public int byteSymbolCount() { return _rootByteSymbols.size(); }
+        public int charSymbolCount() { return _rootCharSymbols.size(); }
+    }
+
+    final static String JSON = "{ \"a\" : 3, \"aaa\" : 4, \"_a\" : 0 }";
+
+    public void testByteSymbolsWithClose() throws Exception
+    {
+        _testWithClose(true);
+    }
+
+    public void testByteSymbolsWithEOF() throws Exception
+    {
+        MyJsonFactory f = new MyJsonFactory();
+        JsonParser jp = _getParser(f, JSON, true);
+        while (jp.nextToken() != null) {
+            // shouldn't update before hitting end
+            assertEquals(0, f.byteSymbolCount());
+        }
+        // but now should have it after hitting EOF
+        assertEquals(3, f.byteSymbolCount());
+        jp.close();
+        assertEquals(3, f.byteSymbolCount());
+    }
+
+    public void testCharSymbolsWithClose() throws Exception
+    {
+        _testWithClose(false);
+    }
+
+    public void testCharSymbolsWithEOF() throws Exception
+    {
+        MyJsonFactory f = new MyJsonFactory();
+        JsonParser jp = _getParser(f, JSON, false);
+        while (jp.nextToken() != null) {
+            // shouldn't update before hitting end
+            assertEquals(0, f.charSymbolCount());
+        }
+        // but now should have it
+        assertEquals(3, f.charSymbolCount());
+        jp.close();
+        assertEquals(3, f.charSymbolCount());
+    }
+
+    /*
+    /**********************************************************
+    /* Helper methods
+    /**********************************************************
+     */
+
+    private void _testWithClose(boolean useBytes) throws IOException
+    {
+        MyJsonFactory f = new MyJsonFactory();
+        JsonParser jp = _getParser(f, JSON, useBytes);
+        // Let's check 2 names
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+
+        // shouldn't update before close or EOF:
+        assertEquals(0, useBytes ? f.byteSymbolCount() : f.charSymbolCount());
+        jp.close();
+        // but should after close
+        assertEquals(2, useBytes ? f.byteSymbolCount() : f.charSymbolCount());
+    }
+
+    private JsonParser _getParser(MyJsonFactory f, String doc, boolean useBytes) throws IOException
+    {
+        JsonParser jp;
+        if (useBytes) {
+            jp = f.createParser(doc.getBytes("UTF-8"));
+            assertEquals(UTF8StreamJsonParser.class, jp.getClass());
+            assertEquals(0, f.byteSymbolCount());
+        } else {
+            jp = f.createParser(doc);
+            assertEquals(ReaderBasedJsonParser.class, jp.getClass());
+            assertEquals(0, f.charSymbolCount());
+        }
+        return jp;
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolTables.java b/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolTables.java
new file mode 100644
index 0000000..ed3bb28
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/sym/TestSymbolTables.java
@@ -0,0 +1,109 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.io.IOException;
+
+public class TestSymbolTables extends com.fasterxml.jackson.test.BaseTest
+{
+    // 11 3-char snippets that hash to 0xFFFF (with default JDK hashCode() calc),
+    // and which can be combined as
+    // sequences, like, say, 11x11x11 (1331) 9-character thingies
+    final static String[] CHAR_COLLISION_SNIPPETS_31 = {
+        "@~}", "@\u007f^", "A_}", "A`^", 
+        "Aa?", "B@}", "BA^", "BB?", 
+        "C!}", "C\"^", "C#?"
+    };
+
+    final static String[] CHAR_COLLISIONS;
+    static {
+        final String[] SNIPPETS = CHAR_COLLISION_SNIPPETS_31;
+        
+        final int len = SNIPPETS.length;
+        CHAR_COLLISIONS = new String[len*len*len];
+        int ix = 0;
+        for (int i1 = 0; i1 < len; ++i1) {
+            for (int i2 = 0; i2 < len; ++i2) {
+                for (int i3 = 0; i3 < len; ++i3) {
+                    CHAR_COLLISIONS[ix++] = SNIPPETS[i1]+SNIPPETS[i2] + SNIPPETS[i3];
+                }
+            }
+        }
+    }
+
+    /*
+    public void testCharBasedCollisions()
+    {
+        CharsToNameCanonicalizer sym = CharsToNameCanonicalizer.createRoot(0);
+
+        // first, verify that we'd get a few collisions...
+        try {
+            int firstHash = 0;
+            for (String str : CHAR_COLLISIONS) {
+                int hash = sym.calcHash(str);
+                if (firstHash == 0) {
+                    firstHash = hash;
+                } else {
+                    assertEquals(firstHash, hash); 
+                }
+                sym.findSymbol(str.toCharArray(), 0, str.length(), hash);
+            }
+            fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+            verifyException(e, "exceeds maximum");
+            // should fail right after addition:
+            assertEquals(CharsToNameCanonicalizer.MAX_COLL_CHAIN_LENGTH+1, sym.maxCollisionLength());
+            assertEquals(CharsToNameCanonicalizer.MAX_COLL_CHAIN_LENGTH+1, sym.collisionCount());
+            // one "non-colliding" entry (head of collision chain), thus:
+            assertEquals(CharsToNameCanonicalizer.MAX_COLL_CHAIN_LENGTH+2, sym.size());
+        }
+    }
+    */
+
+    // Test for verifying stability of hashCode, wrt collisions, using
+    // synthetic field name generation and character-based input
+    public void testSyntheticWithChars()
+    {
+        // pass seed, to keep results consistent:
+        CharsToNameCanonicalizer symbols = CharsToNameCanonicalizer.createRoot(1);
+        final int COUNT = 6000;
+        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(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(1401, symbols.collisionCount());
+        // esp. with collisions; first got about 30
+        assertEquals(4, 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
+    {
+        // pass seed, to keep results consistent:
+        BytesToNameCanonicalizer symbols =
+                BytesToNameCanonicalizer.createRoot(33333).makeChild(true, true);
+        final int COUNT = 6000;
+        for (int i = 0; i < COUNT; ++i) {
+            String id = fieldNameFor(i);
+            int[] quads = BytesToNameCanonicalizer.calcQuads(id.getBytes("UTF-8"));
+            symbols.addName(id, quads, quads.length);
+        }
+        assertEquals(COUNT, symbols.size());
+        assertEquals(8192, symbols.bucketCount());
+
+//System.out.printf("Byte stuff: collisions %d, max-coll %d\n", symbols.collisionCount(), symbols.maxCollisionLength());
+    
+        // Fewer collisions than with chars, but still quite a few
+        assertEquals(1686, symbols.collisionCount());
+        // but not super long collision chains:
+        assertEquals(9, symbols.maxCollisionLength());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/test/PackageVersion.java b/src/test/java/com/fasterxml/jackson/core/test/PackageVersion.java
new file mode 100644
index 0000000..b6bbfb7
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/test/PackageVersion.java
@@ -0,0 +1,17 @@
+package com.fasterxml.jackson.core.test;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Helper class used for verifying that auto-generated <code>PackageVersion</code>
+ * classes can be used for verification.
+ */
+public final class PackageVersion implements Versioned {
+	@Override
+	public Version version() {
+		return VersionUtil.parseVersion(
+	            "23.42.64738-foobar", "foobar-group", "foobar-artifact");
+	}
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/test/TestPackageVersion.java b/src/test/java/com/fasterxml/jackson/core/test/TestPackageVersion.java
new file mode 100644
index 0000000..928740c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/test/TestPackageVersion.java
@@ -0,0 +1,14 @@
+package com.fasterxml.jackson.core.test;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+public class TestPackageVersion extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testPackageVersion()
+    {
+        Version expected = new Version(23, 42, 64738, "foobar", "foobar-group", "foobar-artifact");
+        assertEquals(expected, VersionUtil.packageVersionFor(this.getClass()));
+        assertEquals(expected, VersionUtil.versionFor(this.getClass()));
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestCharTypes.java b/src/test/java/com/fasterxml/jackson/core/util/TestCharTypes.java
new file mode 100644
index 0000000..8401c36
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestCharTypes.java
@@ -0,0 +1,17 @@
+package com.fasterxml.jackson.core.util;
+
+import com.fasterxml.jackson.core.io.CharTypes;
+
+public class TestCharTypes
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testQuoting()
+    {
+        StringBuilder sb = new StringBuilder();
+        CharTypes.appendQuoted(sb, "\n");
+        assertEquals("\\n", sb.toString());
+        sb = new StringBuilder();
+        CharTypes.appendQuoted(sb, "\u0000");
+        assertEquals("\\u0000", sb.toString());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java b/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java
new file mode 100644
index 0000000..a461e50
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestDelegates extends com.fasterxml.jackson.test.BaseTest
+{
+    /**
+     * 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());
+    }
+
+    /**
+     * Test default, non-overridden generator delegate.
+     */
+    public void testGeneratorDelegate() throws IOException
+    {
+        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());
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestNumberPrinting.java b/src/test/java/com/fasterxml/jackson/core/util/TestNumberPrinting.java
new file mode 100644
index 0000000..cd83509
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestNumberPrinting.java
@@ -0,0 +1,102 @@
+package com.fasterxml.jackson.core.util;
+
+import java.util.Random;
+
+import com.fasterxml.jackson.core.io.NumberOutput;
+
+/**
+ * Set of basic unit tests for verifying that the low-level number
+ * printingg methods work as expected.
+ */
+public class TestNumberPrinting
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testIntPrinting()
+        throws Exception
+    {
+        assertIntPrint(0);
+        assertIntPrint(-3);
+        assertIntPrint(1234);
+        assertIntPrint(-1234);
+        assertIntPrint(56789);
+        assertIntPrint(-56789);
+        assertIntPrint(999999);
+        assertIntPrint(-999999);
+        assertIntPrint(1000000);
+        assertIntPrint(-1000000);
+        assertIntPrint(10000001);
+        assertIntPrint(-10000001);
+        assertIntPrint(-100000012);
+        assertIntPrint(100000012);
+        assertIntPrint(1999888777);
+        assertIntPrint(-1999888777);
+        assertIntPrint(Integer.MAX_VALUE);
+        assertIntPrint(Integer.MIN_VALUE);
+
+        Random rnd = new Random(12345L);
+        for (int i = 0; i < 251000; ++i) {
+            assertIntPrint(rnd.nextInt());
+        }
+    }
+
+    public void testLongPrinting()
+        throws Exception
+    {
+        // First, let's just cover couple of edge cases
+        assertLongPrint(0L, 0);
+        assertLongPrint(1L, 0);
+        assertLongPrint(-1L, 0);
+        assertLongPrint(Long.MAX_VALUE, 0);
+        assertLongPrint(Long.MIN_VALUE, 0);
+        assertLongPrint(Long.MAX_VALUE-1L, 0);
+        assertLongPrint(Long.MIN_VALUE+1L, 0);
+
+        Random rnd = new Random(12345L);
+        // Bigger value space, need more iterations for long
+        for (int i = 0; i < 678000; ++i) {
+            long l = ((long) rnd.nextInt() << 32) | (long) rnd.nextInt();
+            assertLongPrint(l, i);
+        }
+    }
+
+    /*
+    ////////////////////////////////////////////////////////
+    // Internal methods
+    ////////////////////////////////////////////////////////
+     */
+
+    private void assertIntPrint(int value)
+    {
+        String exp = ""+value;
+        String act = printToString(value);
+
+        if (!exp.equals(act)) {
+            assertEquals("Expected conversion (exp '"+exp+"', len "+exp.length()+"; act len "+act.length()+")", exp, act);
+        }
+    }
+
+    private void assertLongPrint(long value, int index)
+    {
+        String exp = ""+value;
+        String act = printToString(value);
+
+        if (!exp.equals(act)) {
+            assertEquals("Expected conversion (exp '"+exp+"', len "+exp.length()+"; act len "+act.length()+"; number index "+index+")", exp, act);
+        }
+    }
+
+    private String printToString(int value)
+    {
+        char[] buffer = new char[12];
+        int offset = NumberOutput.outputInt(value, buffer, 0);
+        return new String(buffer, 0, offset);
+    }
+
+    private String printToString(long value)
+    {
+        char[] buffer = new char[22];
+        int offset = NumberOutput.outputLong(value, buffer, 0);
+        return new String(buffer, 0, offset);
+    }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestSerializedString.java b/src/test/java/com/fasterxml/jackson/core/util/TestSerializedString.java
new file mode 100644
index 0000000..528baf8
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestSerializedString.java
@@ -0,0 +1,60 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import com.fasterxml.jackson.core.SerializableString;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+/**
+ * Simple unit tests to try to verify that the default
+ * {@link SerializableString} implementation works as expected.
+ */
+public class TestSerializedString
+    extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testAppending() throws IOException
+    {
+        final String INPUT = "\"quo\\ted\"";
+        final String QUOTED = "\\\"quo\\\\ted\\\"";
+        
+        SerializableString sstr = new SerializedString(INPUT);
+        // sanity checks first:
+        assertEquals(sstr.getValue(), INPUT);
+        assertEquals(QUOTED, new String(sstr.asQuotedChars()));
+
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        assertEquals(QUOTED.length(), sstr.writeQuotedUTF8(bytes));
+        assertEquals(QUOTED, bytes.toString("UTF-8"));
+        bytes.reset();
+        assertEquals(INPUT.length(), sstr.writeUnquotedUTF8(bytes));
+        assertEquals(INPUT, bytes.toString("UTF-8"));
+
+        byte[] buffer = new byte[100];
+        assertEquals(QUOTED.length(), sstr.appendQuotedUTF8(buffer, 3));
+        assertEquals(QUOTED, new String(buffer, 3, QUOTED.length()));
+        Arrays.fill(buffer, (byte) 0);
+        assertEquals(INPUT.length(), sstr.appendUnquotedUTF8(buffer, 5));
+        assertEquals(INPUT, new String(buffer, 5, INPUT.length()));
+    }
+
+    public void testFailedAccess() throws IOException
+    {
+        final String INPUT = "Bit longer text";
+        SerializableString sstr = new SerializedString(INPUT);
+
+        final byte[] buffer = new byte[INPUT.length() - 2];
+        final char[] ch = new char[INPUT.length() - 2];
+        final ByteBuffer bbuf = ByteBuffer.allocate(INPUT.length() - 2);
+        
+        assertEquals(-1, sstr.appendQuotedUTF8(buffer, 0));
+        assertEquals(-1, sstr.appendQuoted(ch, 0));
+        assertEquals(-1, sstr.putQuotedUTF8(bbuf));
+
+        bbuf.rewind();
+        assertEquals(-1, sstr.appendUnquotedUTF8(buffer, 0));
+        assertEquals(-1, sstr.appendUnquoted(ch, 0));
+        assertEquals(-1, sstr.putUnquotedUTF8(bbuf));
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java b/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java
new file mode 100644
index 0000000..c4be4cd
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java
@@ -0,0 +1,65 @@
+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.test.BaseTest
+{
+    /**
+     * Trivially simple basic test to ensure all basic append
+     * methods work
+     */
+    public void testSimple()
+    {
+        TextBuffer tb = new TextBuffer(new BufferRecycler());
+        tb.append('a');
+        tb.append(new char[] { 'X', 'b' }, 1, 1);
+        tb.append("c", 0, 1);
+        assertEquals(3, tb.contentsAsArray().length);
+        assertEquals("abc", tb.toString());
+
+        assertNotNull(tb.expandCurrentSegment());
+    }
+
+    public void testLonger()
+    {
+        TextBuffer tb = new TextBuffer(new BufferRecycler());
+        for (int i = 0; i < 2000; ++i) {
+            tb.append("abc", 0, 3);
+        }
+        String str = tb.contentsAsString();
+        assertEquals(6000, str.length());
+        assertEquals(6000, tb.contentsAsArray().length);
+
+        tb.resetWithShared(new char[] { 'a' }, 0, 1);
+        assertEquals(1, tb.toString().length());
+    }
+
+      public void testLongAppend()
+      {
+          final int len = TextBuffer.MAX_SEGMENT_LEN * 3 / 2;
+          StringBuilder sb = new StringBuilder(len);
+          for (int i = 0; i < len; ++i) {
+              sb.append('x');
+          }
+         final String STR = sb.toString();
+         final String EXP = "a" + STR + "c";
+ 
+         // ok: first test with String:
+         TextBuffer tb = new TextBuffer(new BufferRecycler());
+         tb.append('a');
+         tb.append(STR, 0, len);
+         tb.append('c');
+         assertEquals(len+2, tb.size());
+         assertEquals(EXP, tb.contentsAsString());
+ 
+         // then char[]
+         tb = new TextBuffer(new BufferRecycler());
+         tb.append('a');
+         tb.append(STR.toCharArray(), 0, len);
+         tb.append('c');
+         assertEquals(len+2, tb.size());
+         assertEquals(EXP, tb.contentsAsString());
+      }
+}
diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestVersionUtil.java b/src/test/java/com/fasterxml/jackson/core/util/TestVersionUtil.java
new file mode 100644
index 0000000..c4aabdd
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/util/TestVersionUtil.java
@@ -0,0 +1,30 @@
+package com.fasterxml.jackson.core.util;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.json.PackageVersion;
+import com.fasterxml.jackson.core.json.UTF8JsonGenerator;
+
+public class TestVersionUtil extends com.fasterxml.jackson.test.BaseTest
+{
+    public void testVersionPartParsing()
+    {
+        assertEquals(13, VersionUtil.parseVersionPart("13"));
+        assertEquals(27, VersionUtil.parseVersionPart("27.8"));
+        assertEquals(0, VersionUtil.parseVersionPart("-3"));
+    }
+
+    public void testVersionParsing()
+    {
+        assertEquals(new Version(1, 2, 15, "foo", "group", "artifact"),
+                VersionUtil.parseVersion("1.2.15-foo", "group", "artifact"));
+    }
+
+    public void testMavenVersionParsing() {
+        assertEquals(new Version(1, 2, 3, "SNAPSHOT", "foo.bar", "foo-bar"),
+                VersionUtil.mavenVersionFor(TestVersionUtil.class.getClassLoader(), "foo.bar", "foo-bar"));
+    }
+
+    public void testPackageVersionMatches() {
+        assertEquals(PackageVersion.VERSION, VersionUtil.versionFor(UTF8JsonGenerator.class));
+    }
+}
diff --git a/src/test/java/com/fasterxml/jackson/test/BaseTest.java b/src/test/java/com/fasterxml/jackson/test/BaseTest.java
new file mode 100644
index 0000000..a4cf87a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/test/BaseTest.java
@@ -0,0 +1,432 @@
+package com.fasterxml.jackson.test;
+
+import java.io.*;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import com.fasterxml.jackson.core.*;
+
+//import static org.junit.Assert.*;
+
+public abstract class BaseTest
+    extends TestCase
+{
+    protected final static String FIELD_BASENAME = "f";
+    
+    /*
+    /**********************************************************
+    /* Some sample documents:
+    /**********************************************************
+     */
+
+    protected final static int SAMPLE_SPEC_VALUE_WIDTH = 800;
+    protected final static int SAMPLE_SPEC_VALUE_HEIGHT = 600;
+    protected final static String SAMPLE_SPEC_VALUE_TITLE = "View from 15th Floor";
+    protected final static String SAMPLE_SPEC_VALUE_TN_URL = "http://www.example.com/image/481989943";
+    protected final static int SAMPLE_SPEC_VALUE_TN_HEIGHT = 125;
+    protected final static String SAMPLE_SPEC_VALUE_TN_WIDTH = "100";
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID1 = 116;
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID2 = 943;
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID3 = 234;
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID4 = 38793;
+
+    protected final static String SAMPLE_DOC_JSON_SPEC = 
+        "{\n"
+        +"  \"Image\" : {\n"
+        +"    \"Width\" : "+SAMPLE_SPEC_VALUE_WIDTH+",\n"
+        +"    \"Height\" : "+SAMPLE_SPEC_VALUE_HEIGHT+","
+        +"\"Title\" : \""+SAMPLE_SPEC_VALUE_TITLE+"\",\n"
+        +"    \"Thumbnail\" : {\n"
+        +"      \"Url\" : \""+SAMPLE_SPEC_VALUE_TN_URL+"\",\n"
+        +"\"Height\" : "+SAMPLE_SPEC_VALUE_TN_HEIGHT+",\n"
+        +"      \"Width\" : \""+SAMPLE_SPEC_VALUE_TN_WIDTH+"\"\n"
+        +"    },\n"
+        +"    \"IDs\" : ["+SAMPLE_SPEC_VALUE_TN_ID1+","+SAMPLE_SPEC_VALUE_TN_ID2+","+SAMPLE_SPEC_VALUE_TN_ID3+","+SAMPLE_SPEC_VALUE_TN_ID4+"]\n"
+        +"  }"
+        +"}"
+        ;
+
+    /*
+    /**********************************************************
+    /* Helper classes (beans)
+    /**********************************************************
+     */
+    
+    /**
+     * Sample class from Jackson tutorial ("JacksonInFiveMinutes")
+     */
+    protected static class FiveMinuteUser {
+        public enum Gender { MALE, FEMALE };
+
+        public static class Name
+        {
+          private String _first, _last;
+
+          public Name() { }
+          public Name(String f, String l) {
+              _first = f;
+              _last = l;
+          }
+          
+          public String getFirst() { return _first; }
+          public String getLast() { return _last; }
+
+          public void setFirst(String s) { _first = s; }
+          public void setLast(String s) { _last = s; }
+
+          @Override
+          public boolean equals(Object o)
+          {
+              if (o == this) return true;
+              if (o == null || o.getClass() != getClass()) return false;
+              Name other = (Name) o;
+              return _first.equals(other._first) && _last.equals(other._last); 
+          }
+        }
+
+        private Gender _gender;
+        private Name _name;
+        private boolean _isVerified;
+        private byte[] _userImage;
+
+        public FiveMinuteUser() { }
+
+        public FiveMinuteUser(String first, String last, boolean verified, Gender g, byte[] data)
+        {
+            _name = new Name(first, last);
+            _isVerified = verified;
+            _gender = g;
+            _userImage = data;
+        }
+        
+        public Name getName() { return _name; }
+        public boolean isVerified() { return _isVerified; }
+        public Gender getGender() { return _gender; }
+        public byte[] getUserImage() { return _userImage; }
+
+        public void setName(Name n) { _name = n; }
+        public void setVerified(boolean b) { _isVerified = b; }
+        public void setGender(Gender g) { _gender = g; }
+        public void setUserImage(byte[] b) { _userImage = b; }
+
+        @Override
+        public boolean equals(Object o)
+        {
+            if (o == this) return true;
+            if (o == null || o.getClass() != getClass()) return false;
+            FiveMinuteUser other = (FiveMinuteUser) o;
+            if (_isVerified != other._isVerified) return false;
+            if (_gender != other._gender) return false; 
+            if (!_name.equals(other._name)) return false;
+            byte[] otherImage = other._userImage;
+            if (otherImage.length != _userImage.length) return false;
+            for (int i = 0, len = _userImage.length; i < len; ++i) {
+                if (_userImage[i] != otherImage[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+    
+    /*
+    /**********************************************************
+    /* High-level helpers
+    /**********************************************************
+     */
+
+    protected void verifyJsonSpecSampleDoc(JsonParser jp, boolean verifyContents)
+        throws IOException
+    {
+        verifyJsonSpecSampleDoc(jp, verifyContents, true);
+    }
+
+    protected void verifyJsonSpecSampleDoc(JsonParser jp, boolean verifyContents,
+            boolean requireNumbers)
+        throws IOException
+    {
+        if (!jp.hasCurrentToken()) {
+            jp.nextToken();
+        }
+        assertToken(JsonToken.START_OBJECT, jp.getCurrentToken()); // main object
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Image'
+        if (verifyContents) {
+            verifyFieldName(jp, "Image");
+        }
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken()); // 'image' object
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Width'
+        if (verifyContents) {
+            verifyFieldName(jp, "Width");
+        }
+
+        verifyIntToken(jp.nextToken(), requireNumbers);
+        if (verifyContents) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_WIDTH);
+        }
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Height'
+        if (verifyContents) {
+            verifyFieldName(jp, "Height");
+        }
+
+        verifyIntToken(jp.nextToken(), requireNumbers);
+        if (verifyContents) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_HEIGHT);
+        }
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Title'
+        if (verifyContents) {
+            verifyFieldName(jp, "Title");
+        }
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals(SAMPLE_SPEC_VALUE_TITLE, getAndVerifyText(jp));
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Thumbnail'
+        if (verifyContents) {
+            verifyFieldName(jp, "Thumbnail");
+        }
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken()); // 'thumbnail' object
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Url'
+        if (verifyContents) {
+            verifyFieldName(jp, "Url");
+        }
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        if (verifyContents) {
+            assertEquals(SAMPLE_SPEC_VALUE_TN_URL, getAndVerifyText(jp));
+        }
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Height'
+        if (verifyContents) {
+            verifyFieldName(jp, "Height");
+        }
+        verifyIntToken(jp.nextToken(), requireNumbers);
+        if (verifyContents) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_HEIGHT);
+        }
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Width'
+        if (verifyContents) {
+            verifyFieldName(jp, "Width");
+        }
+        // Width value is actually a String in the example
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        if (verifyContents) {
+            assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, getAndVerifyText(jp));
+        }
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken()); // 'thumbnail' object
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'IDs'
+        assertToken(JsonToken.START_ARRAY, jp.nextToken()); // 'ids' array
+        verifyIntToken(jp.nextToken(), requireNumbers); // ids[0]
+        if (verifyContents) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID1);
+        }
+        verifyIntToken(jp.nextToken(), requireNumbers); // ids[1]
+        if (verifyContents) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID2);
+        }
+        verifyIntToken(jp.nextToken(), requireNumbers); // ids[2]
+        if (verifyContents) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID3);
+        }
+        verifyIntToken(jp.nextToken(), requireNumbers); // ids[3]
+        if (verifyContents) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID4);
+        }
+        assertToken(JsonToken.END_ARRAY, jp.nextToken()); // 'ids' array
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken()); // 'image' object
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken()); // main object
+    }
+
+    private void verifyIntToken(JsonToken t, boolean requireNumbers)
+    {
+        if (t == JsonToken.VALUE_NUMBER_INT) {
+            return;
+        }
+        if (requireNumbers) { // to get error
+            assertToken(JsonToken.VALUE_NUMBER_INT, t);
+        }
+        // if not number, must be String
+        if (t != JsonToken.VALUE_STRING) {
+            fail("Expected INT or STRING value, got "+t);
+        }
+    }
+    
+    protected void verifyFieldName(JsonParser jp, String expName)
+        throws IOException
+    {
+        assertEquals(expName, jp.getText());
+        assertEquals(expName, jp.getCurrentName());
+    }
+
+    protected void verifyIntValue(JsonParser jp, long expValue)
+        throws IOException
+    {
+        // First, via textual
+        assertEquals(String.valueOf(expValue), jp.getText());
+    }
+    
+    /*
+    /**********************************************************
+    /* Parser/generator construction
+    /**********************************************************
+     */
+
+    protected JsonParser createParserUsingReader(String input)
+        throws IOException, JsonParseException
+    {
+        return createParserUsingReader(new JsonFactory(), input);
+    }
+
+    protected JsonParser createParserUsingReader(JsonFactory f, String input)
+        throws IOException, JsonParseException
+    {
+        return f.createParser(new StringReader(input));
+    }
+
+    protected JsonParser createParserUsingStream(String input, String encoding)
+        throws IOException, JsonParseException
+    {
+        return createParserUsingStream(new JsonFactory(), input, encoding);
+    }
+
+    protected JsonParser createParserUsingStream(JsonFactory f,
+                                                 String input, String encoding)
+        throws IOException, JsonParseException
+    {
+
+        /* 23-Apr-2008, tatus: UTF-32 is not supported by JDK, have to
+         *   use our own codec too (which is not optimal since there's
+         *   a chance both encoder and decoder might have bugs, but ones
+         *   that cancel each other out or such)
+         */
+        byte[] data;
+        if (encoding.equalsIgnoreCase("UTF-32")) {
+            data = encodeInUTF32BE(input);
+        } else {
+            data = input.getBytes(encoding);
+        }
+        InputStream is = new ByteArrayInputStream(data);
+        return f.createParser(is);
+    }
+
+    /*
+    /**********************************************************
+    /* Additional assertion methods
+    /**********************************************************
+     */
+
+    protected void assertToken(JsonToken expToken, JsonToken actToken)
+    {
+        if (actToken != expToken) {
+            fail("Expected token "+expToken+", current token "+actToken);
+        }
+    }
+
+    protected void assertToken(JsonToken expToken, JsonParser jp)
+    {
+        assertToken(expToken, jp.getCurrentToken());
+    }
+
+    protected void assertType(Object ob, Class<?> expType)
+    {
+        if (ob == null) {
+            fail("Expected an object of type "+expType.getName()+", got null");
+        }
+        Class<?> cls = ob.getClass();
+        if (!expType.isAssignableFrom(cls)) {
+            fail("Expected type "+expType.getName()+", got "+cls.getName());
+        }
+    }
+
+    protected void verifyException(Throwable e, String... matches)
+    {
+        String msg = e.getMessage();
+        String lmsg = (msg == null) ? "" : msg.toLowerCase();
+        for (String match : matches) {
+            String lmatch = match.toLowerCase();
+            if (lmsg.indexOf(lmatch) >= 0) {
+                return;
+            }
+        }
+        fail("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\"");
+    }
+
+    /**
+     * Method that gets textual contents of the current token using
+     * available methods, and ensures results are consistent, before
+     * returning them
+     */
+    protected String getAndVerifyText(JsonParser jp)
+        throws IOException, JsonParseException
+    {
+        // Ok, let's verify other accessors
+        int actLen = jp.getTextLength();
+        char[] ch = jp.getTextCharacters();
+        String str2 = new String(ch, jp.getTextOffset(), actLen);
+        String str = jp.getText();
+
+        if (str.length() !=  actLen) {
+            fail("Internal problem (jp.token == "+jp.getCurrentToken()+"): jp.getText().length() ['"+str+"'] == "+str.length()+"; jp.getTextLength() == "+actLen);
+        }
+        assertEquals("String access via getText(), getTextXxx() must be the same", str, str2);
+
+        return str;
+    }
+
+    /*
+    /**********************************************************
+    /* And other helpers
+    /**********************************************************
+     */
+
+    protected byte[] encodeInUTF32BE(String input)
+    {
+        int len = input.length();
+        byte[] result = new byte[len * 4];
+        int ptr = 0;
+        for (int i = 0; i < len; ++i, ptr += 4) {
+            char c = input.charAt(i);
+            result[ptr] = result[ptr+1] = (byte) 0;
+            result[ptr+2] = (byte) (c >> 8);
+            result[ptr+3] = (byte) c;
+        }
+        return result;
+    }
+
+    public String quote(String str) {
+        return '"'+str+'"';
+    }
+
+    protected void fieldNameFor(StringBuilder sb, int index)
+    {
+        /* let's do something like "f1.1" to exercise different
+         * field names (important for byte-based codec)
+         * Other name shuffling done mostly just for fun... :)
+         */
+        sb.append(FIELD_BASENAME);
+        sb.append(index);
+        if (index > 50) {
+            sb.append('.');
+            if (index > 200) {
+                sb.append(index);
+                if (index > 4000) { // and some even longer symbols...
+                    sb.append(".").append(index);
+                }
+            } else {
+                sb.append(index >> 3); // divide by 8
+            }
+        }
+    }
+
+    protected String fieldNameFor(int index)
+    {
+        StringBuilder sb = new StringBuilder(16);
+        fieldNameFor(sb, index);
+        return sb.toString();
+    }
+
+}
diff --git a/src/test/java/perf/ConcurrencyReadTest.java b/src/test/java/perf/ConcurrencyReadTest.java
new file mode 100644
index 0000000..5de6ccd
--- /dev/null
+++ b/src/test/java/perf/ConcurrencyReadTest.java
@@ -0,0 +1,80 @@
+package perf;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Manual performance test to try out various synchronization
+ * methods for symbol tables.
+ */
+public class ConcurrencyReadTest
+{
+    private final static int THREADS = 50;
+    
+    private void test() throws Exception
+    {
+        final JsonFactory jf = new JsonFactory();
+        final byte[] INPUT = "{\"a\":1}".getBytes("UTF-8");
+        final AtomicInteger count = new AtomicInteger();
+        
+        for (int i = 0; i < THREADS; ++i) {
+            new Thread(new Runnable() {
+                @Override
+                public void run()
+                {
+                    try {
+                        while (true) {
+                            parse(jf, INPUT);
+                            count.addAndGet(1);
+                        }
+                    } catch (IOException e) {
+                        System.err.println("PROBLEM: "+e);
+                    }
+                }
+            }).start();
+        }
+
+        // wait slightly....
+        Thread.sleep(200L);
+        
+        double totalTime = 0.0;
+        double totalCount = 0.0;
+
+        while (true) {
+            long start = System.currentTimeMillis();
+            int startCount = count.get();
+
+            Thread.sleep(1000L);
+            
+            int done = count.get() - startCount;
+            long time = System.currentTimeMillis() - start;
+
+            totalTime += time;
+            totalCount += done;
+            
+            double rate = (double) done / (double) time;
+            System.out.printf("Rate: %.1f (avg: %.1f)\n", rate, totalCount/totalTime);
+        }
+    }
+
+    protected void parse(JsonFactory jf, byte[] input) throws IOException
+    {
+        JsonParser jp = jf.createParser(input, 0, input.length);
+        while (jp.nextToken() != null) {
+            ;
+        }
+        jp.close();
+    }
+    
+    public static void main(String[] args) throws Exception
+    {
+        if (args.length != 0) {
+            System.err.println("Usage: java ...");
+            System.exit(1);
+        }
+        new ConcurrencyReadTest().test();
+    }
+
+}
diff --git a/src/test/resources/META-INF/maven/foo/bar/foo-bar/pom.properties b/src/test/resources/META-INF/maven/foo/bar/foo-bar/pom.properties
new file mode 100644
index 0000000..5ae4d16
--- /dev/null
+++ b/src/test/resources/META-INF/maven/foo/bar/foo-bar/pom.properties
@@ -0,0 +1,3 @@
+groupId=foo.bar
+artifactId=foo-bar
+version=1.2.3-SNAPSHOT
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jackson-core.git



More information about the pkg-java-commits mailing list