package com.fasterxml.jackson.core.base; import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.json.JsonWriteContext; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.core.util.VersionUtil; /** * This base class implements part of API that a JSON generator exposes * to applications, adds shared internal methods that sub-classes * can use and adds some abstract methods sub-classes must implement. */ public abstract class GeneratorBase extends JsonGenerator { /* /********************************************************** /* Configuration /********************************************************** */ protected ObjectCodec _objectCodec; /** * Bit flag composed of bits that indicate which * {@link com.fasterxml.jackson.core.JsonGenerator.Feature}s * are enabled. */ protected int _features; /** * Flag set to indicate that implicit conversion from number * to JSON String is needed (as per * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#WRITE_NUMBERS_AS_STRINGS}). */ protected boolean _cfgNumbersAsStrings; /* /********************************************************** /* State /********************************************************** */ /** * Object that keeps track of the current contextual state * of the generator. */ protected JsonWriteContext _writeContext; /** * Flag that indicates whether generator is closed or not. Gets * set when it is closed by an explicit call * ({@link #close}). */ protected boolean _closed; /* /********************************************************** /* Life-cycle /********************************************************** */ protected GeneratorBase(int features, ObjectCodec codec) { super(); _features = features; _writeContext = JsonWriteContext.createRootContext(); _objectCodec = codec; _cfgNumbersAsStrings = isEnabled(Feature.WRITE_NUMBERS_AS_STRINGS); } /** * Implemented with detection that tries to find "VERSION.txt" in same * package as the implementation class. */ @Override public Version version() { return VersionUtil.versionFor(getClass()); } /* /********************************************************** /* Configuration /********************************************************** */ @Override public JsonGenerator enable(Feature f) { _features |= f.getMask(); if (f == Feature.WRITE_NUMBERS_AS_STRINGS) { _cfgNumbersAsStrings = true; } else if (f == Feature.ESCAPE_NON_ASCII) { setHighestNonEscapedChar(127); } return this; } @Override public JsonGenerator disable(Feature f) { _features &= ~f.getMask(); if (f == Feature.WRITE_NUMBERS_AS_STRINGS) { _cfgNumbersAsStrings = false; } else if (f == Feature.ESCAPE_NON_ASCII) { setHighestNonEscapedChar(0); } return this; } //public JsonGenerator configure(Feature f, boolean state) { } @Override public final boolean isEnabled(Feature f) { return (_features & f.getMask()) != 0; } @Override public JsonGenerator useDefaultPrettyPrinter() { /* 28-Sep-2012, tatu: As per [Issue#84], should not override a * pretty printer if one already assigned. */ if (getPrettyPrinter() != null) { return this; } return setPrettyPrinter(new DefaultPrettyPrinter()); } @Override public JsonGenerator setCodec(ObjectCodec oc) { _objectCodec = oc; return this; } @Override public final ObjectCodec getCodec() { return _objectCodec; } /* /********************************************************** /* Public API, accessors /********************************************************** */ /** * Note: co-variant return type. */ @Override public final JsonWriteContext getOutputContext() { return _writeContext; } /* /********************************************************** /* Public API, write methods, structural /********************************************************** */ //public void writeStartArray() throws IOException, JsonGenerationException //public void writeEndArray() throws IOException, JsonGenerationException //public void writeStartObject() throws IOException, JsonGenerationException //public void writeEndObject() throws IOException, JsonGenerationException /* /********************************************************** /* Public API, write methods, textual /********************************************************** */ @Override public void writeFieldName(SerializableString name) throws IOException, JsonGenerationException { writeFieldName(name.getValue()); } //public abstract void writeString(String text) throws IOException, JsonGenerationException; //public abstract void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException; //public abstract void writeRaw(String text) throws IOException, JsonGenerationException; //public abstract void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException; @Override public void writeString(SerializableString text) throws IOException, JsonGenerationException { writeString(text.getValue()); } @Override public void writeRawValue(String text) throws IOException, JsonGenerationException { _verifyValueWrite("write raw value"); writeRaw(text); } @Override public void writeRawValue(String text, int offset, int len) throws IOException, JsonGenerationException { _verifyValueWrite("write raw value"); writeRaw(text, offset, len); } @Override public void writeRawValue(char[] text, int offset, int len) throws IOException, JsonGenerationException { _verifyValueWrite("write raw value"); writeRaw(text, offset, len); } @Override public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength) throws IOException, JsonGenerationException { // Let's implement this as "unsupported" to make it easier to add new parser impls _reportUnsupportedOperation(); return 0; } /* /********************************************************** /* Public API, write methods, primitive /********************************************************** */ // Not implemented at this level, added as placeholders /* public abstract void writeNumber(int i) public abstract void writeNumber(long l) public abstract void writeNumber(double d) public abstract void writeNumber(float f) public abstract void writeNumber(BigDecimal dec) public abstract void writeBoolean(boolean state) public abstract void writeNull() */ /* /********************************************************** /* Public API, write methods, POJOs, trees /********************************************************** */ @Override public void writeObject(Object value) throws IOException, JsonProcessingException { if (value == null) { // important: call method that does check value write: writeNull(); } else { /* 02-Mar-2009, tatu: we are NOT to call _verifyValueWrite here, * because that will be done when codec actually serializes * contained POJO. If we did call it it would advance state * causing exception later on */ if (_objectCodec != null) { _objectCodec.writeValue(this, value); return; } _writeSimpleObject(value); } } @Override public void writeTree(TreeNode rootNode) throws IOException, JsonProcessingException { // As with 'writeObject()', we are not check if write would work if (rootNode == null) { writeNull(); } else { if (_objectCodec == null) { throw new IllegalStateException("No ObjectCodec defined for the generator, can not serialize JsonNode-based trees"); } _objectCodec.writeValue(this, rootNode); } } /* /********************************************************** /* Public API, low-level output handling /********************************************************** */ @Override public abstract void flush() throws IOException; @Override public void close() throws IOException { _closed = true; } @Override public boolean isClosed() { return _closed; } /* /********************************************************** /* Public API, copy-through methods /********************************************************** */ @Override public final void copyCurrentEvent(JsonParser jp) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); // sanity check; what to do? if (t == null) { _reportError("No current event to copy"); } switch(t) { case START_OBJECT: writeStartObject(); break; case END_OBJECT: writeEndObject(); break; case START_ARRAY: writeStartArray(); break; case END_ARRAY: writeEndArray(); break; case FIELD_NAME: writeFieldName(jp.getCurrentName()); break; case VALUE_STRING: if (jp.hasTextCharacters()) { writeString(jp.getTextCharacters(), jp.getTextOffset(), jp.getTextLength()); } else { writeString(jp.getText()); } break; case VALUE_NUMBER_INT: switch (jp.getNumberType()) { case INT: writeNumber(jp.getIntValue()); break; case BIG_INTEGER: writeNumber(jp.getBigIntegerValue()); break; default: writeNumber(jp.getLongValue()); } break; case VALUE_NUMBER_FLOAT: switch (jp.getNumberType()) { case BIG_DECIMAL: writeNumber(jp.getDecimalValue()); break; case FLOAT: writeNumber(jp.getFloatValue()); break; default: writeNumber(jp.getDoubleValue()); } break; case VALUE_TRUE: writeBoolean(true); break; case VALUE_FALSE: writeBoolean(false); break; case VALUE_NULL: writeNull(); break; case VALUE_EMBEDDED_OBJECT: writeObject(jp.getEmbeddedObject()); break; default: _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); 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"); } protected void _reportUnsupportedOperation() { throw new UnsupportedOperationException("Operation not supported by generator of type "+getClass().getName()); } }