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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
21 package org.oran.smo.yangtools.parser.model.yangdom;
23 import java.util.ArrayList;
24 import java.util.List;
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;
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
41 * Every YANG statement is represented as DOM element. Every DOM element is comprised of two parts:
42 * the name and the value.
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>
58 * @author Mark Hollmann
60 public class YangDomElement {
62 private final String name;
63 private final String value; // possibly null
64 private String nameValue;
66 private final int lineNumber;
68 private YangDomElement parentElement;
69 private final List<YangDomElement> children = new ArrayList<>();
71 private final YangDomDocumentRoot documentRoot;
73 public YangDomElement(final String name, final String value, final YangDomElement parentElement, final int lineNumber) {
75 * We intern the name. Saves quite a bit of memory.
77 this.name = name.intern();
79 this.parentElement = parentElement;
80 this.lineNumber = lineNumber;
82 if (parentElement != null) {
83 parentElement.children.add(this);
84 this.documentRoot = parentElement.getDocumentRoot();
86 this.documentRoot = (YangDomDocumentRoot) this;
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.
94 public String getName() {
99 * Returns the argument of the statement. The semantics of the returned value depend
100 * on the statement. May return null.
102 public String getValue() {
107 * Returns null if the value is null, otherwise the trimmed value.
109 public String getTrimmedValueOrNull() {
110 return value == null ? null : value.trim();
114 * Returns empty string if the value is null, otherwise the trimmed value.
116 public String getTrimmedValueOrEmpty() {
117 return value == null ? "" : value.trim();
120 public String getNameValue() {
122 if (nameValue == null) {
123 final StringBuilder sb = new StringBuilder();
133 nameValue = sb.toString();
139 public YangDomElement getParentElement() {
140 return parentElement;
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.
147 public List<YangDomElement> getChildren() {
151 public YangDomDocumentRoot getDocumentRoot() {
155 public int getLineNumber() {
159 public YangModel getYangModel() {
160 return getDocumentRoot().getYangModel();
163 public ModulePrefixResolver getPrefixResolver() {
164 return getYangModel().getPrefixResolver();
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.
171 public void reparent(final YangDomElement childElement) {
172 if (childElement.parentElement != null) {
173 childElement.parentElement.children.remove(childElement);
175 this.children.add(childElement);
176 childElement.parentElement = this;
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.
183 public void remove() {
184 if (parentElement == null) {
188 parentElement.children.remove(this);
189 parentElement = null;
193 * Processes the token stream and recursively builds the DOM tree.
195 void processTokens(final ParserExecutionContext context, final TokenIterator iter) {
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
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.
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."));
220 * Wups - iterator exhausted? Thats wrong.
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."));
228 final Token token1 = iter.getToken(0);
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.
234 if (token1.type == TokenType.RIGHT_BRACE) {
239 final Token token2 = iter.getToken(1);
240 final Token token3 = iter.getToken(2);
243 * The following can be the case now (all valid):
245 * A.) <statement> <argument> ;
246 * B.) <statement> <argument> { ... stuff ... }
248 * D.) <statement> { ... stuff ... }
251 * We could also have this here (all invalid):
255 if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.STRING && token3 != null && token3.type == TokenType.SEMI_COLON) {
257 * Case A) Simple statement with argument and finished with ; so no nesting. We consume all
260 * Example: "max-elements 100 ;"
263 new YangDomElement(token1.value, token2.value, this, token1.lineNumber);
265 } else if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.STRING && token3 != null && token3.type == TokenType.LEFT_BRACE) {
267 * Case B) Statement and argument followed by curly opening braces. Only consume two tokens
268 * (not the opening brace). Recurse down the tree.
270 * Example: "leaf my-leaf { type string; }"
273 final YangDomElement newYangDomElement = new YangDomElement(token1.value, token2.value, this,
275 newYangDomElement.processTokens(context, iter);
277 } else if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.SEMI_COLON) {
279 * Case C) Simple statement without argument, no nesting. We consume the two tokens.
281 * Example: "input ;" (lame example, one wouldn't usually see this in a model...)
284 new YangDomElement(token1.value, null, this, token1.lineNumber);
286 } else if (token1.type == TokenType.STRING && token2 != null && token2.type == TokenType.LEFT_BRACE) {
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.
291 * Example: "input { leaf my-leaf { ... }}"
294 final YangDomElement newYangDomElement = new YangDomElement(token1.value, null, this, token1.lineNumber);
295 newYangDomElement.processTokens(context, iter);
297 } else if (token1.type == TokenType.SEMI_COLON) {
299 * Case E) - unnecessary semicolon, swallow it - technically it is invalid syntax,
300 * but we are lenient.
303 context.addFinding(new Finding(getYangModel(), token1.lineNumber,
304 ParserFindingType.P055_SUPERFLUOUS_STATEMENT.toString(), "The extra semicolon is unnecessary."));
306 } else if (token1.type == TokenType.LEFT_BRACE) {
308 * Case F) - unexpected left-brace - should not be here.
311 context.addFinding(new Finding(getYangModel(), token1.lineNumber,
312 ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT.toString(), "Unexpected opening curly brace."));
316 context.addFinding(new Finding(getYangModel(), token1.lineNumber,
317 ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT.toString(),
318 "Unexpected content '" + token1.value + "'."));
324 public String getSimplifiedPath() {
325 final StringBuilder sb = new StringBuilder(200);
327 return sb.toString();
330 private void prependPath(final StringBuilder sb) {
331 if (!name.equals(CY.MODULE) && !name.equals(CY.SUBMODULE) && parentElement != null) {
332 parentElement.prependPath(sb);
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);
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.
358 public boolean equals(Object obj) {
359 return (this == obj);
363 public String toString() {
364 return value == null ? name : name + " " + value;
368 public int hashCode() {
369 return getNameValue().hashCode();