com.fitbur.jackson.databind.util.TokenBuffer Maven / Gradle / Ivy
package com.fitbur.jackson.databind.util;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.TreeMap;
import com.fitbur.jackson.core.*;
import com.fitbur.jackson.core.base.ParserMinimalBase;
import com.fitbur.jackson.core.json.JsonReadContext;
import com.fitbur.jackson.core.json.JsonWriteContext;
import com.fitbur.jackson.core.util.ByteArrayBuilder;
import com.fitbur.jackson.databind.*;
/**
* 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.
*
* Note that before version 2.0, this class was located in the "core"
* bundle, not data-binding; but since it was only used by data binding,
* was moved here to reduce size of core package
*/
public class TokenBuffer
/* Won't use JsonGeneratorBase, to minimize overhead for validity
* checking
*/
extends JsonGenerator
{
protected final static int DEFAULT_GENERATOR_FEATURES = JsonGenerator.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.fitbur.jackson.core.JsonGenerator.Feature}s
* are enabled.
*
* NOTE: most features have no effect on this class
*/
protected int _generatorFeatures;
protected boolean _closed;
/**
* @since 2.3
*/
protected boolean _hasNativeTypeIds;
/**
* @since 2.3
*/
protected boolean _hasNativeObjectIds;
/**
* @since 2.3
*/
protected boolean _mayHaveNativeIds;
/**
* Flag set during construction, if use of {@link BigDecimal} is to be forced
* on all floating-point values.
*
* @since 2.7
*/
protected boolean _forceBigDecimal;
/*
/**********************************************************
/* 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 _appendAt;
/**
* If native type ids supported, this is the id for following
* value (or first token of one) to be written.
*/
protected Object _typeId;
/**
* If native object ids supported, this is the id for following
* value (or first token of one) to be written.
*/
protected Object _objectId;
/**
* Do we currently have a native type or object id buffered?
*/
protected boolean _hasNativeId = false;
/*
/**********************************************************
/* 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.
*
* @deprecated since 2.3 preferred variant is one that takes {@link JsonParser} or additional boolean parameter.
*/
@Deprecated
public TokenBuffer(ObjectCodec codec) {
this(codec, false);
}
/**
* @param codec Object codec to use for stream-based object
* conversion through parser/generator interfaces. If null,
* such methods can not be used.
* @param hasNativeIds Whether resulting {@link JsonParser} (if created)
* is considered to support native type and object ids
*/
public TokenBuffer(ObjectCodec codec, boolean hasNativeIds)
{
_objectCodec = codec;
_generatorFeatures = DEFAULT_GENERATOR_FEATURES;
_writeContext = JsonWriteContext.createRootContext(null);
// at first we have just one segment
_first = _last = new Segment();
_appendAt = 0;
_hasNativeTypeIds = hasNativeIds;
_hasNativeObjectIds = hasNativeIds;
_mayHaveNativeIds = _hasNativeTypeIds | _hasNativeObjectIds;
}
/**
* @since 2.3
*/
public TokenBuffer(JsonParser p) {
this(p, null);
}
/**
* @since 2.7
*/
public TokenBuffer(JsonParser p, DeserializationContext ctxt)
{
_objectCodec = p.getCodec();
_generatorFeatures = DEFAULT_GENERATOR_FEATURES;
_writeContext = JsonWriteContext.createRootContext(null);
// at first we have just one segment
_first = _last = new Segment();
_appendAt = 0;
_hasNativeTypeIds = p.canReadTypeId();
_hasNativeObjectIds = p.canReadObjectId();
_mayHaveNativeIds = _hasNativeTypeIds | _hasNativeObjectIds;
_forceBigDecimal = (ctxt == null) ? false
: ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
}
/**
* @since 2.7
*/
public TokenBuffer forceUseOfBigDecimal(boolean b) {
_forceBigDecimal = b;
return this;
}
@Override
public Version version() {
return com.fitbur.jackson.databind.cfg.PackageVersion.VERSION;
}
/**
* Method used to create a {@link JsonParser} that can read contents
* stored in this buffer. Will use default _objectCodec
for
* object conversions.
*
* 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.
*
* 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, _hasNativeTypeIds, _hasNativeObjectIds);
}
/**
* @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(), _hasNativeTypeIds, _hasNativeObjectIds);
p.setLocation(src.getTokenLocation());
return p;
}
/*
/**********************************************************
/* Additional accessors
/**********************************************************
*/
public JsonToken firstToken() {
if (_first != null) {
return _first.type(0);
}
return null;
}
/*
/**********************************************************
/* Other custom methods not needed for implementing interfaces
/**********************************************************
*/
/**
* Helper method that will append contents of given buffer into this
* buffer.
* Not particularly optimized; can be made faster if there is need.
*
* @return This buffer
*/
@SuppressWarnings("resource")
public TokenBuffer append(TokenBuffer other) throws IOException
{
// Important? If source has native ids, need to store
if (!_hasNativeTypeIds) {
_hasNativeTypeIds = other.canWriteTypeId();
}
if (!_hasNativeObjectIds) {
_hasNativeObjectIds = other.canWriteObjectId();
}
_mayHaveNativeIds = _hasNativeTypeIds | _hasNativeObjectIds;
JsonParser p = other.asParser();
while (p.nextToken() != null) {
copyCurrentStructure(p);
}
return this;
}
/**
* Helper method that will write all contents of this buffer
* using given {@link JsonGenerator}.
*
* Note: this method would be enough to implement
* JsonSerializer
for TokenBuffer
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 gen) throws IOException
{
Segment segment = _first;
int ptr = -1;
final boolean checkIds = _mayHaveNativeIds;
boolean hasIds = checkIds && (segment.hasIds());
while (true) {
if (++ptr >= Segment.TOKENS_PER_SEGMENT) {
ptr = 0;
segment = segment.next();
if (segment == null) break;
hasIds = checkIds && (segment.hasIds());
}
JsonToken t = segment.type(ptr);
if (t == null) break;
if (hasIds) {
Object id = segment.findObjectId(ptr);
if (id != null) {
gen.writeObjectId(id);
}
id = segment.findTypeId(ptr);
if (id != null) {
gen.writeTypeId(id);
}
}
// Note: copied from 'copyCurrentEvent'...
switch (t) {
case START_OBJECT:
gen.writeStartObject();
break;
case END_OBJECT:
gen.writeEndObject();
break;
case START_ARRAY:
gen.writeStartArray();
break;
case END_ARRAY:
gen.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) {
gen.writeFieldName((SerializableString) ob);
} else {
gen.writeFieldName((String) ob);
}
}
break;
case VALUE_STRING:
{
Object ob = segment.get(ptr);
if (ob instanceof SerializableString) {
gen.writeString((SerializableString) ob);
} else {
gen.writeString((String) ob);
}
}
break;
case VALUE_NUMBER_INT:
{
Object n = segment.get(ptr);
if (n instanceof Integer) {
gen.writeNumber((Integer) n);
} else if (n instanceof BigInteger) {
gen.writeNumber((BigInteger) n);
} else if (n instanceof Long) {
gen.writeNumber((Long) n);
} else if (n instanceof Short) {
gen.writeNumber((Short) n);
} else {
gen.writeNumber(((Number) n).intValue());
}
}
break;
case VALUE_NUMBER_FLOAT:
{
Object n = segment.get(ptr);
if (n instanceof Double) {
gen.writeNumber(((Double) n).doubleValue());
} else if (n instanceof BigDecimal) {
gen.writeNumber((BigDecimal) n);
} else if (n instanceof Float) {
gen.writeNumber(((Float) n).floatValue());
} else if (n == null) {
gen.writeNull();
} else if (n instanceof String) {
gen.writeNumber((String) n);
} else {
throw new JsonGenerationException(String.format(
"Unrecognized value type for VALUE_NUMBER_FLOAT: %s, can not serialize",
n.getClass().getName()), gen);
}
}
break;
case VALUE_TRUE:
gen.writeBoolean(true);
break;
case VALUE_FALSE:
gen.writeBoolean(false);
break;
case VALUE_NULL:
gen.writeNull();
break;
case VALUE_EMBEDDED_OBJECT:
{
Object value = segment.get(ptr);
if (value instanceof RawValue) {
((RawValue) value).serialize(gen);
} else {
gen.writeObject(value);
}
}
break;
default:
throw new RuntimeException("Internal error: should never end up through this code path");
}
}
}
/**
* Helper method used by standard deserializer.
*
* @since 2.3
*/
public TokenBuffer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
if (p.getCurrentTokenId() != JsonToken.FIELD_NAME.id()) {
copyCurrentStructure(p);
return this;
}
/* 28-Oct-2014, tatu: As per [databind#592], need to support a special case of starting from
* FIELD_NAME, which is taken to mean that we are missing START_OBJECT, but need
* to assume one did exist.
*/
JsonToken t;
writeStartObject();
do {
copyCurrentStructure(p);
} while ((t = p.nextToken()) == JsonToken.FIELD_NAME);
if (t != JsonToken.END_OBJECT) {
throw ctxt.mappingException("Expected END_OBJECT after copying contents of a JsonParser into TokenBuffer, got "+t);
}
writeEndObject();
return this;
}
@Override
@SuppressWarnings("resource")
public String toString()
{
// Let's print up to 100 first tokens...
final int MAX_COUNT = 100;
StringBuilder sb = new StringBuilder();
sb.append("[TokenBuffer: ");
/*
sb.append("NativeTypeIds=").append(_hasNativeTypeIds).append(",");
sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(",");
*/
JsonParser jp = asParser();
int count = 0;
final boolean hasNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;
while (true) {
JsonToken t;
try {
t = jp.nextToken();
if (t == null) break;
if (hasNativeIds) {
_appendNativeIds(sb);
}
if (count < MAX_COUNT) {
if (count > 0) {
sb.append(", ");
}
sb.append(t.toString());
if (t == JsonToken.FIELD_NAME) {
sb.append('(');
sb.append(jp.getCurrentName());
sb.append(')');
}
}
} catch (IOException ioe) { // should never occur
throw new IllegalStateException(ioe);
}
++count;
}
if (count >= MAX_COUNT) {
sb.append(" ... (truncated ").append(count-MAX_COUNT).append(" entries)");
}
sb.append(']');
return sb.toString();
}
private final void _appendNativeIds(StringBuilder sb)
{
Object objectId = _last.findObjectId(_appendAt-1);
if (objectId != null) {
sb.append("[objectId=").append(String.valueOf(objectId)).append(']');
}
Object typeId = _last.findTypeId(_appendAt-1);
if (typeId != null) {
sb.append("[typeId=").append(String.valueOf(typeId)).append(']');
}
}
/*
/**********************************************************
/* 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(SerializationFeature f, boolean state) { }
@Override
public boolean isEnabled(Feature f) {
return (_generatorFeatures & f.getMask()) != 0;
}
@Override
public int getFeatureMask() {
return _generatorFeatures;
}
@Override
@Deprecated
public JsonGenerator setFeatureMask(int mask) {
_generatorFeatures = mask;
return this;
}
@Override
public JsonGenerator overrideStdFeatures(int values, int mask) {
int oldState = getFeatureMask();
_generatorFeatures = (oldState & ~mask) | (values & mask);
return this;
}
@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: capability introspection
/**********************************************************
*/
/**
* Since we can efficiently store byte[]
, yes.
*/
@Override
public boolean canWriteBinaryNatively() {
return true;
}
/*
/**********************************************************
/* 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
{
_append(JsonToken.START_ARRAY);
_writeContext = _writeContext.createChildArrayContext();
}
@Override
public final void writeEndArray() throws IOException
{
_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
{
_append(JsonToken.START_OBJECT);
_writeContext = _writeContext.createChildObjectContext();
}
@Override
public final void writeEndObject() throws IOException
{
_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
{
_append(JsonToken.FIELD_NAME, name);
_writeContext.writeFieldName(name);
}
@Override
public void writeFieldName(SerializableString name) throws IOException
{
_append(JsonToken.FIELD_NAME, name);
_writeContext.writeFieldName(name.getValue());
}
/*
/**********************************************************
/* JsonGenerator implementation: write methods, textual
/**********************************************************
*/
@Override
public void writeString(String text) throws IOException {
if (text == null) {
writeNull();
} else {
_appendValue(JsonToken.VALUE_STRING, text);
}
}
@Override
public void writeString(char[] text, int offset, int len) throws IOException {
writeString(new String(text, offset, len));
}
@Override
public void writeString(SerializableString text) throws IOException {
if (text == null) {
writeNull();
} else {
_appendValue(JsonToken.VALUE_STRING, text);
}
}
@Override
public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException
{
// could add support for buffering if we really want it...
_reportUnsupportedOperation();
}
@Override
public void writeUTF8String(byte[] text, int offset, int length) throws IOException
{
// could add support for buffering if we really want it...
_reportUnsupportedOperation();
}
@Override
public void writeRaw(String text) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRaw(String text, int offset, int len) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRaw(SerializableString text) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRaw(char[] text, int offset, int len) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRaw(char c) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRawValue(String text) throws IOException {
_appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text));
}
@Override
public void writeRawValue(String text, int offset, int len) throws IOException {
if (offset > 0 || len != text.length()) {
text = text.substring(offset, offset+len);
}
_appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text));
}
@Override
public void writeRawValue(char[] text, int offset, int len) throws IOException {
_appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new String(text, offset, len));
}
/*
/**********************************************************
/* JsonGenerator implementation: write methods, primitive types
/**********************************************************
*/
@Override
public void writeNumber(short i) throws IOException {
_appendValue(JsonToken.VALUE_NUMBER_INT, Short.valueOf(i));
}
@Override
public void writeNumber(int i) throws IOException {
_appendValue(JsonToken.VALUE_NUMBER_INT, Integer.valueOf(i));
}
@Override
public void writeNumber(long l) throws IOException {
_appendValue(JsonToken.VALUE_NUMBER_INT, Long.valueOf(l));
}
@Override
public void writeNumber(double d) throws IOException {
_appendValue(JsonToken.VALUE_NUMBER_FLOAT, Double.valueOf(d));
}
@Override
public void writeNumber(float f) throws IOException {
_appendValue(JsonToken.VALUE_NUMBER_FLOAT, Float.valueOf(f));
}
@Override
public void writeNumber(BigDecimal dec) throws IOException {
if (dec == null) {
writeNull();
} else {
_appendValue(JsonToken.VALUE_NUMBER_FLOAT, dec);
}
}
@Override
public void writeNumber(BigInteger v) throws IOException {
if (v == null) {
writeNull();
} else {
_appendValue(JsonToken.VALUE_NUMBER_INT, v);
}
}
@Override
public void writeNumber(String encodedValue) throws IOException {
/* 03-Dec-2010, tatu: related to [JACKSON-423], should try to keep as numeric
* identity as long as possible
*/
_appendValue(JsonToken.VALUE_NUMBER_FLOAT, encodedValue);
}
@Override
public void writeBoolean(boolean state) throws IOException {
_appendValue(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE);
}
@Override
public void writeNull() throws IOException {
_appendValue(JsonToken.VALUE_NULL);
}
/*
/***********************************************************
/* JsonGenerator implementation: write methods for POJOs/trees
/***********************************************************
*/
@Override
public void writeObject(Object value) throws IOException
{
if (value == null) {
writeNull();
return;
}
Class> raw = value.getClass();
if (raw == byte[].class || (value instanceof RawValue)) {
_appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, value);
return;
}
if (_objectCodec == null) {
/* 28-May-2014, tatu: Tricky choice here; if no codec, should we
* err out, or just embed? For now, do latter.
*/
// throw new JsonMappingException("No ObjectCodec configured for TokenBuffer, writeObject() called");
_appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, value);
} else {
_objectCodec.writeValue(this, value);
}
}
@Override
public void writeTree(TreeNode node) throws IOException
{
if (node == null) {
writeNull();
return;
}
if (_objectCodec == null) {
// as with 'writeObject()', is codec optional?
_appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, node);
} else {
_objectCodec.writeTree(this, node);
}
}
/*
/***********************************************************
/* JsonGenerator implementation; binary
/***********************************************************
*/
@Override
public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException
{
/* 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);
}
/**
* Although we could support this method, it does not necessarily make
* sense: we can not make good use of streaming because buffer must
* hold all the data. Because of this, currently this will simply
* throw {@link UnsupportedOperationException}
*/
@Override
public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength) {
throw new UnsupportedOperationException();
}
/*
/***********************************************************
/* JsonGenerator implementation: native ids
/***********************************************************
*/
@Override
public boolean canWriteTypeId() {
return _hasNativeTypeIds;
}
@Override
public boolean canWriteObjectId() {
return _hasNativeObjectIds;
}
@Override
public void writeTypeId(Object id) {
_typeId = id;
_hasNativeId = true;
}
@Override
public void writeObjectId(Object id) {
_objectId = id;
_hasNativeId = true;
}
/*
/**********************************************************
/* JsonGenerator implementation; pass-through copy
/**********************************************************
*/
@Override
public void copyCurrentEvent(JsonParser p) throws IOException
{
if (_mayHaveNativeIds) {
_checkNativeIds(p);
}
switch (p.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(p.getCurrentName());
break;
case VALUE_STRING:
if (p.hasTextCharacters()) {
writeString(p.getTextCharacters(), p.getTextOffset(), p.getTextLength());
} else {
writeString(p.getText());
}
break;
case VALUE_NUMBER_INT:
switch (p.getNumberType()) {
case INT:
writeNumber(p.getIntValue());
break;
case BIG_INTEGER:
writeNumber(p.getBigIntegerValue());
break;
default:
writeNumber(p.getLongValue());
}
break;
case VALUE_NUMBER_FLOAT:
if (_forceBigDecimal) {
/* 10-Oct-2015, tatu: Ideally we would first determine whether underlying
* number is already decoded into a number (in which case might as well
* access as number); or is still retained as text (in which case we
* should further defer decoding that may not need BigDecimal):
*/
writeNumber(p.getDecimalValue());
} else {
switch (p.getNumberType()) {
case BIG_DECIMAL:
writeNumber(p.getDecimalValue());
break;
case FLOAT:
writeNumber(p.getFloatValue());
break;
default:
writeNumber(p.getDoubleValue());
}
}
break;
case VALUE_TRUE:
writeBoolean(true);
break;
case VALUE_FALSE:
writeBoolean(false);
break;
case VALUE_NULL:
writeNull();
break;
case VALUE_EMBEDDED_OBJECT:
writeObject(p.getEmbeddedObject());
break;
default:
throw new RuntimeException("Internal error: should never end up through this code path");
}
}
@Override
public void copyCurrentStructure(JsonParser jp) throws IOException
{
JsonToken t = jp.getCurrentToken();
// Let's handle field-name separately first
if (t == JsonToken.FIELD_NAME) {
if (_mayHaveNativeIds) {
_checkNativeIds(jp);
}
writeFieldName(jp.getCurrentName());
t = jp.nextToken();
// fall-through to copy the associated value
}
if (_mayHaveNativeIds) {
_checkNativeIds(jp);
}
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);
}
}
private final void _checkNativeIds(JsonParser jp) throws IOException
{
if ((_typeId = jp.getTypeId()) != null) {
_hasNativeId = true;
}
if ((_objectId = jp.getObjectId()) != null) {
_hasNativeId = true;
}
}
/*
/**********************************************************
/* Internal methods
/**********************************************************
*/
protected final void _append(JsonToken type)
{
Segment next = _hasNativeId
? _last.append(_appendAt, type, _objectId, _typeId)
: _last.append(_appendAt, type);
if (next == null) {
++_appendAt;
} else {
_last = next;
_appendAt = 1; // since we added first at 0
}
}
protected final void _append(JsonToken type, Object value)
{
Segment next = _hasNativeId
? _last.append(_appendAt, type, value, _objectId, _typeId)
: _last.append(_appendAt, type, value);
if (next == null) {
++_appendAt;
} else {
_last = next;
_appendAt = 1;
}
}
/**
* Similar to {@link #_append(JsonToken)} but also updates context with
* knowledge that a scalar value was written
*
* @since 2.6.4
*/
protected final void _appendValue(JsonToken type)
{
_writeContext.writeValue();
Segment next = _hasNativeId
? _last.append(_appendAt, type, _objectId, _typeId)
: _last.append(_appendAt, type);
if (next == null) {
++_appendAt;
} else {
_last = next;
_appendAt = 1; // since we added first at 0
}
}
/**
* Similar to {@link #_append(JsonToken,Object)} but also updates context with
* knowledge that a scalar value was written
*
* @since 2.6.4
*/
protected final void _appendValue(JsonToken type, Object value)
{
_writeContext.writeValue();
Segment next = _hasNativeId
? _last.append(_appendAt, type, value, _objectId, _typeId)
: _last.append(_appendAt, type, value);
if (next == null) {
++_appendAt;
} else {
_last = next;
_appendAt = 1;
}
}
protected final void _appendRaw(int rawType, Object value)
{
Segment next = _hasNativeId
? _last.appendRaw(_appendAt, rawType, value, _objectId, _typeId)
: _last.appendRaw(_appendAt, rawType, value);
if (next == null) {
++_appendAt;
} else {
_last = next;
_appendAt = 1;
}
}
@Override
protected void _reportUnsupportedOperation() {
throw new UnsupportedOperationException("Called operation not supported for TokenBuffer");
}
/*
/**********************************************************
/* Supporting classes
/**********************************************************
*/
protected final static class Parser
extends ParserMinimalBase
{
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
protected ObjectCodec _codec;
/**
* @since 2.3
*/
protected final boolean _hasNativeTypeIds;
/**
* @since 2.3
*/
protected final boolean _hasNativeObjectIds;
protected final boolean _hasNativeIds;
/*
/**********************************************************
/* 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,
boolean hasNativeTypeIds,
boolean hasNativeObjectIds)
{
super(0);
_segment = firstSeg;
_segmentPtr = -1; // not yet read
_codec = codec;
_parsingContext = JsonReadContext.createRootContext(null);
_hasNativeTypeIds = hasNativeTypeIds;
_hasNativeObjectIds = hasNativeObjectIds;
_hasNativeIds = (hasNativeTypeIds | hasNativeObjectIds);
}
public void setLocation(JsonLocation l) {
_location = l;
}
@Override
public ObjectCodec getCodec() { return _codec; }
@Override
public void setCodec(ObjectCodec c) { _codec = c; }
@Override
public Version version() {
return com.fitbur.jackson.databind.cfg.PackageVersion.VERSION;
}
/*
/**********************************************************
/* Extended API beyond JsonParser
/**********************************************************
*/
public JsonToken peekNextToken() throws IOException
{
// 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
{
// 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(null);
}
}
return _currToken;
}
@Override
public String nextFieldName() throws IOException
{
// inlined common case from nextToken()
if (_closed || (_segment == null)) return null;
int ptr = _segmentPtr+1;
if (ptr < Segment.TOKENS_PER_SEGMENT && _segment.type(ptr) == JsonToken.FIELD_NAME) {
_segmentPtr = ptr;
Object ob = _segment.get(ptr); // inlined _currentObject();
String name = (ob instanceof String) ? ((String) ob) : ob.toString();
_parsingContext.setCurrentName(name);
return name;
}
return (nextToken() == JsonToken.FIELD_NAME) ? getCurrentName() : null;
}
@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() {
// 25-Jun-2015, tatu: as per [databind#838], needs to be same as ParserBase
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
JsonReadContext parent = _parsingContext.getParent();
return parent.getCurrentName();
}
return _parsingContext.getCurrentName();
}
@Override
public void overrideCurrentName(String name)
{
// Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
JsonReadContext ctxt = _parsingContext;
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
ctxt = ctxt.getParent();
}
try {
ctxt.setCurrentName(name);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*
/**********************************************************
/* 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();
default:
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
{
Number n = getNumberValue();
if (n instanceof BigInteger) {
return (BigInteger) n;
}
if (getNumberType() == NumberType.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
{
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);
default:
}
// float or double
return BigDecimal.valueOf(n.doubleValue());
}
@Override
public double getDoubleValue() throws IOException {
return getNumberValue().doubleValue();
}
@Override
public float getFloatValue() throws IOException {
return getNumberValue().floatValue();
}
@Override
public int getIntValue() throws IOException
{
// optimize common case:
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
return ((Number) _currentObject()).intValue();
}
return getNumberValue().intValue();
}
@Override
public long getLongValue() throws IOException {
return getNumberValue().longValue();
}
@Override
public NumberType getNumberType() throws IOException
{
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 BigInteger) return NumberType.BIG_INTEGER;
if (n instanceof Float) return NumberType.FLOAT;
if (n instanceof Short) return NumberType.INT; // should be SHORT
return null;
}
@Override
public final Number getNumberValue() throws IOException {
_checkIsNumber();
Object value = _currentObject();
if (value instanceof Number) {
return (Number) value;
}
// Difficult to really support numbers-as-Strings; but let's try.
// NOTE: no access to DeserializationConfig, unfortunately, so can not
// try to determine Double/BigDecimal preference...
if (value instanceof String) {
String str = (String) value;
if (str.indexOf('.') >= 0) {
return Double.parseDouble(str);
}
return Long.parseLong(str);
}
if (value == null) {
return null;
}
throw new IllegalStateException("Internal error: entry should be a Number, but is of type "
+value.getClass().getName());
}
/*
/**********************************************************
/* Public API, access to token information, other
/**********************************************************
*/
@Override
public Object getEmbeddedObject()
{
if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
return _currentObject();
}
return null;
}
@Override
@SuppressWarnings("resource")
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);
} else {
_byteBuilder.reset();
}
_decodeBase64(str, builder, b64variant);
return builder.toByteArray();
}
@Override
public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException
{
byte[] data = getBinaryValue(b64variant);
if (data != null) {
out.write(data, 0, data.length);
return data.length;
}
return 0;
}
/*
/**********************************************************
/* Public API, native ids
/**********************************************************
*/
@Override
public boolean canReadObjectId() {
return _hasNativeObjectIds;
}
@Override
public boolean canReadTypeId() {
return _hasNativeTypeIds;
}
@Override
public Object getTypeId() {
return _segment.findTypeId(_segmentPtr);
}
@Override
public Object getObjectId() {
return _segment.findObjectId(_segmentPtr);
}
/*
/**********************************************************
/* 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 <= 15 values in JsonToken enum
TOKEN_TYPES_BY_INDEX = new JsonToken[16];
JsonToken[] t = JsonToken.values();
// and reserve entry 0 for "not available"
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];
/**
* Lazily constructed Map for storing native type and object ids, if any
*/
protected TreeMap _nativeIds;
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 int rawType(int index)
{
long l = _tokenTypes;
if (index > 0) {
l >>= (index << 2);
}
int ix = ((int) l) & 0xF;
return ix;
}
public Object get(int index) {
return _tokens[index];
}
public Segment next() { return _next; }
/**
* Accessor for checking whether this segment may have native
* type or object ids.
*/
public boolean hasIds() {
return _nativeIds != null;
}
// // // 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 objectId, Object typeId)
{
if (index < TOKENS_PER_SEGMENT) {
set(index, tokenType, objectId, typeId);
return null;
}
_next = new Segment();
_next.set(0, tokenType, objectId, typeId);
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 Segment append(int index, JsonToken tokenType, Object value,
Object objectId, Object typeId)
{
if (index < TOKENS_PER_SEGMENT) {
set(index, tokenType, value, objectId, typeId);
return null;
}
_next = new Segment();
_next.set(0, tokenType, value, objectId, typeId);
return _next;
}
public Segment appendRaw(int index, int rawTokenType, Object value)
{
if (index < TOKENS_PER_SEGMENT) {
set(index, rawTokenType, value);
return null;
}
_next = new Segment();
_next.set(0, rawTokenType, value);
return _next;
}
public Segment appendRaw(int index, int rawTokenType, Object value,
Object objectId, Object typeId)
{
if (index < TOKENS_PER_SEGMENT) {
set(index, rawTokenType, value, objectId, typeId);
return null;
}
_next = new Segment();
_next.set(0, rawTokenType, value, objectId, typeId);
return _next;
}
private void set(int index, JsonToken tokenType)
{
/* Assumption here is that there are no overwrites, just appends;
* and so no masking is needed (nor explicit setting of null)
*/
long typeCode = tokenType.ordinal();
if (index > 0) {
typeCode <<= (index << 2);
}
_tokenTypes |= typeCode;
}
private void set(int index, JsonToken tokenType,
Object objectId, Object typeId)
{
long typeCode = tokenType.ordinal();
if (index > 0) {
typeCode <<= (index << 2);
}
_tokenTypes |= typeCode;
assignNativeIds(index, objectId, typeId);
}
private void set(int index, JsonToken tokenType, Object value)
{
_tokens[index] = value;
long typeCode = tokenType.ordinal();
if (index > 0) {
typeCode <<= (index << 2);
}
_tokenTypes |= typeCode;
}
private void set(int index, JsonToken tokenType, Object value,
Object objectId, Object typeId)
{
_tokens[index] = value;
long typeCode = tokenType.ordinal();
if (index > 0) {
typeCode <<= (index << 2);
}
_tokenTypes |= typeCode;
assignNativeIds(index, objectId, typeId);
}
private void set(int index, int rawTokenType, Object value)
{
_tokens[index] = value;
long typeCode = (long) rawTokenType;
if (index > 0) {
typeCode <<= (index << 2);
}
_tokenTypes |= typeCode;
}
private void set(int index, int rawTokenType, Object value, Object objectId, Object typeId)
{
_tokens[index] = value;
long typeCode = (long) rawTokenType;
if (index > 0) {
typeCode <<= (index << 2);
}
_tokenTypes |= typeCode;
assignNativeIds(index, objectId, typeId);
}
private final void assignNativeIds(int index, Object objectId, Object typeId)
{
if (_nativeIds == null) {
_nativeIds = new TreeMap();
}
if (objectId != null) {
_nativeIds.put(_objectIdIndex(index), objectId);
}
if (typeId != null) {
_nativeIds.put(_typeIdIndex(index), typeId);
}
}
/**
* @since 2.3
*/
public Object findObjectId(int index) {
return (_nativeIds == null) ? null : _nativeIds.get(_objectIdIndex(index));
}
/**
* @since 2.3
*/
public Object findTypeId(int index) {
return (_nativeIds == null) ? null : _nativeIds.get(_typeIdIndex(index));
}
private final int _typeIdIndex(int i) { return i+i; }
private final int _objectIdIndex(int i) { return i+i+1; }
}
}