Added codec for w3c.Document to yang-data-api with schema support
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / XmlTreeBuilder.java
1 /*
2  * Copyright (c) 2013 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.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import java.io.InputStream;
13 import java.net.URI;
14 import java.util.ArrayList;
15 import java.util.Stack;
16
17 import javax.xml.stream.XMLEventReader;
18 import javax.xml.stream.XMLInputFactory;
19 import javax.xml.stream.XMLStreamException;
20 import javax.xml.stream.events.Characters;
21 import javax.xml.stream.events.StartElement;
22 import javax.xml.stream.events.XMLEvent;
23
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
26 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
27 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
28 import org.opendaylight.yangtools.yang.data.api.Node;
29 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
30
31 /**
32  * The XML Tree Builder is builder utility class designed to facilitate of
33  * loading, reading and parsing XML documents into YANG DOM represented by
34  * yang-data-api. <br>
35  * The method {@link #buildDataTree(InputStream)} is designed to take and parse
36  * XML as {@link InputStream}. The output of the operation SHOULD be root
37  * <code>CompositeNode</code> or <code>SimpleElement</code> depends by which
38  * element XML begins. The XML header is omitted by XML parser.
39  * 
40  * @author Lukas Sedlak
41  * 
42  * @see CompositeNode
43  * @see SimpleNode
44  * @see Node
45  */
46 public final class XmlTreeBuilder {
47
48     private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
49     private static XMLEventReader eventReader;
50
51     private XmlTreeBuilder() {
52     }
53
54     /**
55      * The method is designed to take and parse XML as {@link InputStream}. The
56      * output of the operation SHOULD be root <code>CompositeNode</code> or
57      * <code>SimpleElement</code> depends on element that XML document begins.
58      * The XML header is omitted by XML parser.
59      * 
60      * @param inputStream
61      *            XML Input Stream
62      * @return root <code>Node</code> element conformant to XML start element in
63      *         most cases it will be CompositeNode which contains child Nodes
64      * @throws XMLStreamException
65      */
66     public static Node<?> buildDataTree(final InputStream inputStream) throws XMLStreamException {
67         eventReader = xmlInputFactory.createXMLEventReader(inputStream);
68
69         final Stack<Node<?>> processingQueue = new Stack<>();
70         Node<?> parentNode = null;
71         Node<?> root = null;
72         while (eventReader.hasNext()) {
73             final XMLEvent event = eventReader.nextEvent();
74
75             if (event.isStartElement()) {
76                 final StartElement startElement = event.asStartElement();
77                 if (!processingQueue.isEmpty()) {
78                     parentNode = processingQueue.peek();
79                 }
80                 MutableCompositeNode compParentNode = null;
81                 if (parentNode instanceof MutableCompositeNode) {
82                     compParentNode = (MutableCompositeNode) parentNode;
83                 }
84                 Node<?> newNode = null;
85                 if (isCompositeNodeEvent(event)) {
86                     newNode = resolveCompositeNodeFromStartElement(startElement, compParentNode);
87                 } else if (isSimpleNodeEvent(event)) {
88                     newNode = resolveSimpleNodeFromStartElement(startElement, compParentNode);
89                 }
90
91                 if (newNode != null) {
92                     processingQueue.push(newNode);
93                     if (compParentNode != null) {
94                         compParentNode.getChildren().add(newNode);
95                     }
96                 }
97             } else if (event.isEndElement()) {
98                 root = processingQueue.pop();
99                 if (root instanceof MutableCompositeNode) {
100                     ((MutableCompositeNode) root).init();
101                 }
102             }
103         }
104         return root;
105     }
106
107     /**
108      * Checks if the XMLEvent is compliant to SimpleNode tag that contains only
109      * characters value. If the SimpleNode is composed only by empty XML tag
110      * (i.e. {@code <emptyTag />} or {@code<emptyTag></emptyTag>}) the result
111      * will be also <code>true</code>.
112      * 
113      * @param event
114      *            actual XMLEvent that is processed
115      * @return <code>true</code> only and only if the XMLEvent Start Element is
116      *         Simple element tag and contains character values or is empty XML
117      *         tag.
118      * @throws XMLStreamException
119      * 
120      * @see SimpleNode
121      */
122     private static boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException {
123         checkArgument(event != null, "XML Event cannot be NULL!");
124         if (event.isStartElement()) {
125             if (eventReader.hasNext()) {
126                 final XMLEvent innerEvent;
127                 innerEvent = eventReader.peek();
128                 if (innerEvent.isCharacters()) {
129                     final Characters chars = innerEvent.asCharacters();
130                     if (!chars.isWhiteSpace()) {
131                         return true;
132                     }
133                 } else if (innerEvent.isEndElement()) {
134                     return true;
135                 }
136             }
137         }
138         return false;
139     }
140
141     /**
142      * Checks if XMLEvent is equivalent to CompositeNode Event. The
143      * CompositeNode Event is XML element that conforms to the XML element that
144      * contains 1..N XML child elements. (i.e. {@code <compositeNode>
145      *  <simpleNode>data</simpleNode>
146      * </compositeNode>})
147      * 
148      * @param event
149      *            actual XMLEvent that is processed
150      * @return <code>true</code> only if XML Element contains 1..N child
151      *         elements, otherwise returns <code>false</code>
152      * @throws XMLStreamException
153      * 
154      * @see CompositeNode
155      */
156     private static boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException {
157         checkArgument(event != null, "XML Event cannot be NULL!");
158         if (event.isStartElement()) {
159             if (eventReader.hasNext()) {
160                 XMLEvent innerEvent;
161                 innerEvent = eventReader.peek();
162                 if (innerEvent.isCharacters()) {
163                     Characters chars = innerEvent.asCharacters();
164                     if (chars.isWhiteSpace()) {
165                         eventReader.nextEvent();
166                         innerEvent = eventReader.peek();
167                     }
168                 }
169                 if (innerEvent.isStartElement()) {
170                     return true;
171                 }
172             }
173         }
174         return false;
175     }
176
177     /**
178      * Creates and returns <code>SimpleNode</code> instance from actually
179      * processed XML Start Element.
180      * 
181      * @param startElement
182      *            actual XML Start Element that is processed
183      * @param parent
184      *            Parent CompositeNode
185      * @return <code>new SimpleNode</code> instance from actually processed XML
186      *         Start Element
187      * @throws XMLStreamException
188      * 
189      * @see SimpleNode
190      */
191     private static SimpleNode<String> resolveSimpleNodeFromStartElement(final StartElement startElement,
192             CompositeNode parent) throws XMLStreamException {
193         checkArgument(startElement != null, "Start Element cannot be NULL!");
194         String data = null;
195
196         if (eventReader.hasNext()) {
197             final XMLEvent innerEvent = eventReader.peek();
198             if (innerEvent.isCharacters()) {
199                 final Characters chars = innerEvent.asCharacters();
200                 if (!chars.isWhiteSpace()) {
201                     data = innerEvent.asCharacters().getData();
202                 }
203             } else if (innerEvent.isEndElement()) {
204                 data = "";
205             }
206         }
207         return NodeFactory.createImmutableSimpleNode(resolveElementQName(startElement), parent, data);
208     }
209
210     /**
211      * Creates and returns <code>MutableCompositeNode</code> instance from
212      * actually processed XML Start Element.
213      * 
214      * @param startElement
215      *            actual XML Start Element that is processed
216      * @param parent
217      *            Parent CompositeNode
218      * @return <code>new MutableCompositeNode</code> instance from actually
219      *         processed XML Start Element
220      * 
221      * @see CompositeNode
222      * @see MutableCompositeNode
223      */
224     private static MutableCompositeNode resolveCompositeNodeFromStartElement(final StartElement startElement,
225             CompositeNode parent) {
226         checkArgument(startElement != null, "Start Element cannot be NULL!");
227
228         return NodeFactory.createMutableCompositeNode(resolveElementQName(startElement), parent,
229                 new ArrayList<Node<?>>(), ModifyAction.CREATE, null);
230     }
231
232     /**
233      * Extract and retrieve XML Element QName to OpenDaylight QName.
234      * 
235      * @param element
236      *            Start Element
237      * @return QName instance composed of <code>elements</code> Namespace and
238      *         Local Part.
239      * 
240      * @see QName
241      */
242     private static QName resolveElementQName(final StartElement element) {
243         checkArgument(element != null, "Start Element cannot be NULL!");
244
245         final String nsURI = element.getName().getNamespaceURI();
246         final String localName = element.getName().getLocalPart();
247         return new QName(URI.create(nsURI), localName);
248     }
249 }