1 package com.fasterxml.jackson.dataformat.xml.deser;
2
3 import java.io.IOException;
4 import java.io.Writer;
5 import java.math.BigDecimal;
6 import java.math.BigInteger;
7 import java.util.Set;
8
9 import javax.xml.stream.XMLStreamException;
10 import javax.xml.stream.XMLStreamReader;
11 import javax.xml.stream.XMLStreamWriter;
12
13 import com.fasterxml.jackson.core.*;
14 import com.fasterxml.jackson.core.base.ParserMinimalBase;
15 import com.fasterxml.jackson.core.io.IOContext;
16 import com.fasterxml.jackson.core.util.ByteArrayBuilder;
17 import com.fasterxml.jackson.dataformat.xml.PackageVersion;
18 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
19 import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
20
21 /**
22  * {@link JsonParser} implementation that exposes XML structure as
23  * set of JSON events that can be used for data binding.
24  */

25 public class FromXmlParser
26     extends ParserMinimalBase
27 {
28     /**
29      * The default name placeholder for XML text segments is empty
30      * String ("").
31      */

32     public final static String DEFAULT_UNNAMED_TEXT_PROPERTY = "";
33
34     /**
35      * Enumeration that defines all togglable features for XML parsers.
36      */

37     public enum Feature implements FormatFeature
38     {
39         /**
40          * Feature that indicates whether XML Empty elements (ones where there are
41          * no separate start and end tages, but just one tag that ends with "/>")
42          * are exposed as {@link JsonToken#VALUE_NULL}) or not. If they are not
43          * returned as `null` tokens, they will be returned as {@link JsonToken#VALUE_STRING}
44          * tokens with textual value of "" (empty String).
45          *<p>
46          * Default setting is `truefor backwards compatibility.
47          *
48          * @since 2.9
49          */

50         EMPTY_ELEMENT_AS_NULL(true)
51         ;
52
53         final boolean _defaultState;
54         final int _mask;
55         
56         /**
57          * Method that calculates bit set (flags) of all features that
58          * are enabled by default.
59          */

60         public static int collectDefaults()
61         {
62             int flags = 0;
63             for (Feature f : values()) {
64                 if (f.enabledByDefault()) {
65                     flags |= f.getMask();
66                 }
67             }
68             return flags;
69         }
70         
71         private Feature(boolean defaultState) {
72             _defaultState = defaultState;
73             _mask = (1 << ordinal());
74         }
75
76         @Override public boolean enabledByDefault() { return _defaultState; }
77         @Override public int getMask() { return _mask; }
78         @Override public boolean enabledIn(int flags) { return (flags & getMask()) != 0; }
79     }
80
81     /**
82      * In cases where a start element has both attributes and non-empty textual
83      * value, we have to create a bogus property; we will use this as
84      * the property name.
85      *<p>
86      * Name used for pseudo-property used for returning XML Text value (which does
87      * not have actual element name to use). Defaults to empty String, but
88      * may be changed for inter-operability reasons: JAXB, for example, uses
89      * "value" as name.
90      * 
91      * @since 2.1
92      */

93     protected String _cfgNameForTextElement = DEFAULT_UNNAMED_TEXT_PROPERTY;
94
95     /*
96     /**********************************************************
97     /* Configuration
98     /**********************************************************
99      */

100
101     /**
102      * Bit flag composed of bits that indicate which
103      * {@link FromXmlParser.Feature}s
104      * are enabled.
105      */

106     protected int _formatFeatures;
107
108     protected ObjectCodec _objectCodec;
109
110     /*
111     /**********************************************************
112     /* I/O state
113     /**********************************************************
114      */

115
116     /**
117      * Flag that indicates whether parser is closed or not. Gets
118      * set when parser is either closed by explicit call
119      * ({@link #close}) or when end-of-input is reached.
120      */

121     protected boolean _closed;
122
123     final protected IOContext _ioContext;
124
125     /*
126     /**********************************************************
127     /* Parsing state
128     /**********************************************************
129      */

130
131     /**
132      * Information about parser context, context in which
133      * the next token is to be parsed (root, array, object).
134      */

135     protected XmlReadContext _parsingContext;
136
137     protected final XmlTokenStream _xmlTokens;
138     /**
139      * 
140      * We need special handling to keep track of whether a value
141      * may be exposed as simple leaf value.
142      */

143     protected boolean _mayBeLeaf;
144
145     protected JsonToken _nextToken;
146
147     protected String _currText;
148
149     /*
150     /**********************************************************
151     /* Parsing state, parsed values
152     /**********************************************************
153      */

154
155     /**
156      * ByteArrayBuilder is needed if 'getBinaryValue' is called. If so,
157      * we better reuse it for remainder of content.
158      */

159     protected ByteArrayBuilder _byteArrayBuilder = null;
160
161     /**
162      * We will hold on to decoded binary data, for duration of
163      * current event, so that multiple calls to
164      * {@link #getBinaryValue} will not need to decode data more
165      * than once.
166      */

167     protected byte[] _binaryValue;
168
169     /*
170     /**********************************************************
171     /* Life-cycle
172     /**********************************************************
173      */

174
175     public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
176             ObjectCodec codec, XMLStreamReader xmlReader)
177         throws IOException
178     {
179         super(genericParserFeatures);
180         _formatFeatures = xmlFeatures;
181         _ioContext = ctxt;
182         _objectCodec = codec;
183         _parsingContext = XmlReadContext.createRootContext(-1, -1);
184         _xmlTokens = new XmlTokenStream(xmlReader, ctxt.getSourceReference(),
185                 _formatFeatures);
186
187         // 04-Jan-2019, tatu: Root-level nulls need slightly specific handling;
188         //    changed in 2.10.2
189         if (_xmlTokens.hasXsiNil()) {
190             _nextToken = JsonToken.VALUE_NULL;
191         } else if (_xmlTokens.getCurrentToken() == XmlTokenStream.XML_START_ELEMENT) {
192             _nextToken = JsonToken.START_OBJECT;
193         } else {
194             _reportError("Internal problem: invalid starting state (%d)", _xmlTokens.getCurrentToken());
195         }
196     }
197
198     @Override
199     public Version version() {
200         return PackageVersion.VERSION;
201     }
202     
203     @Override
204     public ObjectCodec getCodec() {
205         return _objectCodec;
206     }
207
208     @Override
209     public void setCodec(ObjectCodec c) {
210         _objectCodec = c;
211     }
212
213     /**
214      * @since 2.1
215      */

