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.statements;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
29 import java.util.Map.Entry;
32 import org.oran.smo.yangtools.parser.ParserExecutionContext;
33 import org.oran.smo.yangtools.parser.PrefixResolver;
34 import org.oran.smo.yangtools.parser.findings.Finding;
35 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
36 import org.oran.smo.yangtools.parser.model.ConformanceType;
37 import org.oran.smo.yangtools.parser.model.ModuleIdentity;
38 import org.oran.smo.yangtools.parser.model.ModulePrefixResolver;
39 import org.oran.smo.yangtools.parser.model.statements.yang.CY;
40 import org.oran.smo.yangtools.parser.model.statements.yang.YConfig;
41 import org.oran.smo.yangtools.parser.model.statements.yang.YDescription;
42 import org.oran.smo.yangtools.parser.model.statements.yang.YErrorMessage;
43 import org.oran.smo.yangtools.parser.model.statements.yang.YReference;
44 import org.oran.smo.yangtools.parser.model.statements.yang.YStatus;
45 import org.oran.smo.yangtools.parser.model.util.GrammarHelper;
46 import org.oran.smo.yangtools.parser.model.yangdom.YangDomDocumentRoot;
47 import org.oran.smo.yangtools.parser.model.yangdom.YangDomElement;
48 import org.oran.smo.yangtools.parser.util.QNameHelper;
51 * Base class for all statements. Once a YAM has been parsed into a Yang DOM, the statement
52 * tree will be build. Every statement in the model, no matter if core or extension, is
53 * represented by an instance of this class.
55 * @author Mark Hollmann
57 public abstract class AbstractStatement {
60 * The root of the model owning this statement.
62 protected YangModelRoot modelRoot;
65 * The underlying DOM element
67 protected final YangDomElement domElement;
70 * Parent and children of this statement, respectively. Both can change as result of various
71 * operations, such as augment, uses, deviate.
73 protected AbstractStatement parent;
74 protected final List<AbstractStatement> children = new ArrayList<>();
77 * The module and statement name
79 private StatementModuleAndName statementModuleAndName = null;
82 * The namespace in which this statement effectively sits. This is important for statements as
83 * part of groupings/typedef that get pulled-in (the effective namespace will be the namespace
84 * of the module using the grouping/typedef, not the namespace of the module in which the
85 * grouping/typedef is defined).
87 private String effectiveNamespace = null;
90 * The effective status of this statement.
92 private String effectiveStatus = null;
95 * The effective ConformanceType of this statement. Only of significant interest if protocol-
96 * accessible objects of conformance "import-only" are actually retained in the schema (which
97 * is usually undesirable)
99 private ConformanceType effectiveConformanceType = null;
102 * The effective config value of this statement.
104 private boolean effectiveConfig = true;
107 * Findings made in respect of this statement, if any.
109 private Set<Finding> findings = null;
112 * Gives an app using the parser the possibility to attach custom data to this statement.
113 * May be used by tooling.
115 private Map<String, Object> customAppData = null;
118 * Special constructor for root. Only the YangModelRoot constructor will invoke this.
120 protected AbstractStatement(final YangDomDocumentRoot domDocumentRoot) {
121 this.modelRoot = (YangModelRoot) this;
122 this.domElement = domDocumentRoot;
127 public AbstractStatement(final AbstractStatement parentStatement, final YangDomElement domElement) {
128 this.modelRoot = parentStatement.getYangModelRoot();
129 this.domElement = domElement;
131 this.parent = parentStatement;
132 parentStatement.children.add(this);
135 public YangModelRoot getYangModelRoot() {
139 public AbstractStatement getParentStatement() {
144 * Returns the child statements of this statement. Do not modify the returned list.
146 public List<AbstractStatement> getChildStatements() {
150 public boolean isExtension() {
151 return getStatementModuleAndName().isExtensionStatement();
155 * Returns all child statements that are extensions.
157 public List<ExtensionStatement> getExtensionChildStatements() {
159 List<ExtensionStatement> result = null;
161 for (final AbstractStatement child : children) {
162 if (child.isExtension()) {
163 if (result == null) {
164 result = new ArrayList<>(4);
166 result.add((ExtensionStatement) child);
170 return result != null ? result : Collections.emptyList();
174 * Returns all child statements that are not extensions (ie. they are core YANG statements).
176 public List<AbstractStatement> getNonExtensionChildStatements() {
178 if (children.isEmpty()) {
179 return Collections.emptyList();
182 final List<AbstractStatement> result = new ArrayList<>();
183 for (final AbstractStatement child : children) {
184 if (!child.isExtension()) {
192 public YangDomElement getDomElement() {
197 * Returns the prefix resolver for this statement. The prefix resolver is the exact same as that
198 * of the underlying DOM element. Different statements within the same schema tree may use a
199 * different prefix resolver - this is typically then the case where statements are augmented-into/
200 * used-by a module, and the original prefix context must be retained.
202 public ModulePrefixResolver getPrefixResolver() {
203 return domElement.getPrefixResolver();
207 * Returns the name of the statement. If the statement is an extension, only the
208 * name of the extension will be returned, not the prefix.
210 public String getStatementName() {
211 return getStatementModuleAndName().getStatementName();
215 * Returns the statement name and the module defining it.
217 public StatementModuleAndName getStatementModuleAndName() {
220 * All type-safe classes representing YANG core language statements, and some extension classes,
221 * will override this. This logic should only ever fire if a client has manually created a YANG
222 * core statement, or a factory was not available for an extension statement.
224 if (statementModuleAndName == null) {
226 final String statementPrefix = QNameHelper.extractPrefix(domElement.getName());
227 final String statementName = QNameHelper.extractName(domElement.getName());
229 if (PrefixResolver.NO_PREFIX.equals(statementPrefix)) {
230 if (CY.isYangCoreStatementName(statementName)) {
231 statementModuleAndName = CY.getStatementForName(statementName);
234 * Probably a spelling mistake, or possibly the absolute root '/' which is not a real
235 * statement. We still create a SMAN.
237 statementModuleAndName = new StatementModuleAndName(CY.YANG_CORE_MODULE_NAME, statementName);
243 final ModuleIdentity moduleForPrefix = getPrefixResolver().getModuleForPrefix(statementPrefix);
244 if (moduleForPrefix == null) {
246 * Unresolvable prefix, would have been caught elsewhere. We use the prefix as module name
247 * which is wrong, of course, but at least this method here will never return null and
248 * cause a NPE somewhere else...
250 statementModuleAndName = new StatementModuleAndName(statementPrefix, statementName);
255 statementModuleAndName = new StatementModuleAndName(moduleForPrefix.getModuleName(), statementName);
260 return statementModuleAndName;
263 public void addFinding(final Finding finding) {
264 if (findings == null) {
265 findings = new HashSet<>();
267 findings.add(finding);
270 public void removeFinding(final Finding finding) {
271 if (findings != null) {
272 findings.remove(finding);
277 * Returns the findings for this statement. Returns an empty set if no findings found.
279 public Set<Finding> getFindings() {
280 return findings == null ? Collections.<Finding> emptySet() : findings;
284 * Add arbitrary app-specific data to this statement. Typically used by tooling.
286 public void setCustomAppData(final String key) {
287 setCustomAppData(key, null);
291 * Add arbitrary app-specific data to this statement. Typically used by tooling.
293 public void setCustomAppData(final String key, final Object value) {
294 if (customAppData == null) {
295 customAppData = new HashMap<>();
297 customAppData.put(key, value);
301 * Returns the value, if any, for app-specific data. Where the application data value is
302 * null, hasCustomAppData() should be used instead.
304 @SuppressWarnings("unchecked")
305 public <T> T getCustomAppData(final String key) {
306 return customAppData == null ? null : (T) customAppData.get(key);
309 public boolean hasCustomAppData(final String key) {
310 return customAppData == null ? false : customAppData.containsKey(key);
314 * Returns the identifier defined by the statement. Overridden by those statements that
315 * have an identifier.
317 public String getStatementIdentifier() {
322 * Overridden by statements that define "schema nodes". See RFC, chapter 3 (terminology):
324 * "schema node: A node in the schema tree. One of action, container, leaf,
325 * leaf-list, list, choice, case, rpc, input, output, notification, anydata,
328 public boolean definesSchemaNode() {
333 * Overridden by statements that define "data nodes". See RFC, chapter 3 (terminology):
335 * "data node: A node in the schema tree that can be instantiated in a
336 * data tree. One of container, leaf, leaf-list, list, anydata, and
339 public boolean definesDataNode() {
344 * The statement argument types. Values are similar to the ones shown in chapter 13.1, but do not align fully.
346 public enum StatementArgumentType {
347 /** The statement does not have an argument. */
349 /** The argument is a name; for example, the name of a leaf. */
351 /** The argument is a value. */
353 /** The argument is free text. */
355 /** The argument is a condition */
357 /** The argument is a (sub-)module name */
359 /** The argument is a URI */
361 /** The argument is a date. */
363 /** The argument is a target node. */
365 /** The argument is a tag. */
370 * Returns the argument type
372 public abstract StatementArgumentType getArgumentType();
375 * Denotes whether the order of multiple statements of this type under the same parent
376 * matters - i.e., if the order was to be changed in the model, would this result in a
377 * semantic change? For the vast majority of statements this does not matter, but sometimes
378 * it does (think multiple default statements under a leaf-list).
380 public boolean orderUnderParentMatters() {
385 * Returns the effective status of the statement. This value will be calculated once all
386 * groupings, deviations and augmentations have been processed.
388 public String getEffectiveStatus() {
389 return effectiveStatus == null ? YStatus.CURRENT : effectiveStatus;
392 public void setEffectiveStatus(final String effectiveStatus) {
393 this.effectiveStatus = effectiveStatus;
396 public boolean isEffectiveStatusCurrent() {
397 return YStatus.CURRENT.equals(getEffectiveStatus());
400 public boolean isEffectiveStatusDeprecated() {
401 return YStatus.DEPRECATED.equals(getEffectiveStatus());
404 public boolean isEffectiveStatusObsolete() {
405 return YStatus.OBSOLETE.equals(getEffectiveStatus());
408 public boolean isEffectiveConfigTrue() {
409 return effectiveConfig;
412 public boolean is(final StatementModuleAndName statementModuleAndName) {
413 return getStatementModuleAndName().equals(statementModuleAndName);
417 * Returns the 'description' statement, if any, that is a child of this statement.
418 * Use getDescriptionValue() to get the actual value of the description.
420 public YDescription getDescription() {
421 return getChild(CY.STMT_DESCRIPTION);
425 * Returns the value of the 'description' statement, if any, that is a child of this statement.
427 public String getDescriptionValue() {
428 final YDescription yDescription = getDescription();
429 return yDescription == null ? null : yDescription.getValue();
433 * Returns the 'reference' statement, if any, that is a child of this statement.
435 public YReference getReference() {
436 return getChild(CY.STMT_REFERENCE);
440 * Returns the value of the 'reference' statement, if any, that is a child of this statement.
442 public String getReferenceValue() {
443 final YReference yReference = getReference();
444 return yReference == null ? null : yReference.getValue();
448 * Returns the error message text of this statement, or the value of a 'error-message'
449 * child statement. Otherwise returns null.
451 public String getErrorMessageText() {
452 if (is(CY.STMT_ERROR_MESSAGE)) {
453 return domElement.getValue();
456 final YErrorMessage child = getChild(CY.STMT_ERROR_MESSAGE);
457 return child != null ? child.getValue() : null;
461 * Returns the statement names that are allowed as children of this statement according to the
462 * RFC. Extension classes should override this method.
464 public List<String> getStatementsAllowedAsChild() {
465 final List<String> allowedStatements = new ArrayList<>(20);
466 allowedStatements.addAll(getMandatorySingleChildStatementNames());
467 allowedStatements.addAll(getMandatoryMultipleChildStatementNames());
468 allowedStatements.addAll(getOptionalSingleChildStatementNames());
469 allowedStatements.addAll(getOptionalMultipleChildStatementNames());
470 return allowedStatements;
473 public final List<String> getOptionalSingleChildStatementNames() {
474 return CY.getOptionalSingleChildren(this.domElement.getName());
477 public final List<String> getOptionalMultipleChildStatementNames() {
478 return CY.getOptionalMultipleChildren(this.domElement.getName());
481 public final List<String> getMandatorySingleChildStatementNames() {
482 return CY.getMandatorySingleChildren(this.domElement.getName());
485 public final List<String> getMandatoryMultipleChildStatementNames() {
486 return CY.getMandatoryMultipleChildren(this.domElement.getName());
490 * Processes this statement by recursively creating child statements.
492 protected void process(final ParserExecutionContext context) {
495 * The normal case is that this statement is a build-in YANG statement.
496 * However, it could very well also be an extension statement.
501 * If it is an extension, then really any statement can be below it. There is no RFC-defined way
502 * how this can be expressed in YANG, so really we must be able to handle everything. Hence,
503 * extract whatever is underneath the extension - if it makes sense or not is a different issue,
504 * and a problem of the extension.
506 extractAllChildStatementsFromDomExceptExtensions(context);
507 extractChildExtensions(context);
513 * It is a build-in YANG statement. We extract all possible (optional, mandatory) child statements.
515 final List<String> mandatoryChildSingletonStatementNames = getMandatorySingleChildStatementNames();
516 final List<String> mandatoryChildMultipleStatementNames = getMandatoryMultipleChildStatementNames();
517 final List<String> optionalChildSingletonStatementNames = getOptionalSingleChildStatementNames();
518 final List<String> optionalChildMultipleStatementNames = getOptionalMultipleChildStatementNames();
520 extractMandatorySingletonChildStatementsFromDom(context, mandatoryChildSingletonStatementNames);
521 extractMandatoryMultipleChildStatementsFromDom(context, mandatoryChildMultipleStatementNames);
522 extractOptionalSingletonChildStatementsFromDom(context, optionalChildSingletonStatementNames);
523 extractOptionalMultipleChildStatementsFromDom(context, optionalChildMultipleStatementNames);
525 extractChildExtensions(context);
530 * Check at this point that there are not any statements here that are not expected according to RFC.
532 for (final YangDomElement childDomElement : domElement.getChildren()) {
533 final String childName = childDomElement.getName();
535 if (optionalChildSingletonStatementNames.contains(childName) || optionalChildMultipleStatementNames
536 .contains(childName) || mandatoryChildSingletonStatementNames.contains(
537 childName) || mandatoryChildMultipleStatementNames.contains(childName)) {
539 * All ok, it is a statement that we are expecting and according to the RFC is fine to be here.
541 } else if (childName.contains(":")) {
543 * That's ok as well, it is an extension statement, ignore.
547 * Some other in-build YANG statement that should not be a child of this statement.
549 if (CY.isYangCoreStatementName(childName)) {
550 context.addFinding(new Finding(childDomElement, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT
551 .toString(), "Statement '" + childName + "' is not allowed under '" + domElement
554 context.addFinding(new Finding(childDomElement, ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT
555 .toString(), "'" + childName + "' is not part of the core YANG language."));
562 * Indicate that processing of this statement, and all sub-statements, is finished.
564 subtreeProcessed(context);
568 * Performs statement-specific validation. Statements sometimes override.
570 protected void validate(final ParserExecutionContext context) {
572 * The vast majority of statements require some kind of
573 * argument, so check that. For the few statements that
574 * don't require this, the method will be overridden.
576 validateArgumentNotNullNotEmpty(context);
580 * May be overriden by subclasses if they wish to perform any further processing after the
581 * statement and all its children have been fully processed.
583 protected void subtreeProcessed(final ParserExecutionContext context) {
587 * Extracts from the DOM tree all statements no matter what they are, except extensions
589 private void extractAllChildStatementsFromDomExceptExtensions(final ParserExecutionContext context) {
590 for (final YangDomElement childDomElement : domElement.getChildren()) {
591 if (!childDomElement.getName().contains(":")) {
592 final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context, childDomElement,
594 childStatement.process(context);
600 * Extracts from the DOM all child nodes of the specified statement names. Each of these
601 * may be present 0..1, i.e. once-only.
603 private void extractOptionalSingletonChildStatementsFromDom(final ParserExecutionContext context,
604 final List<String> optionalSingletonChildStatementNames) {
606 if (optionalSingletonChildStatementNames.isEmpty()) {
610 final Set<String> namesOfStatementsAlreadyExtracted = new HashSet<>();
612 for (final YangDomElement childDomElement : domElement.getChildren()) {
613 final String domElementName = childDomElement.getName();
615 if (optionalSingletonChildStatementNames.contains(domElementName)) {
617 if (!namesOfStatementsAlreadyExtracted.contains(domElementName)) {
618 namesOfStatementsAlreadyExtracted.add(domElementName);
619 final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context,
620 childDomElement, this);
621 childStatement.process(context);
624 * The same statement exists more than once, but is only allowed once. We generate the
625 * finding on the parent.
627 context.addFinding(new Finding(domElement, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT.toString(),
628 "Child statement '" + domElementName + "' cannot exist more than once under '" + domElement
636 * Extracts from the DOM all child nodes of the specified statement names. Each of these may be
637 * present 0..n, i.e. multiple times (or not at all).
639 private void extractOptionalMultipleChildStatementsFromDom(final ParserExecutionContext context,
640 final List<String> optionalMultipleChildStatementNames) {
642 if (optionalMultipleChildStatementNames.isEmpty()) {
646 for (final YangDomElement childDomElement : domElement.getChildren()) {
647 final String domElementNode = childDomElement.getName();
649 if (optionalMultipleChildStatementNames.contains(domElementNode)) {
650 final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context, childDomElement,
652 childStatement.process(context);
658 * Extracts from the DOM all child nodes of the specified statement names. Each of these must be
659 * present 1..1, i.e. exactly once.
661 private void extractMandatorySingletonChildStatementsFromDom(final ParserExecutionContext context,
662 final List<String> mandatorySingletonChildStatementNames) {
664 if (mandatorySingletonChildStatementNames.isEmpty()) {
668 final Set<String> namesOfStatementsYetToExtract = new HashSet<>(mandatorySingletonChildStatementNames);
670 for (final YangDomElement childDomElement : domElement.getChildren()) {
671 final String domElementName = childDomElement.getName();
673 if (mandatorySingletonChildStatementNames.contains(domElementName)) {
675 if (namesOfStatementsYetToExtract.contains(domElementName)) {
676 namesOfStatementsYetToExtract.remove(domElementName);
677 final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context,
678 childDomElement, this);
679 childStatement.process(context);
682 * The same statement exists more than once, but is only allowed once. We generate the
683 * finding on the parent.
685 context.addFinding(new Finding(domElement, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT.toString(),
686 "Child statement '" + domElementName + "' cannot exist more than once under '" + domElement
692 if (!namesOfStatementsYetToExtract.isEmpty()) {
693 for (final String notSuppliedStatement : namesOfStatementsYetToExtract) {
694 context.addFinding(new Finding(this, ParserFindingType.P019_MISSING_REQUIRED_CHILD_STATEMENT.toString(),
695 "Statement '" + notSuppliedStatement + "' required under '" + domElement.getName() + "'."));
701 * Extracts from the DOM all child nodes of the specified statement names. Each of these must be
702 * present 1..n, i.e. at least once, but possibly more often.
704 private void extractMandatoryMultipleChildStatementsFromDom(final ParserExecutionContext context,
705 final List<String> mandatoryMultipleChildStatementNames) {
707 if (mandatoryMultipleChildStatementNames.isEmpty()) {
711 final Set<String> namesOfStatementsYetToExtract = new HashSet<>(mandatoryMultipleChildStatementNames);
713 for (final YangDomElement childDomElement : domElement.getChildren()) {
714 final String domElementNode = childDomElement.getName();
716 if (mandatoryMultipleChildStatementNames.contains(domElementNode)) {
717 namesOfStatementsYetToExtract.remove(domElementNode);
718 final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context, childDomElement,
720 childStatement.process(context);
724 if (!namesOfStatementsYetToExtract.isEmpty()) {
725 for (final String notSuppliedStatement : namesOfStatementsYetToExtract) {
726 context.addFinding(new Finding(this, ParserFindingType.P019_MISSING_REQUIRED_CHILD_STATEMENT,
727 "Statement '" + notSuppliedStatement + "' required at least once under '" + domElement
734 * Extract any extension statement.
736 private void extractChildExtensions(final ParserExecutionContext context) {
738 for (final YangDomElement childDomElement : domElement.getChildren()) {
739 if (childDomElement.getName().contains(":")) {
740 final AbstractStatement extensionStatement = StatementFactory.createYangExtensionStatement(context,
741 childDomElement, this);
742 if (extensionStatement != null) {
743 extensionStatement.process(context);
750 * Assigns the supplied namespace to all statements in this sub-tree. A namespace is really
751 * only relevant for data nodes, but w simply apply the namespace to all statements to make
754 public void assignEffectiveNamespaceToStatementTree(final String namespace) {
755 effectiveNamespace = namespace;
756 for (final AbstractStatement child : children) {
757 child.assignEffectiveNamespaceToStatementTree(namespace);
762 * Returns the effective namespace of the statement. Statements (usually data nodes) within a
763 * type-safe statement tree may have different namespaces due to augmentations having been
766 public final String getEffectiveNamespace() {
767 return effectiveNamespace;
771 * Assigns the supplied ConformanceType to all statements in this sub-tree. Again, really
772 * only of interest of data nodes, but we simply assign it to all statements.
774 public void assignEffectiveConformanceTypeToStatementTree(final ConformanceType conformanceType) {
775 effectiveConformanceType = conformanceType;
776 for (final AbstractStatement child : children) {
777 child.assignEffectiveConformanceTypeToStatementTree(conformanceType);
782 * Returns the effective conformance type of the statement. Statements (usually data nodes)
783 * within a type-safe statement tree may have different conformance due to augmentations
784 * having been merged in.
786 public ConformanceType getEffectiveConformanceType() {
787 return effectiveConformanceType;
791 * Assigns the config value of the parent to this statement, unless the statement has
792 * a 'config' statement itself as child, in which case that will be used from now on.
794 public void assignEffectiveConfigToStatementTree(final boolean configValueOfParent) {
797 * Config value may switch.
799 boolean configValueToUse = configValueOfParent;
801 final YConfig yConfig = getChild(CY.STMT_CONFIG);
802 if (yConfig != null && configValueToUse == true) {
804 * This statement has a config statement. So use this value from now on for this statement
805 * and all statements below. Note that switching back from 'config false' to 'config true' is
806 * not allowed, and we will effectively ignore that.
808 configValueToUse = yConfig.isConfigTrue();
812 * Certain statements can never carry configuration data. For those statements,
813 * the config value will always be forced to false, no matter if they have an
814 * explicit 'config' underneath.
816 if (is(CY.STMT_ACTION) || is(CY.STMT_RPC) || is(CY.STMT_NOTIFICATION)) {
817 configValueToUse = false;
820 effectiveConfig = configValueToUse;
822 for (final AbstractStatement child : children) {
823 child.assignEffectiveConfigToStatementTree(configValueToUse);
828 * Returns whether this statement has at least one child instance of the specified statement.
830 public boolean hasAtLeastOneChildOf(final StatementModuleAndName statementModuleAndName) {
831 for (final AbstractStatement child : children) {
832 if (child.getStatementModuleAndName().equals(statementModuleAndName)) {
840 * Returns all children of this statement of the specified module and statement name.
842 @SuppressWarnings("unchecked")
843 public <T extends AbstractStatement> List<T> getChildren(final StatementModuleAndName soughtSman) {
844 final List<T> result = new ArrayList<>();
845 for (final AbstractStatement child : children) {
846 if (child.getStatementModuleAndName().equals(soughtSman)) {
847 result.add((T) child);
854 * Returns all children of the specified statement module/names.
856 @SuppressWarnings("unchecked")
857 public <T extends AbstractStatement> List<T> getChildren(
858 final Set<StatementModuleAndName> soughtStatementModulesAndNames) {
859 final List<T> result = new ArrayList<>();
860 for (final AbstractStatement child : children) {
861 if (soughtStatementModulesAndNames.contains(child.getStatementModuleAndName())) {
862 result.add((T) child);
869 * Returns the first child of this statement of the specified module and statement name, or null if not found.
871 @SuppressWarnings("unchecked")
872 public <T extends AbstractStatement> T getChild(final StatementModuleAndName soughtStatementModuleAndName) {
873 for (final AbstractStatement child : children) {
874 if (child.getStatementModuleAndName().equals(soughtStatementModuleAndName)) {
882 * Clones the children of this statement. Will result in a recursive invocation, i.e.
883 * clones the complete subtree. The cloning keeps the original prefix resolvers in place.
884 * Note that findings on the statements will not be cloned.
886 public void cloneFrom(final AbstractStatement orig) {
888 copyPropertiesFrom(orig);
891 * Clone the children. This will also clone extensions.
893 for (final AbstractStatement childToClone : orig.children) {
894 final AbstractStatement clonedStatement = StatementFactory.cloneYangStatement(childToClone, this);
895 clonedStatement.cloneFrom(childToClone);
900 * Copies the statement properties from the supplied statement into this statement.
902 public void copyPropertiesFrom(final AbstractStatement orig) {
905 * Clone-over the contents of any app data.
907 if (orig.customAppData != null) {
908 for (final Entry<String, Object> origEntry : orig.customAppData.entrySet()) {
909 if (origEntry.getValue() instanceof List) {
910 this.setCustomAppData(origEntry.getKey(), new ArrayList<>((List<?>) origEntry.getValue())); // deep copy
912 this.setCustomAppData(origEntry.getKey(), origEntry.getValue());
917 this.effectiveConformanceType = orig.effectiveConformanceType;
918 this.effectiveConfig = orig.effectiveConfig;
919 this.effectiveNamespace = orig.effectiveNamespace;
920 this.effectiveStatus = orig.effectiveStatus;
924 * All existing children of the same type of statement are removed, and replaced by the supplied statement.
926 public <T extends AbstractStatement> void replaceChildrenWith(final T replaceWith) {
927 removeAllChildrenOfStatement(replaceWith.getStatementModuleAndName());
928 addChild(replaceWith);
932 * All existing children of the same statement are removed, and replaced by the supplied statements.
934 public <T extends AbstractStatement> void replaceChildrenWith(final List<T> replaceWith) {
935 if (!replaceWith.isEmpty()) {
936 removeAllChildrenOfStatement(replaceWith.get(0).getStatementModuleAndName());
938 addChildren(replaceWith);
942 * All existing children of the supplied type of statement are removed
944 @SuppressWarnings("unchecked")
945 private <T extends AbstractStatement> void removeAllChildrenOfStatement(
946 final StatementModuleAndName statementModuleAndName) {
947 for (final T child : (List<T>) getChildren(statementModuleAndName)) {
953 * Replace one specific child statement with another.
955 public <T extends AbstractStatement> void replaceChild(final T toBeReplaced, final T replaceWith) {
956 removeChild(toBeReplaced);
957 addChild(replaceWith);
961 * Replace one specific child statement with another at the exact same place in the statement tree.
963 public <T extends AbstractStatement> void replaceChildInPlace(final T toBeReplaced, final T replaceWith) {
964 final int oldPos = removeChild(toBeReplaced);
965 addChild(replaceWith, oldPos);
969 * Removes a number of statements as children from this statement.
971 public <T extends AbstractStatement> void removeChildren(final List<T> statementsToRemove) {
972 for (final T statementToRemove : statementsToRemove) {
973 removeChild(statementToRemove);
978 * Removes a specific child statement. Returns the index position at which the child was placed in the statement tree.
980 public <T extends AbstractStatement> int removeChild(final T statementToRemove) {
982 final int indexOf = this.children.indexOf(statementToRemove);
985 * The child is unlinked from this object here. The child becomes in effect detached from the tree and is
986 * not-reachable anymore. (It should also be GC'ed fairly soon.)
988 statementToRemove.parent = null;
989 this.children.remove(statementToRemove);
995 * Add a number of statements as children to this statement. If the statements are part of the
996 * statement tree they will be detached from their parent first (i.e. will be effectively
999 public <T extends AbstractStatement> void addChildren(final List<T> statementsToAdd) {
1000 for (final T statementToAdd : statementsToAdd) {
1001 addChild(statementToAdd);
1006 * Add a statement as child to this statement. If the statement is part of the statement tree
1007 * it will be detached from its parent first (i.e. will be effectively re-parented).
1009 public <T extends AbstractStatement> void addChild(final T statementToAdd) {
1010 addChild(statementToAdd, this.children.size());
1014 * Add a statement as child to this statement at the specified position in the statement tree.
1015 * If the statement is part of the statement tree it will be detached from its parent first
1016 * (i.e. will be effectively re-parented).
1018 public <T extends AbstractStatement> void addChild(final T statementToAdd, final int pos) {
1020 * Unlink the statement that is being added from it's current parent and hook it under "this" here.
1021 * The YangModelRoot must be likewise updated, as ownership in the tree has changed. Note: The
1022 * prefix resolver is NOT modified, as the prefixes may be different between the original definition
1023 * and this tree here.
1025 if (statementToAdd.parent != null) {
1026 statementToAdd.parent.children.remove(statementToAdd);
1028 this.children.add(pos, statementToAdd);
1029 statementToAdd.parent = this;
1030 statementToAdd.modelRoot = this.modelRoot;
1034 * A result of TRUE means validation succeeded (all ok).
1036 protected boolean validateArgumentIsTrueOrFalse(final ParserExecutionContext context) {
1037 final boolean validateNotNull = validateNotNull(context, domElement.getValue(),
1038 ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT,
1039 "statement '" + getStatementName() + "' requires 'true' or 'false' as argument.");
1040 final boolean validateIsTrueOrFalse = validateIsTrueOrFalse(context, domElement.getValue(),
1041 ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT,
1042 "statement '" + getStatementName() + "' requires 'true' or 'false' as argument.");
1043 return (validateNotNull && validateIsTrueOrFalse);
1047 * A result of TRUE means validation succeeded (all ok).
1049 protected boolean validateArgumentNotNullNotEmpty(final ParserExecutionContext context) {
1050 final boolean validateNotNull = validateNotNull(context, domElement.getValue(),
1051 ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT,
1052 "statement '" + getStatementName() + "' requires an argument.");
1053 final boolean validateNotEmpty = validateNotEmpty(context, domElement.getValue(),
1054 ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT,
1055 "The argument for statement '" + getStatementName() + "' cannot be empty.");
1056 return (validateNotNull && validateNotEmpty);
1060 * A result of TRUE means validation succeeded (all ok).
1062 protected boolean validateArgumentNotNull(final ParserExecutionContext context) {
1063 return validateNotNull(context, domElement.getValue(), ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT,
1064 "statement '" + getStatementName() + "' requires an argument.");
1068 * A result of TRUE means validation succeeded (all ok).
1070 protected boolean validateArgumentIsNull(final ParserExecutionContext context) {
1071 return validateIsNull(context, domElement.getValue(), ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT,
1072 "statement '" + getStatementName() + "' does not take an argument.");
1076 * A result of TRUE means validation succeeded (all ok).
1078 protected boolean validateDocumentationArgumentNotEmpty(final ParserExecutionContext context) {
1079 return validateNotEmpty(context, domElement.getValue(), ParserFindingType.P101_EMPTY_DOCUMENTATION_VALUE,
1080 "statement '" + getStatementName() + "' requires some text as argument.");
1083 private boolean validateNotNull(final ParserExecutionContext context, final String valueToCheck,
1084 final ParserFindingType findingType, final String message) {
1085 if (valueToCheck == null) {
1086 issueFinding(context, findingType, message);
1092 private boolean validateIsNull(final ParserExecutionContext context, final String valueToCheck,
1093 final ParserFindingType findingType, final String message) {
1094 if (valueToCheck != null) {
1095 issueFinding(context, findingType, message);
1101 private boolean validateNotEmpty(final ParserExecutionContext context, final String valueToCheck,
1102 final ParserFindingType findingType, final String message) {
1103 if (valueToCheck != null && valueToCheck.trim().isEmpty()) {
1104 issueFinding(context, findingType, message);
1110 private boolean validateIsTrueOrFalse(final ParserExecutionContext context, final String valueToCheck,
1111 final ParserFindingType findingType, final String message) {
1112 if (valueToCheck != null && !valueToCheck.equals("true") && !valueToCheck.equals("false")) {
1113 issueFinding(context, findingType, message);
1119 protected void validateIsYangIdentifier(final ParserExecutionContext context, final String valueToCheck) {
1121 if (valueToCheck == null || valueToCheck.isEmpty()) {
1125 final boolean isYangIdentifier = GrammarHelper.isYangIdentifier(valueToCheck);
1126 if (!isYangIdentifier) {
1127 issueFinding(context, ParserFindingType.P052_INVALID_YANG_IDENTIFIER,
1128 "'" + valueToCheck + "' is not a valid YANG identifier.");
1132 protected void validateIsYangIdentifierReference(final ParserExecutionContext context, final String valueToCheck) {
1134 if (valueToCheck == null || valueToCheck.isEmpty()) {
1138 final boolean isYangIdentifierReference = GrammarHelper.isYangIdentifierReference(valueToCheck);
1139 if (!isYangIdentifierReference) {
1140 issueFinding(context, ParserFindingType.P052_INVALID_YANG_IDENTIFIER,
1141 "'" + valueToCheck + "' is not a valid YANG identifier-reference.");
1145 private void issueFinding(final ParserExecutionContext context, final ParserFindingType findingType,
1146 final String message) {
1147 context.addFinding(new Finding(this, findingType, message));
1151 public String toString() {
1152 return domElement.toString();