4ce9bce6499a333fbb6538e611dd97dcff9f342b
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / impl / YinStatementParserImpl.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
9 package org.opendaylight.yangtools.yang.parser.impl;
10
11 import com.google.common.base.Preconditions;
12 import java.net.URI;
13 import java.net.URISyntaxException;
14 import java.util.ArrayList;
15 import java.util.List;
16 import javax.xml.stream.XMLStreamConstants;
17 import javax.xml.stream.XMLStreamException;
18 import javax.xml.stream.XMLStreamReader;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.common.YangConstants;
21 import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
22 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
23 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
24 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
25 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
26 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
27 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
28 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
29 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
30 import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter;
31 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.TypeUtils;
32 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 public class YinStatementParserImpl {
37
38     private static final Logger LOG = LoggerFactory.getLogger(YinStatementParserImpl.class);
39
40     private StatementWriter writer;
41     private QNameToStatementDefinition stmtDef;
42     private PrefixToModule prefixes;
43     private final String sourceName;
44     private String uriStr;
45     private boolean isType = false;
46     private boolean action = true;
47     private boolean yinElement = false;
48     private List<String> toBeSkipped = new ArrayList<>();
49
50     public YinStatementParserImpl(String sourceName) {
51         this.sourceName = Preconditions.checkNotNull(sourceName);
52     }
53
54     /**
55      *
56      * This method is supposed to be called in linkage phase, when YinStatementParserImpl instance has already been
57      * created.
58      * When done, start walking through YIN source
59      *
60      *
61      * @param writer - instance of StatementWriter to emit declared statements
62      * @param stmtDef - map of valid statement definitions for linkage phase
63      *
64      */
65     public void setAttributes(StatementWriter writer, QNameToStatementDefinition stmtDef) {
66         this.writer = writer;
67         this.stmtDef = stmtDef;
68     }
69
70     /**
71      *
72      * This method is supposed to be called in any phase but linkage, when YinStatementParserImpl instance has already
73      * been created.
74      * When done, start walking through YIN source
75      *
76      * @param writer - instance of StatementWriter to emit declared statements
77      * @param stmtDef - map of valid statement definitions for any phase but linkage
78      * @param prefixes - map of valid prefixes for any phase but linkage
79      *
80      */
81     public void setAttributes(StatementWriter writer, QNameToStatementDefinition stmtDef, PrefixToModule prefixes) {
82         this.writer = writer;
83         this.stmtDef = stmtDef;
84         this.prefixes = prefixes;
85     }
86
87     /**
88      *
89      * This method executes parsing YIN source and emitting declared statements via attached StatementWriter
90      *
91      * @param inputReader - instance of XMlStreamReader, allows forward, read-only access to XML.
92      *
93      */
94     public void walk(XMLStreamReader inputReader) {
95         try {
96             while (inputReader.hasNext()) {
97                 inputReader.next();
98                 if (inputReader.hasName() && inputReader.getEventType() == XMLStreamConstants.START_ELEMENT) {
99                     enterStatement(inputReader);
100                 }
101
102                 if (inputReader.hasName() && inputReader.getEventType() == XMLStreamConstants.END_ELEMENT) {
103                     exitStatement(inputReader);
104                 }
105             }
106         } catch (XMLStreamException e) {
107             LOG.warn("Fatal error detecting the next state of XMLStreamReader", e);
108         } catch (URISyntaxException e) {
109             LOG.warn("Given string {} violates RFC2396", uriStr, e);
110         }
111     }
112
113     private void startStatement(QName identifier, StatementSourceReference ref) {
114         writer.startStatement(identifier, ref);
115     }
116
117     private void argumentValue(XMLStreamReader inputReader, StatementSourceReference ref, QName identifier, boolean
118             yinElement) {
119         if (yinElement) {
120             writeTextOnlyElement(inputReader, ref);
121         } else {
122             writeNormalizedAttributeValue(inputReader, identifier, ref);
123         }
124     }
125
126     private void endStatement(StatementSourceReference ref) {
127         writer.endStatement(ref);
128     }
129
130     private void enterStatement(XMLStreamReader inputReader) throws URISyntaxException {
131         final StatementSourceReference ref = DeclarationInTextSource.atPosition(sourceName, inputReader
132                 .getLocation().getLineNumber(), inputReader.getLocation().getColumnNumber());
133         uriStr = inputReader.getNamespaceURI();
134         final QName identifier = new QName(new URI(uriStr), getElementFullName(inputReader));
135         if (yinElement && toBeSkipped.isEmpty()) {
136             //at yin element, it has be read as argument
137             argumentValue(inputReader, ref, identifier, true);
138         } else {
139             if (isStatementWithYinElement(identifier, stmtDef)) {
140                 //at statement with yin element, so next statement will be read as argument
141                 yinElement = true;
142             }
143
144             final QName validStatementDefinition = Utils.getValidStatementDefinition(prefixes, stmtDef, identifier);
145
146             //main part -> valid statement for actual phase
147             if (stmtDef != null && validStatementDefinition != null && toBeSkipped.isEmpty()) {
148                 if (identifier.equals(Rfc6020Mapping.TYPE.getStatementName())) {
149                     isType = true;
150                 } else {
151                     startStatement(validStatementDefinition, ref);
152                     if (isStatementWithYinElement(identifier, stmtDef)) {
153                         action = false;
154                     }
155                 }
156             } else {
157                 //if statement not found through all phases, throw exception
158                 if (writer.getPhase().equals(ModelProcessingPhase.FULL_DECLARATION)) {
159                     throw new SourceException(String.format("%s is not a YIN statement or use of extension.",
160                             identifier.getLocalName()), ref);
161                 } else {
162                     //otherwise skip it (statement not to be read yet)
163                     action = false;
164                     toBeSkipped.add(getElementFullName(inputReader));
165                 }
166             }
167
168             if (isType) {
169                 writeTypeStmtAndArg(inputReader, identifier, ref);
170             } else if (action & isStatementWithArgument(identifier, stmtDef)) {
171                 argumentValue(inputReader, ref, identifier, false);
172             } else {
173                 action = true;
174             }
175         }
176     }
177
178     private void exitStatement(XMLStreamReader inputReader) throws URISyntaxException {
179         final String statementName = getElementFullName(inputReader);
180         final QName identifier = new QName(new URI(inputReader.getNamespaceURI()), statementName);
181         final StatementSourceReference ref = DeclarationInTextSource.atPosition(sourceName, inputReader
182                 .getLocation().getLineNumber(), inputReader.getLocation().getColumnNumber());
183         final QName validStatementDefinition = Utils.getValidStatementDefinition(prefixes, stmtDef, identifier);
184
185         if ((stmtDef != null && validStatementDefinition != null && toBeSkipped.isEmpty()) && !yinElement) {
186             endStatement(ref);
187         }
188
189         //back to normal mode
190         if (yinElement) {
191             yinElement = false;
192         }
193
194         if (toBeSkipped.contains(statementName)) {
195             toBeSkipped.remove(statementName);
196         }
197     }
198
199     private void writeTextOnlyElement(XMLStreamReader inputReader, StatementSourceReference ref) {
200         try {
201             writer.argumentValue(inputReader.getElementText(), ref);
202         } catch (XMLStreamException e) {
203             LOG.warn("Current event is not a START_ELEMENT or a non text element is encountered ", ref, e);
204         }
205     }
206
207     private void writeNormalizedAttributeValue(XMLStreamReader inputReader, QName
208             identifier, StatementSourceReference ref) {
209         final String attributeValue = getAttributeValue(inputReader, identifier, stmtDef);
210         if (attributeValue != null)
211             writer.argumentValue(attributeValue, ref);
212     }
213
214     private void writeTypeStmtAndArg(XMLStreamReader inputReader, QName identifier, StatementSourceReference ref) {
215         String argument = getAttributeValue(inputReader, identifier, stmtDef);
216         if (TypeUtils.isYangTypeBodyStmtString(argument)) {
217             startStatement(new QName(YangConstants.RFC6020_YIN_NAMESPACE, argument), ref);
218         } else {
219             startStatement(new QName(YangConstants.RFC6020_YIN_NAMESPACE, Rfc6020Mapping
220                     .TYPE.getStatementName().getLocalName()), ref);
221         }
222         argumentValue(inputReader, ref, identifier, false);
223         isType = false;
224     }
225
226     private static String getElementFullName(XMLStreamReader inputReader) {
227         if (!inputReader.getPrefix().isEmpty()) {
228             return inputReader.getPrefix() + ":" + inputReader.getLocalName();
229         } else {
230             return inputReader.getLocalName();
231         }
232     }
233
234     private static boolean isStatementWithArgument(QName identifier, QNameToStatementDefinition stmtDef) {
235         if (stmtDef != null && stmtDef.get(Utils.trimPrefix(identifier)) == null) {
236             return false;
237         } else if (((StatementSupport) stmtDef.get(Utils.trimPrefix(identifier)))
238                 .getPublicView().getArgumentName() == null) {
239             return false;
240         }
241         return true;
242     }
243
244     private static boolean isStatementWithYinElement(QName identifier, QNameToStatementDefinition stmtDef) {
245         final StatementDefinition statementDefinition = stmtDef.get(Utils.trimPrefix(identifier));
246         if (statementDefinition != null) {
247             return ((Rfc6020Mapping) ((StatementSupport) statementDefinition).getPublicView()).isArgumentYinElement();
248         } else {
249             return false;
250         }
251     }
252
253     private static String getAttributeValue(XMLStreamReader inputReader, QName identifier, QNameToStatementDefinition
254             stmtDef) {
255         String namespace = null;
256         return inputReader.getAttributeValue(namespace, (((StatementSupport) stmtDef.get(Utils.trimPrefix(identifier)))
257                 .getPublicView()).getArgumentName().getLocalName());
258     }
259 }