216     public void setXMLTextElementName(String name) {
217         _cfgNameForTextElement = name;
218     }
219     
220     /**
221      * XML format does require support from custom {@link ObjectCodec}
222      * (that is, {@link XmlMapper}), so need to return true here.
223      * 
224      * @return True since XML format does require support from codec
225      */

226     @Override
227     public boolean requiresCustomCodec() {
228         return true;
229     }
230     
231     /*
232     /**********************************************************
233     /* Extended API, configuration
234     /**********************************************************
235      */

236
237     public FromXmlParser enable(Feature f) {
238         _formatFeatures |= f.getMask();
239         _xmlTokens.setFormatFeatures(_formatFeatures);
240         return this;
241     }
242
243     public FromXmlParser disable(Feature f) {
244         _formatFeatures &= ~f.getMask();
245         _xmlTokens.setFormatFeatures(_formatFeatures);
246         return this;
247     }
248
249     public final boolean isEnabled(Feature f) {
250         return (_formatFeatures & f.getMask()) != 0;
251     }
252
253     public FromXmlParser configure(Feature f, boolean state) {
254         if (state) {
255             enable(f);
256         } else {
257             disable(f);
258         }
259         return this;
260     }
261
262     /*                                                                                       
263     /**********************************************************                              
264     /* FormatFeature support                                                                             
265     /**********************************************************                              
266      */

267
268     @Override
269     public int getFormatFeatures() {
270         return _formatFeatures;
271     }
272
273     @Override
274     public JsonParser overrideFormatFeatures(int values, int mask) {
275         _formatFeatures = (_formatFeatures & ~mask) | (values & mask);
276         _xmlTokens.setFormatFeatures(_formatFeatures);
277         return this;
278     }
279
280     /*
281     /**********************************************************
282     /* Extended API, access to some internal components
283     /**********************************************************
284      */

