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