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 `true` for 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