285
286     /**
287      * Method that allows application direct access to underlying
288      * Stax {@link XMLStreamWriter}. Note that use of writer is
289      * discouraged, and may interfere with processing of this writer;
290      * however, occasionally it may be necessary.
291      *<p>
292      * Note: writer instance will always be of type
293      * {@link org.codehaus.stax2.XMLStreamWriter2} (including
294      * Typed Access API) so upcasts are safe.
295      */

296     public XMLStreamReader getStaxReader() {
297         return _xmlTokens.getXmlReader();
298     }
299
300     /*
301     /**********************************************************
302     /* Internal API
303     /**********************************************************
304      */

305
306     /**
307      * Method that may be called to indicate that specified names
308      * (only local parts retained currently: this may be changed in
309      * future) should be considered "auto-wrapping", meaning that
310      * they will be doubled to contain two opening elements, two
311      * matching closing elements. This is needed for supporting
312      * handling of so-called "unwrapped" array types, something
313      * XML mappings like JAXB often use.
314      *<p>
315      * NOTE: this method is considered part of internal implementation
316      * interface, and it is <b>NOT</b> guaranteed to remain unchanged
317      * between minor versions (it is however expected not to change in
318      * patch versions). So if you have to use it, be prepared for
319      * possible additional work.
320      * 
321      * @since 2.1
322      */

323     public void addVirtualWrapping(Set<String> namesToWrap)
324     {
325 //System.out.println("addVirtualWrapping("+namesToWrap+")");
326         // 17-Sep-2012, tatu: Not 100% sure why, but this is necessary to avoid
327         //   problems with Lists-in-Lists properties
328         // 12-May-2020, tatu: But as per [dataformat-xml#86] NOT for root element
329         //   (would still like to know why work-around needed ever, but...)
330         if (!_parsingContext.inRoot()
331                  && !_parsingContext.getParent().inRoot()) {
332             String name = _xmlTokens.getLocalName();
333             if ((name != null) && namesToWrap.contains(name)) {
334 //System.out.println("REPEAT from addVirtualWrapping() for '"+name+"'");
335                 _xmlTokens.repeatStartElement();
336             }
337         }
338         _parsingContext.setNamesToWrap(namesToWrap);
339     }
340
341     /*
342     /**********************************************************
343     /* JsonParser impl
344     /**********************************************************
345      */

346     
347     /**
348      * Method that can be called to get the name associated with
349      * the current event.
350      */

351     @Override
352     public String getCurrentName() throws IOException
353     {
354         // start markers require information from parent
355         String name;
356         if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
357             XmlReadContext parent = _parsingContext.getParent();
358             name = parent.getCurrentName();
359         } else {
360             name = _parsingContext.getCurrentName();
361         }
362         // sanity check
363         if (name == null) {
364             throw new IllegalStateException("Missing name, in state: "+_currToken);
365         }
366         return name;
367     }
368
369     @Override
370     public void overrideCurrentName(String name)
371     {
372         // Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
373         XmlReadContext ctxt = _parsingContext;
374         if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
375             ctxt = ctxt.getParent();
376         }
377         ctxt.setCurrentName(name);
378     }
379     
380     @Override
381     public void close() throws IOException
382     {
383         if (!_closed) {
384             _closed = true;
385             try {
386                 if (_ioContext.isResourceManaged() || isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)) {
387                     _xmlTokens.closeCompletely();
388                 } else {
389                     _xmlTokens.close();
390                 }
391             } catch (XMLStreamException e) {
392                 StaxUtil.throwAsParseException(e, this);
393             } finally {
394                 // Also, internal buffer(s) can now be released as well
395                 _releaseBuffers();
396             }
397         }
398     }
399
400     @Override
401     public boolean isClosed() { return _closed; }
402
403     @Override
404     public XmlReadContext getParsingContext() {
405         return _parsingContext;
406     }
407
408     /**
409      * Method that return the <b>starting</b> location of the current
410      * token; that is, position of the first character from input
411      * that starts the current token.
412      */

413     @Override
414     public JsonLocation getTokenLocation() {
415         return _xmlTokens.getTokenLocation();
416     }
417
418     /**
419      * Method that returns location of the last processed character;
420      * usually for error reporting purposes
421      */

