diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactory.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactory.java index ad97266e9..69bd2a4f1 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactory.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactory.java @@ -1,6 +1,8 @@ package com.fasterxml.jackson.dataformat.xml; import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import javax.xml.stream.*; @@ -517,6 +519,34 @@ public ToXmlGenerator createGenerator(OutputStream out, JsonEncoding enc) throws _generatorFeatures, _xmlGeneratorFeatures, _objectCodec, _createXmlWriter(ctxt, out), _nameProcessor); } + + /** + * Method for constructing a {@link ToXmlGenerator} for writing XML content + * using specified output stream. + * Encoding to use must be specified. + *

+ * Underlying stream is NOT owned by the generator constructed, + * so that generator will NOT close the output stream when + * {@link ToXmlGenerator#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. + * + * @param out OutputStream to use for writing JSON content + * @param encoding Character encoding to use + * @return a {@link ToXmlGenerator} instance + * @throws IOException + * @since 2.16 + */ + public ToXmlGenerator createGenerator(OutputStream out, Charset encoding) throws IOException + { + // false -> we won't manage the stream unless explicitly directed to + final IOContext ctxt = _createContext(_createContentReference(out), false); + return new ToXmlGenerator(ctxt, + _generatorFeatures, _xmlGeneratorFeatures, + _objectCodec, _createXmlWriter(ctxt, out, encoding), _nameProcessor, encoding); + } @Override public ToXmlGenerator createGenerator(Writer out) throws IOException @@ -689,10 +719,15 @@ protected JsonGenerator _createGenerator(Writer out, IOContext ctxt) throws IOEx */ protected XMLStreamWriter _createXmlWriter(IOContext ctxt, OutputStream out) throws IOException + { + return _createXmlWriter(ctxt, out, StandardCharsets.UTF_8); + } + + protected final XMLStreamWriter _createXmlWriter(IOContext ctxt, OutputStream out, Charset encoding) throws IOException { XMLStreamWriter sw; try { - sw = _xmlOutputFactory.createXMLStreamWriter(_decorate(ctxt, out), "UTF-8"); + sw = _xmlOutputFactory.createXMLStreamWriter(_decorate(ctxt, out), encoding.name()); } catch (Exception e) { throw new JsonGenerationException(e.getMessage(), e, null); } diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlMapper.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlMapper.java index 09d2c4d97..79d0e61cc 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlMapper.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlMapper.java @@ -1,6 +1,12 @@ package com.fasterxml.jackson.dataformat.xml; +import java.io.DataOutput; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.text.DateFormat; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; @@ -8,14 +14,20 @@ import javax.xml.stream.XMLStreamWriter; import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.exc.StreamWriteException; +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.io.DataOutputAsStream; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.core.util.ByteArrayBuilder; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.cfg.CoercionInputShape; +import com.fasterxml.jackson.databind.cfg.ContextAttributes; import com.fasterxml.jackson.databind.cfg.MapperBuilder; import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory; import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; +import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.type.LogicalType; import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser; import com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext; @@ -305,7 +317,7 @@ public void setXmlNameProcessor(XmlNameProcessor processor) { public XmlFactory getFactory() { return (XmlFactory) _jsonFactory; } - + public ObjectMapper configure(ToXmlGenerator.Feature f, boolean state) { ((XmlFactory)_jsonFactory).configure(f, state); return this; @@ -392,4 +404,195 @@ public void writeValue(XMLStreamWriter w0, Object value) throws IOException { // NOTE: above call should do flush(); and we should NOT close here. // Finally, 'g' has no buffers to release. } + + /** + * Method that can be used to serialize any Java value as + * a byte array. + * + * @param value value to serialize as XML bytes + * @param encoding character encoding for the XML output + * @return byte array representing the XML output + * @throws JsonProcessingException + * @since 2.16 + */ + public byte[] writeValueAsBytes(Object value, Charset encoding) throws JsonProcessingException { + try (ByteArrayBuilder bb = new ByteArrayBuilder(_jsonFactory._getBufferRecycler())) { + _writeValueAndClose(createGenerator(bb, encoding), value); + final byte[] result = bb.toByteArray(); + bb.release(); + return result; + } catch (JsonProcessingException e) { // to support [JACKSON-758] + throw e; + } catch (IOException e) { // shouldn't really happen, but is declared as possibility so: + throw JsonMappingException.fromUnexpectedIOE(e); + } + } + + /** + * Method that can be used to serialize any Java value as + * XML output, written to File provided. + * + * @param resultFile the file to write to + * @param value the value to serialize + * @param encoding character encoding for the XML output + * @throws IOException + * @throws StreamWriteException + * @throws DatabindException + * @since 2.16 + */ + public void writeValue(File resultFile, Object value, Charset encoding) + throws IOException, StreamWriteException, DatabindException + { + _writeValueAndClose(createGenerator(resultFile, encoding), value); + } + + /** + * Method that can be used to serialize any Java value as + * JSON output, using output stream provided (using encoding + * {@link JsonEncoding#UTF8}). + *

+ * Note: method does not close the underlying stream explicitly + * here; however, {@link JsonFactory} this mapper uses may choose + * to close the stream depending on its settings (by default, + * it will try to close it when {@link JsonGenerator} we construct + * is closed). + * + * @since 2.16 + */ + public void writeValue(OutputStream out, Object value, Charset encoding) + throws IOException, StreamWriteException, DatabindException + { + _writeValueAndClose(createGenerator(out, encoding), value); + } + + public void writeValue(DataOutput out, Object value, Charset encoding) + throws IOException, StreamWriteException, DatabindException + { + _writeValueAndClose(createGenerator(out, encoding), value); + } + + @Override + public ObjectWriter writer() { + return new XmlWriter(super.writer()); + } + + @Override + public ObjectWriter writer(SerializationFeature feature) { + return new XmlWriter(super.writer(feature)); + } + + @Override + public ObjectWriter writer(SerializationFeature first, SerializationFeature... other) { + return new XmlWriter(super.writer(first, other)); + } + + @Override + public ObjectWriter writer(DateFormat df) { + return new XmlWriter(super.writer(df)); + } + + @Override + public ObjectWriter writerWithView(Class serializationView) { + return new XmlWriter(super.writerWithView(serializationView)); + } + + @Override + public ObjectWriter writerFor(Class rootType) { + return new XmlWriter(super.writerFor(rootType)); + } + + @Override + public ObjectWriter writerFor(TypeReference rootType) { + return new XmlWriter(super.writerFor(rootType)); + } + + @Override + public ObjectWriter writerFor(JavaType rootType) { + return new XmlWriter(super.writerFor(rootType)); + } + + @Override + public ObjectWriter writer(PrettyPrinter pp) { + return new XmlWriter(super.writer(pp)); + } + + @Override + public ObjectWriter writerWithDefaultPrettyPrinter() { + return new XmlWriter(super.writerWithDefaultPrettyPrinter()); + } + + @Override + public ObjectWriter writer(FilterProvider filterProvider) { + return new XmlWriter(super.writer(filterProvider)); + } + + @Override + public ObjectWriter writer(FormatSchema schema) { + return new XmlWriter(super.writer(schema)); + } + + @Override + public ObjectWriter writer(Base64Variant defaultBase64) { + return new XmlWriter(super.writer(defaultBase64)); + } + + @Override + public ObjectWriter writer(CharacterEscapes escapes) { + return new XmlWriter(super.writer(escapes)); + } + + @Override + public ObjectWriter writer(ContextAttributes attrs) { + return new XmlWriter(super.writer(attrs)); + } + + /** @deprecated */ + @Override + @Deprecated + public ObjectWriter writerWithType(Class rootType) { + return new XmlWriter(super.writerWithType(rootType)); + } + + /** @deprecated */ + @Override + @Deprecated + public ObjectWriter writerWithType(TypeReference rootType) { + return new XmlWriter(super.writerWithType(rootType)); + } + + /** @deprecated */ + @Override + @Deprecated + public ObjectWriter writerWithType(JavaType rootType) { + return new XmlWriter(super.writerWithType(rootType)); + } + + public static XmlWriter toXmlWriter(final ObjectWriter objectWriter) { + if (objectWriter instanceof XmlWriter) { + return (XmlWriter) objectWriter; + } + return new XmlWriter(objectWriter); + } + + protected final JsonGenerator createGenerator(OutputStream out, Charset encoding) throws IOException { + this._assertNotNull("out", out); + JsonGenerator g = ((XmlFactory) _jsonFactory).createGenerator(out, encoding); + this._serializationConfig.initialize(g); + return g; + } + + protected final JsonGenerator createGenerator(File outputFile, Charset encoding) throws IOException { + _assertNotNull("outputFile", outputFile); + JsonGenerator g = ((XmlFactory) _jsonFactory).createGenerator( + new FileOutputStream(outputFile), encoding); + _serializationConfig.initialize(g); + return g; + } + + protected final JsonGenerator createGenerator(DataOutput out, Charset encoding) throws IOException { + this._assertNotNull("out", out); + JsonGenerator g = ((XmlFactory) _jsonFactory).createGenerator(new DataOutputAsStream(out), encoding); + this._serializationConfig.initialize(g); + return g; + } } diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlWriter.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlWriter.java new file mode 100644 index 000000000..111173d9f --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlWriter.java @@ -0,0 +1,553 @@ +package com.fasterxml.jackson.dataformat.xml; + +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.exc.StreamWriteException; +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.io.DataOutputAsStream; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.core.util.ByteArrayBuilder; +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.PropertyName; +import com.fasterxml.jackson.databind.SequenceWriter; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.ContextAttributes; +import com.fasterxml.jackson.databind.cfg.DatatypeFeature; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; +import com.fasterxml.jackson.databind.ser.FilterProvider; +import com.fasterxml.jackson.databind.type.TypeFactory; + +import java.io.DataOutput; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.charset.Charset; +import java.text.DateFormat; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicReference; + +public final class XmlWriter extends ObjectWriter { + + private final ObjectWriter _objectWriter; + + public XmlWriter(final ObjectWriter objectWriter) { + super(objectWriter, objectWriter.getConfig()); + _objectWriter = objectWriter; + } + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + @Override + public ObjectWriter with(SerializationFeature feature) { + return new XmlWriter(_objectWriter.with(feature)); + } + + @Override + public ObjectWriter with(SerializationFeature first, SerializationFeature... other) { + return new XmlWriter(_objectWriter.with(first, other)); + } + + @Override + public ObjectWriter withFeatures(SerializationFeature... features) { + return new XmlWriter(_objectWriter.withFeatures(features)); + } + + @Override + public ObjectWriter without(SerializationFeature feature) { + return new XmlWriter(_objectWriter.without(feature)); + } + + @Override + public ObjectWriter without(SerializationFeature first, SerializationFeature... other) { + return new XmlWriter(_objectWriter.without(first, other)); + } + + @Override + public ObjectWriter withoutFeatures(SerializationFeature... features) { + return new XmlWriter(_objectWriter.withoutFeatures(features)); + } + + @Override + public ObjectWriter with(DatatypeFeature feature) { + return new XmlWriter(_objectWriter.with(feature)); + } + + @Override + public ObjectWriter withFeatures(DatatypeFeature... features) { + return new XmlWriter(_objectWriter.withFeatures(features)); + } + + @Override + public ObjectWriter without(DatatypeFeature feature) { + return new XmlWriter(_objectWriter.without(feature)); + } + + @Override + public ObjectWriter withoutFeatures(DatatypeFeature... features) { + return new XmlWriter(_objectWriter.withoutFeatures(features)); + } + + @Override + public ObjectWriter with(JsonGenerator.Feature feature) { + return new XmlWriter(_objectWriter.with(feature)); + } + + @Override + public ObjectWriter withFeatures(JsonGenerator.Feature... features) { + return new XmlWriter(_objectWriter.withFeatures(features)); + } + + @Override + public ObjectWriter without(JsonGenerator.Feature feature) { + return new XmlWriter(_objectWriter.without(feature)); + } + + @Override + public ObjectWriter withoutFeatures(JsonGenerator.Feature... features) { + return new XmlWriter(_objectWriter.withoutFeatures(features)); + } + + @Override + public ObjectWriter with(StreamWriteFeature feature) { + return super.with(feature); + } + + @Override + public ObjectWriter without(StreamWriteFeature feature) { + return new XmlWriter(_objectWriter.without(feature)); + } + + @Override + public ObjectWriter with(FormatFeature feature) { + return new XmlWriter(_objectWriter.with(feature)); + } + + @Override + public ObjectWriter withFeatures(FormatFeature... features) { + return new XmlWriter(_objectWriter.withFeatures(features)); + } + + @Override + public ObjectWriter without(FormatFeature feature) { + return new XmlWriter(_objectWriter.without(feature)); + } + + @Override + public ObjectWriter withoutFeatures(FormatFeature... features) { + return new XmlWriter(_objectWriter.withoutFeatures(features)); + } + + @Override + public ObjectWriter forType(JavaType rootType) { + return new XmlWriter(_objectWriter.forType(rootType)); + } + + @Override + public ObjectWriter forType(Class rootType) { + return new XmlWriter(_objectWriter.forType(rootType)); + } + + @Override + public ObjectWriter forType(TypeReference rootType) { + return new XmlWriter(_objectWriter.forType(rootType)); + } + + @Override + public ObjectWriter withType(JavaType rootType) { + return new XmlWriter(_objectWriter.withType(rootType)); + } + + @Override + public ObjectWriter withType(Class rootType) { + return new XmlWriter(_objectWriter.withType(rootType)); + } + + @Override + public ObjectWriter withType(TypeReference rootType) { + return new XmlWriter(_objectWriter.withType(rootType)); + } + + @Override + public ObjectWriter with(DateFormat df) { + return new XmlWriter(_objectWriter.with(df)); + } + + @Override + public ObjectWriter withDefaultPrettyPrinter() { + return new XmlWriter(_objectWriter.withDefaultPrettyPrinter()); + } + + @Override + public ObjectWriter with(FilterProvider filterProvider) { + return new XmlWriter(_objectWriter.with(filterProvider)); + } + + @Override + public ObjectWriter with(PrettyPrinter pp) { + return new XmlWriter(_objectWriter.with(pp)); + } + + @Override + public ObjectWriter withRootName(String rootName) { + return new XmlWriter(_objectWriter.withRootName(rootName)); + } + + @Override + public ObjectWriter withRootName(PropertyName rootName) { + return new XmlWriter(_objectWriter.withRootName(rootName)); + } + + @Override + public ObjectWriter withoutRootName() { + return new XmlWriter(_objectWriter.withoutRootName()); + } + + @Override + public ObjectWriter with(FormatSchema schema) { + return new XmlWriter(_objectWriter.with(schema)); + } + + @Override + public ObjectWriter withSchema(FormatSchema schema) { + return new XmlWriter(_objectWriter.withSchema(schema)); + } + + @Override + public ObjectWriter withView(Class view) { + return new XmlWriter(_objectWriter.withView(view)); + } + + @Override + public ObjectWriter with(Locale l) { + return new XmlWriter(_objectWriter.with(l)); + } + + @Override + public ObjectWriter with(TimeZone tz) { + return new XmlWriter(_objectWriter.with(tz)); + } + + @Override + public ObjectWriter with(Base64Variant b64variant) { + return new XmlWriter(_objectWriter.with(b64variant)); + } + + @Override + public ObjectWriter with(CharacterEscapes escapes) { + return new XmlWriter(_objectWriter.with(escapes)); + } + + @Override + public ObjectWriter with(JsonFactory f) { + return new XmlWriter(_objectWriter.with(f)); + } + + @Override + public ObjectWriter with(ContextAttributes attrs) { + return new XmlWriter(_objectWriter.with(attrs)); + } + + @Override + public ObjectWriter withAttributes(Map attrs) { + return new XmlWriter(_objectWriter.withAttributes(attrs)); + } + + @Override + public ObjectWriter withAttribute(Object key, Object value) { + return new XmlWriter(_objectWriter.withAttribute(key, value)); + } + + @Override + public ObjectWriter withoutAttribute(Object key) { + return new XmlWriter(_objectWriter.withoutAttribute(key)); + } + + @Override + public ObjectWriter withRootValueSeparator(String sep) { + return new XmlWriter(_objectWriter.withRootValueSeparator(sep)); + } + + @Override + public ObjectWriter withRootValueSeparator(SerializableString sep) { + return new XmlWriter(_objectWriter.withRootValueSeparator(sep)); + } + + @Override + public JsonGenerator createGenerator(OutputStream out) throws IOException { + return _objectWriter.createGenerator(out); + } + + @Override + public JsonGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException { + return _objectWriter.createGenerator(out, enc); + } + + @Override + public JsonGenerator createGenerator(Writer w) throws IOException { + return _objectWriter.createGenerator(w); + } + + @Override + public JsonGenerator createGenerator(File outputFile, JsonEncoding enc) throws IOException { + return _objectWriter.createGenerator(outputFile, enc); + } + + @Override + public JsonGenerator createGenerator(DataOutput out) throws IOException { + return _objectWriter.createGenerator(out); + } + + @Override + public SequenceWriter writeValues(File out) throws IOException { + return _objectWriter.writeValues(out); + } + + public SequenceWriter writeValues(File out, Charset charset) throws IOException { + return this._newSequenceWriter(false, this.createGenerator(out, charset), true); + } + + @Override + public SequenceWriter writeValues(JsonGenerator g) throws IOException { + return _objectWriter.writeValues(g); + } + + @Override + public SequenceWriter writeValues(Writer out) throws IOException { + return _objectWriter.writeValues(out); + } + + @Override + public SequenceWriter writeValues(OutputStream out) throws IOException { + return _objectWriter.writeValues(out); + } + + public SequenceWriter writeValues(OutputStream out, Charset charset) throws IOException { + return this._newSequenceWriter(false, this.createGenerator(out, charset), true); + } + + @Override + public SequenceWriter writeValues(DataOutput out) throws IOException { + return _objectWriter.writeValues(out); + } + + public SequenceWriter writeValues(DataOutput out, Charset charset) throws IOException { + return this._newSequenceWriter(false, this.createGenerator(out, charset), true); + } + + @Override + public SequenceWriter writeValuesAsArray(File out) throws IOException { + return _objectWriter.writeValuesAsArray(out); + } + + public SequenceWriter writeValuesAsArray(File out, Charset encoding) throws IOException { + return this._newSequenceWriter(true, createGenerator(out, encoding), true); + } + + @Override + public SequenceWriter writeValuesAsArray(JsonGenerator gen) throws IOException { + return _objectWriter.writeValuesAsArray(gen); + } + + @Override + public SequenceWriter writeValuesAsArray(Writer out) throws IOException { + return _objectWriter.writeValuesAsArray(out); + } + + @Override + public SequenceWriter writeValuesAsArray(OutputStream out) throws IOException { + return _objectWriter.writeValuesAsArray(out); + } + + public SequenceWriter writeValuesAsArray(OutputStream out, Charset encoding) throws IOException { + return this._newSequenceWriter(true, createGenerator(out, encoding), true); + } + + @Override + public SequenceWriter writeValuesAsArray(DataOutput out) throws IOException { + return _objectWriter.writeValuesAsArray(out); + } + + public SequenceWriter writeValuesAsArray(DataOutput out, Charset encoding) throws IOException { + return this._newSequenceWriter(true, createGenerator(out, encoding), true); + } + + @Override + public boolean isEnabled(SerializationFeature f) { + return _objectWriter.isEnabled(f); + } + + @Override + public boolean isEnabled(MapperFeature f) { + return _objectWriter.isEnabled(f); + } + + @Override + public boolean isEnabled(DatatypeFeature f) { + return _objectWriter.isEnabled(f); + } + + @Override + public boolean isEnabled(JsonParser.Feature f) { + return _objectWriter.isEnabled(f); + } + + @Override + public boolean isEnabled(JsonGenerator.Feature f) { + return _objectWriter.isEnabled(f); + } + + @Override + public boolean isEnabled(StreamWriteFeature f) { + return _objectWriter.isEnabled(f); + } + + @Override + public SerializationConfig getConfig() { + return _objectWriter.getConfig(); + } + + @Override + public JsonFactory getFactory() { + return _objectWriter.getFactory(); + } + + @Override + public TypeFactory getTypeFactory() { + return _objectWriter.getTypeFactory(); + } + + @Override + public boolean hasPrefetchedSerializer() { + return _objectWriter.hasPrefetchedSerializer(); + } + + @Override + public ContextAttributes getAttributes() { + return _objectWriter.getAttributes(); + } + + @Override + public void writeValue(JsonGenerator g, Object value) throws IOException { + _objectWriter.writeValue(g, value); + } + + @Override + public void writeValue(File resultFile, Object value) throws IOException, StreamWriteException, DatabindException { + _objectWriter.writeValue(resultFile, value); + } + + public void writeValue(File resultFile, Object value, Charset encoding) + throws IOException, StreamWriteException, DatabindException { + _writeValueAndClose(createGenerator(resultFile, encoding), value); + } + + @Override + public void writeValue(OutputStream out, Object value) throws IOException, StreamWriteException, DatabindException { + _objectWriter.writeValue(out, value); + } + + public void writeValue(OutputStream out, Object value, Charset encoding) + throws IOException, StreamWriteException, DatabindException { + _writeValueAndClose(createGenerator(out, encoding), value); + } + + @Override + public void writeValue(Writer w, Object value) throws IOException, StreamWriteException, DatabindException { + _objectWriter.writeValue(w, value); + } + + @Override + public void writeValue(DataOutput out, Object value) throws IOException, StreamWriteException, DatabindException { + _objectWriter.writeValue(out, value); + } + + public void writeValue(DataOutput out, Object value, Charset encoding) + throws IOException, StreamWriteException, DatabindException { + _writeValueAndClose(createGenerator(out, encoding), value); + } + + @Override + public String writeValueAsString(Object value) throws JsonProcessingException { + return _objectWriter.writeValueAsString(value); + } + + @Override + public byte[] writeValueAsBytes(Object value) throws JsonProcessingException { + return _objectWriter.writeValueAsBytes(value); + } + + /** + * Method that can be used to serialize any Java value as + * a byte array. Functionally equivalent to calling + * {@link #writeValue(Writer,Object)} with {@link java.io.ByteArrayOutputStream} + * and getting bytes, but more efficient. + * + * @param value value to serialize as XML bytes + * @param encoding character encoding for the XML output + * @return byte array representing the XML output + * @throws JsonProcessingException + */ + public byte[] writeValueAsBytes(Object value, Charset encoding) + throws JsonProcessingException + { + // Although 'close()' is NOP, use auto-close to avoid lgtm complaints + try (ByteArrayBuilder bb = new ByteArrayBuilder(_generatorFactory._getBufferRecycler())) { + _writeValueAndClose(createGenerator(bb, encoding), value); + final byte[] result = bb.toByteArray(); + bb.release(); + return result; + } catch (JsonProcessingException e) { // to support [JACKSON-758] + throw e; + } catch (IOException e) { // shouldn't really happen, but is declared as possibility so: + throw JsonMappingException.fromUnexpectedIOE(e); + } + } + + @Override + public void acceptJsonFormatVisitor(JavaType type, JsonFormatVisitorWrapper visitor) throws JsonMappingException { + _objectWriter.acceptJsonFormatVisitor(type, visitor); + } + + @Override + public void acceptJsonFormatVisitor(Class type, JsonFormatVisitorWrapper visitor) throws JsonMappingException { + _objectWriter.acceptJsonFormatVisitor(type, visitor); + } + + @Override + public boolean canSerialize(Class type) { + return _objectWriter.canSerialize(type); + } + + @Override + public boolean canSerialize(Class type, AtomicReference cause) { + return _objectWriter.canSerialize(type, cause); + } + + private JsonGenerator createGenerator(OutputStream out, Charset charset) throws IOException { + _assertNotNull("out", out); + return _configureGenerator(((XmlFactory) _generatorFactory).createGenerator(out, charset)); + } + + private JsonGenerator createGenerator(DataOutput out, Charset charset) throws IOException { + _assertNotNull("out", out); + return _configureGenerator(((XmlFactory) _generatorFactory) + .createGenerator(new DataOutputAsStream(out), charset)); + } + + private JsonGenerator createGenerator(File out, Charset charset) throws IOException { + _assertNotNull("out", out); + return _configureGenerator(((XmlFactory) _generatorFactory) + .createGenerator(new FileOutputStream(out), charset)); + } +} diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java index 735a171f4..fb7e9656f 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java @@ -3,6 +3,8 @@ import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.*; import javax.xml.XMLConstants; @@ -216,6 +218,13 @@ private Feature(boolean defaultState) { */ protected XmlNameProcessor.XmlName _nameToEncode = new XmlNameProcessor.XmlName(); + /** + * The character encoding for the XML output + * + * @since 2.16 + */ + protected final Charset _encoding; + /* /********************************************************** /* Life-cycle @@ -224,16 +233,24 @@ private Feature(boolean defaultState) { public ToXmlGenerator(IOContext ctxt, int stdFeatures, int xmlFeatures, ObjectCodec codec, XMLStreamWriter sw, XmlNameProcessor nameProcessor) + { + this(ctxt, stdFeatures, xmlFeatures, codec, sw, nameProcessor, StandardCharsets.UTF_8); + } + + public ToXmlGenerator(IOContext ctxt, int stdFeatures, int xmlFeatures, + ObjectCodec codec, XMLStreamWriter sw, XmlNameProcessor nameProcessor, + Charset encoding) { super(stdFeatures, codec, ctxt); _formatFeatures = xmlFeatures; _streamWriteConstraints = ctxt.streamWriteConstraints(); _originalXmlWriter = sw; + _encoding = encoding; _xmlWriter = Stax2WriterAdapter.wrapIfNecessary(sw); _stax2Emulation = (_xmlWriter != sw); _nameProcessor = nameProcessor; _xmlPrettyPrinter = (_cfgPrettyPrinter instanceof XmlPrettyPrinter) ? - (XmlPrettyPrinter) _cfgPrettyPrinter : null; + (XmlPrettyPrinter) _cfgPrettyPrinter : null; } /** @@ -248,9 +265,9 @@ public void initGenerator() throws IOException _initialized = true; try { if (Feature.WRITE_XML_1_1.enabledIn(_formatFeatures)) { - _xmlWriter.writeStartDocument("UTF-8", "1.1"); + _xmlWriter.writeStartDocument(_encoding.name(), "1.1"); } else if (Feature.WRITE_XML_DECLARATION.enabledIn(_formatFeatures)) { - _xmlWriter.writeStartDocument("UTF-8", "1.0"); + _xmlWriter.writeStartDocument(_encoding.name(), "1.0"); } else { return; } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestCharset.java b/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestCharset.java new file mode 100644 index 000000000..ddc2be230 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestCharset.java @@ -0,0 +1,54 @@ +package com.fasterxml.jackson.dataformat.xml.ser; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.XmlTestBase; +import com.fasterxml.jackson.dataformat.xml.XmlWriter; + +import java.io.IOException; +import java.nio.charset.Charset; + +public class TestCharset extends XmlTestBase +{ + static class StringBean { + public String 象形字; + } + + public void testBig5Bytes() throws IOException + { + Charset big5 = Charset.forName("Big5"); + StringBean stringBean = new StringBean(); + stringBean.象形字 = "pictogram"; + XmlMapper xmlMapper = new XmlMapper(); + xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_1_1, true); + byte[] xml = xmlMapper.writeValueAsBytes(stringBean, big5); + String xmlText = new String(xml, big5); + String expected = + "<象形字>pictogram"; + assertEquals(expected, xmlText); + } + + public void testBig5RoundTrip() throws IOException + { + Charset big5 = Charset.forName("Big5"); + StringBean stringBean = new StringBean(); + stringBean.象形字 = "pictogram"; + XmlMapper xmlMapper = new XmlMapper(); + xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_1_1, true); + byte[] xml = xmlMapper.writeValueAsBytes(stringBean, big5); + StringBean stringBean1 = xmlMapper.readValue(xml, StringBean.class); + assertEquals(stringBean.象形字, stringBean1.象形字); + } + + public void testBig5ObjectWriter() throws IOException + { + Charset big5 = Charset.forName("Big5"); + StringBean stringBean = new StringBean(); + stringBean.象形字 = "pictogram"; + XmlMapper xmlMapper = new XmlMapper(); + xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_1_1, true); + XmlWriter writer = (XmlWriter) xmlMapper.writer(); + byte[] xml = writer.writeValueAsBytes(stringBean, big5); + System.out.write(xml); + System.out.println(); + } +}