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