3523742e0f0ccba455727454ce95156aad002815
[yangtools.git] / codec / yang-data-codec-gson / src / main / java / org / opendaylight / yangtools / yang / data / codec / gson / JSONNormalizedNodeStreamWriter.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.codec.gson;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.gson.stream.JsonWriter;
14 import java.io.IOException;
15 import java.util.Collection;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.NoSuchElementException;
19 import java.util.regex.Pattern;
20 import javax.xml.transform.dom.DOMSource;
21 import org.checkerframework.checker.regex.qual.Regex;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.opendaylight.yangtools.rfc8528.model.api.MountPointLabel;
24 import org.opendaylight.yangtools.yang.common.XMLNamespace;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
29 import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
31 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
32 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.MountPointExtension;
33 import org.opendaylight.yangtools.yang.data.util.NormalizedNodeStreamWriterStack;
34 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
39 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
42 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
43 import org.w3c.dom.Element;
44 import org.w3c.dom.Node;
45 import org.w3c.dom.NodeList;
46 import org.w3c.dom.Text;
47
48 /**
49  * This implementation will create JSON output as output stream.
50  *
51  * <p>
52  * Values of leaf and leaf-list are NOT translated according to codecs.
53  */
54 public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter, MountPointExtension {
55     private static final class Exclusive extends JSONNormalizedNodeStreamWriter {
56         Exclusive(final JSONCodecFactory codecFactory, final NormalizedNodeStreamWriterStack tracker,
57                 final JsonWriter writer, final JSONStreamWriterRootContext rootContext) {
58             super(codecFactory, tracker, writer, rootContext);
59         }
60
61         @Override
62         public void close() throws IOException {
63             flush();
64             closeWriter();
65         }
66     }
67
68     private static final class Nested extends JSONNormalizedNodeStreamWriter {
69         Nested(final JSONCodecFactory codecFactory, final NormalizedNodeStreamWriterStack tracker,
70                 final JsonWriter writer, final JSONStreamWriterRootContext rootContext) {
71             super(codecFactory, tracker, writer, rootContext);
72         }
73
74         @Override
75         public void close() throws IOException {
76             flush();
77             // The caller "owns" the writer, let them close it
78         }
79     }
80
81     /**
82      * RFC6020 deviation: we are not required to emit empty containers unless they
83      * are marked as 'presence'.
84      */
85     private static final boolean DEFAULT_EMIT_EMPTY_CONTAINERS = true;
86
87     @Regex
88     private static final String NUMBER_STRING = "-?\\d+(\\.\\d+)?";
89     private static final Pattern NUMBER_PATTERN = Pattern.compile(NUMBER_STRING);
90
91     @Regex
92     private static final String NOT_DECIMAL_NUMBER_STRING = "-?\\d+";
93     private static final Pattern NOT_DECIMAL_NUMBER_PATTERN = Pattern.compile(NOT_DECIMAL_NUMBER_STRING);
94
95     private final NormalizedNodeStreamWriterStack tracker;
96     private final JSONCodecFactory codecs;
97     private final JsonWriter writer;
98     private JSONStreamWriterContext context;
99
100     JSONNormalizedNodeStreamWriter(final JSONCodecFactory codecFactory, final NormalizedNodeStreamWriterStack tracker,
101             final JsonWriter writer, final JSONStreamWriterRootContext rootContext) {
102         this.writer = requireNonNull(writer);
103         codecs = requireNonNull(codecFactory);
104         this.tracker = requireNonNull(tracker);
105         context = requireNonNull(rootContext);
106     }
107
108     /**
109      * Create a new stream writer, which writes to the specified output stream.
110      *
111      * <p>
112      * The codec factory can be reused between multiple writers.
113      *
114      * <p>
115      * Returned writer is exclusive user of JsonWriter, which means it will start
116      * top-level JSON element and ends it.
117      *
118      * <p>
119      * This instance of writer can be used only to emit one top level element,
120      * otherwise it will produce incorrect JSON. Closing this instance will close
121      * the writer too.
122      *
123      * @param codecFactory JSON codec factory
124      * @param jsonWriter JsonWriter
125      * @return A stream writer instance
126      */
127     public static NormalizedNodeStreamWriter createExclusiveWriter(final JSONCodecFactory codecFactory,
128             final JsonWriter jsonWriter) {
129         return createExclusiveWriter(codecFactory, jsonWriter, null);
130     }
131
132     /**
133      * Create a new stream writer, which writes to the specified output stream.
134      *
135      * <p>
136      * The codec factory can be reused between multiple writers.
137      *
138      * <p>
139      * Returned writer is exclusive user of JsonWriter, which means it will start
140      * top-level JSON element and ends it.
141      *
142      * <p>
143      * This instance of writer can be used only to emit one top level element,
144      * otherwise it will produce incorrect JSON. Closing this instance will close
145      * the writer too.
146      *
147      * @param codecFactory JSON codec factory
148      * @param jsonWriter JsonWriter
149      * @param initialNs Initial namespace, can be null
150      * @return A stream writer instance
151      */
152     public static NormalizedNodeStreamWriter createExclusiveWriter(final JSONCodecFactory codecFactory,
153             final JsonWriter jsonWriter, final @Nullable XMLNamespace initialNs) {
154         return new Exclusive(codecFactory, NormalizedNodeStreamWriterStack.of(codecFactory.getEffectiveModelContext()),
155             jsonWriter, new JSONStreamWriterExclusiveRootContext(initialNs));
156     }
157
158     /**
159      * Create a new stream writer, which writes to the specified output stream.
160      *
161      * <p>
162      * The codec factory can be reused between multiple writers.
163      *
164      * <p>
165      * Returned writer is exclusive user of JsonWriter, which means it will start
166      * top-level JSON element and ends it.
167      *
168      * <p>
169      * This instance of writer can be used only to emit one top level element,
170      * otherwise it will produce incorrect JSON. Closing this instance will close
171      * the writer too.
172      *
173      * @param codecFactory JSON codec factory
174      * @param rootNode Root node inference
175      * @param initialNs Initial namespace
176      * @param jsonWriter JsonWriter
177      * @return A stream writer instance
178      */
179     public static NormalizedNodeStreamWriter createExclusiveWriter(final JSONCodecFactory codecFactory,
180             final EffectiveStatementInference rootNode, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
181         return new Exclusive(codecFactory, NormalizedNodeStreamWriterStack.of(rootNode), jsonWriter,
182             new JSONStreamWriterExclusiveRootContext(initialNs));
183     }
184
185     /**
186      * Create a new stream writer, which writes to the specified output stream.
187      *
188      * <p>
189      * The codec factory can be reused between multiple writers.
190      *
191      * <p>
192      * Returned writer is exclusive user of JsonWriter, which means it will start
193      * top-level JSON element and ends it.
194      *
195      * <p>
196      * This instance of writer can be used only to emit one top level element,
197      * otherwise it will produce incorrect JSON. Closing this instance will close
198      * the writer too.
199      *
200      * @param codecFactory JSON codec factory
201      * @param path Schema Path
202      * @param initialNs Initial namespace
203      * @param jsonWriter JsonWriter
204      * @return A stream writer instance
205      */
206     public static NormalizedNodeStreamWriter createExclusiveWriter(final JSONCodecFactory codecFactory,
207             final Absolute path, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
208         return new Exclusive(codecFactory,
209             NormalizedNodeStreamWriterStack.of(codecFactory.getEffectiveModelContext(), path), jsonWriter,
210             new JSONStreamWriterExclusiveRootContext(initialNs));
211     }
212
213     /**
214      * Create a new stream writer, which writes to the specified output stream.
215      *
216      * <p>
217      * The codec factory can be reused between multiple writers.
218      *
219      * <p>
220      * Returned writer can be used emit multiple top level element,
221      * but does not start / close parent JSON object, which must be done
222      * by user providing {@code jsonWriter} instance in order for
223      * JSON to be valid. Closing this instance <strong>will not</strong>
224      * close the wrapped writer; the caller must take care of that.
225      *
226      * @param codecFactory JSON codec factory
227      * @param jsonWriter JsonWriter
228      * @return A stream writer instance
229      */
230     public static NormalizedNodeStreamWriter createNestedWriter(final JSONCodecFactory codecFactory,
231             final JsonWriter jsonWriter) {
232         return createNestedWriter(codecFactory, jsonWriter, null);
233     }
234
235     /**
236      * Create a new stream writer, which writes to the specified output stream.
237      *
238      * <p>
239      * The codec factory can be reused between multiple writers.
240      *
241      * <p>
242      * Returned writer can be used emit multiple top level element,
243      * but does not start / close parent JSON object, which must be done
244      * by user providing {@code jsonWriter} instance in order for
245      * JSON to be valid. Closing this instance <strong>will not</strong>
246      * close the wrapped writer; the caller must take care of that.
247      *
248      * @param codecFactory JSON codec factory
249      * @param initialNs Initial namespace
250      * @param jsonWriter JsonWriter
251      * @return A stream writer instance
252      */
253     public static NormalizedNodeStreamWriter createNestedWriter(final JSONCodecFactory codecFactory,
254             final JsonWriter jsonWriter, final @Nullable XMLNamespace initialNs) {
255         return new Nested(codecFactory, NormalizedNodeStreamWriterStack.of(codecFactory.getEffectiveModelContext()),
256             jsonWriter, new JSONStreamWriterSharedRootContext(initialNs));
257     }
258
259     /**
260      * Create a new stream writer, which writes to the specified output stream.
261      *
262      * <p>
263      * The codec factory can be reused between multiple writers.
264      *
265      * <p>
266      * Returned writer can be used emit multiple top level element,
267      * but does not start / close parent JSON object, which must be done
268      * by user providing {@code jsonWriter} instance in order for
269      * JSON to be valid. Closing this instance <strong>will not</strong>
270      * close the wrapped writer; the caller must take care of that.
271      *
272      * @param codecFactory JSON codec factory
273      * @param path Schema Path
274      * @param initialNs Initial namespace
275      * @param jsonWriter JsonWriter
276      * @return A stream writer instance
277      */
278     public static NormalizedNodeStreamWriter createNestedWriter(final JSONCodecFactory codecFactory,
279             final Absolute path, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
280         return new Nested(codecFactory,
281             NormalizedNodeStreamWriterStack.of(codecFactory.getEffectiveModelContext(), path), jsonWriter,
282             new JSONStreamWriterSharedRootContext(initialNs));
283     }
284
285     /**
286      * Create a new stream writer, which writes to the specified output stream.
287      *
288      * <p>
289      * The codec factory can be reused between multiple writers.
290      *
291      * <p>
292      * Returned writer can be used emit multiple top level element,
293      * but does not start / close parent JSON object, which must be done
294      * by user providing {@code jsonWriter} instance in order for
295      * JSON to be valid. Closing this instance <strong>will not</strong>
296      * close the wrapped writer; the caller must take care of that.
297      *
298      * @param codecFactory JSON codec factory
299      * @param rootNode Root node inference
300      * @param initialNs Initial namespace
301      * @param jsonWriter JsonWriter
302      * @return A stream writer instance
303      */
304     public static NormalizedNodeStreamWriter createNestedWriter(final JSONCodecFactory codecFactory,
305             final EffectiveStatementInference rootNode, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
306         return new Nested(codecFactory, NormalizedNodeStreamWriterStack.of(rootNode), jsonWriter,
307             new JSONStreamWriterSharedRootContext(initialNs));
308     }
309
310     @Override
311     public Collection<? extends Extension> supportedExtensions() {
312         return List.of(this);
313     }
314
315     @Override
316     public void startLeafNode(final NodeIdentifier name) throws IOException {
317         tracker.startLeafNode(name);
318         context.emittingChild(codecs.getEffectiveModelContext(), writer);
319         context.writeChildJsonIdentifier(codecs.getEffectiveModelContext(), writer, name.getNodeType());
320     }
321
322     @Override
323     public final void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
324         tracker.startLeafSet(name);
325         context = new JSONStreamWriterListContext(context, name);
326     }
327
328     @Override
329     public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
330         tracker.startLeafSetEntryNode(name);
331         context.emittingChild(codecs.getEffectiveModelContext(), writer);
332     }
333
334     @Override
335     public final void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
336         tracker.startLeafSet(name);
337         context = new JSONStreamWriterListContext(context, name);
338     }
339
340     /*
341      * Warning suppressed due to static final constant which triggers a warning
342      * for the call to schema.isPresenceContainer().
343      */
344     @Override
345     public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
346         final SchemaNode schema = tracker.startContainerNode(name);
347         final boolean isPresence = schema instanceof ContainerSchemaNode container
348             ? container.isPresenceContainer() : DEFAULT_EMIT_EMPTY_CONTAINERS;
349         context = new JSONStreamWriterNamedObjectContext(context, name, isPresence);
350     }
351
352     @Override
353     public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
354         tracker.startList(name);
355         context = new JSONStreamWriterListContext(context, name);
356     }
357
358     @Override
359     public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
360         tracker.startListItem(name);
361         context = new JSONStreamWriterObjectContext(context, name, DEFAULT_EMIT_EMPTY_CONTAINERS);
362     }
363
364     @Override
365     public final void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
366         tracker.startList(name);
367         context = new JSONStreamWriterListContext(context, name);
368     }
369
370     @Override
371     public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
372             throws IOException {
373         tracker.startListItem(identifier);
374         context = new JSONStreamWriterObjectContext(context, identifier, DEFAULT_EMIT_EMPTY_CONTAINERS);
375     }
376
377     @Override
378     public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
379         tracker.startList(name);
380         context = new JSONStreamWriterListContext(context, name);
381     }
382
383     @Override
384     public final void startChoiceNode(final NodeIdentifier name, final int childSizeHint) {
385         tracker.startChoiceNode(name);
386         context = new JSONStreamWriterInvisibleContext(context);
387     }
388
389     @Override
390     public final void startAugmentationNode(final AugmentationIdentifier identifier) {
391         tracker.startAugmentationNode(identifier);
392         context = new JSONStreamWriterInvisibleContext(context);
393     }
394
395     @Override
396     public final boolean startAnydataNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
397         if (NormalizedAnydata.class.isAssignableFrom(objectModel)) {
398             tracker.startAnydataNode(name);
399             context.emittingChild(codecs.getEffectiveModelContext(), writer);
400             context.writeChildJsonIdentifier(codecs.getEffectiveModelContext(), writer, name.getNodeType());
401             return true;
402         }
403
404         return false;
405     }
406
407     @Override
408     public final NormalizedNodeStreamWriter startMountPoint(final MountPointLabel label,
409             final MountPointContext mountCtx) throws IOException {
410         final EffectiveModelContext ctx = mountCtx.getEffectiveModelContext();
411         return new Nested(codecs.rebaseTo(ctx), NormalizedNodeStreamWriterStack.of(ctx), writer,
412             new JSONStreamWriterSharedRootContext(context.getNamespace()));
413     }
414
415     @Override
416     public final boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
417         if (DOMSource.class.isAssignableFrom(objectModel)) {
418             tracker.startAnyxmlNode(name);
419             context.emittingChild(codecs.getEffectiveModelContext(), writer);
420             context.writeChildJsonIdentifier(codecs.getEffectiveModelContext(), writer, name.getNodeType());
421             return true;
422         }
423         return false;
424     }
425
426     @Override
427     public final void endNode() throws IOException {
428         tracker.endNode();
429         context = context.endNode(codecs.getEffectiveModelContext(), writer);
430     }
431
432     @Override
433     public final void flush() throws IOException {
434         writer.flush();
435     }
436
437     final void closeWriter() throws IOException {
438         if (!(context instanceof JSONStreamWriterRootContext)) {
439             throw new IOException("Unexpected root context " + context);
440         }
441
442         context.endNode(codecs.getEffectiveModelContext(), writer);
443         writer.close();
444     }
445
446     @Override
447     public void scalarValue(final Object value) throws IOException {
448         final Object current = tracker.getParent();
449         if (current instanceof TypedDataSchemaNode typed) {
450             writeValue(value, codecs.codecFor(typed, tracker));
451         } else if (current instanceof AnydataSchemaNode) {
452             writeAnydataValue(value);
453         } else {
454             throw new IllegalStateException(String.format("Cannot emit scalar %s for %s", value, current));
455         }
456     }
457
458     @Override
459     public void domSourceValue(final DOMSource value) throws IOException {
460         final Object current = tracker.getParent();
461         checkState(current instanceof AnyxmlSchemaNode, "Cannot emit DOMSource %s for %s", value, current);
462         // FIXME: should have a codec based on this :)
463         writeAnyXmlValue(value);
464     }
465
466     @SuppressWarnings("unchecked")
467     private void writeValue(final Object value, final JSONCodec<?> codec) throws IOException {
468         ((JSONCodec<Object>) codec).writeValue(writer, value);
469     }
470
471     private void writeAnydataValue(final Object value) throws IOException {
472         if (value instanceof NormalizedAnydata normalized) {
473             writeNormalizedAnydata(normalized);
474         } else {
475             throw new IllegalStateException("Unexpected anydata value " + value);
476         }
477     }
478
479     private void writeNormalizedAnydata(final NormalizedAnydata anydata) throws IOException {
480         // Adjust state to point to parent node and ensure it can handle data tree nodes
481         final SchemaInferenceStack.Inference inference;
482         try {
483             final SchemaInferenceStack stack = SchemaInferenceStack.ofInference(anydata.getInference());
484             stack.exitToDataTree();
485             inference = stack.toInference();
486         } catch (IllegalArgumentException | IllegalStateException | NoSuchElementException e) {
487             throw new IOException("Cannot emit " + anydata, e);
488         }
489
490         anydata.writeTo(JSONNormalizedNodeStreamWriter.createNestedWriter(
491             codecs.rebaseTo(inference.getEffectiveModelContext()), inference, context.getNamespace(), writer));
492     }
493
494     private void writeAnyXmlValue(final DOMSource anyXmlValue) throws IOException {
495         writeXmlNode(anyXmlValue.getNode());
496     }
497
498     private void writeXmlNode(final Node node) throws IOException {
499         if (isArrayElement(node)) {
500             writeArrayContent(node);
501             return;
502         }
503         final Element firstChildElement = getFirstChildElement(node);
504         if (firstChildElement == null) {
505             writeXmlValue(node);
506         } else {
507             writeObjectContent(firstChildElement);
508         }
509     }
510
511     private void writeArrayContent(final Node node) throws IOException {
512         writer.beginArray();
513         handleArray(node);
514         writer.endArray();
515     }
516
517     private void writeObjectContent(final Element firstChildElement) throws IOException {
518         writer.beginObject();
519         writeObject(firstChildElement);
520         writer.endObject();
521     }
522
523     private static boolean isArrayElement(final Node node) {
524         if (Node.ELEMENT_NODE == node.getNodeType()) {
525             final String nodeName = node.getNodeName();
526             for (Node nextNode = node.getNextSibling(); nextNode != null; nextNode = nextNode.getNextSibling()) {
527                 if (Node.ELEMENT_NODE == nextNode.getNodeType() && nodeName.equals(nextNode.getNodeName())) {
528                     return true;
529                 }
530             }
531         }
532         return false;
533     }
534
535     private void handleArray(final Node node) throws IOException {
536         final Element parentNode = (Element)node.getParentNode();
537         final NodeList elementsList = parentNode.getElementsByTagName(node.getNodeName());
538         for (int i = 0, length = elementsList.getLength(); i < length; i++) {
539             final Node arrayElement = elementsList.item(i);
540             final Element parent = (Element)arrayElement.getParentNode();
541             if (parentNode.isSameNode(parent)) {
542                 final Element firstChildElement = getFirstChildElement(arrayElement);
543                 if (firstChildElement != null) {
544                     writeObjectContent(firstChildElement);
545                 } else {
546                     // It may be scalar
547                     writeXmlValue(arrayElement);
548                 }
549             }
550         }
551     }
552
553     private void writeObject(Node node) throws IOException {
554         final var previousNodeNames = new HashSet<String>();
555         while (node != null) {
556             if (Node.ELEMENT_NODE == node.getNodeType()) {
557                 final var nodeName = node.getNodeName();
558                 if (previousNodeNames.add(nodeName)) {
559                     writer.name(nodeName);
560                     writeXmlNode(node);
561                 }
562             }
563             node = node.getNextSibling();
564         }
565     }
566
567     private void writeXmlValue(final Node node) throws IOException {
568         Text firstChild = getFirstChildText(node);
569         String childNodeText = firstChild != null ? firstChild.getWholeText() : "";
570         childNodeText = childNodeText != null ? childNodeText.trim() : "";
571
572         if (NUMBER_PATTERN.matcher(childNodeText).matches()) {
573             writer.value(parseNumber(childNodeText));
574             return;
575         }
576         switch (childNodeText) {
577             case "null" -> writer.nullValue();
578             case "false" -> writer.value(false);
579             case "true" -> writer.value(true);
580             default -> writer.value(childNodeText);
581         }
582     }
583
584     private static Element getFirstChildElement(final Node node) {
585         final NodeList children = node.getChildNodes();
586         for (int i = 0, length = children.getLength(); i < length; i++) {
587             final Node childNode = children.item(i);
588             if (Node.ELEMENT_NODE == childNode.getNodeType()) {
589                 return (Element) childNode;
590             }
591         }
592         return null;
593     }
594
595     private static Text getFirstChildText(final Node node) {
596         final NodeList children = node.getChildNodes();
597         for (int i = 0, length = children.getLength(); i < length; i++) {
598             final Node childNode = children.item(i);
599             if (Node.TEXT_NODE == childNode.getNodeType()) {
600                 return (Text) childNode;
601             }
602         }
603         return null;
604     }
605
606     // json numbers are 64 bit wide floating point numbers - in java terms it is either long or double
607     private static Number parseNumber(final String numberText) {
608         if (NOT_DECIMAL_NUMBER_PATTERN.matcher(numberText).matches()) {
609             return Long.valueOf(numberText);
610         }
611
612         return Double.valueOf(numberText);
613     }
614 }