422     @Override
423     public JsonLocation getCurrentLocation() {
424         return _xmlTokens.getCurrentLocation();
425     }
426
427     /**
428      * Since xml representation can not really distinguish between array
429      * and object starts (both are represented with elements), this method
430      * is overridden and taken to mean that expecation is that the current
431      * start element is to mean 'start array', instead of default of
432      * 'start object'.
433      */

434     @Override
435     public boolean isExpectedStartArrayToken()
436     {
437         JsonToken t = _currToken;
438         if (t == JsonToken.START_OBJECT) {            
439             _currToken = JsonToken.START_ARRAY;
440             // Ok: must replace current context with array as well
441             _parsingContext.convertToArray();
442 //System.out.println(" FromXmlParser.isExpectedArrayStart(): OBJ->Array");
443             // And just in case a field name was to be returned, wipe it
444             // 06-Jan-2015, tatu: Actually, could also be empty Object buffered; if so, convert...
445             if (_nextToken == JsonToken.END_OBJECT) {
446                 _nextToken = JsonToken.END_ARRAY;
447             } else {
448                 _nextToken = null;
449             }
450             // and last thing, [dataformat-xml#33], better ignore attributes
451             _xmlTokens.skipAttributes();
452             return true;
453         }
454 //System.out.println(" isExpectedArrayStart?: t="+t);
455         return (t == JsonToken.START_ARRAY);
456     }
457
458     // DEBUGGING
459     /*
460     @Override
461     public JsonToken nextToken() throws IOException
462     {
463         JsonToken t = nextToken0();
464         if (t != null) {
465             switch (t) {
466             case FIELD_NAME:
467                 System.out.println("FromXmlParser.nextToken(): JsonToken.FIELD_NAME '"+_parsingContext.getCurrentName()+"'");
468                 break;
469             case VALUE_STRING:
470                 System.out.println("FromXmlParser.nextToken(): JsonToken.VALUE_STRING '"+getText()+"'");
471                 break;
472             default:
473                 System.out.println("FromXmlParser.nextToken(): "+t);
474             }
475         }
476         return t;
477     }
478     */

