Add XMLNamespace
[yangtools.git] / yang / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / repo / YinStatementStreamSource.java
1 /*
2  * Copyright (c) 2015 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.parser.rfc7950.repo;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.yangtools.yang.parser.rfc7950.repo.StatementSourceReferenceHandler.extractRef;
13
14 import com.google.common.annotations.Beta;
15 import com.google.common.cache.CacheBuilder;
16 import com.google.common.cache.CacheLoader;
17 import com.google.common.cache.LoadingCache;
18 import java.util.Optional;
19 import javax.xml.transform.TransformerException;
20 import org.opendaylight.yangtools.concepts.AbstractSimpleIdentifiable;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.common.XMLNamespace;
23 import org.opendaylight.yangtools.yang.common.YangVersion;
24 import org.opendaylight.yangtools.yang.model.api.meta.ArgumentDefinition;
25 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
26 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
27 import org.opendaylight.yangtools.yang.model.repo.api.YinDomSchemaSource;
28 import org.opendaylight.yangtools.yang.model.repo.api.YinXmlSchemaSource;
29 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
30 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
31 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
32 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
33 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
34 import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource;
35 import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter;
36 import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter.ResumedStatement;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39 import org.w3c.dom.Attr;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.NamedNodeMap;
42 import org.w3c.dom.Node;
43 import org.w3c.dom.NodeList;
44
45 /**
46  * A {@link StatementStreamSource} based on a {@link YinXmlSchemaSource}. Internal implementation works on top
47  * of {@link YinDomSchemaSource} and its DOM document.
48  *
49  * @author Robert Varga
50  */
51 @Beta
52 public final class YinStatementStreamSource extends AbstractSimpleIdentifiable<SourceIdentifier>
53         implements StatementStreamSource {
54     private static final Logger LOG = LoggerFactory.getLogger(YinStatementStreamSource.class);
55     private static final LoadingCache<String, XMLNamespace> NS_CACHE = CacheBuilder.newBuilder().weakValues().build(
56         new CacheLoader<String, XMLNamespace>() {
57             @Override
58             public XMLNamespace load(final String key) {
59                 return XMLNamespace.of(key).intern();
60             }
61         });
62     private final Node root;
63
64     private YinStatementStreamSource(final SourceIdentifier identifier, final Node root) {
65         super(identifier);
66         this.root = requireNonNull(root);
67     }
68
69     public static StatementStreamSource create(final YinXmlSchemaSource source) throws TransformerException {
70         return create(YinDomSchemaSource.transform(source));
71     }
72
73     public static StatementStreamSource create(final YinDomSchemaSource source) {
74         return new YinStatementStreamSource(source.getIdentifier(), source.getSource().getNode());
75     }
76
77     private static StatementDefinition getValidDefinition(final Node node, final StatementWriter writer,
78             final QNameToStatementDefinition stmtDef, final StatementSourceReference ref) {
79         final XMLNamespace uri = NS_CACHE.getUnchecked(node.getNamespaceURI());
80         final StatementDefinition def = stmtDef.getByNamespaceAndLocalName(uri, node.getLocalName());
81
82         if (def == null) {
83             SourceException.throwIf(writer.getPhase().equals(ModelProcessingPhase.FULL_DECLARATION), ref,
84                 "%s is not a YIN statement or use of extension.", node.getLocalName());
85         }
86         return def;
87     }
88
89     private static boolean processAttribute(final int childId, final Attr attr, final StatementWriter writer,
90             final QNameToStatementDefinition stmtDef, final StatementSourceReference ref) {
91         final Optional<? extends ResumedStatement> optResumed = writer.resumeStatement(childId);
92         if (optResumed.isPresent()) {
93             final ResumedStatement resumed = optResumed.get();
94             checkState(resumed.isFullyDefined(), "Statement %s is not fully defined", resumed);
95             return true;
96         }
97
98         final StatementDefinition def = getValidDefinition(attr, writer, stmtDef, ref);
99         if (def == null) {
100             return false;
101         }
102
103         final String value = attr.getValue();
104         writer.startStatement(childId, def.getStatementName(), value.isEmpty() ? null : value, ref);
105         writer.storeStatement(0, true);
106         writer.endStatement(ref);
107         return true;
108     }
109
110     private static String getArgValue(final Element element, final QName argName, final boolean yinElement) {
111         if (yinElement) {
112             final NodeList children = element.getElementsByTagNameNS(argName.getNamespace().toString(),
113                 argName.getLocalName());
114             if (children.getLength() == 0) {
115                 return null;
116             }
117             return children.item(0).getTextContent();
118         }
119
120         final Attr attr = element.getAttributeNode(argName.getLocalName());
121         if (attr == null) {
122             return null;
123         }
124
125         return attr.getValue();
126     }
127
128     private static boolean processElement(final int childId, final Element element, final StatementWriter writer,
129             final QNameToStatementDefinition stmtDef) {
130
131         final Optional<? extends ResumedStatement> optResumed = writer.resumeStatement(childId);
132         final StatementSourceReference ref;
133         final QName argName;
134         final boolean allAttrs;
135         final boolean allElements;
136         if (optResumed.isPresent()) {
137             final ResumedStatement resumed = optResumed.get();
138             if (resumed.isFullyDefined()) {
139                 return true;
140             }
141
142             final StatementDefinition def = resumed.getDefinition();
143             ref = resumed.getSourceReference();
144             final Optional<ArgumentDefinition> optArgDef = def.getArgumentDefinition();
145             if (optArgDef.isPresent()) {
146                 final ArgumentDefinition argDef = optArgDef.get();
147                 argName = argDef.getArgumentName();
148                 allAttrs = argDef.isYinElement();
149                 allElements = !allAttrs;
150             } else {
151                 argName = null;
152                 allAttrs = false;
153                 allElements = false;
154             }
155         } else {
156             ref = extractRef(element);
157             final StatementDefinition def = getValidDefinition(element, writer, stmtDef, ref);
158             if (def == null) {
159                 LOG.debug("Skipping element {}", element);
160                 return false;
161             }
162
163             final String argValue;
164             final Optional<ArgumentDefinition> optArgDef = def.getArgumentDefinition();
165             if (optArgDef.isPresent()) {
166                 final ArgumentDefinition argDef = optArgDef.get();
167                 argName = argDef.getArgumentName();
168                 allAttrs = argDef.isYinElement();
169                 allElements = !allAttrs;
170
171                 argValue = getArgValue(element, argName, allAttrs);
172                 SourceException.throwIfNull(argValue, ref, "Statement {} is missing mandatory argument %s",
173                     def.getStatementName(), argName);
174             } else {
175                 argName = null;
176                 argValue = null;
177                 allAttrs = false;
178                 allElements = false;
179             }
180
181             writer.startStatement(childId, def.getStatementName(), argValue, ref);
182         }
183
184         // Child counter
185         int childCounter = 0;
186         boolean fullyDefined = true;
187
188         // First process any statements defined as attributes. We need to skip argument, if present
189         final NamedNodeMap attributes = element.getAttributes();
190         if (attributes != null) {
191             for (int i = 0, len = attributes.getLength(); i < len; ++i) {
192                 final Attr attr = (Attr) attributes.item(i);
193                 if ((allAttrs || !isArgument(argName, attr))
194                         && !processAttribute(childCounter++, attr, writer, stmtDef, ref)) {
195                     fullyDefined = false;
196                 }
197             }
198         }
199
200         // Now process child elements, if present
201         final NodeList children = element.getChildNodes();
202         for (int i = 0, len = children.getLength(); i < len; ++i) {
203             final Node child = children.item(i);
204             if (child.getNodeType() == Node.ELEMENT_NODE && (allElements || !isArgument(argName, child))
205                     && !processElement(childCounter++, (Element) child, writer, stmtDef)) {
206                 fullyDefined = false;
207             }
208         }
209
210         writer.storeStatement(childCounter, fullyDefined);
211         writer.endStatement(ref);
212         return fullyDefined;
213     }
214
215     private static boolean isArgument(final QName argName, final Node node) {
216         return argName != null && argName.getLocalName().equals(node.getLocalName()) && node.getPrefix() == null;
217     }
218
219     private void walkTree(final StatementWriter writer, final QNameToStatementDefinition stmtDef) {
220         final NodeList children = root.getChildNodes();
221
222         int childCounter = 0;
223         for (int i = 0, len = children.getLength(); i < len; ++i) {
224             final Node child = children.item(i);
225             if (child.getNodeType() == Node.ELEMENT_NODE) {
226                 processElement(childCounter++, (Element) child, writer, stmtDef);
227             }
228         }
229     }
230
231     @Override
232     public void writePreLinkage(final StatementWriter writer, final QNameToStatementDefinition stmtDef) {
233         walkTree(writer, stmtDef);
234     }
235
236     @Override
237     public void writeLinkage(final StatementWriter writer, final QNameToStatementDefinition stmtDef,
238             final PrefixToModule preLinkagePrefixes, final YangVersion yangVersion) {
239         walkTree(writer, stmtDef);
240     }
241
242     @Override
243     public void writeLinkageAndStatementDefinitions(final StatementWriter writer,
244             final QNameToStatementDefinition stmtDef, final PrefixToModule prefixes,  final YangVersion yangVersion) {
245         walkTree(writer, stmtDef);
246     }
247
248     @Override
249     public void writeFull(final StatementWriter writer, final QNameToStatementDefinition stmtDef,
250             final PrefixToModule prefixes, final YangVersion yangVersion) {
251         walkTree(writer, stmtDef);
252     }
253 }