1 package com.fasterxml.jackson.dataformat.xml.ser;
2
3 import java.io.*;
4 import java.math.BigDecimal;
5 import java.math.BigInteger;
6 import java.util.*;
7
8 import javax.xml.namespace.QName;
9 import javax.xml.stream.XMLStreamException;
10 import javax.xml.stream.XMLStreamWriter;
11
12 import org.codehaus.stax2.XMLStreamWriter2;
13 import org.codehaus.stax2.ri.Stax2WriterAdapter;
14
15 import com.fasterxml.jackson.core.*;
16 import com.fasterxml.jackson.core.base.GeneratorBase;
17 import com.fasterxml.jackson.core.io.IOContext;
18 import com.fasterxml.jackson.core.json.JsonWriteContext;
19 import com.fasterxml.jackson.dataformat.xml.XmlPrettyPrinter;
20 import com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter;
21 import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
22
23 /**
24  * {@link JsonGenerator} that outputs JAXB-style XML output instead of JSON content.
25  * Operation requires calling code (usually either standard Jackson serializers,
26  * or in some cases (like <code>BeanSerializer</code>) customised ones) to do
27  * additional configuration calls beyond regular {@link JsonGenerator} API,
28  * mostly to pass namespace information.
29  */

30 public final class ToXmlGenerator
31     extends GeneratorBase
32 {
33     /**
34      * If we support optional definition of element names, this is the element
35      * name to use...
36      */

37     protected final static String DEFAULT_UNKNOWN_ELEMENT = "unknown";
38     
39     /**
40      * Enumeration that defines all togglable extra XML-specific features
41      */

42     public enum Feature implements FormatFeature
43     {
44         /**
45          * Feature that controls whether XML declaration should be written before
46          * when generator is initialized (true) or not (false)
47          */

48         WRITE_XML_DECLARATION(false),
49
50         /**
51          * Feature that controls whether output should be done as XML 1.1; if so,
52          * certain aspects may differ from default (1.0) processing: for example,
53          * XML declaration will be automatically added (regardless of setting
54          * <code>WRITE_XML_DECLARATION</code>) as this is required for reader to
55          * know to use 1.1 compliant handling. XML 1.1 can be used to allow quoted
56          * control characters (Ascii codes 0 through 31) as well as additional linefeeds
57          * and name characters.
58          */

59         WRITE_XML_1_1(false),
60
61         /**
62          * Feature that controls whether serialization of Java {@code null} values adds
63          * XML attribute of `xsi:nil`, as defined by XML Schema (see
64          * <a href="https://www.oreilly.com/library/view/xml-in-a/0596007647/re166.html">this article</a>
65          * for details) or not.
66          * If enabled, `xsi:nil` attribute will be added to the empty element; if disabled,
67          * it will not.
68          *<p>
69          * Feature is disabled by default for backwards compatibility.
70          *
71          * @since 2.10
72          */

73         WRITE_NULLS_AS_XSI_NIL(false)
74         ;
75
76         final boolean _defaultState;
77         final int _mask;
78
79         /**
80          * Method that calculates bit set (flags) of all features that
81          * are enabled by default.
82          */

83         public static int collectDefaults()
84         {
85             int flags = 0;
86             for (Feature f : values()) {
87                 if (f.enabledByDefault()) {
88                     flags |= f.getMask();
89                 }
90             }
91             return flags;
92         }
93
94         private Feature(boolean defaultState) {
95             _defaultState = defaultState;
96             _mask = (1 << ordinal());
97         }
98
99         @Override public boolean enabledByDefault() { return _defaultState; }
100         @Override public int getMask() { return _mask; }
101         @Override public boolean enabledIn(int flags) { return (flags & getMask()) != 0; }
102     }
103
104     /*
105     /**********************************************************
106     /* Configuration
107     /**********************************************************
108      */

109
110     final protected XMLStreamWriter2 _xmlWriter;
111
112     final protected XMLStreamWriter _originalXmlWriter;
113     
114     /**
115      * Marker flag set if the underlying stream writer has to emulate
116      * Stax2 API: this is problematic if trying to use {@link #writeRaw} calls.
117      */

118     final protected boolean _stax2Emulation;
119     
120     final protected IOContext _ioContext;
121
122     /**
123      * Bit flag composed of bits that indicate which
124      * {@link ToXmlGenerator.Feature}s
125      * are enabled.
126      */

127     protected int _formatFeatures;
128
129     /**
130      * We may need to use XML-specific indentation as well
131      */

132     protected XmlPrettyPrinter _xmlPrettyPrinter;
133     
134     /*
135     /**********************************************************
136     /* XML Output state
137     /**********************************************************
138      */

139
140     /**
141      * Marker set when {@link #initGenerator()} has been called or not.
142      * 
143      * @since 2.2
144      */

145     protected boolean _initialized;
146     
147     /**
148      * Element or attribute name to use for next output call.
149      * Assigned by either code that initiates serialization
150      * or bean serializer.
151      */

152     protected QName _nextName = null;
153
154     /**
155      * Marker flag that indicates whether next name to write
156      * implies an attribute (true) or element (false)
157      */

158     protected boolean _nextIsAttribute = false;
159
160     /**
161      * Marker flag used to indicate that the next write of a (property)
162      * value should be done without using surrounding start/end
163      * elements. Flag is to be cleared once unwrapping has been triggered
164      * once.
165      */

166     protected boolean _nextIsUnwrapped = false;
167
168     /**
169      * Marker flag used to indicate that the next write of a (property)
170      * value should be as CData
171      */

172     protected boolean _nextIsCData = false;
173     
174     /**
175      * To support proper serialization of arrays it is necessary to keep
176      * stack of element names, so that we can "revert" to earlier 
177      */

178     protected LinkedList<QName> _elementNameStack = new LinkedList<QName>();
179     
180     /*
181     /**********************************************************
182     /* Life-cycle
183     /**********************************************************
184      */

185
186     public ToXmlGenerator(IOContext ctxt, int stdFeatures, int xmlFeatures,
187             ObjectCodec codec, XMLStreamWriter sw)
188     {
189         super(stdFeatures, codec);
190         _formatFeatures = xmlFeatures;
191         _ioContext = ctxt;
192         _originalXmlWriter = sw;
193         _xmlWriter = Stax2WriterAdapter.wrapIfNecessary(sw);
194         _stax2Emulation = (_xmlWriter != sw);
195         _xmlPrettyPrinter = (_cfgPrettyPrinter instanceof XmlPrettyPrinter) ?
196                 (XmlPrettyPrinter) _cfgPrettyPrinter : null;
197     }
198
199     /**
200      * Method called before writing any other output, to optionally
201      * output XML declaration.
202      */

203     public void initGenerator()  throws IOException
204     {
205         if (_initialized) {
206             return;
207         }
208         _initialized = true;
209         try {
210             if (Feature.WRITE_XML_1_1.enabledIn(_formatFeatures)) {
211                 _xmlWriter.writeStartDocument("UTF-8""1.1");
212             } else if (Feature.WRITE_XML_DECLARATION.enabledIn(_formatFeatures)) {
213                 _xmlWriter.writeStartDocument("UTF-8""1.0");
214             } else {
215                 return;
216             }
217             // as per [dataformat-xml#172], try adding indentation
218             if (_xmlPrettyPrinter != null) {
219                 // ... but only if it is likely to succeed:
220                 if (!_stax2Emulation) {
221                     _xmlPrettyPrinter.writePrologLinefeed(_xmlWriter);
222                 }
223             }
224         } catch (XMLStreamException e) {
225             StaxUtil.throwAsGenerationException(e, this);
226         }
227     }
228
229     /*
230     /**********************************************************
231     /* Overridden methods, configuration
232     /**********************************************************
233      */

234
235     @Override
236     protected PrettyPrinter _constructDefaultPrettyPrinter() {
237         return new DefaultXmlPrettyPrinter();
238     }
239
240     @Override
241     public JsonGenerator setPrettyPrinter(PrettyPrinter pp) {
242         _cfgPrettyPrinter = pp;
243         _xmlPrettyPrinter = (pp instanceof XmlPrettyPrinter) ?
244                (XmlPrettyPrinter) pp : null;
245         return this;
246     }
247
248     @Override
249     public Object getOutputTarget() {
250         // Stax2 does not expose underlying target, so best we can do is to return
251         // the Stax XMLStreamWriter instance:
252         return _originalXmlWriter;
253     }
254
255     /**
256      * Stax2 does not expose buffered content amount, so we can only return
257      * <code>-1</code> from here
258      */

259     @Override
260     public int getOutputBuffered() {
261         return -1;
262     }
263
264     @Override
265     public int getFormatFeatures() {
266         return _formatFeatures;
267     }
268
269     @Override // since 2.7
270     public JsonGenerator overrideFormatFeatures(int values, int mask)
271     {
272         int oldF = _formatFeatures;
273         int newF = (_formatFeatures & ~mask) | (values & mask);
274
275         if (oldF != newF) {
276             _formatFeatures = newF;
277         }
278         return this;
279     }
280
281     /*
282     /**********************************************************
283     /* Extended API, configuration
284     /**********************************************************
285      */

286
287     public ToXmlGenerator enable(Feature f) {
288         _formatFeatures |= f.getMask();
289         return this;
290     }
291
292     public ToXmlGenerator disable(Feature f) {
293         _formatFeatures &= ~f.getMask();
294         return this;
295     }
296
297     public final boolean isEnabled(Feature f) {
298         return (_formatFeatures & f.getMask()) != 0;
299     }
300
301     public ToXmlGenerator configure(Feature f, boolean state) {
302         if (state) {
303             enable(f);
304         } else {
305             disable(f);
306         }
307         return this;
308     }
309
310     @Override
311     public boolean canWriteFormattedNumbers() { return true; }
312
313     // @since 2.7.5
314     public boolean inRoot() {
315         return _writeContext.inRoot();
316     }
317
318     /*
319     /**********************************************************
320     /* Extended API, access to some internal components
321     /**********************************************************
322      */

323
324     /**
325      * Method that allows application direct access to underlying
326      * Stax {@link XMLStreamWriter}. Note that use of writer is
327      * discouraged, and may interfere with processing of this writer;
328      * however, occasionally it may be necessary.
329      *<p>
330      * Note: writer instance will always be of type
331      * {@link org.codehaus.stax2.XMLStreamWriter2} (including
332      * Typed Access API) so upcasts are safe.
333      */

334     public XMLStreamWriter getStaxWriter() {
335         return _xmlWriter;
336     }
337     
338     /*
339     /**********************************************************
340     /* Extended API, passing XML specific settings
341     /**********************************************************
342      */

343
344     public void setNextIsAttribute(boolean isAttribute)
345     {
346         _nextIsAttribute = isAttribute;
347     }
348
349     public void setNextIsUnwrapped(boolean isUnwrapped)
350     {
351         _nextIsUnwrapped = isUnwrapped;
352     }
353
354     public void setNextIsCData(boolean isCData)
355     {
356         _nextIsCData = isCData;
357     }
358     
359     public final void setNextName(QName name)
360     {
361         _nextName = name;
362     }
363
364     /**
365      * Method that does same as {@link #setNextName}, unless
366      * a name has already been set.
367      * 
368      * @since 2.1.2
369      */

370     public final boolean setNextNameIfMissing(QName name)
371     {
372         if (_nextName == null) {
373             _nextName = name;
374             return true;
375         }
376         return false;
377     }
378     
379     /**
380      * Methdod called when a structured (collection, array, map) is being
381      * output.
382      * 
383      * @param wrapperName Element used as wrapper around elements, if any (null if none)
384      * @param wrappedName Element used around individual content items (can not
385      *   be null)
386      */

387     public void startWrappedValue(QName wrapperName, QName wrappedName) throws IOException
388     {
389         if (wrapperName != null) {
390             try {
391                 if (_xmlPrettyPrinter != null) {
392                     _xmlPrettyPrinter.writeStartElement(_xmlWriter,
393                             wrapperName.getNamespaceURI(), wrapperName.getLocalPart());
394                 } else {
395                     _xmlWriter.writeStartElement(wrapperName.getNamespaceURI(), wrapperName.getLocalPart());
396                 }
397             } catch (XMLStreamException e) {
398                 StaxUtil.throwAsGenerationException(e, this);
399             }
400         }
401         this.setNextName(wrappedName);
402     }
403
404     /**
405      * Method called after a structured collection output has completed
406      */

407     public void finishWrappedValue(QName wrapperName, QName wrappedName) throws IOException
408     {
409         // First: wrapper to close?
410         if (wrapperName != null) {
411             try {
412                 if (_xmlPrettyPrinter != null) {
413                     _xmlPrettyPrinter.writeEndElement(_xmlWriter, _writeContext.getEntryCount());
414                 } else {
415                     _xmlWriter.writeEndElement();
416                 }
417             } catch (XMLStreamException e) {
418                 StaxUtil.throwAsGenerationException(e, this);
419             }
420         }
421     }
422
423     /**
424      * Trivial helper method called when to add a replicated wrapper name
425      * 
426      * @since 2.2
427      */

428     public void writeRepeatedFieldName() throws IOException
429     {
430         if (_writeContext.writeFieldName(_nextName.getLocalPart()) == JsonWriteContext.STATUS_EXPECT_VALUE) {
431             _reportError("Can not write a field name, expecting a value");
432         }
433     }
434     
435     /*
436     /**********************************************************
437     /* JsonGenerator method overrides
438     /**********************************************************
439      */

440     
441     /* Most overrides in this section are just to make methods final,
442      * to allow better inlining...
443      */

444
445     @Override
446     public final void writeFieldName(String name) throws IOException
447     {
448         if (_writeContext.writeFieldName(name) == JsonWriteContext.STATUS_EXPECT_VALUE) {
449             _reportError("Can not write a field name, expecting a value");
450         }
451         // Should this ever get called?
452         String ns = (_nextName == null) ? "" : _nextName.getNamespaceURI();
453         setNextName(new QName(ns, name));
454     }
455     
456     @Override
457     public final void writeStringField(String fieldName, String value) throws IOException
458     {
459         writeFieldName(fieldName);
460         writeString(value);
461     }
462
463     // 03-Aug-2017, tatu: We could use this as mentioned in comment below BUT
464     //    since there is no counterpart for deserialization this will not
465     //    help us. Approaches that could/would help probably require different
466     //    handling...
467     //
468     //    See [dataformat-xml#4] for more context.
469     
470     /*
471     // @since 2.9
472     public WritableTypeId writeTypePrefix(WritableTypeId typeIdDef) throws IOException
473     {
474         // 03-Aug-2017, tatu: Due to XML oddities, we do need to massage things
475         //     a bit: specifically, change WRAPPER_ARRAY into WRAPPER_OBJECT, always
476         if (typeIdDef.include == WritableTypeId.Inclusion.WRAPPER_ARRAY) {
477             typeIdDef.include = WritableTypeId.Inclusion.WRAPPER_OBJECT;
478         }
479         return super.writeTypePrefix(typeIdDef);
480     }
481     */

482
483     /*
484     /**********************************************************
485     /* JsonGenerator output method implementations, structural
486     /**********************************************************
487      */

488
489     @Override
490     public final void writeStartArray() throws IOException
491     {
492         _verifyValueWrite("start an array");
493         _writeContext = _writeContext.createChildArrayContext();
494         if (_cfgPrettyPrinter != null) {
495             _cfgPrettyPrinter.writeStartArray(this);
496         } else {
497             // nothing to do here; no-operation
498         }
499     }
500     
501     @Override
502     public final void writeEndArray() throws IOException
503     {
504         if (!_writeContext.inArray()) {
505             _reportError("Current context not Array but "+_writeContext.typeDesc());
506         }
507         if (_cfgPrettyPrinter != null) {
508             _cfgPrettyPrinter.writeEndArray(this, _writeContext.getEntryCount());
509         } else {
510             // nothing to do here; no-operation
511         }
512         _writeContext = _writeContext.getParent();
513     }
514
515     @Override
516     public final void writeStartObject() throws IOException
517     {
518         _verifyValueWrite("start an object");
519         _writeContext = _writeContext.createChildObjectContext();
520         if (_cfgPrettyPrinter != null) {
521             _cfgPrettyPrinter.writeStartObject(this);
522         } else {
523             _handleStartObject();
524         }
525     }
526
527     @Override
528     public final void writeEndObject() throws IOException
529     {
530         if (!_writeContext.inObject()) {
531             _reportError("Current context not Object but "+_writeContext.typeDesc());
532         }
533         _writeContext = _writeContext.getParent();
534         if (_cfgPrettyPrinter != null) {
535             // as per [Issue#45], need to suppress indentation if only attributes written:
536             int count = _nextIsAttribute ? 0 : _writeContext.getEntryCount();
537             _cfgPrettyPrinter.writeEndObject(this, count);
538         } else {
539             _handleEndObject();
540         }
541     }
542
543     // note: public just because pretty printer needs to make a callback
544     public final void _handleStartObject() throws IOException
545     {
546         if (_nextName == null) {
547             handleMissingName();
548         }
549         // Need to keep track of names to make Lists work correctly
550         _elementNameStack.addLast(_nextName);
551         try {
552             _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
553         } catch (XMLStreamException e) {
554             StaxUtil.throwAsGenerationException(e, this);
555         }
556     }
557     
558     // note: public just because pretty printer needs to make a callback
559     public final void _handleEndObject() throws IOException
560     {
561         // We may want to repeat same element, so:
562         if (_elementNameStack.isEmpty()) {
563             throw new JsonGenerationException("Can not write END_ELEMENT without open START_ELEMENT"this);
564         }
565         _nextName = _elementNameStack.removeLast();
566         try {
567             // note: since attributes don't nest, can only have one attribute active, so:
568             _nextIsAttribute = false;
569             _xmlWriter.writeEndElement();
570             // [databind-xml#172]: possibly also need indentation
571             if (_elementNameStack.isEmpty() && (_xmlPrettyPrinter != null)) {
572                 // ... but only if it is likely to succeed:
573                 if (!_stax2Emulation) {
574                     _xmlPrettyPrinter.writePrologLinefeed(_xmlWriter);
575                 }
576             }
577         } catch (XMLStreamException e) {
578             StaxUtil.throwAsGenerationException(e, this);
579         }
580     }
581     
582     /*
583     /**********************************************************
584     /* Output method implementations, textual
585     /**********************************************************
586      */

587
588     @Override
589     public void writeFieldName(SerializableString name) throws IOException
590     {
591         writeFieldName(name.getValue());
592     }
593
594     @Override
595     public void writeString(String text) throws IOException
596     {
597         if (text == null) { // [dataformat-xml#413]
598             writeNull();
599             return;
600         }
601         _verifyValueWrite("write String value");
602         if (_nextName == null) {
603             handleMissingName();
604         }
605         try {
606             if (_nextIsAttribute) { // must write attribute name and value with one call
607                 _xmlWriter.writeAttribute(_nextName.getNamespaceURI(), _nextName.getLocalPart(), text);
608             } else if (checkNextIsUnwrapped()) {
609                 // [dataformat-xml#56] Should figure out how to prevent indentation for end element
610                 //   but for now, let's just make sure structure is correct
611                 //if (_xmlPrettyPrinter != null) { ... }
612                 if(_nextIsCData) {
613                     _xmlWriter.writeCData(text);
614                 } else {
615                     _xmlWriter.writeCharacters(text);
616                 }
617             } else if (_xmlPrettyPrinter != null) {
618                 _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
619                         _nextName.getNamespaceURI(), _nextName.getLocalPart(),
620                         text, _nextIsCData);
621             } else {
622                 _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
623                 if(_nextIsCData) {
624                     _xmlWriter.writeCData(text);
625                 } else {
626                     _xmlWriter.writeCharacters(text);
627                 }
628                 _xmlWriter.writeEndElement();
629             } 
630         } catch (XMLStreamException e) {
631             StaxUtil.throwAsGenerationException(e, this);
632         }
633     }    
634     
635     @Override
636     public void writeString(char[] text, int offset, int len) throws IOException
637     {
638         _verifyValueWrite("write String value");
639         if (_nextName == null) {
640             handleMissingName();
641         }
642         try {
643             if (_nextIsAttribute) {
644                 _xmlWriter.writeAttribute(_nextName.getNamespaceURI(), _nextName.getLocalPart(), new String(text, offset, len));
645             } else if (checkNextIsUnwrapped()) {
646                 // should we consider pretty-printing or not?
647                 if(_nextIsCData) {
648                     _xmlWriter.writeCData(text, offset, len);
649                 } else {
650                     _xmlWriter.writeCharacters(text, offset, len);
651                 }
652             } else if (_xmlPrettyPrinter != null) {
653                 _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
654                         _nextName.getNamespaceURI(), _nextName.getLocalPart(),
655                         text, offset, len, _nextIsCData);
656             } else {
657                 _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
658                 if(_nextIsCData) {
659                     _xmlWriter.writeCData(text, offset, len);
660                 } else {
661                     _xmlWriter.writeCharacters(text, offset, len);
662                 }
663                 _xmlWriter.writeEndElement();
664             }
665         } catch (XMLStreamException e) {
666             StaxUtil.throwAsGenerationException(e, this);
667         }
668     }
669
670     @Override
671     public void writeString(SerializableString text) throws IOException {
672         writeString(text.getValue());
673     }
674     
675     @Override
676     public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException
677     {
678         // could add support for this case if we really want it (and can make Stax2 support it)
679         _reportUnsupportedOperation();
680     }
681
682     @Override
683     public void writeUTF8String(byte[] text, int offset, int length) throws IOException
684     {
685         // could add support for this case if we really want it (and can make Stax2 support it)
686         _reportUnsupportedOperation();
687     }
688
689     /*
690     /**********************************************************
691     /* Output method implementations, unprocessed ("raw")
692     /**********************************************************
693      */