479
480 //    public JsonToken nextToken0() throws IOException
481     @Override
482     public JsonToken nextToken() throws IOException
483     {
484         _binaryValue = null;
485         if (_nextToken != null) {
486             JsonToken t = _nextToken;
487             _currToken = t;
488             _nextToken = null;
489             switch (t) {
490             case START_OBJECT:
491                 _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
492                 break;
493             case START_ARRAY:
494                 _parsingContext = _parsingContext.createChildArrayContext(-1, -1);
495                 break;
496             case END_OBJECT:
497             case END_ARRAY:
498                 _parsingContext = _parsingContext.getParent();
499                 break;
500             case FIELD_NAME:
501                 _parsingContext.setCurrentName(_xmlTokens.getLocalName());
502                 break;
503             default// VALUE_STRING, VALUE_NULL
504                 // should be fine as is?
505             }
506             return t;
507         }
508         int token;
509         try {
510             token = _xmlTokens.next();
511         } catch (XMLStreamException e) {
512             token = StaxUtil.throwAsParseException(e, this);
513         }
514         // Need to have a loop just because we may have to eat/convert
515         // a start-element that indicates an array element.
516         while (token == XmlTokenStream.XML_START_ELEMENT) {
517             // If we thought we might get leaf, no such luck
518             if (_mayBeLeaf) {
519                 // leave _mayBeLeaf set, as we start a new context
520                 _nextToken = JsonToken.FIELD_NAME;
521                 _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
522                 return (_currToken = JsonToken.START_OBJECT);
523             }
524             if (_parsingContext.inArray()) {
525                 // Yup: in array, so this element could be verified; but it won't be
526                 // reported anyway, and we need to process following event.
527                 try {
528                     token = _xmlTokens.next();
529                 } catch (XMLStreamException e) {
530                     StaxUtil.throwAsParseException(e, this);
531                 }
532                 _mayBeLeaf = true;
533                 continue;
534             }
535             String name = _xmlTokens.getLocalName();
536             _parsingContext.setCurrentName(name);
537
538             // Ok: virtual wrapping can be done by simply repeating current START_ELEMENT.
539             // Couple of ways to do it; but start by making _xmlTokens replay the thing...
540             if (_parsingContext.shouldWrap(name)) {
541 //System.out.println("REPEAT from nextToken()");
542                 _xmlTokens.repeatStartElement();
543             }
544
545             _mayBeLeaf = true;
546             // Ok: in array context we need to skip reporting field names.
547             // But what's the best way to find next token?
548             return (_currToken = JsonToken.FIELD_NAME);
549         }
550
551         // Ok; beyond start element, what do we get?
552         while (true) {
553             switch (token) {
554             case XmlTokenStream.XML_END_ELEMENT:
555                 // Simple, except that if this is a leaf, need to suppress end:
556                 if (_mayBeLeaf) {
557                     _mayBeLeaf = false;
558                     if (_parsingContext.inArray()) {
559                         // 06-Jan-2015, tatu: as per [dataformat-xml#180], need to
560                         //    expose as empty Object, not null
561                         _nextToken = JsonToken.END_OBJECT;
562                         _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
563                         return (_currToken = JsonToken.START_OBJECT);
564                     }
565                     // 07-Sep-2019, tatu: for [dataformat-xml#353], must NOT return second null
566                     if (_currToken != JsonToken.VALUE_NULL) {
567                         return (_currToken = JsonToken.VALUE_NULL);
568                     }
569                 }
570                 _currToken = _parsingContext.inArray() ? JsonToken.END_ARRAY : JsonToken.END_OBJECT;
571                 _parsingContext = _parsingContext.getParent();
572                 return _currToken;
573
574             case XmlTokenStream.XML_ATTRIBUTE_NAME:
575                 // If there was a chance of leaf node, no more...
576                 if (_mayBeLeaf) {
577                     _mayBeLeaf = false;
578                     _nextToken = JsonToken.FIELD_NAME;
579                     _currText = _xmlTokens.getText();
580                     _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
581                     return (_currToken = JsonToken.START_OBJECT);
582                 }
583                 _parsingContext.setCurrentName(_xmlTokens.getLocalName());
584                 return (_currToken = JsonToken.FIELD_NAME);
585             case XmlTokenStream.XML_ATTRIBUTE_VALUE:
586                 _currText = _xmlTokens.getText();
587                 return (_currToken = JsonToken.VALUE_STRING);
588             case XmlTokenStream.XML_TEXT:
589                 _currText = _xmlTokens.getText();
590                 if (_mayBeLeaf) {
591                     _mayBeLeaf = false;
592                     // One more refinement (pronunced like "hack") is that if
593                     // we had an empty String (or all white space), and we are
594                     // deserializing an array, we better hide the empty text.
595                     // Also: must skip following END_ELEMENT
596                     try {
597                         _xmlTokens.skipEndElement();
598                     } catch (XMLStreamException e) {
599                         StaxUtil.throwAsParseException(e, this);
600                     }
601                     if (_parsingContext.inArray()) {
602                         if (_isEmpty(_currText)) {
603                             // 06-Jan-2015, tatu: as per [dataformat-xml#180], need to
604                             //    expose as empty Object, not null (or, worse, as used to
605                             //    be done, by swallowing the token)
606                             _nextToken = JsonToken.END_OBJECT;
607                             _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
608                             return (_currToken = JsonToken.START_OBJECT);
609                         }
610                     }
611                     return (_currToken = JsonToken.VALUE_STRING);
612                 }
613                 // [dataformat-xml#177]: empty text may also need to be skipped
614                 // but... [dataformat-xml#191]: looks like we can't short-cut, must
615                 // loop over again
616                 if (_parsingContext.inObject()) {
617                     if ((_currToken != JsonToken.FIELD_NAME) && _isEmpty(_currText)) {
618                         try {
619                             token = _xmlTokens.next();
620                         } catch (XMLStreamException e) {
621                             StaxUtil.throwAsParseException(e, this);
622                         }
623                         continue;
624                     }
625                 }
626                 // If not a leaf (or otherwise ignorable), need to transform into property...
627                 _parsingContext.setCurrentName(_cfgNameForTextElement);
628                 _nextToken = JsonToken.VALUE_STRING;
629                 return (_currToken = JsonToken.FIELD_NAME);
630             case XmlTokenStream.XML_END:
631                 return (_currToken = null);
632             default:
633                 return _internalErrorUnknownToken(token);
634             }
635         }
636     }
637
638     /*
639     /**********************************************************
640     /* Overrides of specialized nextXxx() methods
641     /**********************************************************
642      */

