75bf88a555c3020d0a1afa08d091efc42595b1d6
[smo/teiv.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2024 Ericsson
4  *  Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21 package org.oran.smo.yangtools.parser.model.yangdom;
22
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.oran.smo.yangtools.parser.ParserExecutionContext;
27 import org.oran.smo.yangtools.parser.findings.Finding;
28 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
29 import org.oran.smo.yangtools.parser.model.ModulePrefixResolver;
30 import org.oran.smo.yangtools.parser.model.YangModel;
31 import org.oran.smo.yangtools.parser.model.parser.Token;
32 import org.oran.smo.yangtools.parser.model.parser.TokenIterator;
33 import org.oran.smo.yangtools.parser.model.parser.Token.TokenType;
34 import org.oran.smo.yangtools.parser.model.statements.yang.CY;
35
36 /**
37  * Represents a single schema node in the schema tree for a single YAM. Conceptually, a Yang DOM
38  * can be compared to an XML DOM; that is, a structured file is broken up into a tree-representation
39  * of the content.
40  * <p/>
41  * Every YANG statement is represented as DOM element. Every DOM element is comprised of two parts:
42  * the name and the value.
43  * <ul>
44  * <li>The name-part is always the name of a YANG statement. Where the statement is part of the core
45  * YANG language (as defined in RFC 7950) the name-part is simply the statement name (for example,
46  * "leaf-list"). Where the statement is <i>usage</i> of an extension (as opposed to the <i>definition</i>
47  * of an extension), the name-part is always a combination of a prefix and the name of the extension
48  * (for example, "md:annotation" (see RFC 7952)).</li>
49  * <li>The value-part depends on the statement; to be more precise, the semantics of the statement.
50  * Most core YANG language statements require an argument, and the value-part will be the argument value.
51  * For example, for a "leaf-list" statement, the value-part would be the name of the leaf-list (so,
52  * for a "leaf-list my-super-leaf-list" the value-part of the DOM element would be "my-super-leaf-list").
53  * However, not all statements support arguments (for example, the "input" statement), and for those
54  * the value-part will be null. Also, not all extensions support arguments, and for those the value-part
55  * will typically be null as well.</li>
56  * </ul>
57  *
58  * @author Mark Hollmann
59  */
60 public class YangDomElement {
61
62     private final String name;
63     private final String value;         // possibly null
64     private String nameValue;
65
66     private final int lineNumber;
67
68     private YangDomElement parentElement;
69     private final List<YangDomElement> children = new ArrayList<>();
70
71     private final YangDomDocumentRoot documentRoot;
72
73     public YangDomElement(final String name, final String value, final YangDomElement parentElement, final int lineNumber) {
74         /*
75          * We intern the name. Saves quite a bit of memory.
76          */
77         this.name = name.intern();
78         this.value = value;
79         this.parentElement = parentElement;
80         this.lineNumber = lineNumber;
81
82         if (parentElement != null) {
83             parentElement.children.add(this);
84             this.documentRoot = parentElement.getDocumentRoot();
85         } else {
86             this.documentRoot = (YangDomDocumentRoot) this;
87         }
88     }
89
90     /**
91      * Returns the name of the statement. This will be the name of a statement part of the
92      * core YANG language, or the name of a prefixed extension.
93      */
94     public String getName() {
95         return name;
96     }
97
98     /**
99      * Returns the argument of the statement. The semantics of the returned value depend
100      * on the statement. May return null.
101      */
102     public String getValue() {
103         return value;
104     }
105
106     /**
107      * Returns null if the value is null, otherwise the trimmed value.
108      */
109     public String getTrimmedValueOrNull() {
110         return value == null ? null : value.trim();
111     }
112
113     /**
114      * Returns empty string if the value is null, otherwise the trimmed value.
115      */
116     public String getTrimmedValueOrEmpty() {
117         return value == null ? "" : value.trim();
118     }
119
120     public String getNameValue() {
121
122         if (nameValue == null) {
123             final StringBuilder sb = new StringBuilder();
124
125             sb.append('\'');
126             sb.append(name);
127             if (value != null) {
128                 sb.append(' ');
129                 sb.append(value);
130             }
131             sb.append('\'');
132
133             nameValue = sb.toString();
134         }
135
136         return nameValue;
137     }
138
139     public YangDomElement getParentElement() {
140         return parentElement;
141     }
142
143     /**
144      * Returns the list of child DOM elements, in the order in which they are in the YAM.
145      * The returned must not be modified.
146      */
147     public List<YangDomElement> getChildren() {
148         return children;
149     }
150
151     public YangDomDocumentRoot getDocumentRoot() {
152         return documentRoot;
153     }
154
155     public int getLineNumber() {
156         return lineNumber;
157     }
158
159     public YangModel getYangModel() {
160         return getDocumentRoot().getYangModel();
161     }
162
163     public ModulePrefixResolver getPrefixResolver() {
164         return getYangModel().getPrefixResolver();
165     }
166
167     /**
168      * The given DOM element is added as child to this element, and removed from its previous parent. This is typically
169      * done when injecting additional statements into the statement tree.
170      */
171     public void reparent(final YangDomElement childElement) {
172         if (childElement.parentElement != null) {
173             childElement.parentElement.children.remove(childElement);
174         }
175         this.children.add(childElement);
176         childElement.parentElement = this;
177     }
178
179     /**
180      * Removes this DOM element from under its parent. This in effect detaches the DOM element, and its complete
181      * sub-tree, from the parent.
182      */
183     public void remove() {
184         if (parentElement == null) {
185             return;
186         }
187
188         parentElement.children.remove(this);
189         parentElement = null;
190     }
191
192     /**
193      * Processes the token stream and recursively builds the DOM tree.
194      */
195     void processTokens(final ParserExecutionContext context, final TokenIterator iter) {
196
197         /*
198          * We are at the beginning of an element, hence there is a left-bracket to start. We don't
199          * need to explicitly check that, other code logic will makes sure that this method is
200          * invoked such that the next token is a left brace. We can safely skip it and advance
201          * the iterator.
202          */
203         iter.advance(1);
204
205         /*
206          * It's possible (though unlikely) that we have encountered a sequence "{}" - which
207          * doesn't really make sense, but we are nice about it and handle it leniently.
208          */
209         if (!iter.done() && iter.getToken(0).type == TokenType.RIGHT_BRACE) {
210             context.addFinding(new Finding(getYangModel(), iter.getToken(0).lineNumber,
211                     ParserFindingType.P055_SUPERFLUOUS_STATEMENT.toString(),
212                     "Encountered '{}', which does nothing. Replace with ';' or un-comment the contents."));
213             iter.advance(1);
214             return;
215         }
216
217         while (true) {
218
219             /*
220              * Wups - iterator exhausted? Thats wrong.
221              */
222             if (iter.done()) {
223                 context.addFinding(new Finding(getYangModel(), ParserFindingType.P014_INVALID_SYNTAX_AT_DOCUMENT_END,
224                         "Unexpected end of document. A closing curly brace is probably missing."));
225                 return;
226             }
227
228             final Token token1 = iter.getToken(0);
229
230             /*
231              * Are we at the end of this element, i.e have processed all statements
232              * underneath (denoted by closing curly brace)? Then exit out here.
233              */
234             if (token1.type == TokenType.RIGHT_BRACE) {
235                 iter.advance(1);
236                 return;
237             }
238
239             final Token token2 = iter.getToken(1);
240             final Token token3 = iter.getToken(2);
241
242             /**
243              * The following can be the case now (all valid):
244              *
245              * A.) <statement> <argument> ;
246              * B.) <statement> <argument> { ... stuff ... }
247              * C.) <statement> ;
248              * D.) <statement> { ... stuff ... }
249              * E.) ;
250              *
251              * We could also have this here (all invalid):
252              * F.) {
253              * G.) <statement> }
254              */
255             if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.STRING && token3 != null && token3.type == TokenType.SEMI_COLON) {
256                 /*
257                  * Case A) Simple statement with argument and finished with ; so no nesting. We consume all
258                  * three tokens.
259                  *
260                  * Example: "max-elements 100 ;"
261                  */
262                 iter.advance(3);
263                 new YangDomElement(token1.value, token2.value, this, token1.lineNumber);
264
265             } else if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.STRING && token3 != null && token3.type == TokenType.LEFT_BRACE) {
266                 /*
267                  * Case B) Statement and argument followed by curly opening braces. Only consume two tokens
268                  * (not the opening brace). Recurse down the tree.
269                  *
270                  * Example: "leaf my-leaf { type string; }"
271                  */
272                 iter.advance(2);
273                 final YangDomElement newYangDomElement = new YangDomElement(token1.value, token2.value, this,
274                         token1.lineNumber);
275                 newYangDomElement.processTokens(context, iter);
276
277             } else if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.SEMI_COLON) {
278                 /*
279                  * Case C) Simple statement without argument, no nesting. We consume the two tokens.
280                  *
281                  * Example: "input ;"        (lame example, one wouldn't usually see this in a model...)
282                  */
283                 iter.advance(2);
284                 new YangDomElement(token1.value, null, this, token1.lineNumber);
285
286             } else if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.LEFT_BRACE) {
287                 /*
288                  * Case D) Statement only, followed by curly opening braces with more content. No argument
289                  * so we only consume first token only (not the opening brace). Recurse down the tree.
290                  *
291                  * Example: "input { leaf my-leaf { ... }}"
292                  */
293                 iter.advance(1);
294                 final YangDomElement newYangDomElement = new YangDomElement(token1.value, null, this, token1.lineNumber);
295                 newYangDomElement.processTokens(context, iter);
296
297             } else if (token1.type == TokenType.SEMI_COLON) {
298                 /*
299                  * Case E) - unnecessary semicolon, swallow it - technically it is invalid syntax,
300                  * but we are lenient.
301                  */
302                 iter.advance(1);
303                 context.addFinding(new Finding(getYangModel(), token1.lineNumber,
304                         ParserFindingType.P055_SUPERFLUOUS_STATEMENT.toString(), "The extra semicolon is unnecessary."));
305
306             } else if (token1.type == TokenType.LEFT_BRACE) {
307                 /*
308                  * Case F) - unexpected left-brace - should not be here.
309                  */
310                 iter.advance(1);
311                 context.addFinding(new Finding(getYangModel(), token1.lineNumber,
312                         ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT.toString(), "Unexpected opening curly brace."));
313
314             } else {
315
316                 context.addFinding(new Finding(getYangModel(), token1.lineNumber,
317                         ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT.toString(),
318                         "Unexpected content '" + token1.value + "'."));
319                 return;
320             }
321         }
322     }
323
324     public String getSimplifiedPath() {
325         final StringBuilder sb = new StringBuilder(200);
326         prependPath(sb);
327         return sb.toString();
328     }
329
330     private void prependPath(final StringBuilder sb) {
331         if (!name.equals(CY.MODULE) && !name.equals(CY.SUBMODULE) && parentElement != null) {
332             parentElement.prependPath(sb);
333         }
334
335         sb.append('/');
336         sb.append(name);
337         if (value != null) {
338
339             sb.append('=');
340
341             final boolean isPath = name.equals(CY.AUGMENT) || name.equals(CY.DEVIATION) || name.equals(CY.USES) || name
342                     .equals(CY.WHEN) || name.equals(CY.MUST);
343             if (isPath) {
344                 sb.append('(');
345                 sb.append(value);
346                 sb.append(')');
347             } else {
348                 sb.append(value);
349             }
350         }
351     }
352
353     /*
354      * This is really not needed as it is the same as in Object - however, created to
355      * enforce that equality check always operates based on objects, and nothing else.
356      */
357     @Override
358     public boolean equals(Object obj) {
359         return (this == obj);
360     }
361
362     @Override
363     public String toString() {
364         return value == null ? name : name + " " + value;
365     }
366
367     @Override
368     public int hashCode() {
369         return getNameValue().hashCode();
370     }
371 }