[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.
+
+[](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