643
644     /*
645     @Override
646     public String nextFieldName() throws IOException {
647         if (nextToken() == JsonToken.FIELD_NAME) {
648             return getCurrentName();
649         }
650         return null;
651     }
652     */

653
654     /**
655      * Method overridden to support more reliable deserialization of
656      * String collections.
657      */

658     @Override
659     public String nextTextValue() throws IOException
660     {
661         _binaryValue = null;
662         if (_nextToken != null) {
663             JsonToken t = _nextToken;
664             _currToken = t;
665             _nextToken = null;
666
667             // expected case; yes, got a String
668             if (t == JsonToken.VALUE_STRING) {
669                 return _currText;
670             }
671             _updateState(t);
672             return null;
673         }
674
675         int token;
676
677         try {
678             token = _xmlTokens.next();
679         } catch (XMLStreamException e) {
680             token = StaxUtil.throwAsParseException(e, this);
681         }
682
683         // mostly copied from 'nextToken()'
684         while (token == XmlTokenStream.XML_START_ELEMENT) {
685             if (_mayBeLeaf) {
686                 _nextToken = JsonToken.FIELD_NAME;
687                 _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
688                 _currToken = JsonToken.START_OBJECT;
689                 return null;
690             }
691             if (_parsingContext.inArray()) {
692                 try {
693                     token = _xmlTokens.next();
694                 } catch (XMLStreamException e) {
695                     StaxUtil.throwAsParseException(e, this);
696                 }
697                 _mayBeLeaf = true;
698                 continue;
699             }
700             String name = _xmlTokens.getLocalName();
701             _parsingContext.setCurrentName(name);
702             if (_parsingContext.shouldWrap(name)) {
703 //System.out.println("REPEAT from nextTextValue()");
704                 _xmlTokens.repeatStartElement();
705             }
706             _mayBeLeaf = true;
707             _currToken = JsonToken.FIELD_NAME;
708             return null;
709         }
710
711         // Ok; beyond start element, what do we get?
712         switch (token) {
713         case XmlTokenStream.XML_END_ELEMENT:
714             if (_mayBeLeaf) {
715                 // NOTE: this is different from nextToken() -- produce "", NOT null
716                 _mayBeLeaf = false;
717                 _currToken = JsonToken.VALUE_STRING;
718                 return (_currText = "");
719             }
720             _currToken = _parsingContext.inArray() ? JsonToken.END_ARRAY : JsonToken.END_OBJECT;
721             _parsingContext = _parsingContext.getParent();
722             break;
723         case XmlTokenStream.XML_ATTRIBUTE_NAME:
724             // If there was a chance of leaf node, no more...
725             if (_mayBeLeaf) {
726                 _mayBeLeaf = false;
727                 _nextToken = JsonToken.FIELD_NAME;
728                 _currText = _xmlTokens.getText();
729                 _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
730                 _currToken = JsonToken.START_OBJECT;
731             } else {
732                 _parsingContext.setCurrentName(_xmlTokens.getLocalName());
733                 _currToken = JsonToken.FIELD_NAME;
734             }
735             break;
736         case XmlTokenStream.XML_ATTRIBUTE_VALUE:
737             _currToken = JsonToken.VALUE_STRING;
738             return (_currText = _xmlTokens.getText());
739         case XmlTokenStream.XML_TEXT:
740             _currText = _xmlTokens.getText();
741             if (_mayBeLeaf) {
742                 _mayBeLeaf = false;
743                 // Also: must skip following END_ELEMENT
744                 try {
745                     _xmlTokens.skipEndElement();
746                 } catch (XMLStreamException e) {
747                     StaxUtil.throwAsParseException(e, this);
748                 }
749                 // NOTE: this is different from nextToken() -- NO work-around
750                 // for otherwise empty List/array
751                 _currToken = JsonToken.VALUE_STRING;
752                 return _currText;
753             }
754             // If not a leaf, need to transform into property...
755             _parsingContext.setCurrentName(_cfgNameForTextElement);
756             _nextToken = JsonToken.VALUE_STRING;
757             _currToken = JsonToken.FIELD_NAME;
758             break;
759         case XmlTokenStream.XML_END:
760             _currToken = null;
761         default:
762             return _internalErrorUnknownToken(token);
763         }
764         return null;
765     }
766
767
768     private void _updateState(JsonToken t)
769     {
770         switch (t) {
771         case START_OBJECT:
772             _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
773             break;
774         case START_ARRAY:
775             _parsingContext = _parsingContext.createChildArrayContext(-1, -1);
776             break;
777         case END_OBJECT:
778         case END_ARRAY:
779             _parsingContext = _parsingContext.getParent();
780             break;
781         case FIELD_NAME:
782             _parsingContext.setCurrentName(_xmlTokens.getLocalName());
783             break;
784         default:
785             _internalErrorUnknownToken(t);
786         }
787     }
788
789     /*
790     /**********************************************************
791     /* Public API, access to token information, text
792     /**********************************************************
793      */