694
695     @Override
696     public void writeRawValue(String text) throws IOException {
697         // [dataformat-xml#39]
698         if (_stax2Emulation) {
699             _reportUnimplementedStax2("writeRawValue");
700         }
701         try {
702             _verifyValueWrite("write raw value");
703             if (_nextName == null) {
704                 handleMissingName();
705             }
706
707             if (_nextIsAttribute) {
708                 _xmlWriter.writeAttribute(_nextName.getNamespaceURI(), _nextName.getLocalPart(), text);
709             } else {
710                 _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
711                 _xmlWriter.writeRaw(text);
712                 _xmlWriter.writeEndElement();
713             }
714         } catch (XMLStreamException e) {
715             StaxUtil.throwAsGenerationException(e, this);
716         }
717     }
718
719     @Override
720     public void writeRawValue(String text, int offset, int len) throws IOException {
721         // [dataformat-xml#39]
722         if (_stax2Emulation) {
723             _reportUnimplementedStax2("writeRawValue");
724         }
725         try {
726             _verifyValueWrite("write raw value");
727             if (_nextName == null) {
728                 handleMissingName();
729             }
730
731             if (_nextIsAttribute) {
732                 _xmlWriter.writeAttribute(_nextName.getNamespaceURI(), _nextName.getLocalPart(), text.substring(offset, offset + len));
733             } else {
734                 _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
735                 _xmlWriter.writeRaw(text, offset, len);
736                 _xmlWriter.writeEndElement();
737             }
738         } catch (XMLStreamException e) {
739             StaxUtil.throwAsGenerationException(e, this);
740         }
741     }
742
743     @Override
744     public void writeRawValue(char[] text, int offset, int len) throws IOException {
745         // [dataformat-xml#39]
746         if (_stax2Emulation) {
747             _reportUnimplementedStax2("writeRawValue");
748         }
749         _verifyValueWrite("write raw value");
750         if (_nextName == null) {
751             handleMissingName();
752         }
753         try {
754             if (_nextIsAttribute) {
755                 _xmlWriter.writeAttribute(_nextName.getNamespaceURI(), _nextName.getLocalPart(), new String(text, offset, len));
756             } else {
757                 _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
758                 _xmlWriter.writeRaw(text, offset, len);
759                 _xmlWriter.writeEndElement();
760             }
761         } catch (XMLStreamException e) {
762             StaxUtil.throwAsGenerationException(e, this);
763         }
764     }
765
766     @Override
767     public void writeRawValue(SerializableString text) throws IOException {
768         _reportUnsupportedOperation();
769     }
770
771     @Override
772     public void writeRaw(String text) throws IOException
773     {
774         // [dataformat-xml#39]
775         if (_stax2Emulation) {
776             _reportUnimplementedStax2("writeRaw");
777         }
778         try {
779             _xmlWriter.writeRaw(text);
780         } catch (XMLStreamException e) {
781             StaxUtil.throwAsGenerationException(e, this);
782         }
783     }
784
785     @Override
786     public void writeRaw(String text, int offset, int len) throws IOException
787     {
788         // [dataformat-xml#39]
789         if (_stax2Emulation) {
790             _reportUnimplementedStax2("writeRaw");
791         }
792         try {
793             _xmlWriter.writeRaw(text, offset, len);
794         } catch (XMLStreamException e) {
795             StaxUtil.throwAsGenerationException(e, this);
796         }
797     }
798
799     @Override
800     public void writeRaw(char[] text, int offset, int len) throws IOException
801     {
802         // [dataformat-xml#39]
803         if (_stax2Emulation) {
804             _reportUnimplementedStax2("writeRaw");
805         }
806         try {
807             _xmlWriter.writeRaw(text, offset, len);
808         } catch (XMLStreamException e) {
809             StaxUtil.throwAsGenerationException(e, this);
810         }
811     }
812
813     @Override
814     public void writeRaw(char c) throws IOException
815     {
816         writeRaw(String.valueOf(c));
817     }
818     
819     /*
820     /**********************************************************
821     /* Output method implementations, base64-encoded binary
822     /**********************************************************
823      */

