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