794
795     @Override
796     public String getText() throws IOException
797     {
798         if (_currToken == null) {
799             return null;
800         }
801         switch (_currToken) {
802         case FIELD_NAME:
803             return getCurrentName();
804         case VALUE_STRING:
805             return _currText;
806         default:
807             return _currToken.asString();
808         }
809     }
810
811     // @since 2.1
812     @Override
813     public final String getValueAsString() throws IOException {
814         return getValueAsString(null);
815     }
816
817     @Override
818     public String getValueAsString(String defValue) throws IOException
819     {
820         JsonToken t = _currToken;
821         if (t == null) {
822             return null;
823         }
824         switch (t) {
825         case FIELD_NAME:
826             return getCurrentName();
827         case VALUE_STRING:
828             return _currText;
829         case START_OBJECT:
830             // the interesting case; may be able to convert certain kinds of
831             // elements (specifically, ones with attributes, CDATA only content)
832             // into VALUE_STRING
833             try {
834                 String str = _xmlTokens.convertToString();
835                 if (str != null) {
836                     // need to convert token, as well as "undo" START_OBJECT
837                     // note: Should NOT update context, because we will still be getting
838                     // matching END_OBJECT, which will undo contexts properly
839                     _parsingContext = _parsingContext.getParent();
840                     _currToken = JsonToken.VALUE_STRING;
841                     _nextToken = null;
842                     // One more thing: must explicitly skip the END_OBJECT that would follow
843                     try {
844                         _xmlTokens.skipEndElement();
845                     } catch (XMLStreamException e) {
846                         StaxUtil.throwAsParseException(e, this);
847                     }
848                     return (_currText = str);
849                 }
850             } catch (XMLStreamException e) {
851                 StaxUtil.throwAsParseException(e, this);
852             }
853             return null;
854         default:
855             if (_currToken.isScalarValue()) {
856                 return _currToken.asString();
857             }
858         }
859         return defValue;
860     }
861     
862     @Override
863     public char[] getTextCharacters() throws IOException {
864         String text = getText();
865         return (text == null)  ? null : text.toCharArray();
866     }
867
868     @Override
869     public int getTextLength() throws IOException {
870         String text = getText();
871         return (text == null)  ? 0 : text.length();
872     }
873
874     @Override
875     public int getTextOffset() throws IOException {
876         return 0;
877     }
878
879     /**
880      * XML input actually would offer access to character arrays; but since
881      * we must coalesce things it cannot really be exposed.
882      */

883     @Override
884     public boolean hasTextCharacters()
885     {
886         return false;
887     }
888
889     @Override // since 2.8
890     public int getText(Writer writer) throws IOException
891     {
892         String str = getText();
893         if (str == null) {
894             return 0;
895         }
896         writer.write(str);
897         return str.length();
898     }
899     
900     /*
901     /**********************************************************
902     /* Public API, access to token information, binary
903     /**********************************************************
904      */