824
825     @Override
826     public void writeBinary(Base64Variant b64variant,
827             byte[] data, int offset, int len) throws IOException
828     {
829         if (data == null) {
830             writeNull();
831             return;
832         }
833         _verifyValueWrite("write Binary value");
834         if (_nextName == null) {
835             handleMissingName();
836         }
837         try {
838             if (_nextIsAttribute) {
839                 // Stax2 API only has 'full buffer' write method:
840                 byte[] fullBuffer = toFullBuffer(data, offset, len);
841                 _xmlWriter.writeBinaryAttribute("", _nextName.getNamespaceURI(), _nextName.getLocalPart(), fullBuffer);
842             } else if (checkNextIsUnwrapped()) {
843                 // should we consider pretty-printing or not?
844                 _xmlWriter.writeBinary(data, offset, len);
845             } else {
846                 if (_xmlPrettyPrinter != null) {
847                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
848                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
849                             data, offset, len);
850                 } else {
851                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
852                     _xmlWriter.writeBinary(data, offset, len);
853                     _xmlWriter.writeEndElement();
854                 }
855             }
856         } catch (XMLStreamException e) {
857             StaxUtil.throwAsGenerationException(e, this);
858         }
859     }
860
861     @Override
862     public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength) throws IOException
863     {
864         if (data == null) {
865             writeNull();
866             return 0;
867         }
868         _verifyValueWrite("write Binary value");
869         if (_nextName == null) {
870             handleMissingName();
871         }
872         try {
873             if (_nextIsAttribute) {
874                 // Stax2 API only has 'full buffer' write method:
875                 byte[] fullBuffer = toFullBuffer(data, dataLength);
876                 _xmlWriter.writeBinaryAttribute("", _nextName.getNamespaceURI(), _nextName.getLocalPart(), fullBuffer);
877             } else if (checkNextIsUnwrapped()) {
878               // should we consider pretty-printing or not?
879                 writeStreamAsBinary(data, dataLength);
880
881             } else {
882                 if (_xmlPrettyPrinter != null) {
883                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
884                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
885                             toFullBuffer(data, dataLength), 0, dataLength);
886                 } else {
887                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
888                     writeStreamAsBinary(data, dataLength);
889                     _xmlWriter.writeEndElement();
890                 }
891             }
892         } catch (XMLStreamException e) {
893             StaxUtil.throwAsGenerationException(e, this);
894         }
895
896         return dataLength;
897     }
898
899     private void writeStreamAsBinary(InputStream data, int len) throws IOException, XMLStreamException 
900     {
901         // base64 encodes up to 3 bytes into a 4 bytes string
902         byte[] tmp = new byte[3];
903         int offset = 0;
904         int read;
905         while((read = data.read(tmp, offset, Math.min(3 - offset, len))) != -1) {
906             offset += read;
907             len -= read;
908             if(offset == 3) {
909                 offset = 0;
910                 _xmlWriter.writeBinary(tmp, 0, 3);
911             }
912             if (len == 0) {
913                 break;
914             }
915         }
916
917         // we still have < 3 bytes in the buffer
918         if(offset > 0) {
919             _xmlWriter.writeBinary(tmp, 0, offset);
920         }
921     }
922
923     
924     private byte[] toFullBuffer(byte[] data, int offset, int len)
925     {
926         // might already be ok:
927         if (offset == 0 && len == data.length) {
928             return data;
929         }
930         byte[] result = new byte[len];
931         if (len > 0) {
932             System.arraycopy(data, offset, result, 0, len);
933         }
934         return result;
935     }
936
937     private byte[] toFullBuffer(InputStream data, final int len) throws IOException 
938     {
939         byte[] result = new byte[len];
940         int offset = 0;
941
942         for (; offset < len; ) {
943             int count = data.read(result, offset, len - offset);
944             if (count < 0) {
945                 _reportError("Too few bytes available: missing "+(len - offset)+" bytes (out of "+len+")");
946             }
947             offset += count;
948         }
949         return result;
950     }
951
952     /*
953     /**********************************************************
954     /* Output method implementations, primitive
955     /**********************************************************
956      */

