Populate data/ hierarchy
[yangtools.git] / yang / yang-model-export / src / main / java / org / opendaylight / yangtools / yang / model / export / YinXMLEventReader.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o. 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.model.export;
9
10 import static com.google.common.collect.Iterators.singletonIterator;
11 import static com.google.common.collect.Iterators.transform;
12 import static java.util.Collections.emptyIterator;
13 import static java.util.Objects.requireNonNull;
14
15 import java.nio.charset.StandardCharsets;
16 import java.util.ArrayDeque;
17 import java.util.Arrays;
18 import java.util.Deque;
19 import java.util.Iterator;
20 import java.util.Map.Entry;
21 import java.util.NoSuchElementException;
22 import java.util.Optional;
23 import java.util.Queue;
24 import javax.xml.XMLConstants;
25 import javax.xml.stream.XMLEventFactory;
26 import javax.xml.stream.XMLEventReader;
27 import javax.xml.stream.XMLStreamException;
28 import javax.xml.stream.events.Attribute;
29 import javax.xml.stream.events.Characters;
30 import javax.xml.stream.events.EndElement;
31 import javax.xml.stream.events.StartElement;
32 import javax.xml.stream.events.XMLEvent;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.model.api.meta.ArgumentDefinition;
35 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
36 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
37
38 final class YinXMLEventReader implements XMLEventReader {
39     private static final class OpenElement {
40         final Iterator<? extends DeclaredStatement<?>> children;
41         final QName name;
42
43         OpenElement(final Iterator<? extends DeclaredStatement<?>> children) {
44             this.children = requireNonNull(children);
45             this.name = null;
46         }
47
48         OpenElement(final QName name, final Iterator<? extends DeclaredStatement<?>> children) {
49             this.children = requireNonNull(children);
50             this.name = requireNonNull(name);
51         }
52     }
53
54     private final Deque<OpenElement> stack = new ArrayDeque<>(8);
55     private final Queue<XMLEvent> events = new ArrayDeque<>();
56     private final ModuleNamespaceContext namespaceContext;
57     private final XMLEventFactory eventFactory;
58
59     YinXMLEventReader(final XMLEventFactory eventFactory, final ModuleNamespaceContext namespaceContext,
60             final DeclaredStatement<?> root) {
61         this.eventFactory = requireNonNull(eventFactory);
62         this.namespaceContext = requireNonNull(namespaceContext);
63
64         events.add(eventFactory.createStartDocument(StandardCharsets.UTF_8.name()));
65
66         final StatementDefinition def = root.statementDefinition();
67         final QName name = def.getStatementName();
68         final ArgumentDefinition arg = def.getArgumentDefinition().get();
69
70         events.add(eventFactory.createStartElement(XMLConstants.DEFAULT_NS_PREFIX, name.getNamespace().toString(),
71             name.getLocalName(), singletonIterator(attribute(arg.getArgumentName(), root.rawArgument())),
72             transform(namespaceContext.prefixesAndNamespaces().entrySet().iterator(),
73                 e -> eventFactory.createNamespace(e.getKey(), e.getValue())),
74             namespaceContext));
75
76         stack.push(new OpenElement(name, root.declaredSubstatements().iterator()));
77     }
78
79     @Override
80     public XMLEvent next() {
81         XMLEvent event = events.poll();
82         if (event != null) {
83             return event;
84         }
85
86         nextStatement();
87         event = events.poll();
88         if (event == null) {
89             throw new NoSuchElementException("All events have been processed");
90         }
91         return event;
92     }
93
94     @Override
95     public XMLEvent nextEvent() {
96         return next();
97     }
98
99     @Override
100     public boolean hasNext() {
101         if (events.isEmpty()) {
102             nextStatement();
103             return events.isEmpty();
104         }
105         return true;
106     }
107
108     @Override
109     public XMLEvent peek() throws XMLStreamException {
110         if (events.isEmpty()) {
111             nextStatement();
112         }
113
114         return events.peek();
115     }
116
117     @Override
118     public String getElementText() throws XMLStreamException {
119         XMLEvent current = peek();
120         if (current == null) {
121             throw new XMLStreamException("End of event stream");
122         }
123         if (!(current instanceof StartElement)) {
124             throw new XMLStreamException("Current event is " + current);
125         }
126
127         current = next();
128         if (!(current instanceof Characters)) {
129             throw new XMLStreamException("Encountered non-text event " + current);
130         }
131         final String ret = ((Characters)current).getData();
132
133         current = next();
134         if (!(current instanceof EndElement)) {
135             throw new XMLStreamException("Encountered unexpected event " + current);
136         }
137         return ret;
138     }
139
140     @Override
141     public XMLEvent nextTag() throws XMLStreamException {
142         final XMLEvent next = next();
143         if (next instanceof Characters) {
144             throw new XMLStreamException("Significant characters encountered: " + next);
145         }
146         return next;
147     }
148
149     @Override
150     public Object getProperty(final String name) {
151         throw new IllegalArgumentException("Property " + name + " not supported");
152     }
153
154     @Override
155     public void close() {
156         events.clear();
157         stack.clear();
158     }
159
160     private Attribute attribute(final QName qname, final String value) {
161         final Entry<String, String> ns = namespaceContext.prefixAndNamespaceFor(qname.getModule());
162         return eventFactory.createAttribute(ns.getKey(), ns.getValue(), qname.getLocalName(), value);
163     }
164
165     private StartElement startElement(final QName qname) {
166         final Entry<String, String> ns = namespaceContext.prefixAndNamespaceFor(qname.getModule());
167         return eventFactory.createStartElement(ns.getKey(), ns.getValue(), qname.getLocalName(), emptyIterator(),
168             emptyIterator(), namespaceContext);
169     }
170
171     private EndElement endElement(final QName qname) {
172         final Entry<String, String> ns = namespaceContext.prefixAndNamespaceFor(qname.getModule());
173         return eventFactory.createEndElement(ns.getKey(), ns.getValue(), qname.getLocalName());
174     }
175
176     private void nextStatement() {
177         OpenElement current = stack.peek();
178         if (current == null) {
179             return;
180         }
181
182         do {
183             while (current.children.hasNext()) {
184                 // We have to mind child statement origin and not emit empty implicit children
185                 final DeclaredStatement<?> child = current.children.next();
186                 switch (child.statementOrigin()) {
187                     case CONTEXT:
188                         final Iterator<? extends DeclaredStatement<?>> it = child.declaredSubstatements().iterator();
189                         if (it.hasNext()) {
190                             current = new OpenElement(it);
191                         }
192                         break;
193                     case DECLARATION:
194                         addStatement(child);
195                         return;
196                     default:
197                         throw new IllegalStateException("Unhandled statement origin " + child.statementOrigin());
198                 }
199             }
200
201             if (current.name != null) {
202                 events.add(endElement(current.name));
203             }
204             stack.pop();
205             if (stack.isEmpty()) {
206                 events.add(eventFactory.createEndDocument());
207             }
208         } while (events.isEmpty());
209     }
210
211     private void addStatement(final DeclaredStatement<?> statement) {
212         final StatementDefinition def = statement.statementDefinition();
213         final QName name = def.getStatementName();
214         final Optional<ArgumentDefinition> optArgDef = def.getArgumentDefinition();
215         if (optArgDef.isPresent()) {
216             final ArgumentDefinition argDef = optArgDef.get();
217             final QName argName = argDef.getArgumentName();
218             if (argDef.isYinElement()) {
219                 events.addAll(Arrays.asList(startElement(name), startElement(argName),
220                     eventFactory.createCharacters(statement.rawArgument()), endElement(argName)));
221             } else {
222                 final Entry<String, String> ns = namespaceContext.prefixAndNamespaceFor(name.getModule());
223                 events.add(eventFactory.createStartElement(ns.getKey(), ns.getValue(), name.getLocalName(),
224                     singletonIterator(attribute(argName, statement.rawArgument())), emptyIterator(), namespaceContext));
225             }
226         } else {
227             // No attributes: just emit a start
228             events.add(startElement(name));
229         }
230
231         stack.push(new OpenElement(name, statement.declaredSubstatements().iterator()));
232     }
233 }