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