957
958     @Override
959     public void writeBoolean(boolean value) throws IOException
960     {
961         _verifyValueWrite("write boolean value");
962         if (_nextName == null) {
963             handleMissingName();
964         }
965         try {
966             if (_nextIsAttribute) {
967                 _xmlWriter.writeBooleanAttribute(null, _nextName.getNamespaceURI(), _nextName.getLocalPart(), value);
968             } else if (checkNextIsUnwrapped()) {
969                 // should we consider pretty-printing or not?
970                 _xmlWriter.writeBoolean(value);
971             } else {
972                 if (_xmlPrettyPrinter != null) {
973                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
974                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
975                             value);
976                 } else {
977                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
978                     _xmlWriter.writeBoolean(value);
979                     _xmlWriter.writeEndElement();
980                 }
981             }
982         } catch (XMLStreamException e) {
983             StaxUtil.throwAsGenerationException(e, this);
984         }
985     }
986
987     @Override
988     public void writeNull() throws IOException
989     {
990         _verifyValueWrite("write null value");
991         if (_nextName == null) {
992             handleMissingName();
993         }
994         // !!! TODO: proper use of 'xsd:isNil' ?
995         try {
996             if (_nextIsAttribute) {
997                 /* With attributes, best just leave it out, right? (since there's no way
998                  * to use 'xsi:nil')
999                  */

1000             } else if (checkNextIsUnwrapped()) {
1001                 // as with above, best left unwritten?
1002             } else {
1003                 if (_xmlPrettyPrinter != null) {
1004                     _xmlPrettyPrinter.writeLeafNullElement(_xmlWriter,
1005                             _nextName.getNamespaceURI(), _nextName.getLocalPart());
1006                 } else {
1007                     _xmlWriter.writeEmptyElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
1008                 }
1009             }
1010         } catch (XMLStreamException e) {
1011             StaxUtil.throwAsGenerationException(e, this);
1012         }
1013     }
1014
1015     @Override
1016     public void writeNumber(int i) throws IOException
1017     {
1018         _verifyValueWrite("write number");
1019         if (_nextName == null) {
1020             handleMissingName();
1021         }
1022         try {
1023             if (_nextIsAttribute) {
1024                 _xmlWriter.writeIntAttribute(null, _nextName.getNamespaceURI(), _nextName.getLocalPart(), i);
1025             } else if (checkNextIsUnwrapped()) {
1026                 // should we consider pretty-printing or not?
1027                 _xmlWriter.writeInt(i);
1028             } else {
1029                 if (_xmlPrettyPrinter != null) {
1030                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
1031                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1032                             i);
1033                 } else {
1034                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
1035                     _xmlWriter.writeInt(i);
1036                     _xmlWriter.writeEndElement();
1037                 }
1038             }
1039         } catch (XMLStreamException e) {
1040             StaxUtil.throwAsGenerationException(e, this);
1041         }
1042     }
1043
1044     @Override
1045     public void writeNumber(long l) throws IOException
1046     {
1047         _verifyValueWrite("write number");
1048         if (_nextName == null) {
1049             handleMissingName();
1050         }
1051         try {
1052             if (_nextIsAttribute) {
1053                 _xmlWriter.writeLongAttribute(null, _nextName.getNamespaceURI(), _nextName.getLocalPart(), l);
1054             } else if (checkNextIsUnwrapped()) {
1055                 _xmlWriter.writeLong(l);
1056             } else {
1057                 if (_xmlPrettyPrinter != null) {
1058                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
1059                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1060                             l);
1061                 } else {
1062                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
1063                     _xmlWriter.writeLong(l);
1064                     _xmlWriter.writeEndElement();
1065                 }
1066             }
1067         } catch (XMLStreamException e) {
1068             StaxUtil.throwAsGenerationException(e, this);
1069         }
1070     }
1071
1072     @Override
1073     public void writeNumber(double d) throws IOException
1074     {
1075         _verifyValueWrite("write number");
1076         if (_nextName == null) {
1077             handleMissingName();
1078         }
1079         try {
1080             if (_nextIsAttribute) {
1081                 _xmlWriter.writeDoubleAttribute(null, _nextName.getNamespaceURI(), _nextName.getLocalPart(), d);
1082             } else if (checkNextIsUnwrapped()) {
1083                 _xmlWriter.writeDouble(d);
1084             } else {
1085                 if (_xmlPrettyPrinter != null) {
1086                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
1087                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1088                             d);
1089                 } else {
1090                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
1091                     _xmlWriter.writeDouble(d);
1092                     _xmlWriter.writeEndElement();
1093                 }
1094             }
1095         } catch (XMLStreamException e) {
1096             StaxUtil.throwAsGenerationException(e, this);
1097         }
1098     }
1099
1100     @Override
1101     public void writeNumber(float f) throws IOException
1102     {
1103         _verifyValueWrite("write number");
1104         if (_nextName == null) {
1105             handleMissingName();
1106         }
1107         try {
1108             if (_nextIsAttribute) {
1109                 _xmlWriter.writeFloatAttribute(null, _nextName.getNamespaceURI(), _nextName.getLocalPart(), f);
1110             } else if (checkNextIsUnwrapped()) {
1111                 _xmlWriter.writeFloat(f);
1112             } else {
1113                 if (_xmlPrettyPrinter != null) {
1114                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
1115                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1116                             f);
1117                 } else {
1118                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
1119                     _xmlWriter.writeFloat(f);
1120                     _xmlWriter.writeEndElement();
1121                 }
1122             }
1123         } catch (XMLStreamException e) {
1124             StaxUtil.throwAsGenerationException(e, this);
1125         }
1126     }
1127
1128     @Override
1129     public void writeNumber(BigDecimal dec) throws IOException
1130     {
1131         if (dec == null) {
1132             writeNull();
1133             return;
1134         }
1135         _verifyValueWrite("write number");
1136         if (_nextName == null) {
1137             handleMissingName();
1138         }
1139         boolean usePlain = isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
1140         try {
1141             if (_nextIsAttribute) {
1142                 if (usePlain) {
1143                     _xmlWriter.writeAttribute("", _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1144                             dec.toPlainString());
1145                 } else {
1146                     _xmlWriter.writeDecimalAttribute("", _nextName.getNamespaceURI(), _nextName.getLocalPart(), dec);
1147                 }
1148             } else if (checkNextIsUnwrapped()) {
1149                 if (usePlain) {
1150                     _xmlWriter.writeCharacters(dec.toPlainString());
1151                 } else {
1152                     _xmlWriter.writeDecimal(dec);
1153                 }
1154             } else {
1155                 if (_xmlPrettyPrinter != null) {
1156                     if (usePlain) {
1157                         _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
1158                                 _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1159                                 dec.toPlainString(), false);
1160                     } else {
1161                         _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
1162                                 _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1163                                 dec);
1164                     }
1165                 } else {
1166                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
1167                     if (usePlain) {
1168                          _xmlWriter.writeCharacters(dec.toPlainString());
1169                     } else {
1170                          _xmlWriter.writeDecimal(dec);
1171                     }
1172                     _xmlWriter.writeEndElement();
1173                 }
1174             }
1175         } catch (XMLStreamException e) {
1176             StaxUtil.throwAsGenerationException(e, this);
1177         }
1178     }
1179
1180     @Override
1181     public void writeNumber(BigInteger value) throws IOException
1182     {
1183         if (value == null) {
1184             writeNull();
1185             return;
1186         }
1187         _verifyValueWrite("write number");
1188         if (_nextName == null) {
1189             handleMissingName();
1190         }
1191         try {
1192             if (_nextIsAttribute) {
1193                 _xmlWriter.writeIntegerAttribute("",
1194                         _nextName.getNamespaceURI(), _nextName.getLocalPart(), value);
1195             } else if (checkNextIsUnwrapped()) {
1196                 _xmlWriter.writeInteger(value);
1197             } else {
1198                 if (_xmlPrettyPrinter != null) {
1199                     _xmlPrettyPrinter.writeLeafElement(_xmlWriter,
1200                             _nextName.getNamespaceURI(), _nextName.getLocalPart(),
1201                             value);
1202                 } else {
1203                     _xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
1204                     _xmlWriter.writeInteger(value);
1205                     _xmlWriter.writeEndElement();
1206                 }
1207             }
1208         } catch (XMLStreamException e) {
1209             StaxUtil.throwAsGenerationException(e, this);
1210         }
1211     }
1212
1213     @Override
1214     public void writeNumber(String encodedValue) throws IOException, UnsupportedOperationException
1215     {
1216         writeString(encodedValue);
1217     }
1218
1219     /*
1220     /**********************************************************
1221     /* Implementations, overrides for other methods
1222     /**********************************************************
1223      */

