YANGTOOLS-706: Split out yang-parser-rfc7950
[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 org.opendaylight.yangtools.yang.parser.rfc7950.repo.StatementSourceReferenceHandler.extractRef;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.MoreObjects;
14 import com.google.common.base.Preconditions;
15 import com.google.common.cache.CacheBuilder;
16 import com.google.common.cache.CacheLoader;
17 import com.google.common.cache.LoadingCache;
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import javax.annotation.Nonnull;
21 import javax.xml.transform.TransformerException;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
24 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
25 import org.opendaylight.yangtools.yang.model.repo.api.YinDomSchemaSource;
26 import org.opendaylight.yangtools.yang.model.repo.api.YinXmlSchemaSource;
27 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
28 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
29 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
30 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
31 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
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 YinXmlSchemaSource}. Internal implementation works on top
44  * of {@link YinDomSchemaSource} and its DOM document.
45  *
46  * @author Robert Varga
47  */
48 @Beta
49 public final class YinStatementStreamSource implements StatementStreamSource {
50     private static final Logger LOG = LoggerFactory.getLogger(YinStatementStreamSource.class);
51     private static final LoadingCache<String, URI> URI_CACHE = CacheBuilder.newBuilder().weakValues().build(
52         new CacheLoader<String, URI>() {
53             @Override
54             public URI load(@Nonnull final String key) throws URISyntaxException {
55                 return new URI(key);
56             }
57         });
58     private final SourceIdentifier identifier;
59     private final Node root;
60
61     private YinStatementStreamSource(final SourceIdentifier identifier, final Node root) {
62         this.identifier = Preconditions.checkNotNull(identifier);
63         this.root = Preconditions.checkNotNull(root);
64     }
65
66     public static StatementStreamSource create(final YinXmlSchemaSource source) throws TransformerException {
67         return create(YinDomSchemaSource.transform(source));
68     }
69
70     public static StatementStreamSource create(final YinDomSchemaSource source) {
71         return new YinStatementStreamSource(source.getIdentifier(), source.getSource().getNode());
72     }
73
74     @Override
75     public SourceIdentifier getIdentifier() {
76         return identifier;
77     }
78
79     private static StatementDefinition getValidDefinition(final Node node, final StatementWriter writer,
80             final QNameToStatementDefinition stmtDef, final StatementSourceReference ref) {
81         final URI uri = URI_CACHE.getUnchecked(node.getNamespaceURI());
82         final StatementDefinition def = stmtDef.getByNamespaceAndLocalName(uri, node.getLocalName());
83
84         if (def == null) {
85             SourceException.throwIf(writer.getPhase().equals(ModelProcessingPhase.FULL_DECLARATION), ref,
86                 "%s is not a YIN statement or use of extension.", node.getLocalName());
87         }
88         return def;
89     }
90
91     private static void processAttribute(final int childId, final Attr attr, final StatementWriter writer,
92             final QNameToStatementDefinition stmtDef, final StatementSourceReference ref) {
93         final StatementDefinition def = getValidDefinition(attr, writer, stmtDef, ref);
94         if (def == null) {
95             return;
96         }
97
98         final String value = attr.getValue();
99         writer.startStatement(childId, def.getStatementName(), value.isEmpty() ? null : value, ref);
100         writer.endStatement(ref);
101     }
102
103     private static String getArgValue(final Element element, final QName argName, final boolean yinElement) {
104         if (yinElement) {
105             final NodeList children = element.getElementsByTagNameNS(argName.getNamespace().toString(),
106                 argName.getLocalName());
107             if (children.getLength() == 0) {
108                 return null;
109             }
110             return children.item(0).getTextContent();
111         }
112
113         final Attr attr = element.getAttributeNode(argName.getLocalName());
114         if (attr == null) {
115             return null;
116         }
117
118         return attr.getValue();
119     }
120
121     private static void processElement(final int childId, final Element element, final StatementWriter writer,
122             final QNameToStatementDefinition stmtDef) {
123         final StatementSourceReference ref = extractRef(element);
124         final StatementDefinition def = getValidDefinition(element, writer, stmtDef, ref);
125         if (def == null) {
126             LOG.debug("Skipping element {}", element);
127             return;
128         }
129
130         final QName argName = def.getArgumentName();
131         final String argValue;
132         final boolean allAttrs;
133         final boolean allElements;
134         if (argName != null) {
135             allAttrs = def.isArgumentYinElement();
136             allElements = !allAttrs;
137
138             argValue = getArgValue(element, argName, allAttrs);
139             SourceException.throwIfNull(argValue, ref, "Statement {} is missing mandatory argument %s",
140                 def.getStatementName(), argName);
141         } else {
142             argValue = null;
143             allAttrs = false;
144             allElements = false;
145         }
146
147         writer.startStatement(childId, def.getStatementName(), argValue, ref);
148
149         // Child counter
150         int childCounter = 0;
151
152         // First process any statements defined as attributes. We need to skip argument, if present
153         final NamedNodeMap attributes = element.getAttributes();
154         if (attributes != null) {
155             for (int i = 0, len = attributes.getLength(); i < len; ++i) {
156                 final Attr attr = (Attr) attributes.item(i);
157                 if (allAttrs || !isArgument(argName, attr)) {
158                     processAttribute(childCounter++, attr, writer, stmtDef, ref);
159                 }
160             }
161         }
162
163         // Now process child elements, if present
164         final NodeList children = element.getChildNodes();
165         for (int i = 0, len = children.getLength(); i < len; ++i) {
166             final Node child = children.item(i);
167             if (child.getNodeType() == Node.ELEMENT_NODE) {
168                 if (allElements || !isArgument(argName, child)) {
169                     processElement(childCounter++, (Element) child, writer, stmtDef);
170                 }
171             }
172         }
173
174         writer.endStatement(ref);
175     }
176
177     private static boolean isArgument(final QName argName, final Node node) {
178         return argName != null && argName.getLocalName().equals(node.getLocalName()) && node.getPrefix() == null;
179     }
180
181     private void walkTree(final StatementWriter writer, final QNameToStatementDefinition stmtDef) {
182         final NodeList children = root.getChildNodes();
183
184         int childCounter = 0;
185         for (int i = 0, len = children.getLength(); i < len; ++i) {
186             final Node child = children.item(i);
187             if (child.getNodeType() == Node.ELEMENT_NODE) {
188                 processElement(childCounter++, (Element) child, writer, stmtDef);
189             }
190         }
191     }
192
193     @Override
194     public void writePreLinkage(final StatementWriter writer, final QNameToStatementDefinition stmtDef) {
195         walkTree(writer, stmtDef);
196     }
197
198     @Override
199     public void writeLinkage(final StatementWriter writer, final QNameToStatementDefinition stmtDef,
200             final PrefixToModule preLinkagePrefixes) {
201         walkTree(writer, stmtDef);
202     }
203
204     @Override
205     public void writeLinkageAndStatementDefinitions(final StatementWriter writer,
206             final QNameToStatementDefinition stmtDef, final PrefixToModule prefixes) {
207         walkTree(writer, stmtDef);
208     }
209
210     @Override
211     public void writeFull(final StatementWriter writer, final QNameToStatementDefinition stmtDef,
212             final PrefixToModule prefixes) {
213         walkTree(writer, stmtDef);
214     }
215
216     @Override
217     public String toString() {
218         return MoreObjects.toStringHelper(this).add("identifier", getIdentifier()).toString();
219     }
220 }