905
906     @Override
907     public Object getEmbeddedObject() throws IOException {
908         // no way to embed POJOs for now...
909         return null;
910     }
911
912     @Override
913     public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
914     {
915         if (_currToken != JsonToken.VALUE_STRING &&
916                 (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT || _binaryValue == null)) {
917             _reportError("Current token ("+_currToken+") not VALUE_STRING or VALUE_EMBEDDED_OBJECT, can not access as binary");
918         }
919         /* To ensure that we won't see inconsistent data, better clear up
920          * state...
921          */

922         if (_binaryValue == null) {
923             try {
924                 _binaryValue = _decodeBase64(b64variant);
925             } catch (IllegalArgumentException iae) {
926                 throw _constructError("Failed to decode VALUE_STRING as base64 ("+b64variant+"): "+iae.getMessage());
927             }
928         }        
929         return _binaryValue;
930     }
931
932     @SuppressWarnings("resource")
933     protected byte[] _decodeBase64(Base64Variant b64variant) throws IOException
934     {
935         ByteArrayBuilder builder = _getByteArrayBuilder();
936         final String str = getText();
937         _decodeBase64(str, builder, b64variant);
938         return builder.toByteArray();
939     }
940     
941     /*
942     /**********************************************************
943     /* Numeric accessors
944     /**********************************************************
945      */

946
947     @Override
948     public BigInteger getBigIntegerValue() throws IOException {
949         // TODO Auto-generated method stub
950         return null;
951     }
952
953     @Override
954     public BigDecimal getDecimalValue() throws IOException {
955         // TODO Auto-generated method stub
956         return null;
957     }
958
959     @Override
960     public double getDoubleValue() throws IOException {
961         // TODO Auto-generated method stub
962         return 0;
963     }
964
965     @Override
966     public float getFloatValue() throws IOException {
967         // TODO Auto-generated method stub
968         return 0;
969     }
970
971     @Override
972     public int getIntValue() throws IOException {
973         // TODO Auto-generated method stub
974         return 0;
975     }
976
977     @Override
978     public long getLongValue() throws IOException {
979         // TODO Auto-generated method stub
980         return 0;
981     }
982
983     @Override
984     public NumberType getNumberType() throws IOException {
985         // TODO Auto-generated method stub
986         return null;
987     }
988
989     @Override
990     public Number getNumberValue() throws IOException {
991         // TODO Auto-generated method stub
992         return null;
993     }
994
995     /*
996     /**********************************************************
997     /* Abstract method impls for stuff from JsonParser
998     /**********************************************************
999      */

1000
1001     /**
1002      * Method called when an EOF is encountered between tokens.
1003      * If so, it may be a legitimate EOF, but only iff there
1004      * is no open non-root context.
1005      */

1006     @Override
1007     protected void _handleEOF() throws JsonParseException
1008     {
1009         if (!_parsingContext.inRoot()) {
1010             String marker = _parsingContext.inArray() ? "Array" : "Object";
1011             _reportInvalidEOF(String.format(
1012                     ": expected close marker for %s (start marker at %s)",
1013                     marker,
1014                     _parsingContext.getStartLocation(_ioContext.getSourceReference())),
1015                     null);
1016         }
1017     }
1018
1019     /*
1020     /**********************************************************
1021     /* Internal methods
1022     /**********************************************************
1023      */

1024
1025     /**
1026      * Method called to release internal buffers owned by the base
1027      * parser.
1028      */

1029     protected void _releaseBuffers() throws IOException {
1030         // anything we can/must release? Underlying parser should do all of it, for now?
1031     }
1032
1033     protected ByteArrayBuilder _getByteArrayBuilder()
1034     {
1035         if (_byteArrayBuilder == null) {
1036             _byteArrayBuilder = new ByteArrayBuilder();
1037         } else {
1038             _byteArrayBuilder.reset();
1039         }
1040         return _byteArrayBuilder;
1041     }
1042
1043     protected boolean _isEmpty(String str)
1044     {
1045         int len = (str == null) ? 0 : str.length();
1046         if (len > 0) {
1047             for (int i = 0; i < len; ++i) {
1048                 if (str.charAt(i) > ' ') {
1049                     return false;
1050                 }
1051             }
1052         }
1053         return true;
1054     }
1055
1056     private <T> T  _internalErrorUnknownToken(Object token) {
1057         throw new IllegalStateException("Internal error: unrecognized XmlTokenStream token: "+token);
1058     }
1059 }
1060