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