Check in actual sources, moved to the new package, cleaned up a bit
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..34d9bfe
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Base64Variant.java
@@ -0,0 +1,406 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code and binary code bundles.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+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
+{
+ /**
+ * 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 int[] _asciiToBase64 = new int[128];
+
+ /**
+ * Encoding table used for base 64 decoding when output is done
+ * as characters.
+ */
+ private final char[] _base64ToAsciiC = new char[64];
+
+ /**
+ * Alternative encoding table used for base 64 decoding when output is done
+ * as ascii bytes.
+ */
+ private final byte[] _base64ToAsciiB = new byte[64];
+
+ /*
+ /**********************************************************
+ /* Other configuration
+ /**********************************************************
+ */
+
+ /**
+ * Symbolic name of variant; used for diagnostics/debugging.
+ */
+ final String _name;
+
+ /**
+ * Whether this variant uses padding or not.
+ */
+ final boolean _usesPadding;
+
+ /**
+ * Characted used for padding, if any ({@link #PADDING_CHAR_NONE} if not).
+ */
+ final 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.
+ */
+ final 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;
+ }
+
+ /*
+ /**********************************************************
+ /* 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..a97f9dd
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Base64Variants.java
@@ -0,0 +1,90 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code and binary code bundles.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fasterxml.jackson.core;
+
+/**
+ * Container for commonly used Base64 variants.
+ *
+ * @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;
+ }
+}
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..cdef7c2
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/FormatSchema.java
@@ -0,0 +1,29 @@
+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.
+ *
+ * @since 1.8
+ */
+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.
+ */
+ public 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..7d78fb2
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonEncoding.java
@@ -0,0 +1,47 @@
+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..4ad28ca
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
@@ -0,0 +1,831 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code and binary code bundles.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+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.ByteSourceJsonBootstrapper;
+import com.fasterxml.jackson.core.json.ReaderBasedJsonParser;
+import com.fasterxml.jackson.core.json.UTF8JsonGenerator;
+import com.fasterxml.jackson.core.json.WriterBasedJsonGenerator;
+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.VersionUtil;
+
+/**
+ * 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
+{
+ /**
+ * 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 parser features that are enabled
+ * by default.
+ */
+ final static int DEFAULT_PARSER_FEATURE_FLAGS = JsonParser.Feature.collectDefaults();
+
+ /**
+ * Bitfield (set of flags) of all generator features that are enabled
+ * by default.
+ */
+ final static int DEFAULT_GENERATOR_FEATURE_FLAGS = JsonGenerator.Feature.collectDefaults();
+
+ /*
+ /**********************************************************
+ /* Buffer, symbol table management
+ /**********************************************************
+ */
+
+ /**
+ * This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftRerefence}
+ * 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 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 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 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;
+
+ /*
+ /**********************************************************
+ /* 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(null); }
+
+ public JsonFactory(ObjectCodec oc) { _objectCodec = oc; }
+
+ /*
+ /**********************************************************
+ /* Format detection functionality (since 1.8)
+ /**********************************************************
+ */
+
+ /**
+ * 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;
+ }
+
+ protected MatchStrength hasJSONFormat(InputAccessor acc) throws IOException
+ {
+ return ByteSourceJsonBootstrapper.hasJSONFormat(acc);
+ }
+
+ /*
+ /**********************************************************
+ /* Versioned
+ /**********************************************************
+ */
+
+ @Override
+ public Version version() {
+ // VERSION is included under impl, so can't pass this class:
+ return VersionUtil.versionFor(UTF8JsonGenerator.class);
+ }
+
+ /*
+ /**********************************************************
+ /* Configuration, parser settings
+ /**********************************************************
+ */
+
+ /**
+ * 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)
+ {
+ if (state) {
+ enable(f);
+ } else {
+ disable(f);
+ }
+ return this;
+ }
+
+ /**
+ * 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) {
+ if (state) {
+ enable(f);
+ } else {
+ disable(f);
+ }
+ return this;
+ }
+
+
+ /**
+ * 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;
+ }
+
+ /*
+ /**********************************************************
+ /* Configuration, other
+ /**********************************************************
+ */
+
+ /**
+ * Method for associating a {@link ObjectCodec} (typically
+ * a {@link org.codehaus.jackson.map.ObjectMapper}) 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; }
+
+ /*
+ /**********************************************************
+ /* Reader factories
+ /**********************************************************
+ */
+
+ /**
+ * 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
+ */
+ public JsonParser createJsonParser(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 _createJsonParser(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
+ */
+ public JsonParser createJsonParser(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 _createJsonParser(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
+ */
+ public JsonParser createJsonParser(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 _createJsonParser(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.
+ *<p>
+ *
+ * @param r Reader to use for reading JSON content to parse
+ */
+ public JsonParser createJsonParser(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 _createJsonParser(r, ctxt);
+ }
+
+ /**
+ * Method for constructing parser for parsing
+ * the contents of given byte array.
+ */
+ public JsonParser createJsonParser(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 _createJsonParser(in, ctxt);
+ }
+ }
+ return _createJsonParser(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
+ */
+ public JsonParser createJsonParser(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 _createJsonParser(in, ctxt);
+ }
+ }
+ return _createJsonParser(data, offset, len, ctxt);
+ }
+
+ /**
+ * Method for constructing parser for parsing
+ * contents of given String.
+ */
+ public JsonParser createJsonParser(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 _createJsonParser(r, ctxt);
+ }
+
+ /*
+ /**********************************************************
+ /* Generator factories
+ /**********************************************************
+ */
+
+ /**
+ * 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
+ */
+ public JsonGenerator createJsonGenerator(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 _createUTF8JsonGenerator(out, ctxt);
+ }
+ Writer w = _createWriter(out, enc, ctxt);
+ // [JACKSON-512]: allow wrapping with _outputDecorator
+ if (_outputDecorator != null) {
+ w = _outputDecorator.decorate(ctxt, w);
+ }
+ return _createJsonGenerator(w, ctxt);
+ }
+
+ /**
+ * 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
+ */
+ public JsonGenerator createJsonGenerator(Writer out)
+ throws IOException
+ {
+ IOContext ctxt = _createContext(out, false);
+ // [JACKSON-512]: allow wrapping with _outputDecorator
+ if (_outputDecorator != null) {
+ out = _outputDecorator.decorate(ctxt, out);
+ }
+ return _createJsonGenerator(out, 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).
+ */
+ public JsonGenerator createJsonGenerator(OutputStream out) throws IOException {
+ return createJsonGenerator(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
+ */
+ public JsonGenerator createJsonGenerator(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 _createUTF8JsonGenerator(out, ctxt);
+ }
+ Writer w = _createWriter(out, enc, ctxt);
+ // [JACKSON-512]: allow wrapping with _outputDecorator
+ if (_outputDecorator != null) {
+ w = _outputDecorator.decorate(ctxt, w);
+ }
+ return _createJsonGenerator(w, ctxt);
+ }
+
+ /*
+ /**********************************************************
+ /* 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.
+ */
+ protected JsonParser _createJsonParser(InputStream in, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ return new ByteSourceJsonBootstrapper(ctxt, in).constructParser(_parserFeatures,
+ _objectCodec, _rootByteSymbols, _rootCharSymbols);
+ }
+
+ /**
+ * 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.
+ */
+ protected JsonParser _createJsonParser(Reader r, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ return new ReaderBasedJsonParser(ctxt, _parserFeatures, r, _objectCodec,
+ _rootCharSymbols.makeChild(isEnabled(JsonParser.Feature.CANONICALIZE_FIELD_NAMES),
+ isEnabled(JsonParser.Feature.INTERN_FIELD_NAMES)));
+ }
+
+ /**
+ * 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 _createJsonParser(byte[] data, int offset, int len, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ return new ByteSourceJsonBootstrapper(ctxt, data, offset, len).constructParser(_parserFeatures,
+ _objectCodec, _rootByteSymbols, _rootCharSymbols);
+ }
+
+ /*
+ /**********************************************************
+ /* 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 _createJsonGenerator(Writer out, IOContext ctxt)
+ throws IOException
+ {
+ WriterBasedJsonGenerator gen = new WriterBasedJsonGenerator(ctxt, _generatorFeatures, _objectCodec, out);
+ if (_characterEscapes != null) {
+ gen.setCharacterEscapes(_characterEscapes);
+ }
+ return gen;
+ }
+
+ /**
+ * 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 _createUTF8JsonGenerator(OutputStream out, IOContext ctxt)
+ throws IOException
+ {
+ UTF8JsonGenerator gen = new UTF8JsonGenerator(ctxt, _generatorFeatures, _objectCodec, out);
+ if (_characterEscapes != null) {
+ gen.setCharacterEscapes(_characterEscapes);
+ }
+ return gen;
+ }
+
+ 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) {
+ return new FileInputStream(url.getPath());
+ }
+ }
+ 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..7338d92
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerationException.java
@@ -0,0 +1,27 @@
+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..f32c244
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
@@ -0,0 +1,1175 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code and binary code bundles.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fasterxml.jackson.core;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.io.CharacterEscapes;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+/**
+ * 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, 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.
+ */
+ 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.
+ *
+ * @since 1.3
+ */
+ 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.
+ *
+ * @since 1.7
+ */
+ 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).
+ *
+ * @since 1.8
+ */
+ ESCAPE_NON_ASCII(false)
+
+ ;
+
+ final boolean _defaultState;
+
+ 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) {
+ _defaultState = defaultState;
+ _mask = (1 << ordinal());
+ }
+
+ 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, configuration, initialization
+ /**********************************************************
+ */
+
+ protected JsonGenerator() { }
+
+ /**
+ * 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
+ *
+ * @since 1.8
+ */
+ public void setSchema(FormatSchema schema)
+ {
+ throw new UnsupportedOperationException("Generator of type "+getClass().getName()+" does not support schema of type '"
+ +schema.getSchemaType()+"'");
+ }
+
+ /**
+ * 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
+ *
+ * @since 1.8
+ */
+ public boolean canUseSchema(FormatSchema schema) {
+ return false;
+ }
+
+ /**
+ * @since 1.6
+ */
+ @Override
+ public Version version() {
+ return Version.unknownVersion();
+ }
+
+ /**
+ * 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.
+ *
+ * @since 1.8
+ */
+ public Object getOutputTarget() {
+ return null;
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, configuration
+ /**********************************************************
+ */
+
+ /**
+ * Method for enabling specified parser features:
+ * check {@link Feature} for list of available features.
+ *
+ * @return Generator itself (this), to allow chaining
+ *
+ * @since 1.2
+ */
+ 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
+ *
+ * @since 1.2
+ */
+ 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
+ *
+ * @since 1.2
+ */
+ public 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.
+ *
+ * @since 1.2
+ */
+ public abstract boolean isEnabled(Feature f);
+
+ /**
+ * 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();
+
+ /*
+ /**********************************************************
+ /* 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;
+ }
+
+ /**
+ * 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.
+ *
+ * @since 1.8
+ */
+ 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.
+ *
+ * @since 1.8
+ */
+ public CharacterEscapes getCharacterEscapes() {
+ return null;
+ }
+
+ /**
+ * Method for defining custom escapes factory uses for {@link JsonGenerator}s
+ * it creates.
+ *
+ * @since 1.8
+ */
+ 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.
+ *
+ * @since 1.6
+ */
+ public void writeFieldName(SerializedString name)
+ throws IOException, JsonGenerationException
+ {
+ writeFieldName(name.getValue());
+ }
+
+ /**
+ * 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.
+ *
+ * @since 1.7
+ */
+ public void writeFieldName(SerializableString name)
+ throws IOException, JsonGenerationException
+ {
+ writeFieldName(name.getValue());
+ }
+
+ /*
+ /**********************************************************
+ /* 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.
+ *
+ * @since 1.7
+ */
+ public void writeString(SerializableString text)
+ throws IOException, JsonGenerationException
+ {
+ writeString(text.getValue());
+ }
+
+ /**
+ * 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.
+ *
+ * @since 1.7
+ */
+ 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.
+ *
+ * @since 1.7
+ */
+ 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 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);
+ }
+
+ /*
+ /**********************************************************
+ /* 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.
+ */
+ 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(JsonNode 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.
+ */
+ 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..f72fc0c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonLocation.java
@@ -0,0 +1,139 @@
+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
+ *
+ * @since 1.3
+ */
+ 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
+ */
+ final 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);
+ }
+
+ // 22-Dec-2011, tatu: TODO: add deserializer for this type instead:
+ //@JsonCreator
+ public JsonLocation(/*@JsonProperty("sourceRef")*/ Object sourceRef,
+ /*@JsonProperty("byteOffset")*/ long totalBytes,
+ /*@JsonProperty("charOffset")*/ long totalChars,
+ /*@JsonProperty("lineNr")*/ int lineNr,
+ /*@JsonProperty("columnNr")*/ 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/JsonNode.java b/src/main/java/com/fasterxml/jackson/core/JsonNode.java
new file mode 100644
index 0000000..1577d48
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonNode.java
@@ -0,0 +1,668 @@
+package com.fasterxml.jackson.core;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+
+/**
+ * Base class for all JSON nodes, which form the basis of JSON
+ * Tree Model that Jackson implements.
+ * One way to think of these nodes is to consider them
+ * similar to DOM nodes in XML DOM trees.
+ *<p>
+ * As a general design rule, most accessors ("getters") are included
+ * in this base class, to allow for traversing structure without
+ * type casts. Most mutators, however, need to be accessed through
+ * specific sub-classes (such as <code>org.codehaus.jackson.node.ObjectNode</code>
+ * and <code>org.codehaus.jackson.node.ArrayNode</code>).
+ * This seems sensible because proper type
+ * information is generally available when building or modifying
+ * trees, but less often when reading a tree (newly built from
+ * parsed JSON content).
+ *<p>
+ * Actual concrete sub-classes can be found from package
+ * {@link org.codehaus.jackson.node}, which is in 'mapper' jar
+ * (whereas this class is in 'core' jar, since it is declared as
+ * nominal type for operations in {@link ObjectCodec})
+ */
+public abstract class JsonNode
+ implements Iterable<JsonNode>
+{
+ protected final static List<JsonNode> NO_NODES = Collections.emptyList();
+ protected final static List<String> NO_STRINGS = Collections.emptyList();
+
+ protected JsonNode() { }
+
+ /*
+ /**********************************************************
+ /* Public API, type introspection
+ /**********************************************************
+ */
+
+ // // First high-level division between values, containers and "missing"
+
+ /**
+ * 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.
+ */
+ public boolean isValueNode() { return false; }
+
+ /**
+ * 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.
+ */
+ public boolean isContainerNode() { return false; }
+
+ /**
+ * 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.
+ */
+ public boolean isMissingNode() { return false; }
+
+ // // Then more specific type introspection
+ // // (along with defaults to be overridden)
+
+ /**
+ * @return True if this node represents Json Array
+ */
+ public boolean isArray() { return false; }
+
+ /**
+ * @return True if this node represents Json Object
+ */
+ public boolean isObject() { return false; }
+
+ /**
+ * Method that can be used to check if the node is a wrapper
+ * for a POJO ("Plain Old Java Object" aka "bean".
+ * Returns true only for
+ * instances of {@link org.codehaus.jackson.node.POJONode}.
+ *
+ * @return True if this node wraps a POJO
+ */
+ public boolean isPojo() { return false; }
+
+ /**
+ * @return True if this node represents a numeric Json
+ * value
+ */
+ public boolean isNumber() { return false; }
+
+ /**
+ * @return True if this node represents an integral (integer)
+ * numeric Json value
+ */
+ public boolean isIntegralNumber() { return false; }
+
+ /**
+ * @return True if this node represents a non-integral
+ * numeric Json value
+ */
+ public boolean isFloatingPointNumber() { return false; }
+
+ /**
+ * @return True if this node represents an integral
+ * numeric Json value that withs in Java int value space
+ */
+ public boolean isInt() { return false; }
+
+ /**
+ * @return True if this node represents an integral
+ * numeric Json value that fits in Java long value space
+ * (but not int value space, i.e. {@link #isInt} returns false)
+ */
+ public boolean isLong() { return false; }
+
+ public boolean isDouble() { return false; }
+ public boolean isBigDecimal() { return false; }
+ public boolean isBigInteger() { return false; }
+
+ public boolean isTextual() { return false; }
+
+ /**
+ * Method that can be used to check if this node was created from
+ * Json boolean value (literals "true" and "false").
+ */
+ public boolean isBoolean() { return false; }
+
+ /**
+ * Method that can be used to check if this node was created from
+ * Json liternal null value.
+ */
+ public boolean isNull() { return false; }
+
+ /**
+ * Method that can be used to check if this node represents
+ * binary data (Base64 encoded). Although this will be externally
+ * written as Json String value, {@link #isTextual} will
+ * return false if this method returns true.
+ *
+ * @return True if this node represents base64 encoded binary data
+ */
+ public boolean isBinary() { return false; }
+
+ /**
+ * 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)
+ */
+ public abstract JsonToken asToken();
+
+ /**
+ * If this node is a numeric type (as per {@link #isNumber}),
+ * returns native type that node uses to store the numeric
+ * value.
+ */
+ public abstract JsonParser.NumberType getNumberType();
+
+ /*
+ /**********************************************************
+ /* Public API, straight value access
+ /**********************************************************
+ */
+
+ /**
+ * Method to use for accessing String values.
+ * Does <b>NOT</b> do any conversions for non-String value nodes;
+ * for non-String values (ones for which {@link #isTextual} returns
+ * false) null will be returned.
+ * For String values, null is never returned (but empty Strings may be)
+ *
+ * @return Textual value this node contains, iff it is a textual
+ * json node (comes from Json String value entry)
+ */
+ public String getTextValue() { return null; }
+
+ /**
+ * Method to use for accessing binary content of binary nodes (nodes
+ * for which {@link #isBinary} returns true); or for Text Nodes
+ * (ones for which {@link #getTextValue} returns non-null value),
+ * to read decoded base64 data.
+ * For other types of nodes, returns null.
+ *
+ * @return Binary data this node contains, iff it is a binary
+ * node; null otherwise
+ */
+ public byte[] getBinaryValue() throws IOException
+ {
+ return null;
+ }
+
+ /**
+ * Method to use for accessing JSON boolean values (value
+ * literals 'true' and 'false').
+ * For other types, always returns false.
+ *
+ * @return Textual value this node contains, iff it is a textual
+ * json node (comes from Json String value entry)
+ */
+ public boolean getBooleanValue() { return false; }
+
+ /**
+ * Returns numeric value for this node, <b>if and only if</b>
+ * this node is numeric ({@link #isNumber} returns true); otherwise
+ * returns null
+ *
+ * @return Number value this node contains, if any (null for non-number
+ * nodes).
+ */
+ public Number getNumberValue() { return null; }
+
+ /**
+ * Returns integer value for this node, <b>if and only if</b>
+ * this node is numeric ({@link #isNumber} returns true). For other
+ * types returns 0.
+ * For floating-point numbers, value is truncated using default
+ * Java coercion, similar to how cast from double to int operates.
+ *
+ * @return Integer value this node contains, if any; 0 for non-number
+ * nodes.
+ */
+ public int getIntValue() { return 0; }
+
+ public long getLongValue() { return 0L; }
+ public double getDoubleValue() { return 0.0; }
+ public BigDecimal getDecimalValue() { return BigDecimal.ZERO; }
+ public BigInteger getBigIntegerValue() { return BigInteger.ZERO; }
+
+ /**
+ * Method for accessing value of the specified element of
+ * an array node. For other nodes, null is always 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.
+ */
+ public JsonNode get(int index) { return null; }
+
+ /**
+ * 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.
+ *
+ * @return Node that represent value of the specified field,
+ * if this node is an object and has value for the specified
+ * field. Null otherwise.
+ */
+ public JsonNode get(String fieldName) { return null; }
+
+ /*
+ /**********************************************************
+ /* Public API, value access with conversion(s)/coercion(s)
+ /**********************************************************
+ */
+
+ /**
+ * Method that will return valid String representation of
+ * the container value, if the node is a value node
+ * (method {@link #isValueNode} returns true), otherwise
+ * empty String.
+ */
+ public abstract String asText();
+
+ /**
+ * Method that will try to convert value of this node to a Java <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 types
+ * like Objects and Arrays),
+ * default value of <b>0</b> will be returned; no exceptions are thrown.
+ */
+ public int asInt() {
+ return asInt(0);
+ }
+
+ /**
+ * Method that will try to convert value of this node to a Java <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 types
+ * like Objects and Arrays),
+ * specified <b>defaultValue</b> will be returned; no exceptions are thrown.
+ */
+ public int asInt(int defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Method that will try to convert value of this node to a Java <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 long (including structured types
+ * like Objects and Arrays),
+ * default value of <b>0</b> will be returned; no exceptions are thrown.
+ */
+ public long asLong() {
+ return asLong(0L);
+ }
+
+ /**
+ * Method that will try to convert value of this node to a Java <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 long (including structured types
+ * like Objects and Arrays),
+ * specified <b>defaultValue</b> will be returned; no exceptions are thrown.
+ */
+ public long asLong(long defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Method that will try to convert value of this node 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 asDouble() {
+ return asDouble(0.0);
+ }
+
+ /**
+ * Method that will try to convert value of this node 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 asDouble(double defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Method that will try to convert value of this node to a Java <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 asBoolean() {
+ return asBoolean(false);
+ }
+
+ /**
+ * Method that will try to convert value of this node to a Java <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 asBoolean(boolean defaultValue) {
+ return defaultValue;
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, value find / existence check methods
+ /**********************************************************
+ */
+
+ /**
+ * Method that allows checking whether this node is JSON Object node
+ * and contains value for specified property. If this is the case
+ * (including properties with explicit null values), returns true;
+ * otherwise returns false.
+ *<p>
+ * This method is equivalent to:
+ *<pre>
+ * node.get(fieldName) != null
+ *</pre>
+ * (since return value of get() is node, not value node contains)
+ *
+ * @param fieldName Name of element to check
+ *
+ * @return True if this node is a JSON Object node, and has a property
+ * entry with specified name (with any value, including null value)
+ */
+ public boolean has(String fieldName) {
+ return get(fieldName) != null;
+ }
+
+ /**
+ * Method that allows checking whether this node is JSON Array node
+ * and contains a value for specified index
+ * If this is the case
+ * (including case of specified indexing having null as value), returns true;
+ * otherwise returns false.
+ *<p>
+ * Note: array element indexes are 0-based.
+ *<p>
+ * This method is equivalent to:
+ *<pre>
+ * node.get(index) != null
+ *</pre>
+ *
+ * @param index Index to check
+ *
+ * @return True if this node is a JSON Object node, and has a property
+ * entry with specified name (with any value, including null value)
+ */
+ public boolean has(int index) {
+ return get(index) != null;
+ }
+
+ /**
+ * Method for finding a JSON Object field with specified name in this
+ * node or its child nodes, and returning value it has.
+ * If no matching field is found in this node or its descendants, returns null.
+ *
+ * @param fieldName Name of field to look for
+ *
+ * @return Value of first matching node found, if any; null if none
+ */
+ public abstract JsonNode findValue(String fieldName);
+
+ /**
+ * Method for finding JSON Object fields with specified name, and returning
+ * found ones as a List. Note that sub-tree search ends if a field is found,
+ * so possible children of result nodes are <b>not</b> included.
+ * If no matching fields are found in this node or its descendants, returns
+ * an empty List.
+ *
+ * @param fieldName Name of field to look for
+ */
+ public final List<JsonNode> findValues(String fieldName)
+ {
+ List<JsonNode> result = findValues(fieldName, null);
+ if (result == null) {
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
+ /**
+ * Similar to {@link #findValues}, but will additionally convert
+ * values into Strings, calling {@link #getValueAsText}.
+ */
+ public final List<String> findValuesAsText(String fieldName)
+ {
+ List<String> result = findValuesAsText(fieldName, null);
+ if (result == null) {
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
+ /**
+ * Method similar to {@link #findValue}, but that will return a
+ * "missing node" instead of null if no field is found. Missing node
+ * is a specific kind of node for which {@link #isMissingNode}
+ * returns true; and all value access methods return empty or
+ * missing value.
+ *
+ * @param fieldName Name of field to look for
+ *
+ * @return Value of first matching node found; or if not found, a
+ * "missing node" (non-null instance that has no value)
+ */
+ public abstract JsonNode findPath(String fieldName);
+
+ /**
+ * Method for finding a JSON Object that contains specified field,
+ * within this node or its descendants.
+ * If no matching field is found in this node or its descendants, returns null.
+ *
+ * @param fieldName Name of field to look for
+ *
+ * @return Value of first matching node found, if any; null if none
+ */
+ public abstract JsonNode findParent(String fieldName);
+
+ /**
+ * Method for finding a JSON Object that contains specified field,
+ * within this node or its descendants.
+ * If no matching field is found in this node or its descendants, returns null.
+ *
+ * @param fieldName Name of field to look for
+ *
+ * @return Value of first matching node found, if any; null if none
+ */
+ public final List<JsonNode> findParents(String fieldName)
+ {
+ List<JsonNode> result = findParents(fieldName, null);
+ if (result == null) {
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
+ public abstract List<JsonNode> findValues(String fieldName, List<JsonNode> foundSoFar);
+ public abstract List<String> findValuesAsText(String fieldName, List<String> foundSoFar);
+ public abstract List<JsonNode> findParents(String fieldName, List<JsonNode> foundSoFar);
+
+ /*
+ /**********************************************************
+ /* Public API, container access
+ /**********************************************************
+ */
+
+ /**
+ * 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.
+ */
+ public int size() { return 0; }
+
+ /**
+ * Same as calling {@link #getElements}; implemented so that
+ * convenience "for-each" loop can be used for looping over elements
+ * of JSON Array constructs.
+ */
+ @Override
+ public final Iterator<JsonNode> iterator() { return getElements(); }
+
+ /**
+ * Method for accessing all value nodes of this Node, iff
+ * this node is a JSON Array or Object node. In case of Object node,
+ * field names (keys) are not included, only values.
+ * For other types of nodes, returns empty iterator.
+ */
+ public Iterator<JsonNode> getElements() { return NO_NODES.iterator(); }
+
+ /**
+ * Method for accessing names of all fields for this Node, iff
+ * this node is a JSON Object node.
+ */
+ public Iterator<String> getFieldNames() { return NO_STRINGS.iterator(); }
+
+ /**
+ * @return Iterator that can be used to traverse all key/value pairs for
+ * object nodes; empty iterator (no contents) for other types
+ */
+ public Iterator<Map.Entry<String, JsonNode>> getFields() {
+ Collection<Map.Entry<String, JsonNode>> coll = Collections.emptyList();
+ return coll.iterator();
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, path handling
+ /**********************************************************
+ */
+
+ /**
+ * This method is similar to {@link #get(String)}, except
+ * that instead of returning null if no such value exists (due
+ * to this node not being an object, or object not having value
+ * for the specified field),
+ * a "missing node" (node that returns true for
+ * {@link #isMissingNode}) will be returned. This allows for
+ * convenient and safe chained access via path calls.
+ */
+ public abstract JsonNode path(String fieldName);
+
+ /**
+ * This method is similar to {@link #get(int)}, except
+ * that instead of returning null if no such element exists (due
+ * to index being out of range, or this node not being an array),
+ * a "missing node" (node that returns true for
+ * {@link #isMissingNode}) will be returned. This allows for
+ * convenient and safe chained access via path calls.
+ */
+ public abstract JsonNode path(int index);
+
+ /**
+ * Method that can be called on object nodes, to access a property
+ * that has object value; or if no such property exists, to create and
+ * return such object node.
+ * If node method is called on is not Object node,
+ * or if property exists and has value that is not object node,
+ * {@link UnsupportedOperationException} is thrown
+ */
+ public JsonNode with(String propertyName) {
+ throw new UnsupportedOperationException("JsonNode not of type ObjectNode (but "
+ +getClass().getName()+"), can not call with() on it");
+ }
+
+ /*
+ /**********************************************************
+ /* Public API: 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.
+ */
+ public abstract JsonParser traverse();
+
+ /*
+ /**********************************************************
+ /* Overridden standard methods
+ /**********************************************************
+ */
+
+ /**
+ *<p>
+ * Note: marked as abstract to ensure all implementation
+ * classes define it properly.
+ */
+ @Override
+ public abstract String toString();
+
+ /**
+ * Equality for node objects is defined as full (deep) value
+ * equality. This means that it is possible to compare complete
+ * JSON trees for equality by comparing equality of root nodes.
+ *<p>
+ * Note: marked as abstract to ensure all implementation
+ * classes define it properly and not rely on definition
+ * from {@link java.lang.Object}.
+ */
+ @Override
+ public abstract boolean equals(Object o);
+}
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..d1f53c8
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonParseException.java
@@ -0,0 +1,22 @@
+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..05fc104
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonParser.java
@@ -0,0 +1,1338 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code and binary code bundles.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+ private final static int MAX_BYTE_I = (int) Byte.MAX_VALUE;
+
+ 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 togglable 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 (via factory or parser instance).
+ *<p>
+ * This feature can be changed for parser instances.
+ */
+ 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.
+ *<p>
+ * This feature can be changed for parser instances.
+ */
+ 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.
+ *<p>
+ * This feature can be changed for parser instances.
+ */
+ 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.
+ *<p>
+ * This feature can be changed for parser instances.
+ */
+ 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.
+ *<p>
+ * This feature can be changed for parser instances.
+ */
+ 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.
+ *<p>
+ * This feature can be changed for parser instances.
+ */
+ 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>
+ */
+
+ ALLOW_NON_NUMERIC_NUMBERS(false),
+
+ // // // Controlling canonicalization (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.
+ */
+ 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}).
+ */
+ CANONICALIZE_FIELD_NAMES(true),
+
+
+ ;
+
+ 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;
+
+ /*
+ /**********************************************************
+ /* Minimal generic 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;
+
+ /*
+ /**********************************************************
+ /* 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 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 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;
+ }
+
+ /**
+ * Accessor for getting version of the core package, given a parser instance.
+ */
+ @Override
+ public Version version() {
+ return Version.unknownVersion();
+ }
+
+ /**
+ * 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;
+ }
+
+ /*
+ /**********************************************************
+ /* 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 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;
+ }
+
+ /**
+ * 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;
+ }
+ 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 JsonToken getCurrentToken() {
+ return _currToken;
+ }
+
+ /**
+ * 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 boolean hasCurrentToken() {
+ return _currToken != null;
+ }
+
+
+ /**
+ * 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 void clearCurrentToken() {
+ if (_currToken != null) {
+ _lastClearedToken = _currToken;
+ _currToken = null;
+ }
+ }
+
+ /**
+ * 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();
+
+ /**
+ * 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 JsonToken getLastClearedToken() {
+ return _lastClearedToken;
+ }
+
+ /**
+ * 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, 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 boolean hasTextCharacters() {
+ return false;
+ }
+
+ /*
+ /**********************************************************
+ /* 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?
+ 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
+ {
+ if (_currToken == JsonToken.VALUE_TRUE) return true;
+ if (_currToken == JsonToken.VALUE_FALSE) return false;
+ throw new JsonParseException("Current token ("+_currToken+") 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 Object getEmbeddedObject()
+ throws IOException, JsonParseException
+ {
+ // By default we will always return null
+ return null;
+ }
+
+ /*
+ /**********************************************************
+ /* 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());
+ }
+
+ /*
+ /**********************************************************
+ /* 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 getValueAsInt(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;
+ }
+
+ /*
+ /**********************************************************
+ /* 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 {@link org.codehaus.jackson.map.MappingJsonFactory} 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 {@link org.codehaus.jackson.map.MappingJsonFactory} 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 JsonNode} 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
+ */
+ public JsonNode 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 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());
+ }
+}
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..5cf21cb
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java
@@ -0,0 +1,80 @@
+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 mLocation;
+
+ 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);
+ }
+ mLocation = 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 mLocation;
+ }
+
+ /**
+ * 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();
+ if (loc != null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(msg);
+ sb.append('\n');
+ sb.append(" at ");
+ sb.append(loc.toString());
+ return 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..118f899
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java
@@ -0,0 +1,122 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code and binary code bundles.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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..3859b15
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/JsonToken.java
@@ -0,0 +1,161 @@
+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.
+ *
+ * @since 0.9.7
+ */
+ NOT_AVAILABLE(null),
+
+ /**
+ * START_OBJECT is returned when encountering '{'
+ * which signals starting of an Object value.
+ */
+ START_OBJECT("{"),
+
+ /**
+ * START_OBJECT is returned when encountering '}'
+ * which signals ending of an Object value
+ */
+ END_OBJECT("}"),
+
+ /**
+ * START_OBJECT is returned when encountering '['
+ * which signals starting of an Array value
+ */
+ START_ARRAY("["),
+
+ /**
+ * START_OBJECT 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
+ * {@link JsonNode}-based JSON trees, Maps, Lists and such).
+ *
+ * @since 1.1
+ */
+ 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 Textual 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..1b3507a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/ObjectCodec.java
@@ -0,0 +1,157 @@
+package com.fasterxml.jackson.core;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import com.fasterxml.jackson.core.type.JavaType;
+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
+ * {@link org.codehaus.jackson.map.ObjectMapper}.
+ */
+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 as tree expressed
+ * using set of {@link JsonNode} 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> T readValue(JsonParser jp, JavaType valueType)
+ throws IOException, JsonProcessingException;
+
+ /**
+ * Method to deserialize JSON content as tree expressed
+ * using set of {@link JsonNode} 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 JsonNode readTree(JsonParser jp)
+ throws IOException, JsonProcessingException;
+
+ /**
+ * Method for reading sequence of Objects from parser stream,
+ * all with same specified value type.
+ *
+ * @since 1.9
+ */
+ 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.
+ *
+ * @since 1.9
+ */
+ 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.
+ *
+ * @since 1.9
+ */
+ public abstract <T> Iterator<T> readValues(JsonParser jp, JavaType 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;
+
+ /**
+ * Method to serialize given Json Tree, using generator
+ * provided.
+ */
+ public abstract void writeTree(JsonGenerator jgen, JsonNode rootNode)
+ throws IOException, JsonProcessingException;
+
+ /*
+ /**********************************************************
+ /* API for Tree Model handling
+ /**********************************************************
+ */
+
+ /**
+ * Method for construct root level Object nodes
+ * for Tree Model instances.
+ *
+ * @since 1.2
+ */
+ public abstract JsonNode createObjectNode();
+
+ /**
+ * Method for construct root level Array nodes
+ * for Tree Model instances.
+ *
+ * @since 1.2
+ */
+ public abstract JsonNode createArrayNode();
+
+ /**
+ * Method for constructing a {@link JsonParser} for reading
+ * contents of a JSON tree, as if it was external serialized
+ * JSON content.
+ *
+ * @since 1.3
+ */
+ public abstract JsonParser treeAsTokens(JsonNode 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.
+ *
+ * @since 1.3
+ */
+ public abstract <T> T treeToValue(JsonNode n, Class<T> valueType)
+ throws IOException, JsonProcessingException;
+}
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..710b1b9
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/PrettyPrinter.java
@@ -0,0 +1,166 @@
+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.
+ */
+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).
+ */
+ public 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.
+ */
+ public 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
+ */
+ public 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.
+ */
+ public 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.
+ */
+ public 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.
+ */
+ public 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
+ */
+ public 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.
+ */
+ public 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.
+ */
+ public 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.
+ */
+ public 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..db0c5b3
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/SerializableString.java
@@ -0,0 +1,54 @@
+package com.fasterxml.jackson.core;
+
+/**
+ * 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.
+ *
+ * @since 1.7 (1.6 introduced implementation, but interface extracted later)
+ *
+ * @see com.fasterxml.jackson.core.io.SerializedString
+ */
+public interface SerializableString
+{
+ /**
+ * Returns unquoted String that this object represents (and offers
+ * serialized forms for)
+ */
+ public String getValue();
+
+ /**
+ * Returns length of the (unquoted) String as characters.
+ * Functionally equvalent to:
+ *<pre>
+ * getValue().length();
+ *</pre>
+ */
+ public int charLength();
+
+ /**
+ * 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.
+ */
+ public char[] asQuotedChars();
+
+ /**
+ * Returns UTF-8 encoded version of unquoted String.
+ * Functionally equivalent to (but more efficient than):
+ *<pre>
+ * getValue().getBytes("UTF-8");
+ *</pre>
+ */
+ public 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>
+ */
+ public byte[] asQuotedUTF8();
+}
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..eb8b852
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Version.java
@@ -0,0 +1,90 @@
+package com.fasterxml.jackson.core;
+
+/**
+ * Object that encapsulates version information of a component,
+ * and is return by {@link Versioned#version}.
+ *
+ * @since 1.6
+ */
+public class Version
+ implements Comparable<Version>
+{
+ private final static Version UNKNOWN_VERSION = new Version(0, 0, 0, null);
+
+ protected final int _majorVersion;
+
+ protected final int _minorVersion;
+
+ protected final int _patchLevel;
+
+ /**
+ * Additional information for snapshot versions; null for non-snapshot
+ * (release) versions.
+ */
+ protected final String _snapshotInfo;
+
+ public Version(int major, int minor, int patchLevel,
+ String snapshotInfo)
+ {
+ _majorVersion = major;
+ _minorVersion = minor;
+ _patchLevel = patchLevel;
+ _snapshotInfo = snapshotInfo;
+ }
+
+ /**
+ * 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; }
+
+ @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 _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);
+ }
+
+ @Override
+ public int compareTo(Version other)
+ {
+ int 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..46cb0c2
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/Versioned.java
@@ -0,0 +1,20 @@
+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.
+ *
+ * @since 1.6
+ */
+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).
+ */
+ public 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..b3c5c50
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java
@@ -0,0 +1,491 @@
+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);
+ }
+
+ @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() {
+ 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
+ /**********************************************************
+ */
+
+ //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 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);
+ }
+
+ //public abstract void writeBinary(byte[] data, int offset, int len, boolean includeLFs) throws IOException, JsonGenerationException;
+
+
+ /*
+ /**********************************************************
+ /* 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(JsonNode 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 for the generator, can not serialize JsonNode-based trees");
+ }
+ _objectCodec.writeTree(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:
+ _cantHappen();
+ }
+ }
+
+ @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
+ /**********************************************************
+ */
+
+ protected abstract void _releaseBuffers();
+
+ protected abstract void _verifyValueWrite(String typeMsg)
+ throws IOException, JsonGenerationException;
+
+ protected void _reportError(String msg)
+ throws JsonGenerationException
+ {
+ throw new JsonGenerationException(msg);
+ }
+
+ protected void _cantHappen()
+ {
+ throw new RuntimeException("Internal error: should never end up through this code path");
+ }
+
+ /**
+ * 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).booleanValue());
+ 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() {
+ throw new RuntimeException("Internal error: this code path should never get executed");
+ }
+
+ /**
+ * @since 1.7
+ */
+ 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..1288305
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
@@ -0,0 +1,1068 @@
+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.util.ByteArrayBuilder;
+import com.fasterxml.jackson.core.util.TextBuffer;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * 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 BigDecimal BD_MIN_LONG = new BigDecimal(Long.MIN_VALUE);
+ final static BigDecimal BD_MAX_LONG = new BigDecimal(Long.MAX_VALUE);
+
+ final static BigDecimal BD_MIN_INT = new BigDecimal(Long.MIN_VALUE);
+ final static BigDecimal BD_MAX_INT = new BigDecimal(Long.MAX_VALUE);
+
+ 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 VersionUtil.versionFor(getClass());
+ }
+
+ /*
+ /**********************************************************
+ /* 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 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
+ /**********************************************************
+ */
+
+ @Override
+ public boolean hasTextCharacters()
+ {
+ if (_currToken == JsonToken.VALUE_STRING) {
+ return true; // usually true
+ }
+ if (_currToken == JsonToken.FIELD_NAME) {
+ return _nameCopied;
+ }
+ return false;
+ }
+
+ /*
+ /**********************************************************
+ /* Public low-level accessors
+ /**********************************************************
+ */
+
+ public final long getTokenCharacterOffset() { return _tokenInputTotal; }
+ public final int getTokenLineNr() { return _tokenInputRow; }
+ public final 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 Integer.valueOf(_numberInt);
+ }
+ if ((_numTypesValid & NR_LONG) != 0) {
+ return Long.valueOf(_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 Double.valueOf(_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 final 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 final 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) {
+ // !!! Should check for range...
+ _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(); // should never get here
+ }
+
+ _numTypesValid |= NR_INT;
+ }
+
+ protected void convertNumberToLong()
+ throws IOException, JsonParseException
+ {
+ if ((_numTypesValid & NR_INT) != 0) {
+ _numberLong = (long) _numberInt;
+ } else if ((_numTypesValid & NR_BIGINT) != 0) {
+ // !!! Should check for range...
+ _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(); // should never get here
+ }
+
+ _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(); // should never get here
+ }
+ _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(); // should never get here
+ }
+
+ _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(); // should never get here
+ }
+ _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..f9e39a9
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java
@@ -0,0 +1,539 @@
+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;
+
+/**
+ * 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.
+ *
+ * @since 1.6
+ *
+ * @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';
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ protected ParserMinimalBase() { }
+ protected ParserMinimalBase(int features) {
+ super(features);
+ }
+
+ /*
+ /**********************************************************
+ /* 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;
+
+ //public final JsonToken nextValue()
+
+ @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, 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).booleanValue();
+ }
+ }
+ 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;
+ }
+
+ /*
+ /**********************************************************
+ /* Base64 decoding
+ /**********************************************************
+ */
+
+ /**
+ * Helper method that can be used for base64 decoding in cases where
+ * encoded content has already been read as a String.
+ *
+ * @since 1.9.3
+ */
+ 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()
+ {
+ throw new RuntimeException("Internal error: this code path should never get executed");
+ }
+
+ 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..29c5d27
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/DataFormatDetector.java
@@ -0,0 +1,176 @@
+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.
+ *
+ * @since 1.7
+ */
+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));
+ }
+
+ /*
+ /**********************************************************
+ /* 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..4698377
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/DataFormatMatcher.java
@@ -0,0 +1,117 @@
+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;
+
+ /**
+ * Number of bytes in {@link #_bufferedData} that were read.
+ */
+ 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 bufferedLength,
+ JsonFactory match, MatchStrength strength)
+ {
+ _originalStream = in;
+ _bufferedData = buffered;
+ _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.createJsonParser(_bufferedData, 0, _bufferedLength);
+ }
+ return _match.createJsonParser(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, 0, _bufferedLength);
+ }
+ return new MergedStream(null, _originalStream, _bufferedData, 0, _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..e49550e
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/InputAccessor.java
@@ -0,0 +1,130 @@
+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.
+ *
+ * @since 1.8
+ */
+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.
+ */
+ public boolean hasMoreBytes() throws IOException;
+
+ /**
+ * Returns next byte available, if any; if no more bytes are
+ * available, will throw {@link java.io.EOFException}.
+ */
+ public byte nextByte() throws IOException;
+
+ /**
+ * Method that can be called to reset accessor to read from beginning
+ * of input.
+ */
+ public void reset();
+
+ /*
+ /**********************************************************
+ /* Standard implementation
+ /**********************************************************
+ */
+
+ /**
+ * Basic implementation that reads data from given
+ * {@link InputStream} and buffers it as necessary.
+ */
+ public class Std implements InputAccessor
+ {
+ protected final InputStream _in;
+
+ protected final byte[] _buffer;
+
+ /**
+ * Number of bytes in {@link #_buffer} that are valid
+ * buffered content.
+ */
+ protected int _bufferedAmount;
+
+ /**
+ * 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;
+ _bufferedAmount = 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:
+ _bufferedAmount = inputDocument.length;
+ }
+
+ @Override
+ public boolean hasMoreBytes() throws IOException
+ {
+ if (_ptr < _bufferedAmount) { // already got more
+ return true;
+ }
+ 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;
+ }
+ _bufferedAmount += count;
+ return true;
+ }
+
+ @Override
+ public byte nextByte() throws IOException
+ {
+ // should we just try loading more automatically?
+ if (_ptr >- _bufferedAmount) {
+ if (!hasMoreBytes()) {
+ throw new EOFException("Could not read more than "+_ptr+" bytes (max buffer size: "+_buffer.length+")");
+ }
+ }
+ return _buffer[_ptr++];
+ }
+
+ @Override
+ public void reset() {
+ _ptr = 0;
+ }
+
+ /*
+ /**********************************************************
+ /* Extended API for DataFormatDetector/Matcher
+ /**********************************************************
+ */
+
+ public DataFormatMatcher createMatcher(JsonFactory match, MatchStrength matchStrength)
+ {
+ return new DataFormatMatcher(_in, _buffer, _bufferedAmount, 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..2ba83d4
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/MatchStrength.java
@@ -0,0 +1,64 @@
+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.
+ *
+ * @since 1.8
+ */
+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..bd10394
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/format/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Package that contains interfaces needed for dynamic, pluggable
+ * format (auto)detection; as well as basic utility classes for
+ * simple format detection functionality.
+ *
+ * @since 1.8
+ */
+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..07ab5b0
--- /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..6a8f1b7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java
@@ -0,0 +1,236 @@
+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
+ *
+ * @since 1.2
+ */
+ 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 final static int[] getInputCodeLatin1() { return sInputCodes; }
+ public final static int[] getInputCodeUtf8() { return sInputCodesUtf8; }
+
+ public final static int[] getInputCodeLatin1JsNames() { return sInputCodesJsNames; }
+ public final static int[] getInputCodeUtf8JsNames() { return sInputCodesUtf8JsNames; }
+
+ public final 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 final 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);
+ }
+ }
+ }
+
+ /**
+ * @since 1.6
+ */
+ public static char[] copyHexChars()
+ {
+ return (char[]) HEX_CHARS.clone();
+ }
+
+ /**
+ * @since 1.6
+ */
+ 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..12960b1
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/CharacterEscapes.java
@@ -0,0 +1,72 @@
+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.
+ *
+ * @since 1.8
+ */
+public abstract class CharacterEscapes
+{
+ /**
+ * 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..1ae4fb8
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/IOContext.java
@@ -0,0 +1,239 @@
+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 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 final Object getSourceReference() { return _sourceRef; }
+ public final JsonEncoding getEncoding() { return _encoding; }
+ public final boolean isResourceManaged() { return _managedResource; }
+
+ /*
+ /**********************************************************
+ /* Public API, buffer management
+ /**********************************************************
+ */
+
+ public final 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 final byte[] allocReadIOBuffer()
+ {
+ if (_readIOBuffer != null) {
+ throw new IllegalStateException("Trying to call allocReadIOBuffer() second time");
+ }
+ _readIOBuffer = _bufferRecycler.allocByteBuffer(BufferRecycler.ByteBufferType.READ_IO_BUFFER);
+ return _readIOBuffer;
+ }
+
+ public final byte[] allocWriteEncodingBuffer()
+ {
+ if (_writeEncodingBuffer != null) {
+ throw new IllegalStateException("Trying to call allocWriteEncodingBuffer() second time");
+ }
+ _writeEncodingBuffer = _bufferRecycler.allocByteBuffer(BufferRecycler.ByteBufferType.WRITE_ENCODING_BUFFER);
+ return _writeEncodingBuffer;
+ }
+
+ public final char[] allocTokenBuffer()
+ {
+ if (_tokenCBuffer != null) {
+ throw new IllegalStateException("Trying to call allocTokenBuffer() second time");
+ }
+ _tokenCBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CharBufferType.TOKEN_BUFFER);
+ return _tokenCBuffer;
+ }
+
+ public final char[] allocConcatBuffer()
+ {
+ if (_concatCBuffer != null) {
+ throw new IllegalStateException("Trying to call allocConcatBuffer() second time");
+ }
+ _concatCBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CharBufferType.CONCAT_BUFFER);
+ return _concatCBuffer;
+ }
+
+ public final char[] allocNameCopyBuffer(int minSize)
+ {
+ if (_nameCopyBuffer != null) {
+ throw new IllegalStateException("Trying to call allocNameCopyBuffer() second time");
+ }
+ _nameCopyBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CharBufferType.NAME_COPY_BUFFER, minSize);
+ return _nameCopyBuffer;
+ }
+
+ /**
+ * Method to call when all the processing buffers can be safely
+ * recycled.
+ */
+ public final 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
+ */
+ if (buf != _readIOBuffer) {
+ throw new IllegalArgumentException("Trying to release buffer not owned by the context");
+ }
+ _readIOBuffer = null;
+ _bufferRecycler.releaseByteBuffer(BufferRecycler.ByteBufferType.READ_IO_BUFFER, buf);
+ }
+ }
+
+ public final 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
+ */
+ if (buf != _writeEncodingBuffer) {
+ throw new IllegalArgumentException("Trying to release buffer not owned by the context");
+ }
+ _writeEncodingBuffer = null;
+ _bufferRecycler.releaseByteBuffer(BufferRecycler.ByteBufferType.WRITE_ENCODING_BUFFER, buf);
+ }
+ }
+
+ public final void releaseTokenBuffer(char[] buf)
+ {
+ if (buf != null) {
+ if (buf != _tokenCBuffer) {
+ throw new IllegalArgumentException("Trying to release buffer not owned by the context");
+ }
+ _tokenCBuffer = null;
+ _bufferRecycler.releaseCharBuffer(BufferRecycler.CharBufferType.TOKEN_BUFFER, buf);
+ }
+ }
+
+ public final void releaseConcatBuffer(char[] buf)
+ {
+ if (buf != null) {
+ if (buf != _concatCBuffer) {
+ throw new IllegalArgumentException("Trying to release buffer not owned by the context");
+ }
+ _concatCBuffer = null;
+ _bufferRecycler.releaseCharBuffer(BufferRecycler.CharBufferType.CONCAT_BUFFER, buf);
+ }
+ }
+
+ public final void releaseNameCopyBuffer(char[] buf)
+ {
+ if (buf != null) {
+ if (buf != _nameCopyBuffer) {
+ throw new IllegalArgumentException("Trying to release buffer not owned by the context");
+ }
+ _nameCopyBuffer = null;
+ _bufferRecycler.releaseCharBuffer(BufferRecycler.CharBufferType.NAME_COPY_BUFFER, buf);
+ }
+ }
+}
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..2cf2417
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/InputDecorator.java
@@ -0,0 +1,67 @@
+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.
+ *
+ * @since 1.8
+ */
+public abstract class InputDecorator
+{
+ /**
+ * 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..56805af
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java
@@ -0,0 +1,407 @@
+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.
+ *
+ * @since 1.6
+ */
+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.SoftRerefence}
+ * 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?
+ int escCode = escCodes[input.charAt(inPtr++)];
+ int length = _appendSingleEscape(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, outPtr, 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
+ _throwIllegalSurrogate(ch);
+ }
+ // and if so, followed by another from next range
+ if (inputPtr >= inputEnd) {
+ _throwIllegalSurrogate(ch);
+ }
+ ch = _convertSurrogate(ch, text.charAt(inputPtr++));
+ if (ch > 0x10FFFF) { // illegal, as per RFC 4627
+ _throwIllegalSurrogate(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.
+ */
+ 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
+ _throwIllegalSurrogate(c);
+ }
+ // and if so, followed by another from next range
+ if (inputPtr >= inputEnd) {
+ _throwIllegalSurrogate(c);
+ }
+ c = _convertSurrogate(c, text.charAt(inputPtr++));
+ if (c > 0x10FFFF) { // illegal, as per RFC 4627
+ _throwIllegalSurrogate(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 _appendSingleEscape(int escCode, char[] quoteBuffer)
+ {
+ if (escCode < 0) { // control char, value -(char + 1)
+ int value = -(escCode + 1);
+ 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;
+ }
+ 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();
+ }
+
+ /**
+ * Method called to calculate UTF code point, from a surrogate pair.
+ */
+ private 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);
+ }
+
+ private void _throwIllegalSurrogate(int code)
+ {
+ if (code > 0x10FFFF) { // over max?
+ throw new IllegalArgumentException("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?)
+ throw new IllegalArgumentException("Unmatched first part of surrogate pair (0x"+Integer.toHexString(code)+")");
+ }
+ throw new IllegalArgumentException("Unmatched second part of surrogate pair (0x"+Integer.toHexString(code)+")");
+ }
+ // should we ever get this?
+ throw new IllegalArgumentException("Illegal character point (0x"+Integer.toHexString(code)+") to output");
+ }
+
+}
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..42bb7be
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java
@@ -0,0 +1,303 @@
+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 final 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.
+ *
+ * @since 1.7
+ */
+ public final 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 final 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 final 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 final 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
+ *
+ * @since 1.5.0
+ */
+ public final 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;
+ }
+
+ /**
+ * @since 1.6
+ */
+ 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;
+ }
+
+ /**
+ * @since 1.6
+ */
+ 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;
+ }
+
+ /**
+ * @since 1.6
+ */
+ 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;
+ }
+
+ /**
+ * @since 1.8
+ */
+ public final static double parseDouble(String numStr) throws NumberFormatException
+ {
+ // [JACKSON-486]: avoid some nasty float representations... but should it be MIN_NORMAL or MIN_VALUE?
+ if (NASTY_SMALL_DOUBLE.equals(numStr)) {
+ return Double.MIN_NORMAL;
+ }
+ 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..291213d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/OutputDecorator.java
@@ -0,0 +1,40 @@
+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.
+ *
+ * @since 1.8
+ */
+public abstract class OutputDecorator
+{
+ /**
+ * 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..7cbb16c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java
@@ -0,0 +1,104 @@
+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>.
+ *
+ * @since 1.3
+ */
+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, 0, str.length());
+ }
+
+ /*
+ /**********************************************************
+ /* 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..41dc7ec
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/SerializedString.java
@@ -0,0 +1,114 @@
+package com.fasterxml.jackson.core.io;
+
+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)
+ *
+ * @since 1.6
+ */
+public class SerializedString implements SerializableString
+{
+ 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) { _value = v; }
+
+ /*
+ /**********************************************************
+ /* 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;
+ }
+
+ /*
+ /**********************************************************
+ /* 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..45e1783
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/io/UTF32Reader.java
@@ -0,0 +1,214 @@
+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 final class UTF32Reader
+ extends BaseReader
+{
+ final boolean mBigEndian;
+
+ /**
+ * 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.
+ */
+ char mSurrogate = NULL_CHAR;
+
+ /**
+ * Total read character count; used for error reporting purposes
+ */
+ int mCharCount = 0;
+
+ /**
+ * Total read byte count; used for error reporting purposes
+ */
+ int mByteCount = 0;
+
+ /*
+ ////////////////////////////////////////
+ // Life-cycle
+ ////////////////////////////////////////
+ */
+
+ public UTF32Reader(IOContext ctxt,
+ InputStream in, byte[] buf, int ptr, int len,
+ boolean isBigEndian)
+ {
+ super(ctxt, in, buf, ptr, len);
+ mBigEndian = isBigEndian;
+ }
+
+ /*
+ ////////////////////////////////////////
+ // 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 (mSurrogate != NULL_CHAR) {
+ cbuf[outPtr++] = mSurrogate;
+ mSurrogate = 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 (mBigEndian) {
+ 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
+ mSurrogate = (char) ch;
+ break main_loop;
+ }
+ }
+ cbuf[outPtr++] = (char) ch;
+ if (_ptr >= _length) {
+ break main_loop;
+ }
+ }
+
+ len = outPtr - start;
+ mCharCount += len;
+ return len;
+ }
+
+ /*
+ ////////////////////////////////////////
+ // Internal methods
+ ////////////////////////////////////////
+ */
+
+ private void reportUnexpectedEOF(int gotBytes, int needed)
+ throws IOException
+ {
+ int bytePos = mByteCount + gotBytes;
+ int charPos = mCharCount;
+
+ 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 = mByteCount + _ptr - 1;
+ int charPos = mCharCount + 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
+ {
+ mByteCount += (_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.read(_buffer);
+ if (count < 1) {
+ _length = 0;
+ if (count < 0) { // -1
+ 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.read(_buffer, _length, _buffer.length - _length);
+ if (count < 1) {
+ if (count < 0) { // -1, EOF... no good!
+ 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..87c4bd0
--- /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 protected IOContext _context;
+
+ OutputStream _out;
+
+ byte[] _outBuffer;
+
+ final int _outBufferEnd;
+
+ 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
+ */
+ 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) {
+ throwIllegal(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;
+ throwIllegal(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;
+ throwIllegal(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) {
+ throwIllegal(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
+ throwIllegal(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;
+ throwIllegal(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;
+ throwIllegal(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.
+ */
+ private 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);
+ }
+
+ private void throwIllegal(int code)
+ throws IOException
+ {
+ if (code > 0x10FFFF) { // over max?
+ throw new IOException("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?)
+ throw new IOException("Unmatched first part of surrogate pair (0x"+Integer.toHexString(code)+")");
+ }
+ throw new IOException("Unmatched second part of surrogate pair (0x"+Integer.toHexString(code)+")");
+ }
+
+ // should we ever get this?
+ throw new IOException("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..a1cf9c8
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/ByteSourceJsonBootstrapper.java
@@ -0,0 +1,518 @@
+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
+ /**********************************************************
+ */
+
+ 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 features, ObjectCodec codec, BytesToNameCanonicalizer rootByteSymbols, CharsToNameCanonicalizer rootCharSymbols)
+ throws IOException, JsonParseException
+ {
+ JsonEncoding enc = detectEncoding();
+
+ // As per [JACKSON-259], may want to fully disable canonicalization:
+ boolean canonicalize = JsonParser.Feature.CANONICALIZE_FIELD_NAMES.enabledIn(features);
+ boolean intern = JsonParser.Feature.INTERN_FIELD_NAMES.enabledIn(features);
+ 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, features, _in, codec, can, _inputBuffer, _inputPtr, _inputEnd, _bufferRecyclable);
+ }
+ }
+ return new ReaderBasedJsonParser(_context, features, 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.
+ *
+ * @since 1.8
+ */
+ 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 final 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 final static int skipSpace(InputAccessor acc) throws IOException
+ {
+ if (!acc.hasMoreBytes()) {
+ return -1;
+ }
+ return skipSpace(acc, acc.nextByte());
+ }
+
+ private final 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/JsonReadContext.java b/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java
new file mode 100644
index 0000000..0edc236
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java
@@ -0,0 +1,188 @@
+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 final 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);
+ }
+
+ /**
+ * @since 1.9
+ */
+ public static JsonReadContext createRootContext()
+ {
+ return new JsonReadContext(null, TYPE_ROOT, 1, 0);
+ }
+
+ public final 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 final 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 final String getCurrentName() { return _currentName; }
+
+ @Override
+ public final JsonReadContext getParent() { return _parent; }
+
+ /*
+ /**********************************************************
+ /* Extended API
+ /**********************************************************
+ */
+
+ /**
+ * @return Location pointing to the point where the context
+ * start marker was found
+ */
+ public final 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 final 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 final 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..bee3c62
--- /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 final 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/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
new file mode 100644
index 0000000..4ad9357
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
@@ -0,0 +1,1817 @@
+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;
+
+ /*
+ /**********************************************************
+ /* 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;
+ }
+
+ /*
+ /**********************************************************
+ /* 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 final 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 final 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);
+ }
+
+ protected final 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();
+ }
+ 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();
+ }
+ }
+ 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;
+ }
+
+ /*
+ /**********************************************************
+ /* 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 final 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;
+ }
+ 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 final 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 final 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 final 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 final 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 = 0;
+ 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 * 31) + 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 * 31) + 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.
+ *
+ * @since 1.2
+ */
+ protected final 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 = 0;
+ 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 * 31) + ch;
+ ++ptr;
+ } while (ptr < inputLen);
+ }
+ int start = _inputPtr-1;
+ _inputPtr = ptr;
+ return _parseUnusualFieldName2(start, hash, codes);
+ }
+
+ protected final String _parseApostropheFieldName()
+ throws IOException, JsonParseException
+ {
+ // Note: mostly copy of_parseFieldName
+ int ptr = _inputPtr;
+ int hash = 0;
+ 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 * 31) + 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.
+ *
+ * @since 1.3
+ */
+ protected final 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;
+ }
+
+ /**
+ * @since 1.8
+ */
+ protected final 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;
+ }
+
+ /**
+ * @since 1.2
+ */
+ 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 * 31) + 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 final void _skipCR() throws IOException
+ {
+ if (_inputPtr < _inputEnd || loadMore()) {
+ if (_inputBuffer[_inputPtr] == '\n') {
+ ++_inputPtr;
+ }
+ }
+ ++_currInputRow;
+ _currInputRowStart = _inputPtr;
+ }
+
+ protected final void _skipLF() throws IOException
+ {
+ ++_currInputRow;
+ _currInputRowStart = _inputPtr;
+ }
+
+ private final 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 final 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 final 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 final 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 final 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 final 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
+ *
+ * @since 1.8
+ */
+ protected final void _matchToken(String matchStr, int i)
+ throws IOException, JsonParseException
+ {
+ final int len = matchStr.length();
+
+ do {
+ if (_inputPtr >= _inputEnd) {
+ if (!loadMore()) {
+ _reportInvalidEOFInValue();
+ }
+ }
+ if (_inputBuffer[_inputPtr] != matchStr.charAt(i)) {
+ _reportInvalidToken(matchStr.substring(0, i), "'null', 'true', 'false' or NaN");
+ }
+ ++_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)) {
+ ++_inputPtr;
+ _reportInvalidToken(matchStr.substring(0, i), "'null', 'true', 'false' or NaN");
+ }
+ 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, 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..0d70db7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java
@@ -0,0 +1,1758 @@
+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.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.io.NumberOutput;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+public class UTF8JsonGenerator
+ extends GeneratorBase
+{
+ 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_SPACE = (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' };
+
+ /**
+ * 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;
+
+ /**
+ * Underlying output stream used for writing JSON content.
+ */
+ final protected OutputStream _outputStream;
+
+ /*
+ /**********************************************************
+ /* 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.
+ *
+ * @since 1.8
+ */
+ protected CharacterEscapes _characterEscapes;
+
+ /*
+ /**********************************************************
+ /* Output buffering
+ /**********************************************************
+ */
+
+ /**
+ * 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(features, codec);
+ _ioContext = ctxt;
+ _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(features, codec);
+ _ioContext = ctxt;
+ _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;
+
+ 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.
+ *
+ * @since 1.8
+ */
+ @Override
+ public CharacterEscapes getCharacterEscapes() {
+ return _characterEscapes;
+ }
+
+ @Override
+ public Object getOutputTarget() {
+ return _outputStream;
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden methods
+ /**********************************************************
+ */
+
+ /* Most overrides in this section are just to make methods final,
+ * to allow better inlining...
+ */
+ @Override
+ public final void writeStringField(String fieldName, String value)
+ throws IOException, JsonGenerationException
+ {
+ writeFieldName(fieldName);
+ writeString(value);
+ }
+
+ @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(SerializedString 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);
+ }
+
+ @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());
+ }
+ _writeContext = _writeContext.getParent();
+ if (_cfgPrettyPrinter != null) {
+ _cfgPrettyPrinter.writeEndObject(this, _writeContext.getEntryCount());
+ } else {
+ if (_outputTail >= _outputEnd) {
+ _flushBuffer();
+ }
+ _outputBuffer[_outputTail++] = BYTE_RCURLY;
+ }
+ }
+
+ 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
+ {
+ byte[] raw = name.asQuotedUTF8();
+ if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
+ _writeBytes(raw);
+ return;
+ }
+ if (_outputTail >= _outputEnd) {
+ _flushBuffer();
+ }
+ _outputBuffer[_outputTail++] = BYTE_QUOTE;
+
+ // Can do it all in buffer?
+ final int len = raw.length;
+ if ((_outputTail + len + 1) < _outputEnd) { // yup
+ System.arraycopy(raw, 0, _outputBuffer, _outputTail, len);
+ _outputTail += len;
+ _outputBuffer[_outputTail++] = BYTE_QUOTE;
+ } else {
+ _writeBytes(raw);
+ 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 final 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 final 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;
+ _writeBytes(text.asQuotedUTF8());
+ 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;
+ }
+ }
+
+ // @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;
+ }
+
+ /*
+ /**********************************************************
+ /* Output method implementations, primitive
+ /**********************************************************
+ */
+
+ @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 final 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 final 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 final 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:
+ b = BYTE_SPACE;
+ break;
+ 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:
+ _cantHappen();
+ 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)
+ /* (since 1.8; see [JACKSON-102])
+ /**********************************************************
+ */
+
+ /**
+ * Same as <code>_writeStringSegment2(char[], ...)</code., but with
+ * additional escaping for high-range code points
+ *
+ * @since 1.8
+ */
+ 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
+ *
+ * @since 1.8
+ */
+ private final 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) {
+ throw new JsonGenerationException("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 final 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 final 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 final 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);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* 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 final 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 final 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 final 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..e40aa3a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
@@ -0,0 +1,2950 @@
+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;
+ // 12-Mar-2010, tatus: Sanity check, related to [JACKSON-259]:
+ if (!JsonParser.Feature.CANONICALIZE_FIELD_NAMES.enabledIn(features)) {
+ // should never construct non-canonical UTF-8/byte parser (instead, use Reader)
+ _throwInternal();
+ }
+ }
+
+ @Override
+ public ObjectCodec getCodec() {
+ return _objectCodec;
+ }
+
+ @Override
+ public void setCodec(ObjectCodec c) {
+ _objectCodec = c;
+ }
+
+ /*
+ /**********************************************************
+ /* Former StreamBasedParserBase methods
+ /**********************************************************
+ */
+
+ @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 final 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
+ *
+ * @since 1.6
+ */
+ protected final 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
+ {
+ JsonToken t = _currToken;
+ if (t == JsonToken.VALUE_STRING) {
+ if (_tokenIncomplete) {
+ _tokenIncomplete = false;
+ _finishString(); // only strings can be incomplete
+ }
+ return _textBuffer.contentsAsString();
+ }
+ return _getText2(t);
+ }
+
+ protected final 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();
+ }
+ 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();
+ }
+ }
+ 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;
+ }
+
+ /*
+ /**********************************************************
+ /* 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 final 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 final 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;
+ }
+ }
+ }
+ }
+ _isNextTokenNameNo(i);
+ return false;
+ }
+
+ private final void _isNextTokenNameYes()
+ throws IOException, JsonParseException
+ {
+ // very first thing: common case, colon, value, no white space
+ int i;
+ if (_inputPtr < _inputEnd && _inputBuffer[_inputPtr] == INT_COLON) { // fast case first
+ ++_inputPtr;
+ i = _inputBuffer[_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 final void _isNextTokenNameNo(int i)
+ throws IOException, JsonParseException
+ {
+ // // // and this is back to standard nextToken()
+
+ 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;
+ }
+ 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;
+ }
+
+ @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;
+ }
+ 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 final 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 final 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 final 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 final 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 final 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 final 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 final Name parseFieldName(int q1, int ch, int lastQuadBytes)
+ throws IOException, JsonParseException
+ {
+ return parseEscapedFieldName(_quadBuffer, 0, q1, ch, lastQuadBytes);
+ }
+
+ private final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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.
+ *
+ * @since 1.3
+ */
+ 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 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;
+ }
+
+ protected final void _matchToken(String matchStr, int i)
+ throws IOException, JsonParseException
+ {
+ final int len = matchStr.length();
+
+ do {
+ if (_inputPtr >= _inputEnd) {
+ if (!loadMore()) {
+ _reportInvalidEOF(" in a value");
+ }
+ }
+ if (_inputBuffer[_inputPtr] != matchStr.charAt(i)) {
+ _reportInvalidToken(matchStr.substring(0, i), "'null', 'true', 'false' or NaN");
+ }
+ ++_inputPtr;
+ } while (++i < len);
+
+ // but let's also ensure we either get EOF, or non-alphanum char...
+ if (_inputPtr >= _inputEnd) {
+ if (!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)) {
+ ++_inputPtr;
+ _reportInvalidToken(matchStr.substring(0, i), "'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;
+ }
+ ++_inputPtr;
+ sb.append(c);
+ }
+ _reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg);
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods, ws skipping, escape/unescape
+ /**********************************************************
+ */
+
+ private final 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 final 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
+ *
+ * @since 1.9
+ */
+ private final 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:
+ 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 final 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 final 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 '*/'
+ while ((_inputPtr < _inputEnd) || loadMore()) {
+ int i = (int) _inputBuffer[_inputPtr++] & 0xFF;
+ int code = codes[i];
+ if (code != 0) {
+ switch (code) {
+ case INT_ASTERISK:
+ if (_inputBuffer[_inputPtr] == INT_SLASH) {
+ ++_inputPtr;
+ return;
+ }
+ break;
+ case INT_LF:
+ _skipLF();
+ break;
+ case INT_CR:
+ _skipCR();
+ break;
+ default: // e.g. -1
+ // Is this good enough error message?
+ _reportInvalidChar(i);
+ }
+ }
+ }
+ _reportInvalidEOF(" in a comment");
+ }
+
+ private final 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;
+ default: // e.g. -1
+ // Is this good enough error message?
+ _reportInvalidChar(i);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final void _skipCR() throws IOException
+ {
+ if (_inputPtr < _inputEnd || loadMore()) {
+ if (_inputBuffer[_inputPtr] == BYTE_LF) {
+ ++_inputPtr;
+ }
+ }
+ ++_currInputRow;
+ _currInputRowStart = _inputPtr;
+ }
+
+ protected final 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..5dedc60
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java
@@ -0,0 +1,1815 @@
+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.base.GeneratorBase;
+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 GeneratorBase
+{
+ final protected static int SHORT_WRITE = 32;
+
+ final protected static char[] HEX_CHARS = CharTypes.copyHexChars();
+
+ /**
+ * 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
+ /**********************************************************
+ */
+
+ final protected IOContext _ioContext;
+
+ final protected Writer _writer;
+
+ /*
+ /**********************************************************
+ /* 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.
+ *
+ * @since 1.8
+ */
+ protected CharacterEscapes _characterEscapes;
+
+ /**
+ * When custom escapes are used, this member variable can be used to
+ * store escape to use
+ *
+ * @since 1.8
+ */
+ protected SerializableString _currentEscape;
+
+ /*
+ /**********************************************************
+ /* Output buffering
+ /**********************************************************
+ */
+
+ /**
+ * 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;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ public WriterBasedJsonGenerator(IOContext ctxt, int features, ObjectCodec codec,
+ Writer w)
+ {
+ super(features, codec);
+ _ioContext = ctxt;
+ _writer = w;
+ _outputBuffer = ctxt.allocConcatBuffer();
+ _outputEnd = _outputBuffer.length;
+
+ 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.
+ *
+ * @since 1.8
+ */
+ @Override
+ public CharacterEscapes getCharacterEscapes() {
+ return _characterEscapes;
+ }
+
+ @Override
+ public Object getOutputTarget() {
+ return _writer;
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden methods
+ /**********************************************************
+ */
+
+ /* Most overrides in this section are just to make methods final,
+ * to allow better inlining...
+ */
+
+ @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");
+ }
+ _writeFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
+ }
+
+ @Override
+ public final void writeStringField(String fieldName, String value)
+ throws IOException, JsonGenerationException
+ {
+ writeFieldName(fieldName);
+ writeString(value);
+ }
+
+ @Override
+ public final void writeFieldName(SerializedString 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));
+ }
+
+ @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");
+ }
+ _writeFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
+ }
+
+ /*
+ /**********************************************************
+ /* 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++] = '[';
+ }
+ }
+
+ @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++] = ']';
+ }
+ _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++] = '{';
+ }
+ }
+
+ @Override
+ public final void writeEndObject() throws IOException, JsonGenerationException
+ {
+ if (!_writeContext.inObject()) {
+ _reportError("Current context not an object but "+_writeContext.getTypeDesc());
+ }
+ _writeContext = _writeContext.getParent();
+ if (_cfgPrettyPrinter != null) {
+ _cfgPrettyPrinter.writeEndObject(this, _writeContext.getEntryCount());
+ } else {
+ if (_outputTail >= _outputEnd) {
+ _flushBuffer();
+ }
+ _outputBuffer[_outputTail++] = '}';
+ }
+ }
+
+ 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 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++] = '"';
+ _writeString(name);
+ if (_outputTail >= _outputEnd) {
+ _flushBuffer();
+ }
+ _outputBuffer[_outputTail++] = '"';
+ } else { // non-standard, omit quotes
+ _writeString(name);
+ }
+ }
+
+ protected final 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 final 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));
+ }
+ }
+
+ @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++] = '"';
+ }
+
+ /*
+ /**********************************************************
+ /* Output method implementations, primitive
+ /**********************************************************
+ */
+
+ @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 final 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 final 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 final 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 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) {
+ 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:
+ c = ' ';
+ break;
+ 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 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:
+ _cantHappen();
+ break;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Low-level output handling
+ /**********************************************************
+ */
+
+ @Override
+ public final 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 final 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 final 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)
+ /* (since 1.8; see [JACKSON-102])
+ /**********************************************************
+ */
+
+ /* 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, _maximumNonEscapedChar+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 final void _writeSegmentASCII(int end, final int maxNonEscaped)
+ throws IOException, JsonGenerationException
+ {
+ final int[] escCodes = _outputEscapes;
+ final int escLimit = Math.min(escCodes.length, _maximumNonEscapedChar+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 final 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)
+ /* (since 1.8; see [JACKSON-106])
+ /**********************************************************
+ */
+
+ /* 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 final 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, _maximumNonEscapedChar+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 final 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);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods, low-level writing, other
+ /**********************************************************
+ */
+
+ private final 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 final 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 final 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 final 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 final 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..2359269
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/package-info.java
@@ -0,0 +1,30 @@
+/**
+ * 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
+ * under {@link org.codehaus.jackson.map} and not included here,
+ * except for following base interfaces:
+ * <ul>
+ *<li>{@link com.fasterxml.jackson.core.JsonNode} 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, {@link org.codehaus.jackson.map.ObjectMapper})
+ * can be exposed, without adding direct dependency to implementation.
+ * </li>
+ *</ul>
+ * </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..a8eeaae
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/BytesToNameCanonicalizer.java
@@ -0,0 +1,969 @@
+package com.fasterxml.jackson.core.sym;
+
+import java.util.Arrays;
+
+import com.fasterxml.jackson.core.util.InternCache;
+
+/**
+ * This class is basically a caching symbol table implementation used for
+ * canonicalizing {@link Name}s, constructed directly from a byte-based
+ * input source.
+ *
+ * @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 uniquer (~= random) names.
+ *
+ * @since 1.5
+ */
+ 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;
+
+ final static int MIN_HASH_SIZE = 16;
+
+ 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
+ /**********************************************************
+ */
+
+ final BytesToNameCanonicalizer _parent;
+
+ /*
+ /**********************************************************
+ /* Main table state
+ /**********************************************************
+ */
+
+ /**
+ * Whether canonial symbol Strings are to be intern()ed before added
+ * to the table or not
+ */
+ final boolean _intern;
+
+ // // // First, global information
+
+ /**
+ * Total number of Names in the symbol table
+ */
+ private int _count;
+
+ // // // 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).
+ */
+ private 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)
+ */
+ private int[] _mainHash;
+
+ /**
+ * Array that contains <code>Name</code> instances matching
+ * entries in <code>_mainHash</code>. Contains nulls for unused
+ * entries.
+ */
+ private Name[] _mainNames;
+
+ // // // Then the collision/spill-over area info
+
+ /**
+ * Array of heads of collision bucket chains; size dynamically
+ */
+ private Bucket[] _collList;
+
+ /**
+ * Total number of Names in collision buckets (included in
+ * <code>_count</code> along with primary entries)
+ */
+ private 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)
+ */
+ private 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;
+
+ /*
+ /**********************************************************
+ /* Construction, merging
+ /**********************************************************
+ */
+
+ public static BytesToNameCanonicalizer createRoot()
+ {
+ return new BytesToNameCanonicalizer(DEFAULT_TABLE_SIZE, true);
+ }
+
+ /**
+ * @param intern Whether canonical symbol Strings should be interned
+ * or not
+ */
+ public synchronized BytesToNameCanonicalizer makeChild(boolean canonicalize,
+ boolean intern)
+ {
+ return new BytesToNameCanonicalizer(this, intern);
+ }
+
+ /**
+ * 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()
+ {
+ if (maybeDirty() && _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.
+ */
+ markAsShared();
+ }
+ }
+
+ private BytesToNameCanonicalizer(int hashSize, boolean intern)
+ {
+ _parent = null;
+ _intern = intern;
+ /* Sanity check: let's now allow hash sizes below certain
+ * min. 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;
+ }
+ }
+ initTables(hashSize);
+ }
+
+ /**
+ * Constructor used when creating a child instance
+ */
+ private BytesToNameCanonicalizer(BytesToNameCanonicalizer parent, boolean intern)
+ {
+ _parent = parent;
+ _intern = intern;
+
+ // First, let's copy the state as is:
+ _count = parent._count;
+ _mainHashMask = parent._mainHashMask;
+ _mainHash = parent._mainHash;
+ _mainNames = parent._mainNames;
+ _collList = parent._collList;
+ _collCount = parent._collCount;
+ _collEnd = parent._collEnd;
+ _needRehash = false;
+ // And consider all shared, so far:
+ _mainHashShared = true;
+ _mainNamesShared = true;
+ _collListShared = true;
+ }
+
+ private void initTables(int hashSize)
+ {
+ _count = 0;
+ _mainHash = new int[hashSize];
+ _mainNames = new Name[hashSize];
+ _mainHashShared = false;
+ _mainNamesShared = false;
+ _mainHashMask = hashSize - 1;
+
+ _collListShared = true; // just since it'll need to be allocated
+ _collList = null;
+ _collEnd = 0;
+
+ _needRehash = false;
+ }
+
+ private synchronized void mergeChild(BytesToNameCanonicalizer child)
+ {
+ // Only makes sense if child has more entries
+ int childCount = child._count;
+ if (childCount <= _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 (child.size() > MAX_ENTRIES_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:
+ initTables(DEFAULT_TABLE_SIZE);
+ } else {
+ _count = child._count;
+ _mainHash = child._mainHash;
+ _mainNames = child._mainNames;
+ _mainHashShared = true; // shouldn't matter for parent
+ _mainNamesShared = true; // - "" -
+ _mainHashMask = child._mainHashMask;
+ _collList = child._collList;
+ _collCount = child._collCount;
+ _collEnd = child._collEnd;
+ }
+ }
+
+ private void markAsShared()
+ {
+ _mainHashShared = true;
+ _mainNamesShared = true;
+ _collListShared = true;
+ }
+
+ /*
+ /**********************************************************
+ /* API, accessors
+ /**********************************************************
+ */
+
+ public int size() { return _count; }
+
+ /**
+ * 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;
+ }
+
+ 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 = 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)
+ {
+ /* // Not needed, never gets called
+ 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
+ /**********************************************************
+ */
+
+ /**
+ * @since 1.6.0
+ */
+ public Name addName(String symbolStr, int q1, int q2)
+ {
+ if (_intern) {
+ symbolStr = InternCache.instance.intern(symbolStr);
+ }
+ int hash = (q2 == 0) ? calcHash(q1) : calcHash(q1, q2);
+ Name symbol = constructName(hash, symbolStr, q1, q2);
+ _addSymbol(hash, symbol);
+ return symbol;
+ }
+
+ public Name addName(String symbolStr, int[] quads, int qlen)
+ {
+ if (_intern) {
+ symbolStr = InternCache.instance.intern(symbolStr);
+ }
+ int hash = calcHash(quads, qlen);
+ Name symbol = constructName(hash, symbolStr, quads, qlen);
+ _addSymbol(hash, symbol);
+ return symbol;
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ public final static int calcHash(int firstQuad)
+ {
+ int hash = firstQuad;
+ hash ^= (hash >>> 16); // to xor hi- and low- 16-bits
+ hash ^= (hash >>> 8); // as well as lowest 2 bytes
+ return hash;
+ }
+
+ public final static int calcHash(int firstQuad, int secondQuad)
+ {
+ int hash = (firstQuad * 31) + secondQuad;
+
+ // If this was called for single-quad instance:
+ //int hash = (secondQuad == 0) ? firstQuad : ((firstQuad * 31) + secondQuad);
+
+ hash ^= (hash >>> 16); // to xor hi- and low- 16-bits
+ hash ^= (hash >>> 8); // as well as lowest 2 bytes
+ return hash;
+ }
+
+ public final static int calcHash(int[] quads, int qlen)
+ {
+ // Note: may be called for qlen < 3
+ int hash = quads[0];
+ for (int i = 1; i < qlen; ++i) {
+ hash = (hash * 31) + quads[i];
+ }
+
+ hash ^= (hash >>> 16); // to xor hi- and low- 16-bits
+ hash ^= (hash >>> 8); // as well as lowest 2 bytes
+
+ return hash;
+ }
+
+ /* 26-Nov-2008, tatu: not used currently; if not used in near future,
+ * let's just delete it.
+ */
+ /*
+ public 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
+ _collList[bucket] = new Bucket(symbol, _collList[bucket]);
+ }
+
+ /* 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...
+ return;
+ }
+
+ _collCount = 0;
+ _collEnd = 0;
+ _collListShared = false;
+
+ 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
+ _collList[bucket] = new Bucket(symbol, _collList[bucket]);
+ }
+ } // for (... buckets in the chain ...)
+ } // for (... list of bucket heads ... )
+
+ 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;
+ 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);
+ }
+
+ /*
+ /**********************************************************
+ /* Helper classes
+ /**********************************************************
+ */
+
+ final static class Bucket
+ {
+ protected final Name _name;
+ protected final Bucket _next;
+
+ Bucket(Name name, Bucket next)
+ {
+ _name = name;
+ _next = next;
+ }
+
+ public int length()
+ {
+ int len = 1;
+ for (Bucket curr = _next; curr != null; curr = curr._next) {
+ ++len;
+ }
+ return len;
+ }
+
+ 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..531e866
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java
@@ -0,0 +1,578 @@
+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 (esp. 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 (ie. no modifications done).
+ */
+
+public final class CharsToNameCanonicalizer
+{
+ /**
+ * 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.
+ *
+ * @since 1.5
+ */
+ 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;
+
+ 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;
+
+ /**
+ * 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;
+
+ /*
+ /****************************************
+ /* 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()
+ {
+ return sBootstrapSymbolTable.makeOrphan();
+ }
+
+ /**
+ * 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;
+ 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;
+ // Hard-coded fill factor is 75%
+ _sizeThreshold = (initialSize - (initialSize >> 2));
+ }
+
+ /**
+ * Internal constructor used when creating child instances.
+ */
+ private CharsToNameCanonicalizer(CharsToNameCanonicalizer parent,
+ boolean canonicalize, boolean intern,
+ String[] symbols, Bucket[] buckets, int size)
+ {
+ _parent = parent;
+ _canonicalize = canonicalize;
+ _intern = intern;
+
+ _symbols = symbols;
+ _buckets = buckets;
+ _size = size;
+ // Hard-coded fill factor, 75%
+ int arrayLen = (symbols.length);
+ _sizeThreshold = arrayLen - (arrayLen >> 2);
+ _indexMask = (arrayLen - 1);
+
+ // 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 synchronized CharsToNameCanonicalizer makeChild(boolean canonicalize, boolean intern)
+ {
+ return new CharsToNameCanonicalizer(this, canonicalize, intern, _symbols, _buckets, _size);
+ }
+
+ private CharsToNameCanonicalizer makeOrphan()
+ {
+ return new CharsToNameCanonicalizer(null, true, true, _symbols, _buckets, _size);
+ }
+
+ /**
+ * 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 synchronized 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) {
+ /* 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:
+ initTables(DEFAULT_TABLE_SIZE);
+ } 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!
+ _symbols = child._symbols;
+ _buckets = child._buckets;
+ _size = child._size;
+ _sizeThreshold = child._sizeThreshold;
+ _indexMask = child._indexMask;
+ }
+ /* Dirty flag... well, let's just clear it, to force copying just
+ * in case. Shouldn't really matter, for master tables.
+ * (which this is, given something is merged to it etc)
+ */
+ _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; }
+
+ public boolean maybeDirty() { return _dirty; }
+
+ /*
+ /****************************************
+ /* Public API, accessing symbols:
+ /****************************************
+ */
+
+ public String findSymbol(char[] buffer, int start, int len, int hash)
+ {
+ if (len < 1) { // empty Strings are simplest to handle up front
+ return "";
+ }
+ if (!_canonicalize) { // [JACKSON-259]
+ return new String(buffer, start, len);
+ }
+
+ hash &= _indexMask;
+
+ String sym = _symbols[hash];
+
+ // 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[hash >> 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)
+ */
+ hash = calcHash(buffer, start, len) & _indexMask;
+ }
+ ++_size;
+
+ String newSymbol = new String(buffer, start, len);
+ if (_intern) {
+ newSymbol = InternCache.instance.intern(newSymbol);
+ }
+ // Ok; do we need to add primary entry, or a bucket?
+ if (_symbols[hash] == null) {
+ _symbols[hash] = newSymbol;
+ } else {
+ int bix = hash >> 1;
+ _buckets[bix] = new Bucket(newSymbol, _buckets[bix]);
+ }
+
+ return newSymbol;
+ }
+
+ /**
+ * 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 static int calcHash(char[] buffer, int start, int len) {
+ int hash = (int) buffer[0];
+ for (int i = 1; i < len; ++i) {
+ hash = (hash * 31) + (int) buffer[i];
+ }
+ return hash;
+ }
+
+ public static int calcHash(String key) {
+ int hash = (int) key.charAt(0);
+ for (int i = 1, len = key.length(); i < len; ++i) {
+ hash = (hash * 31) + (int) key.charAt(i);
+
+ }
+ return 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 += _sizeThreshold;
+
+ int count = 0; // let's do sanity check
+
+ /* Need to do two loops, unfortunately, since spill-over area is
+ * only half the size:
+ */
+ for (int i = 0; i < size; ++i) {
+ String symbol = oldSyms[i];
+ if (symbol != null) {
+ ++count;
+ int index = calcHash(symbol) & _indexMask;
+ if (_symbols[index] == null) {
+ _symbols[index] = symbol;
+ } else {
+ int bix = index >> 1;
+ _buckets[bix] = new Bucket(symbol, _buckets[bix]);
+ }
+ }
+ }
+
+ size >>= 1;
+ for (int i = 0; i < size; ++i) {
+ Bucket b = oldBuckets[i];
+ while (b != null) {
+ ++count;
+ String symbol = b.getSymbol();
+ int index = calcHash(symbol) & _indexMask;
+ if (_symbols[index] == null) {
+ _symbols[index] = symbol;
+ } else {
+ int bix = index >> 1;
+ _buckets[bix] = new Bucket(symbol, _buckets[bix]);
+ }
+ b = b.getNext();
+ }
+ }
+
+ if (count != _size) {
+ throw new Error("Internal error on SymbolTable.rehash(): had "+_size+" entries; now have "+count+".");
+ }
+ }
+
+ /*
+ /****************************************
+ /* 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 mNext;
+
+ public Bucket(String symbol, Bucket next) {
+ _symbol = symbol;
+ mNext = next;
+ }
+
+ public String getSymbol() { return _symbol; }
+ public Bucket getNext() { return mNext; }
+
+ public String find(char[] buf, int start, int len) {
+ String sym = _symbol;
+ Bucket b = mNext;
+
+ 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;
+ }
+
+ /* 26-Nov-2008, tatu: not used currently; if not used in near future,
+ * let's just delete it.
+ */
+ /*
+ public String find(String str) {
+ String sym = _symbol;
+ Bucket b = mNext;
+
+ while (true) {
+ if (sym.equals(str)) {
+ 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..d260a9f
--- /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;
+ }
+
+ final 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/JavaType.java b/src/main/java/com/fasterxml/jackson/core/type/JavaType.java
new file mode 100644
index 0000000..86bc769
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/type/JavaType.java
@@ -0,0 +1,432 @@
+package com.fasterxml.jackson.core.type;
+
+import java.lang.reflect.Modifier;
+
+/**
+ * Base class for type token classes used both to contain information
+ * and as keys for deserializers.
+ *<p>
+ * Instances can (only) be constructed by
+ * {@link org.codehaus.jackson.map.type.TypeFactory}.
+ */
+public abstract class JavaType
+{
+ /**
+ * This is the nominal type-erased Class that would be close to the
+ * type represented (but not exactly type, due to type erasure: type
+ * instance may have more information on this).
+ * May be an interface or abstract class, so instantiation
+ * may not be possible.
+ */
+ protected final Class<?> _class;
+
+ protected final int _hashCode;
+
+ /**
+ * Optional handler (codec) that can be attached to indicate
+ * what to use for handling (serializing, deserializing) values of
+ * this specific type.
+ *<p>
+ * Note: untyped (i.e. caller has to cast) because it is used for
+ * different kinds of handlers, with unrelated types.
+ *<p>
+ * TODO: make final and possibly promote to sub-classes
+ */
+ protected /*final*/ Object _valueHandler;
+
+ /**
+ * Optional handler that can be attached to indicate how to handle
+ * additional type metadata associated with this type.
+ *<p>
+ * Note: untyped (i.e. caller has to cast) because it is used for
+ * different kinds of handlers, with unrelated types.
+ *<p>
+ * TODO: make final and possibly promote to sub-classes
+ */
+ protected /*final*/ Object _typeHandler;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ /**
+ * @param raw "Raw" (type-erased) class for this type
+ * @param additionalHash Additional hash code to use, in addition
+ * to hash code of the class name
+ */
+ protected JavaType(Class<?> raw, int additionalHash)
+ {
+ _class = raw;
+ _hashCode = raw.getName().hashCode() + additionalHash;
+ _valueHandler = null;
+ _typeHandler = null;
+ }
+
+ /**
+ * "Copy method" that will construct a new instance that is identical to
+ * this instance, except that it will have specified type handler assigned.
+ *
+ * @return Newly created type instance
+ */
+ public abstract JavaType withTypeHandler(Object h);
+
+ /**
+ * "Copy method" that will construct a new instance that is identical to
+ * this instance, except that its content type will have specified
+ * type handler assigned.
+ *
+ * @return Newly created type instance
+ */
+ public abstract JavaType withContentTypeHandler(Object h);
+
+ /**
+ * "Copy method" that will construct a new instance that is identical to
+ * this instance, except that it will have specified value handler assigned.
+ *
+ * @return Newly created type instance
+ */
+ public abstract JavaType withValueHandler(Object h);
+
+ /**
+ * "Copy method" that will construct a new instance that is identical to
+ * this instance, except that it will have specified content value handler assigned.
+ *
+ * @return Newly created type instance
+ */
+ public abstract JavaType withContentValueHandler(Object h);
+
+ /*
+ /**********************************************************
+ /* Type coercion fluent factory methods
+ /**********************************************************
+ */
+
+ /**
+ * Method that can be called to do a "narrowing" conversions; that is,
+ * to return a type with a raw class that is assignable to the raw
+ * class of this type. If this is not possible, an
+ * {@link IllegalArgumentException} is thrown.
+ * If class is same as the current raw class, instance itself is
+ * returned.
+ */
+ public JavaType narrowBy(Class<?> subclass)
+ {
+ // First: if same raw class, just return this instance
+ if (subclass == _class) {
+ return this;
+ }
+ // Otherwise, ensure compatibility
+ _assertSubclass(subclass, _class);
+ JavaType result = _narrow(subclass);
+
+ // TODO: these checks should NOT actually be needed; above should suffice:
+ if (_valueHandler != result.getValueHandler()) {
+ result = result.withValueHandler(_valueHandler);
+ }
+ if (_typeHandler != result.getTypeHandler()) {
+ result = result.withTypeHandler(_typeHandler);
+ }
+ return result;
+ }
+
+ /**
+ * More efficient version of {@link #narrowBy}, called by
+ * internal framework in cases where compatibility checks
+ * are to be skipped.
+ */
+ public JavaType forcedNarrowBy(Class<?> subclass)
+ {
+ if (subclass == _class) { // can still optimize for simple case
+ return this;
+ }
+ JavaType result = _narrow(subclass);
+ // TODO: these checks should NOT actually be needed; above should suffice:
+ if (_valueHandler != result.getValueHandler()) {
+ result = result.withValueHandler(_valueHandler);
+ }
+ if (_typeHandler != result.getTypeHandler()) {
+ result = result.withTypeHandler(_typeHandler);
+ }
+ return result;
+ }
+
+ /**
+ * Method that can be called to do a "widening" conversions; that is,
+ * to return a type with a raw class that could be assigned from this
+ * type.
+ * If such conversion is not possible, an
+ * {@link IllegalArgumentException} is thrown.
+ * If class is same as the current raw class, instance itself is
+ * returned.
+ */
+ public JavaType widenBy(Class<?> superclass)
+ {
+ // First: if same raw class, just return this instance
+ if (superclass == _class) {
+ return this;
+ }
+ // Otherwise, ensure compatibility
+ _assertSubclass(_class, superclass);
+ return _widen(superclass);
+ }
+
+ protected abstract JavaType _narrow(Class<?> subclass);
+
+ /**
+ *<p>
+ * Default implementation is just to call {@link #_narrow}, since
+ * underlying type construction is usually identical
+ */
+ protected JavaType _widen(Class<?> superclass) {
+ return _narrow(superclass);
+ }
+
+ public abstract JavaType narrowContentsBy(Class<?> contentClass);
+
+ public abstract JavaType widenContentsBy(Class<?> contentClass);
+
+ /*
+ /**********************************************************
+ /* Public API, simple accessors
+ /**********************************************************
+ */
+
+ public final Class<?> getRawClass() { return _class; }
+
+ /**
+ * Method that can be used to check whether this type has
+ * specified Class as its type erasure. Put another way, returns
+ * true if instantiation of this Type is given (type-erased) Class.
+ */
+ public final boolean hasRawClass(Class<?> clz) {
+ return _class == clz;
+ }
+
+ public boolean isAbstract() {
+ return Modifier.isAbstract(_class.getModifiers());
+ }
+
+ /**
+ * Convenience method for checking whether underlying Java type
+ * is a concrete class or not: abstract classes and interfaces
+ * are not.
+ */
+ public boolean isConcrete() {
+ int mod = _class.getModifiers();
+ if ((mod & (Modifier.INTERFACE | Modifier.ABSTRACT)) == 0) {
+ return true;
+ }
+ /* 19-Feb-2010, tatus: Holy mackarel; primitive types
+ * have 'abstract' flag set...
+ */
+ if (_class.isPrimitive()) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isThrowable() {
+ return Throwable.class.isAssignableFrom(_class);
+ }
+
+ public boolean isArrayType() { return false; }
+
+ public final boolean isEnumType() { return _class.isEnum(); }
+
+ public final boolean isInterface() { return _class.isInterface(); }
+
+ public final boolean isPrimitive() { return _class.isPrimitive(); }
+
+ public final boolean isFinal() { return Modifier.isFinal(_class.getModifiers()); }
+
+ /**
+ * @return True if type represented is a container type; this includes
+ * array, Map and Collection types.
+ */
+ public abstract boolean isContainerType();
+
+ /**
+ * @return True if type is either true {@link java.util.Collection} type,
+ * or something similar (meaning it has at least one type parameter,
+ * which describes type of contents)
+ */
+ public boolean isCollectionLikeType() { return false; }
+
+ /**
+ * @return True if type is either true {@link java.util.Map} type,
+ * or something similar (meaning it has at least two type parameter;
+ * first one describing key type, second value type)
+ */
+ public boolean isMapLikeType() { return false; }
+
+ /*
+ /**********************************************************
+ /* 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 boolean hasGenericTypes()
+ {
+ return containedTypeCount() > 0;
+ }
+
+ /**
+ * Method for accessing key type for this type, assuming type
+ * has such a concept (only Map types do)
+ */
+ public JavaType getKeyType() { return null; }
+
+ /**
+ * 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 JavaType getContentType() { return null; }
+
+ /**
+ * 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 int containedTypeCount() { return 0; }
+
+ /**
+ * 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 JavaType containedType(int index) { return null; }
+
+ /**
+ * 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 String containedTypeName(int index) { return null; }
+
+ /*
+ /**********************************************************
+ /* Semi-public API, accessing handlers
+ /**********************************************************
+ */
+
+ /**
+ * Method for accessing value handler associated with this type, if any
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getValueHandler() { return (T) _valueHandler; }
+
+ /**
+ * Method for accessing type handler associated with this type, if any
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getTypeHandler() { return (T) _typeHandler; }
+
+ /*
+ /**********************************************************
+ /* Support for producing signatures (1.6+)
+ /**********************************************************
+ */
+
+ /**
+ * 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();
+
+ /**
+ * Method for accessing signature that contains generic
+ * type information, in form compatible with JVM 1.5
+ * as per JLS. It is a superset of {@link #getErasedSignature},
+ * in that generic information can be automatically removed
+ * if necessary (just remove outermost
+ * angle brackets along with content inside)
+ */
+ public String getGenericSignature() {
+ StringBuilder sb = new StringBuilder(40);
+ getGenericSignature(sb);
+ return sb.toString();
+ }
+
+ /**
+ *
+ * @param sb StringBuilder to append signature to
+ *
+ * @return StringBuilder that was passed in; returned to allow
+ * call chaining
+ */
+ public abstract StringBuilder getGenericSignature(StringBuilder sb);
+
+ /**
+ * Method for accessing signature without generic
+ * type information, in form compatible with all versions
+ * of JVM, and specifically used for type descriptions
+ * when generating byte code.
+ */
+ public String getErasedSignature() {
+ StringBuilder sb = new StringBuilder(40);
+ getErasedSignature(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Method for accessing signature without generic
+ * type information, in form compatible with all versions
+ * of JVM, and specifically used for type descriptions
+ * when generating byte code.
+ *
+ * @param sb StringBuilder to append signature to
+ *
+ * @return StringBuilder that was passed in; returned to allow
+ * call chaining
+ */
+ public abstract StringBuilder getErasedSignature(StringBuilder sb);
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ protected void _assertSubclass(Class<?> subclass, Class<?> superClass)
+ {
+ if (!_class.isAssignableFrom(subclass)) {
+ throw new IllegalArgumentException("Class "+subclass.getName()+" is not assignable to "+_class.getName());
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Standard methods; let's make them abstract to force override
+ /**********************************************************
+ */
+
+ @Override
+ public abstract String toString();
+
+ @Override
+ public abstract boolean equals(Object o);
+
+ @Override
+ public final int hashCode() { return _hashCode; }
+}
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..cc8e10a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/type/TypeReference.java
@@ -0,0 +1,60 @@
+package com.fasterxml.jackson.core.type;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * This class is used to pass full generics type information, and
+ * avoid problems with type erasure (that basically removes most
+ * usable type references from runtime Class objects).
+ * It 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.
+ */
+public abstract class TypeReference<T>
+ implements Comparable<TypeReference<T>>
+{
+ 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 specifc
+ * case when we know an actual use case, and thereby suitable
+ * work arounds 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..efe2865
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/BufferRecycler.java
@@ -0,0 +1,109 @@
+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)
+ ;
+
+ private 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
+ ;
+
+ private 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 final byte[] balloc(int size)
+ {
+ return new byte[size];
+ }
+
+ private final 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..2dd28be
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/ByteArrayBuilder.java
@@ -0,0 +1,294 @@
+/* Jackson JSON-processor.
+ *
+ * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
+ *
+ * Licensed under the License specified in file LICENSE, included with
+ * the source code and binary code bundles.
+ * You may not use this file except in compliance with the License.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.
+ *
+ * @since 1.5
+ */
+ 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
+ *
+ * @since 1.6
+ */
+ 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
+ *
+ * @since 1.6
+ */
+ 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..d77bfe8
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/DefaultPrettyPrinter.java
@@ -0,0 +1,299 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.*;
+import java.util.Arrays;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * 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.
+ */
+public class DefaultPrettyPrinter
+ implements PrettyPrinter
+{
+ /**
+ * 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
+ {
+ public void writeIndentation(JsonGenerator jg, int level)
+ throws IOException, JsonGenerationException;
+
+ /**
+ * @return True if indenter is considered inline (does not add linefeeds),
+ * false otherwise
+ */
+ public boolean isInline();
+ }
+
+ // // // Config, indentation
+
+ /**
+ * By default, let's use only spaces to separate array values.
+ */
+ protected Indenter _arrayIndenter = new FixedSpaceIndenter();
+
+ /**
+ * 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 = new Lf2SpacesIndenter();
+
+ // // // 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 int _nesting = 0;
+
+ /*
+ /**********************************************************
+ /* Life-cycle (construct, configure)
+ /**********************************************************
+ */
+
+ public DefaultPrettyPrinter() { }
+
+ public void indentArraysWith(Indenter i)
+ {
+ _arrayIndenter = (i == null) ? new NopIndenter() : i;
+ }
+
+ public void indentObjectsWith(Indenter i)
+ {
+ _objectIndenter = (i == null) ? new NopIndenter() : i;
+ }
+
+ public void spacesInObjectEntries(boolean b) { _spacesInObjectEntries = b; }
+
+ /*
+ /**********************************************************
+ /* PrettyPrinter impl
+ /**********************************************************
+ */
+
+ @Override
+ public void writeRootValueSeparator(JsonGenerator jg)
+ throws IOException, JsonGenerationException
+ {
+ jg.writeRaw(' ');
+ }
+
+ @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
+ {
+ public NopIndenter() { }
+ @Override
+ public void writeIndentation(JsonGenerator jg, int level) { }
+ @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
+ implements Indenter
+ {
+ public 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
+ implements Indenter
+ {
+ final static String SYSTEM_LINE_SEPARATOR;
+ static {
+ String lf = null;
+ try {
+ lf = System.getProperty("line.separator");
+ } catch (Throwable t) { } // access exception?
+ SYSTEM_LINE_SEPARATOR = (lf == null) ? "\n" : lf;
+ }
+
+ final static int SPACE_COUNT = 64;
+ final static char[] SPACES = new char[SPACE_COUNT];
+ static {
+ Arrays.fill(SPACES, ' ');
+ }
+
+ public Lf2SpacesIndenter() { }
+
+ @Override
+ public boolean isInline() { return false; }
+
+ @Override
+ public void writeIndentation(JsonGenerator jg, int level)
+ throws IOException, JsonGenerationException
+ {
+ jg.writeRaw(SYSTEM_LINE_SEPARATOR);
+ 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/InternCache.java b/src/main/java/com/fasterxml/jackson/core/util/InternCache.java
new file mode 100644
index 0000000..b1cc7d7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/InternCache.java
@@ -0,0 +1,49 @@
+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.
+ */
+@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)
+ */
+ private final static int MAX_ENTRIES = 192;
+
+ 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..bc22934
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java
@@ -0,0 +1,273 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+public class JsonGeneratorDelegate extends JsonGenerator
+{
+ /**
+ * Delegate object that method calls are delegated to.
+ */
+ protected JsonGenerator delegate;
+
+ public JsonGeneratorDelegate(JsonGenerator d) {
+ delegate = d;
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override
+ public void copyCurrentEvent(JsonParser jp) throws IOException, JsonProcessingException {
+ delegate.copyCurrentEvent(jp);
+ }
+
+ @Override
+ public void copyCurrentStructure(JsonParser jp) throws IOException, JsonProcessingException {
+ delegate.copyCurrentStructure(jp);
+ }
+
+ @Override
+ public JsonGenerator disable(Feature f) {
+ return delegate.disable(f);
+ }
+
+ @Override
+ public JsonGenerator enable(Feature f) {
+ return delegate.enable(f);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ delegate.flush();
+ }
+
+ @Override
+ public ObjectCodec getCodec() {
+ return delegate.getCodec();
+ }
+
+ @Override
+ public JsonStreamContext getOutputContext() {
+ return delegate.getOutputContext();
+ }
+
+ @Override
+ public void setSchema(FormatSchema schema) {
+ delegate.setSchema(schema);
+ }
+
+ @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 boolean isClosed() {
+ return delegate.isClosed();
+ }
+
+ @Override
+ public boolean isEnabled(Feature f) {
+ return delegate.isEnabled(f);
+ }
+
+ @Override
+ public JsonGenerator setCodec(ObjectCodec oc) {
+ delegate.setCodec(oc);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator useDefaultPrettyPrinter() {
+ delegate.useDefaultPrettyPrinter();
+ return this;
+ }
+
+ @Override
+ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len)
+ throws IOException, JsonGenerationException
+ {
+ delegate.writeBinary(b64variant, data, offset, len);
+ }
+
+ @Override
+ public void writeBoolean(boolean state) throws IOException, JsonGenerationException {
+ delegate.writeBoolean(state);
+ }
+
+ @Override
+ public void writeEndArray() throws IOException, JsonGenerationException {
+ delegate.writeEndArray();
+ }
+
+ @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(SerializedString name)
+ throws IOException, JsonGenerationException
+ {
+ delegate.writeFieldName(name);
+ }
+
+ @Override
+ public void writeFieldName(SerializableString name)
+ throws IOException, JsonGenerationException
+ {
+ delegate.writeFieldName(name);
+ }
+
+ @Override
+ public void writeNull() throws IOException, JsonGenerationException {
+ delegate.writeNull();
+ }
+
+ @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 writeObject(Object pojo) throws IOException,JsonProcessingException {
+ delegate.writeObject(pojo);
+ }
+
+ @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(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 writeStartArray() throws IOException, JsonGenerationException {
+ delegate.writeStartArray();
+ }
+
+ @Override
+ public void writeStartObject() throws IOException, JsonGenerationException {
+ delegate.writeStartObject();
+ }
+
+ @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);
+ }
+
+ @Override
+ public void writeTree(JsonNode rootNode) throws IOException, JsonProcessingException {
+ delegate.writeTree(rootNode);
+ }
+}
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..a43db72
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java
@@ -0,0 +1,243 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.IOException;
+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.
+ *
+ * @since 1.4
+ */
+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 void setSchema(FormatSchema schema) {
+ delegate.setSchema(schema);
+ }
+
+ @Override
+ public boolean canUseSchema(FormatSchema schema) {
+ return delegate.canUseSchema(schema);
+ }
+
+ @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 void clearCurrentToken() {
+ delegate.clearCurrentToken();
+ }
+
+ @Override
+ public String getCurrentName() throws IOException, JsonParseException {
+ return delegate.getCurrentName();
+ }
+
+ @Override
+ public JsonLocation getCurrentLocation() {
+ return delegate.getCurrentLocation();
+ }
+
+ @Override
+ public JsonToken getLastClearedToken() {
+ return delegate.getLastClearedToken();
+ }
+
+ @Override
+ public JsonStreamContext getParsingContext() {
+ return delegate.getParsingContext();
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, access to token information, text
+ /**********************************************************
+ */
+
+ @Override
+ public String getText() throws IOException, JsonParseException {
+ return delegate.getText();
+ }
+
+ @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 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();
+ }
+
+ @Override
+ public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException {
+ return delegate.getBinaryValue(b64variant);
+ }
+
+ @Override
+ public JsonLocation getTokenLocation() {
+ return delegate.getTokenLocation();
+ }
+
+ @Override
+ public JsonToken nextToken() throws IOException, JsonParseException {
+ return delegate.nextToken();
+ }
+
+ @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..e796898
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/JsonParserSequence.java
@@ -0,0 +1,150 @@
+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
+ *
+ * @author tatu
+ * @since 1.5
+ */
+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..a077833
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/MinimalPrettyPrinter.java
@@ -0,0 +1,152 @@
+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).
+ *
+ * @since 1.6
+ */
+public class MinimalPrettyPrinter
+ implements PrettyPrinter
+{
+ /**
+ * 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);
+ }
+
+ /**
+ * @since 1.9
+ */
+ 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..2188b70
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
@@ -0,0 +1,707 @@
+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 final 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 final 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}.
+ *
+ * @since 1.9
+ */
+ 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;
+ } else {
+ // 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.
+ expand(len); // note: curr != _currentSegment after this
+ System.arraycopy(c, start, _currentSegment, 0, len);
+ _currentSize = len;
+ }
+ }
+
+ 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;
+ } else {
+ // 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.
+ */
+ expand(len);
+ str.getChars(offset, offset+len, _currentSegment, 0);
+ _currentSize = len;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* 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 final 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 final char[] _charArray(int len) {
+ return new char[len];
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/util/TokenBuffer.java b/src/main/java/com/fasterxml/jackson/core/util/TokenBuffer.java
new file mode 100644
index 0000000..b80ea05
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/TokenBuffer.java
@@ -0,0 +1,1219 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.base.ParserMinimalBase;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.core.json.JsonReadContext;
+import com.fasterxml.jackson.core.json.JsonWriteContext;
+
+/**
+ * Utility class used for efficient storage of {@link JsonToken}
+ * sequences, needed for temporary buffering.
+ * Space efficient for different sequence lengths (especially so for smaller
+ * ones; but not significantly less efficient for larger), highly efficient
+ * for linear iteration and appending. Implemented as segmented/chunked
+ * linked list of tokens; only modifications are via appends.
+ *
+ * @since 1.5
+ */
+public class TokenBuffer
+/* Won't use JsonGeneratorBase, to minimize overhead for validity
+ * checking
+ */
+ extends JsonGenerator
+{
+ protected final static int DEFAULT_PARSER_FEATURES = JsonParser.Feature.collectDefaults();
+
+ /*
+ /**********************************************************
+ /* Configuration
+ /**********************************************************
+ */
+
+ /**
+ * Object codec to use for stream-based object
+ * conversion through parser/generator interfaces. If null,
+ * such methods can not be used.
+ */
+ protected ObjectCodec _objectCodec;
+
+ /**
+ * Bit flag composed of bits that indicate which
+ * {@link com.fasterxml.jackson.core.JsonGenerator.Feature}s
+ * are enabled.
+ *<p>
+ * NOTE: most features have no effect on this class
+ */
+ protected int _generatorFeatures;
+
+ protected boolean _closed;
+
+ /*
+ /**********************************************************
+ /* Token buffering state
+ /**********************************************************
+ */
+
+ /**
+ * First segment, for contents this buffer has
+ */
+ protected Segment _first;
+
+ /**
+ * Last segment of this buffer, one that is used
+ * for appending more tokens
+ */
+ protected Segment _last;
+
+ /**
+ * Offset within last segment,
+ */
+ protected int _appendOffset;
+
+ /*
+ /**********************************************************
+ /* Output state
+ /**********************************************************
+ */
+
+ protected JsonWriteContext _writeContext;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ /**
+ * @param codec Object codec to use for stream-based object
+ * conversion through parser/generator interfaces. If null,
+ * such methods can not be used.
+ */
+ public TokenBuffer(ObjectCodec codec)
+ {
+ _objectCodec = codec;
+ _generatorFeatures = DEFAULT_PARSER_FEATURES;
+ _writeContext = JsonWriteContext.createRootContext();
+ // at first we have just one segment
+ _first = _last = new Segment();
+ _appendOffset = 0;
+ }
+
+ /**
+ * Method used to create a {@link JsonParser} that can read contents
+ * stored in this buffer. Will use default <code>_objectCodec</code> for
+ * object conversions.
+ *<p>
+ * Note: instances are not synchronized, that is, they are not thread-safe
+ * if there are concurrent appends to the underlying buffer.
+ *
+ * @return Parser that can be used for reading contents stored in this buffer
+ */
+ public JsonParser asParser()
+ {
+ return asParser(_objectCodec);
+ }
+
+ /**
+ * Method used to create a {@link JsonParser} that can read contents
+ * stored in this buffer.
+ *<p>
+ * Note: instances are not synchronized, that is, they are not thread-safe
+ * if there are concurrent appends to the underlying buffer.
+ *
+ * @param codec Object codec to use for stream-based object
+ * conversion through parser/generator interfaces. If null,
+ * such methods can not be used.
+ *
+ * @return Parser that can be used for reading contents stored in this buffer
+ */
+ public JsonParser asParser(ObjectCodec codec)
+ {
+ return new Parser(_first, codec);
+ }
+
+ /**
+ * @param src Parser to use for accessing source information
+ * like location, configured codec
+ */
+ public JsonParser asParser(JsonParser src)
+ {
+ Parser p = new Parser(_first, src.getCodec());
+ p.setLocation(src.getTokenLocation());
+ return p;
+ }
+
+ /*
+ /**********************************************************
+ /* Other custom methods not needed for implementing interfaces
+ /**********************************************************
+ */
+
+ /**
+ * Helper method that will write all contents of this buffer
+ * using given {@link JsonGenerator}.
+ *<p>
+ * Note: this method would be enough to implement
+ * <code>JsonSerializer</code> for <code>TokenBuffer</code> type;
+ * but we can not have upwards
+ * references (from core to mapper package); and as such we also
+ * can not take second argument.
+ */
+ public void serialize(JsonGenerator jgen)
+ throws IOException, JsonGenerationException
+ {
+ Segment segment = _first;
+ int ptr = -1;
+
+ while (true) {
+ if (++ptr >= Segment.TOKENS_PER_SEGMENT) {
+ ptr = 0;
+ segment = segment.next();
+ if (segment == null) break;
+ }
+ JsonToken t = segment.type(ptr);
+ if (t == null) break;
+
+ // Note: copied from 'copyCurrentEvent'...
+ switch (t) {
+ case START_OBJECT:
+ jgen.writeStartObject();
+ break;
+ case END_OBJECT:
+ jgen.writeEndObject();
+ break;
+ case START_ARRAY:
+ jgen.writeStartArray();
+ break;
+ case END_ARRAY:
+ jgen.writeEndArray();
+ break;
+ case FIELD_NAME:
+ {
+ // 13-Dec-2010, tatu: Maybe we should start using different type tokens to reduce casting?
+ Object ob = segment.get(ptr);
+ if (ob instanceof SerializableString) {
+ jgen.writeFieldName((SerializableString) ob);
+ } else {
+ jgen.writeFieldName((String) ob);
+ }
+ }
+ break;
+ case VALUE_STRING:
+ {
+ Object ob = segment.get(ptr);
+ if (ob instanceof SerializableString) {
+ jgen.writeString((SerializableString) ob);
+ } else {
+ jgen.writeString((String) ob);
+ }
+ }
+ break;
+ case VALUE_NUMBER_INT:
+ {
+ Number n = (Number) segment.get(ptr);
+ if (n instanceof BigInteger) {
+ jgen.writeNumber((BigInteger) n);
+ } else if (n instanceof Long) {
+ jgen.writeNumber(n.longValue());
+ } else {
+ jgen.writeNumber(n.intValue());
+ }
+ }
+ break;
+ case VALUE_NUMBER_FLOAT:
+ {
+ Object n = segment.get(ptr);
+ if (n instanceof BigDecimal) {
+ jgen.writeNumber((BigDecimal) n);
+ } else if (n instanceof Float) {
+ jgen.writeNumber(((Float) n).floatValue());
+ } else if (n instanceof Double) {
+ jgen.writeNumber(((Double) n).doubleValue());
+ } else if (n == null) {
+ jgen.writeNull();
+ } else if (n instanceof String) {
+ jgen.writeNumber((String) n);
+ } else {
+ throw new JsonGenerationException("Unrecognized value type for VALUE_NUMBER_FLOAT: "+n.getClass().getName()+", can not serialize");
+ }
+ }
+ break;
+ case VALUE_TRUE:
+ jgen.writeBoolean(true);
+ break;
+ case VALUE_FALSE:
+ jgen.writeBoolean(false);
+ break;
+ case VALUE_NULL:
+ jgen.writeNull();
+ break;
+ case VALUE_EMBEDDED_OBJECT:
+ jgen.writeObject(segment.get(ptr));
+ break;
+ default:
+ throw new RuntimeException("Internal error: should never end up through this code path");
+ }
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ // Let's print up to 100 first tokens...
+ final int MAX_COUNT = 100;
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("[TokenBuffer: ");
+ JsonParser jp = asParser();
+ int count = 0;
+
+ while (true) {
+ JsonToken t;
+ try {
+ t = jp.nextToken();
+ } catch (IOException ioe) { // should never occur
+ throw new IllegalStateException(ioe);
+ }
+ if (t == null) break;
+ if (count < MAX_COUNT) {
+ if (count > 0) {
+ sb.append(", ");
+ }
+ sb.append(t.toString());
+ }
+ ++count;
+ }
+
+ if (count >= MAX_COUNT) {
+ sb.append(" ... (truncated ").append(count-MAX_COUNT).append(" entries)");
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ /*
+ /**********************************************************
+ /* JsonGenerator implementation: configuration
+ /**********************************************************
+ */
+
+ @Override
+ public JsonGenerator enable(Feature f) {
+ _generatorFeatures |= f.getMask();
+ return this;
+ }
+
+ @Override
+ public JsonGenerator disable(Feature f) {
+ _generatorFeatures &= ~f.getMask();
+ return this;
+ }
+
+ //public JsonGenerator configure(Feature f, boolean state) { }
+
+ @Override
+ public boolean isEnabled(Feature f) {
+ return (_generatorFeatures & f.getMask()) != 0;
+ }
+
+ @Override
+ public JsonGenerator useDefaultPrettyPrinter() {
+ // No-op: we don't indent
+ return this;
+ }
+
+ @Override
+ public JsonGenerator setCodec(ObjectCodec oc) {
+ _objectCodec = oc;
+ return this;
+ }
+
+ @Override
+ public ObjectCodec getCodec() { return _objectCodec; }
+
+ @Override
+ public final JsonWriteContext getOutputContext() { return _writeContext; }
+
+ /*
+ /**********************************************************
+ /* JsonGenerator implementation: low-level output handling
+ /**********************************************************
+ */
+
+ @Override
+ public void flush() throws IOException { /* NOP */ }
+
+ @Override
+ public void close() throws IOException {
+ _closed = true;
+ }
+
+ @Override
+ public boolean isClosed() { return _closed; }
+
+ /*
+ /**********************************************************
+ /* JsonGenerator implementation: write methods, structural
+ /**********************************************************
+ */
+
+ @Override
+ public final void writeStartArray()
+ throws IOException, JsonGenerationException
+ {
+ _append(JsonToken.START_ARRAY);
+ _writeContext = _writeContext.createChildArrayContext();
+ }
+
+ @Override
+ public final void writeEndArray()
+ throws IOException, JsonGenerationException
+ {
+ _append(JsonToken.END_ARRAY);
+ // Let's allow unbalanced tho... i.e. not run out of root level, ever
+ JsonWriteContext c = _writeContext.getParent();
+ if (c != null) {
+ _writeContext = c;
+ }
+ }
+
+ @Override
+ public final void writeStartObject()
+ throws IOException, JsonGenerationException
+ {
+ _append(JsonToken.START_OBJECT);
+ _writeContext = _writeContext.createChildObjectContext();
+ }
+
+ @Override
+ public final void writeEndObject()
+ throws IOException, JsonGenerationException
+ {
+ _append(JsonToken.END_OBJECT);
+ // Let's allow unbalanced tho... i.e. not run out of root level, ever
+ JsonWriteContext c = _writeContext.getParent();
+ if (c != null) {
+ _writeContext = c;
+ }
+ }
+
+ @Override
+ public final void writeFieldName(String name)
+ throws IOException, JsonGenerationException
+ {
+ _append(JsonToken.FIELD_NAME, name);
+ _writeContext.writeFieldName(name);
+ }
+
+ @Override
+ public void writeFieldName(SerializableString name)
+ throws IOException, JsonGenerationException
+ {
+ _append(JsonToken.FIELD_NAME, name);
+ _writeContext.writeFieldName(name.getValue());
+ }
+
+ @Override
+ public void writeFieldName(SerializedString name)
+ throws IOException, JsonGenerationException
+ {
+ _append(JsonToken.FIELD_NAME, name);
+ _writeContext.writeFieldName(name.getValue());
+ }
+
+ /*
+ /**********************************************************
+ /* JsonGenerator implementation: write methods, textual
+ /**********************************************************
+ */
+
+ @Override
+ public void writeString(String text) throws IOException,JsonGenerationException {
+ if (text == null) {
+ writeNull();
+ } else {
+ _append(JsonToken.VALUE_STRING, text);
+ }
+ }
+
+ @Override
+ public void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+ writeString(new String(text, offset, len));
+ }
+
+ @Override
+ public void writeString(SerializableString text) throws IOException, JsonGenerationException {
+ if (text == null) {
+ writeNull();
+ } else {
+ _append(JsonToken.VALUE_STRING, text);
+ }
+ }
+
+ @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();
+ }
+
+ @Override
+ public void writeRaw(String text) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRaw(String text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRaw(char c) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRawValue(String text) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRawValue(String text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRawValue(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ /*
+ /**********************************************************
+ /* JsonGenerator implementation: write methods, primitive types
+ /**********************************************************
+ */
+
+ @Override
+ public void writeNumber(int i) throws IOException, JsonGenerationException {
+ _append(JsonToken.VALUE_NUMBER_INT, Integer.valueOf(i));
+ }
+
+ @Override
+ public void writeNumber(long l) throws IOException, JsonGenerationException {
+ _append(JsonToken.VALUE_NUMBER_INT, Long.valueOf(l));
+ }
+
+ @Override
+ public void writeNumber(double d) throws IOException,JsonGenerationException {
+ _append(JsonToken.VALUE_NUMBER_FLOAT, Double.valueOf(d));
+ }
+
+ @Override
+ public void writeNumber(float f) throws IOException, JsonGenerationException {
+ _append(JsonToken.VALUE_NUMBER_FLOAT, Float.valueOf(f));
+ }
+
+ @Override
+ public void writeNumber(BigDecimal dec) throws IOException,JsonGenerationException {
+ if (dec == null) {
+ writeNull();
+ } else {
+ _append(JsonToken.VALUE_NUMBER_FLOAT, dec);
+ }
+ }
+
+ @Override
+ public void writeNumber(BigInteger v) throws IOException, JsonGenerationException {
+ if (v == null) {
+ writeNull();
+ } else {
+ _append(JsonToken.VALUE_NUMBER_INT, v);
+ }
+ }
+
+ @Override
+ public void writeNumber(String encodedValue) throws IOException, JsonGenerationException {
+ /* 03-Dec-2010, tatu: related to [JACKSON-423], should try to keep as numeric
+ * identity as long as possible
+ */
+ _append(JsonToken.VALUE_NUMBER_FLOAT, encodedValue);
+ }
+
+ @Override
+ public void writeBoolean(boolean state) throws IOException,JsonGenerationException {
+ _append(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE);
+ }
+
+ @Override
+ public void writeNull() throws IOException, JsonGenerationException {
+ _append(JsonToken.VALUE_NULL);
+ }
+
+ /*
+ /***********************************************************
+ /* JsonGenerator implementation: write methods for POJOs/trees
+ /***********************************************************
+ */
+
+ @Override
+ public void writeObject(Object value)
+ throws IOException, JsonProcessingException
+ {
+ // embedded means that no conversions should be done...
+ _append(JsonToken.VALUE_EMBEDDED_OBJECT, value);
+ }
+
+ @Override
+ public void writeTree(JsonNode rootNode)
+ throws IOException, JsonProcessingException
+ {
+ /* 31-Dec-2009, tatu: no need to convert trees either is there?
+ * (note: may need to re-evaluate at some point)
+ */
+ _append(JsonToken.VALUE_EMBEDDED_OBJECT, rootNode);
+ }
+
+ /*
+ /***********************************************************
+ /* JsonGenerator implementation; binary
+ /***********************************************************
+ */
+
+ @Override
+ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len)
+ throws IOException, JsonGenerationException
+ {
+ /* 31-Dec-2009, tatu: can do this using multiple alternatives; but for
+ * now, let's try to limit number of conversions.
+ * The only (?) tricky thing is that of whether to preserve variant,
+ * seems pointless, so let's not worry about it unless there's some
+ * compelling reason to.
+ */
+ byte[] copy = new byte[len];
+ System.arraycopy(data, offset, copy, 0, len);
+ writeObject(copy);
+ }
+
+ /*
+ /**********************************************************
+ /* JsonGenerator implementation; pass-through copy
+ /**********************************************************
+ */
+
+ @Override
+ public void copyCurrentEvent(JsonParser jp) throws IOException, JsonProcessingException
+ {
+ switch (jp.getCurrentToken()) {
+ 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:
+ throw new RuntimeException("Internal error: should never end up through this code path");
+ }
+ }
+
+ @Override
+ public 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);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods
+ /**********************************************************
+ */
+ protected final void _append(JsonToken type) {
+ Segment next = _last.append(_appendOffset, type);
+ if (next == null) {
+ ++_appendOffset;
+ } else {
+ _last = next;
+ _appendOffset = 1; // since we added first at 0
+ }
+ }
+
+ protected final void _append(JsonToken type, Object value) {
+ Segment next = _last.append(_appendOffset, type, value);
+ if (next == null) {
+ ++_appendOffset;
+ } else {
+ _last = next;
+ _appendOffset = 1;
+ }
+ }
+
+ protected void _reportUnsupportedOperation() {
+ throw new UnsupportedOperationException("Called operation not supported for TokenBuffer");
+ }
+
+ /*
+ /**********************************************************
+ /* Supporting classes
+ /**********************************************************
+ */
+
+ protected final static class Parser
+ extends ParserMinimalBase
+ {
+ protected ObjectCodec _codec;
+
+ /*
+ /**********************************************************
+ /* Parsing state
+ /**********************************************************
+ */
+
+ /**
+ * Currently active segment
+ */
+ protected Segment _segment;
+
+ /**
+ * Pointer to current token within current segment
+ */
+ protected int _segmentPtr;
+
+ /**
+ * Information about parser context, context in which
+ * the next token is to be parsed (root, array, object).
+ */
+ protected JsonReadContext _parsingContext;
+
+ protected boolean _closed;
+
+ protected transient ByteArrayBuilder _byteBuilder;
+
+ protected JsonLocation _location = null;
+
+ /*
+ /**********************************************************
+ /* Construction, init
+ /**********************************************************
+ */
+
+ public Parser(Segment firstSeg, ObjectCodec codec)
+ {
+ super(0);
+ _segment = firstSeg;
+ _segmentPtr = -1; // not yet read
+ _codec = codec;
+ _parsingContext = JsonReadContext.createRootContext(-1, -1);
+ }
+
+ public void setLocation(JsonLocation l) {
+ _location = l;
+ }
+
+ @Override
+ public ObjectCodec getCodec() { return _codec; }
+
+ @Override
+ public void setCodec(ObjectCodec c) { _codec = c; }
+
+ /*
+ /**********************************************************
+ /* Extended API beyond JsonParser
+ /**********************************************************
+ */
+
+ public JsonToken peekNextToken()
+ throws IOException, JsonParseException
+ {
+ // closed? nothing more to peek, either
+ if (_closed) return null;
+ Segment seg = _segment;
+ int ptr = _segmentPtr+1;
+ if (ptr >= Segment.TOKENS_PER_SEGMENT) {
+ ptr = 0;
+ seg = (seg == null) ? null : seg.next();
+ }
+ return (seg == null) ? null : seg.type(ptr);
+ }
+
+ /*
+ /**********************************************************
+ /* Closeable implementation
+ /**********************************************************
+ */
+
+ @Override
+ public void close() throws IOException {
+ if (!_closed) {
+ _closed = true;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, traversal
+ /**********************************************************
+ */
+
+ @Override
+ public JsonToken nextToken() throws IOException, JsonParseException
+ {
+ // If we are closed, nothing more to do
+ if (_closed || (_segment == null)) return null;
+
+ // Ok, then: any more tokens?
+ if (++_segmentPtr >= Segment.TOKENS_PER_SEGMENT) {
+ _segmentPtr = 0;
+ _segment = _segment.next();
+ if (_segment == null) {
+ return null;
+ }
+ }
+ _currToken = _segment.type(_segmentPtr);
+ // Field name? Need to update context
+ if (_currToken == JsonToken.FIELD_NAME) {
+ Object ob = _currentObject();
+ String name = (ob instanceof String) ? ((String) ob) : ob.toString();
+ _parsingContext.setCurrentName(name);
+ } else if (_currToken == JsonToken.START_OBJECT) {
+ _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
+ } else if (_currToken == JsonToken.START_ARRAY) {
+ _parsingContext = _parsingContext.createChildArrayContext(-1, -1);
+ } else if (_currToken == JsonToken.END_OBJECT
+ || _currToken == JsonToken.END_ARRAY) {
+ // Closing JSON Object/Array? Close matching context
+ _parsingContext = _parsingContext.getParent();
+ // but allow unbalanced cases too (more close markers)
+ if (_parsingContext == null) {
+ _parsingContext = JsonReadContext.createRootContext(-1, -1);
+ }
+ }
+ return _currToken;
+ }
+
+ @Override
+ public boolean isClosed() { return _closed; }
+
+ /*
+ /**********************************************************
+ /* Public API, token accessors
+ /**********************************************************
+ */
+
+ @Override
+ public JsonStreamContext getParsingContext() { return _parsingContext; }
+
+ @Override
+ public JsonLocation getTokenLocation() { return getCurrentLocation(); }
+
+ @Override
+ public JsonLocation getCurrentLocation() {
+ return (_location == null) ? JsonLocation.NA : _location;
+ }
+
+ @Override
+ public String getCurrentName() { return _parsingContext.getCurrentName(); }
+
+ /*
+ /**********************************************************
+ /* Public API, access to token information, text
+ /**********************************************************
+ */
+
+ @Override
+ public String getText()
+ {
+ // common cases first:
+ if (_currToken == JsonToken.VALUE_STRING
+ || _currToken == JsonToken.FIELD_NAME) {
+ Object ob = _currentObject();
+ if (ob instanceof String) {
+ return (String) ob;
+ }
+ return (ob == null) ? null : ob.toString();
+ }
+ if (_currToken == null) {
+ return null;
+ }
+ switch (_currToken) {
+ case VALUE_NUMBER_INT:
+ case VALUE_NUMBER_FLOAT:
+ Object ob = _currentObject();
+ return (ob == null) ? null : ob.toString();
+ }
+ return _currToken.asString();
+ }
+
+ @Override
+ public char[] getTextCharacters() {
+ String str = getText();
+ return (str == null) ? null : str.toCharArray();
+ }
+
+ @Override
+ public int getTextLength() {
+ String str = getText();
+ return (str == null) ? 0 : str.length();
+ }
+
+ @Override
+ public int getTextOffset() { return 0; }
+
+ @Override
+ public boolean hasTextCharacters() {
+ // We never have raw buffer available, so:
+ return false;
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, access to token information, numeric
+ /**********************************************************
+ */
+
+ @Override
+ public BigInteger getBigIntegerValue() throws IOException, JsonParseException
+ {
+ Number n = getNumberValue();
+ if (n instanceof BigInteger) {
+ return (BigInteger) n;
+ }
+ switch (getNumberType()) {
+ case BIG_DECIMAL:
+ return ((BigDecimal) n).toBigInteger();
+ }
+ // int/long is simple, but let's also just truncate float/double:
+ return BigInteger.valueOf(n.longValue());
+ }
+
+ @Override
+ public BigDecimal getDecimalValue() throws IOException, JsonParseException
+ {
+ Number n = getNumberValue();
+ if (n instanceof BigDecimal) {
+ return (BigDecimal) n;
+ }
+ switch (getNumberType()) {
+ case INT:
+ case LONG:
+ return BigDecimal.valueOf(n.longValue());
+ case BIG_INTEGER:
+ return new BigDecimal((BigInteger) n);
+ }
+ // float or double
+ return BigDecimal.valueOf(n.doubleValue());
+ }
+
+ @Override
+ public double getDoubleValue() throws IOException, JsonParseException {
+ return getNumberValue().doubleValue();
+ }
+
+ @Override
+ public float getFloatValue() throws IOException, JsonParseException {
+ return getNumberValue().floatValue();
+ }
+
+ @Override
+ public int getIntValue() throws IOException, JsonParseException
+ {
+ // optimize common case:
+ if (_currToken == JsonToken.VALUE_NUMBER_INT) {
+ return ((Number) _currentObject()).intValue();
+ }
+ return getNumberValue().intValue();
+ }
+
+ @Override
+ public long getLongValue() throws IOException, JsonParseException {
+ return getNumberValue().longValue();
+ }
+
+ @Override
+ public NumberType getNumberType() throws IOException, JsonParseException
+ {
+ Number n = getNumberValue();
+ if (n instanceof Integer) return NumberType.INT;
+ if (n instanceof Long) return NumberType.LONG;
+ if (n instanceof Double) return NumberType.DOUBLE;
+ if (n instanceof BigDecimal) return NumberType.BIG_DECIMAL;
+ if (n instanceof Float) return NumberType.FLOAT;
+ if (n instanceof BigInteger) return NumberType.BIG_INTEGER;
+ return null;
+ }
+
+ @Override
+ public final Number getNumberValue() throws IOException, JsonParseException {
+ _checkIsNumber();
+ return (Number) _currentObject();
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, access to token information, other
+ /**********************************************************
+ */
+
+ @Override
+ public Object getEmbeddedObject()
+ {
+ if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
+ return _currentObject();
+ }
+ return null;
+ }
+
+ @Override
+ public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException
+ {
+ // First: maybe we some special types?
+ if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
+ // Embedded byte array would work nicely...
+ Object ob = _currentObject();
+ if (ob instanceof byte[]) {
+ return (byte[]) ob;
+ }
+ // fall through to error case
+ }
+ if (_currToken != JsonToken.VALUE_STRING) {
+ throw _constructError("Current token ("+_currToken+") not VALUE_STRING (or VALUE_EMBEDDED_OBJECT with byte[]), can not access as binary");
+ }
+ final String str = getText();
+ if (str == null) {
+ return null;
+ }
+ ByteArrayBuilder builder = _byteBuilder;
+ if (builder == null) {
+ _byteBuilder = builder = new ByteArrayBuilder(100);
+ }
+ _decodeBase64(str, builder, b64variant);
+ return builder.toByteArray();
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods
+ /**********************************************************
+ */
+
+ protected final Object _currentObject() {
+ return _segment.get(_segmentPtr);
+ }
+
+ protected final void _checkIsNumber() throws JsonParseException
+ {
+ if (_currToken == null || !_currToken.isNumeric()) {
+ throw _constructError("Current token ("+_currToken+") not numeric, can not use numeric value accessors");
+ }
+ }
+
+ @Override
+ protected void _handleEOF() throws JsonParseException {
+ _throwInternal();
+ }
+ }
+
+ /**
+ * Individual segment of TokenBuffer that can store up to 16 tokens
+ * (limited by 4 bits per token type marker requirement).
+ * Current implementation uses fixed length array; could alternatively
+ * use 16 distinct fields and switch statement (slightly more efficient
+ * storage, slightly slower access)
+ */
+ protected final static class Segment
+ {
+ public final static int TOKENS_PER_SEGMENT = 16;
+
+ /**
+ * Static array used for fast conversion between token markers and
+ * matching {@link JsonToken} instances
+ */
+ private final static JsonToken[] TOKEN_TYPES_BY_INDEX;
+ static {
+ // ... here we know that there are <= 16 values in JsonToken enum
+ TOKEN_TYPES_BY_INDEX = new JsonToken[16];
+ JsonToken[] t = JsonToken.values();
+ System.arraycopy(t, 1, TOKEN_TYPES_BY_INDEX, 1, Math.min(15, t.length - 1));
+ }
+
+ // // // Linking
+
+ protected Segment _next;
+
+ // // // State
+
+ /**
+ * Bit field used to store types of buffered tokens; 4 bits per token.
+ * Value 0 is reserved for "not in use"
+ */
+ protected long _tokenTypes;
+
+
+ // Actual tokens
+
+ protected final Object[] _tokens = new Object[TOKENS_PER_SEGMENT];
+
+ public Segment() { }
+
+ // // // Accessors
+
+ public JsonToken type(int index)
+ {
+ long l = _tokenTypes;
+ if (index > 0) {
+ l >>= (index << 2);
+ }
+ int ix = ((int) l) & 0xF;
+ return TOKEN_TYPES_BY_INDEX[ix];
+ }
+
+ public Object get(int index) {
+ return _tokens[index];
+ }
+
+ public Segment next() { return _next; }
+
+ // // // Mutators
+
+ public Segment append(int index, JsonToken tokenType)
+ {
+ if (index < TOKENS_PER_SEGMENT) {
+ set(index, tokenType);
+ return null;
+ }
+ _next = new Segment();
+ _next.set(0, tokenType);
+ return _next;
+ }
+
+ public Segment append(int index, JsonToken tokenType, Object value)
+ {
+ if (index < TOKENS_PER_SEGMENT) {
+ set(index, tokenType, value);
+ return null;
+ }
+ _next = new Segment();
+ _next.set(0, tokenType, value);
+ return _next;
+ }
+
+ public void set(int index, JsonToken tokenType)
+ {
+ long typeCode = tokenType.ordinal();
+ /* Assumption here is that there are no overwrites, just appends;
+ * and so no masking is needed
+ */
+ if (index > 0) {
+ typeCode <<= (index << 2);
+ }
+ _tokenTypes |= typeCode;
+ }
+
+ public void set(int index, JsonToken tokenType, Object value)
+ {
+ _tokens[index] = value;
+ long typeCode = tokenType.ordinal();
+ /* Assumption here is that there are no overwrites, just appends;
+ * and so no masking is needed
+ */
+ if (index > 0) {
+ typeCode <<= (index << 2);
+ }
+ _tokenTypes |= typeCode;
+ }
+ }
+}
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..fec896d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/util/VersionUtil.java
@@ -0,0 +1,79 @@
+package com.fasterxml.jackson.core.util;
+
+import java.io.*;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.core.Version;
+
+/**
+ * Functionality for supporting exposing of component {@link Version}s.
+ *
+ * @since 1.6
+ */
+public class VersionUtil
+{
+ public final static String VERSION_FILE = "VERSION.txt";
+
+ private final static Pattern VERSION_SEPARATOR = Pattern.compile("[-_./;:]");
+
+ /**
+ * Helper method that will try to load version information for specified
+ * class. Implementation is simple: 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)
+ {
+ InputStream in;
+ Version version = null;
+
+ try {
+ in = cls.getResourceAsStream(VERSION_FILE);
+ if (in != null) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ version = parseVersion(br.readLine());
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ } catch (IOException e) { }
+ return (version == null) ? Version.unknownVersion() : version;
+ }
+
+ public static Version parseVersion(String versionStr)
+ {
+ if (versionStr == null) return null;
+ versionStr = versionStr.trim();
+ if (versionStr.length() == 0) return null;
+ String[] parts = VERSION_SEPARATOR.split(versionStr);
+ // Let's not bother if there's no separate parts; otherwise use whatever we got
+ if (parts.length < 2) {
+ return null;
+ }
+ int major = parseVersionPart(parts[0]);
+ int minor = parseVersionPart(parts[1]);
+ int patch = (parts.length > 2) ? parseVersionPart(parts[2]) : 0;
+ String snapshot = (parts.length > 3) ? parts[3] : null;
+ return new Version(major, minor, patch, snapshot);
+ }
+
+ 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;
+ }
+}
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;