1224
1225     @Override
1226     protected final void _verifyValueWrite(String typeMsg) throws IOException
1227     {
1228         int status = _writeContext.writeValue();
1229         if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
1230             _reportError("Can not "+typeMsg+", expecting field name");
1231         }
1232     }
1233
1234     /*
1235     /**********************************************************
1236     /* Low-level output handling
1237     /**********************************************************
1238      */

1239
1240     @Override
1241     public void flush() throws IOException
1242     {
1243         if (isEnabled(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)) {
1244             try {
1245                 _xmlWriter.flush();
1246             } catch (XMLStreamException e) {
1247                 StaxUtil.throwAsGenerationException(e, this);
1248             }
1249         }
1250     }
1251
1252     @Override
1253     public void close() throws IOException
1254     {
1255 //        boolean wasClosed = _closed;
1256         super.close();
1257
1258         // First: let's see that we still have buffers...
1259         if (isEnabled(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT)) {
1260             try {
1261                 while (true) {
1262             /* 28-May-2016, tatu: To work around incompatibility introduced by
1263              *     `jackson-core` 2.8 where return type of `getOutputContext()`
1264              *     changed, let's do direct access here.
1265              */

1266 //                    JsonStreamContext ctxt = getOutputContext();
1267             JsonStreamContext ctxt = _writeContext;
1268                     if (ctxt.inArray()) {
1269                         writeEndArray();
1270                     } else if (ctxt.inObject()) {
1271                         writeEndObject();
1272                     } else {
1273                         break;
1274                     }
1275                 }
1276             } catch (ArrayIndexOutOfBoundsException e) {
1277                 /* 29-Nov-2010, tatu: Stupid, stupid SJSXP doesn't do array checks, so we get
1278                  *   hit by this as a collateral problem in some cases. Yuck.
1279                  */

1280                 throw new JsonGenerationException(e, this);
1281             }
1282         }
1283         try {
1284             if (_ioContext.isResourceManaged() || isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET)) {
1285                 _xmlWriter.closeCompletely();
1286             } else {
1287                 _xmlWriter.close();
1288             }
1289         } catch (XMLStreamException e) {
1290             StaxUtil.throwAsGenerationException(e, this);
1291         }
1292     }
1293
1294     @Override
1295     protected void _releaseBuffers() {
1296         // Nothing to do here, as we have no buffers
1297     }
1298
1299     /*
1300     /**********************************************************
1301     /* Internal methods
1302     /**********************************************************
1303      */

1304
1305     /**
1306      * Method called to see if unwrapping is required; and if so,
1307      * clear the flag (so further calls will return 'false' unless
1308      * state is re-set)
1309      */

1310     protected boolean checkNextIsUnwrapped()
1311     {
1312         if (_nextIsUnwrapped) {
1313                 _nextIsUnwrapped = false;
1314                 return true;
1315         }
1316         return false;
1317     }
1318     
1319     protected void handleMissingName() {
1320         throw new IllegalStateException("No element/attribute name specified when trying to output element");
1321     }
1322
1323     /**
1324      * Method called in case access to native Stax2 API implementation is required.
1325      */

1326     protected void  _reportUnimplementedStax2(String missingMethod) throws IOException
1327     {
1328         throw new JsonGenerationException("Underlying Stax XMLStreamWriter (of type "
1329                 +_originalXmlWriter.getClass().getName()
1330                 +") does not implement Stax2 API natively and is missing method '"
1331                 +missingMethod+"': this breaks functionality such as indentation that relies on it. "
1332                 +"You need to upgrade to using compliant Stax implementation like Woodstox or Aalto",
1333                 this);
1334     }
1335 }
1336