b0763b6d5c66461c260664ca8e771f9fbe4f6760
[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.statements;
22
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;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31
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;
49
50 /**
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.
54  *
55  * @author Mark Hollmann
56  */
57 public abstract class AbstractStatement {
58
59     /**
60      * The root of the model owning this statement.
61      */
62     protected YangModelRoot modelRoot;
63
64     /**
65      * The underlying DOM element
66      */
67     protected final YangDomElement domElement;
68
69     /**
70      * Parent and children of this statement, respectively. Both can change as result of various
71      * operations, such as augment, uses, deviate.
72      */
73     protected AbstractStatement parent;
74     protected final List<AbstractStatement> children = new ArrayList<>();
75
76     /**
77      * The module and statement name
78      */
79     private StatementModuleAndName statementModuleAndName = null;
80
81     /**
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).
86      */
87     private String effectiveNamespace = null;
88
89     /**
90      * The effective status of this statement.
91      */
92     private String effectiveStatus = null;
93
94     /**
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)
98      */
99     private ConformanceType effectiveConformanceType = null;
100
101     /**
102      * The effective config value of this statement.
103      */
104     private boolean effectiveConfig = true;
105
106     /**
107      * Findings made in respect of this statement, if any.
108      */
109     private Set<Finding> findings = null;
110
111     /**
112      * Gives an app using the parser the possibility to attach custom data to this statement.
113      * May be used by tooling.
114      */
115     private Map<String, Object> customAppData = null;
116
117     /**
118      * Special constructor for root. Only the YangModelRoot constructor will invoke this.
119      */
120     protected AbstractStatement(final YangDomDocumentRoot domDocumentRoot) {
121         this.modelRoot = (YangModelRoot) this;
122         this.domElement = domDocumentRoot;
123
124         this.parent = null;
125     }
126
127     public AbstractStatement(final AbstractStatement parentStatement, final YangDomElement domElement) {
128         this.modelRoot = parentStatement.getYangModelRoot();
129         this.domElement = domElement;
130
131         this.parent = parentStatement;
132         parentStatement.children.add(this);
133     }
134
135     public YangModelRoot getYangModelRoot() {
136         return modelRoot;
137     }
138
139     public AbstractStatement getParentStatement() {
140         return parent;
141     }
142
143     /**
144      * Returns the child statements of this statement. Do not modify the returned list.
145      */
146     public List<AbstractStatement> getChildStatements() {
147         return children;
148     }
149
150     public boolean isExtension() {
151         return getStatementModuleAndName().isExtensionStatement();
152     }
153
154     /**
155      * Returns all child statements that are extensions.
156      */
157     public List<ExtensionStatement> getExtensionChildStatements() {
158
159         List<ExtensionStatement> result = null;
160
161         for (final AbstractStatement child : children) {
162             if (child.isExtension()) {
163                 if (result == null) {
164                     result = new ArrayList<>(4);
165                 }
166                 result.add((ExtensionStatement) child);
167             }
168         }
169
170         return result != null ? result : Collections.emptyList();
171     }
172
173     /**
174      * Returns all child statements that are not extensions (ie. they are core YANG statements).
175      */
176     public List<AbstractStatement> getNonExtensionChildStatements() {
177
178         if (children.isEmpty()) {
179             return Collections.emptyList();
180         }
181
182         final List<AbstractStatement> result = new ArrayList<>();
183         for (final AbstractStatement child : children) {
184             if (!child.isExtension()) {
185                 result.add(child);
186             }
187         }
188
189         return result;
190     }
191
192     public YangDomElement getDomElement() {
193         return domElement;
194     }
195
196     /**
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.
201      */
202     public ModulePrefixResolver getPrefixResolver() {
203         return domElement.getPrefixResolver();
204     }
205
206     /**
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.
209      */
210     public String getStatementName() {
211         return getStatementModuleAndName().getStatementName();
212     }
213
214     /**
215      * Returns the statement name and the module defining it.
216      */
217     public StatementModuleAndName getStatementModuleAndName() {
218
219         /*
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.
223          */
224         if (statementModuleAndName == null) {
225
226             final String statementPrefix = QNameHelper.extractPrefix(domElement.getName());
227             final String statementName = QNameHelper.extractName(domElement.getName());
228
229             if (PrefixResolver.NO_PREFIX.equals(statementPrefix)) {
230                 if (CY.isYangCoreStatementName(statementName)) {
231                     statementModuleAndName = CY.getStatementForName(statementName);
232                 } else {
233                     /*
234                      * Probably a spelling mistake, or possibly the absolute root '/' which is not a real
235                      * statement. We still create a SMAN.
236                      */
237                     statementModuleAndName = new StatementModuleAndName(CY.YANG_CORE_MODULE_NAME, statementName);
238                 }
239             } else {
240                 /*
241                  * Extension
242                  */
243                 final ModuleIdentity moduleForPrefix = getPrefixResolver().getModuleForPrefix(statementPrefix);
244                 if (moduleForPrefix == null) {
245                     /*
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...
249                      */
250                     statementModuleAndName = new StatementModuleAndName(statementPrefix, statementName);
251                 } else {
252                     /*
253                      * All is well.
254                      */
255                     statementModuleAndName = new StatementModuleAndName(moduleForPrefix.getModuleName(), statementName);
256                 }
257             }
258         }
259
260         return statementModuleAndName;
261     }
262
263     public void addFinding(final Finding finding) {
264         if (findings == null) {
265             findings = new HashSet<>();
266         }
267         findings.add(finding);
268     }
269
270     public void removeFinding(final Finding finding) {
271         if (findings != null) {
272             findings.remove(finding);
273         }
274     }
275
276     /**
277      * Returns the findings for this statement. Returns an empty set if no findings found.
278      */
279     public Set<Finding> getFindings() {
280         return findings == null ? Collections.<Finding> emptySet() : findings;
281     }
282
283     /**
284      * Add arbitrary app-specific data to this statement. Typically used by tooling.
285      */
286     public void setCustomAppData(final String key) {
287         setCustomAppData(key, null);
288     }
289
290     /**
291      * Add arbitrary app-specific data to this statement. Typically used by tooling.
292      */
293     public void setCustomAppData(final String key, final Object value) {
294         if (customAppData == null) {
295             customAppData = new HashMap<>();
296         }
297         customAppData.put(key, value);
298     }
299
300     /**
301      * Returns the value, if any, for app-specific data. Where the application data value is
302      * null, hasCustomAppData() should be used instead.
303      */
304     @SuppressWarnings("unchecked")
305     public <T> T getCustomAppData(final String key) {
306         return customAppData == null ? null : (T) customAppData.get(key);
307     }
308
309     public boolean hasCustomAppData(final String key) {
310         return customAppData == null ? false : customAppData.containsKey(key);
311     }
312
313     /**
314      * Returns the identifier defined by the statement. Overridden by those statements that
315      * have an identifier.
316      */
317     public String getStatementIdentifier() {
318         return "";
319     }
320
321     /**
322      * Overridden by statements that define "schema nodes". See RFC, chapter 3 (terminology):
323      *
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,
326      * and anyxml."
327      */
328     public boolean definesSchemaNode() {
329         return false;
330     }
331
332     /**
333      * Overridden by statements that define "data nodes". See RFC, chapter 3 (terminology):
334      *
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
337      * anyxml."
338      */
339     public boolean definesDataNode() {
340         return false;
341     }
342
343     /**
344      * The statement argument types. Values are similar to the ones shown in chapter 13.1, but do not align fully.
345      */
346     public enum StatementArgumentType {
347         /** The statement does not have an argument. */
348         NO_ARG,
349         /** The argument is a name; for example, the name of a leaf. */
350         NAME,
351         /** The argument is a value. */
352         VALUE,
353         /** The argument is free text. */
354         TEXT,
355         /** The argument is a condition */
356         CONDITION,
357         /** The argument is a (sub-)module name */
358         MODULE,
359         /** The argument is a URI */
360         URI,
361         /** The argument is a date. */
362         DATE,
363         /** The argument is a target node. */
364         TARGET_NODE,
365         /** The argument is a tag. */
366         TAG
367     }
368
369     /**
370      * Returns the argument type
371      */
372     public abstract StatementArgumentType getArgumentType();
373
374     /**
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).
379      */
380     public boolean orderUnderParentMatters() {
381         return false;
382     }
383
384     /**
385      * Returns the effective status of the statement. This value will be calculated once all
386      * groupings, deviations and augmentations have been processed.
387      */
388     public String getEffectiveStatus() {
389         return effectiveStatus == null ? YStatus.CURRENT : effectiveStatus;
390     }
391
392     public void setEffectiveStatus(final String effectiveStatus) {
393         this.effectiveStatus = effectiveStatus;
394     }
395
396     public boolean isEffectiveStatusCurrent() {
397         return YStatus.CURRENT.equals(getEffectiveStatus());
398     }
399
400     public boolean isEffectiveStatusDeprecated() {
401         return YStatus.DEPRECATED.equals(getEffectiveStatus());
402     }
403
404     public boolean isEffectiveStatusObsolete() {
405         return YStatus.OBSOLETE.equals(getEffectiveStatus());
406     }
407
408     public boolean isEffectiveConfigTrue() {
409         return effectiveConfig;
410     }
411
412     public boolean is(final StatementModuleAndName statementModuleAndName) {
413         return getStatementModuleAndName().equals(statementModuleAndName);
414     }
415
416     /**
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.
419      */
420     public YDescription getDescription() {
421         return getChild(CY.STMT_DESCRIPTION);
422     }
423
424     /**
425      * Returns the value of the 'description' statement, if any, that is a child of this statement.
426      */
427     public String getDescriptionValue() {
428         final YDescription yDescription = getDescription();
429         return yDescription == null ? null : yDescription.getValue();
430     }
431
432     /**
433      * Returns the 'reference' statement, if any, that is a child of this statement.
434      */
435     public YReference getReference() {
436         return getChild(CY.STMT_REFERENCE);
437     }
438
439     /**
440      * Returns the value of the 'reference' statement, if any, that is a child of this statement.
441      */
442     public String getReferenceValue() {
443         final YReference yReference = getReference();
444         return yReference == null ? null : yReference.getValue();
445     }
446
447     /**
448      * Returns the error message text of this statement, or the value of a 'error-message'
449      * child statement. Otherwise returns null.
450      */
451     public String getErrorMessageText() {
452         if (is(CY.STMT_ERROR_MESSAGE)) {
453             return domElement.getValue();
454         }
455
456         final YErrorMessage child = getChild(CY.STMT_ERROR_MESSAGE);
457         return child != null ? child.getValue() : null;
458     }
459
460     /**
461      * Returns the statement names that are allowed as children of this statement according to the
462      * RFC. Extension classes should override this method.
463      */
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;
471     }
472
473     public final List<String> getOptionalSingleChildStatementNames() {
474         return CY.getOptionalSingleChildren(this.domElement.getName());
475     }
476
477     public final List<String> getOptionalMultipleChildStatementNames() {
478         return CY.getOptionalMultipleChildren(this.domElement.getName());
479     }
480
481     public final List<String> getMandatorySingleChildStatementNames() {
482         return CY.getMandatorySingleChildren(this.domElement.getName());
483     }
484
485     public final List<String> getMandatoryMultipleChildStatementNames() {
486         return CY.getMandatoryMultipleChildren(this.domElement.getName());
487     }
488
489     /**
490      * Processes this statement by recursively creating child statements.
491      */
492     protected void process(final ParserExecutionContext context) {
493
494         /*
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.
497          */
498
499         if (isExtension()) {
500             /*
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.
505              */
506             extractAllChildStatementsFromDomExceptExtensions(context);
507             extractChildExtensions(context);
508
509             validate(context);
510
511         } else {
512             /*
513              * It is a build-in YANG statement. We extract all possible (optional, mandatory) child statements.
514              */
515             final List<String> mandatoryChildSingletonStatementNames = getMandatorySingleChildStatementNames();
516             final List<String> mandatoryChildMultipleStatementNames = getMandatoryMultipleChildStatementNames();
517             final List<String> optionalChildSingletonStatementNames = getOptionalSingleChildStatementNames();
518             final List<String> optionalChildMultipleStatementNames = getOptionalMultipleChildStatementNames();
519
520             extractMandatorySingletonChildStatementsFromDom(context, mandatoryChildSingletonStatementNames);
521             extractMandatoryMultipleChildStatementsFromDom(context, mandatoryChildMultipleStatementNames);
522             extractOptionalSingletonChildStatementsFromDom(context, optionalChildSingletonStatementNames);
523             extractOptionalMultipleChildStatementsFromDom(context, optionalChildMultipleStatementNames);
524
525             extractChildExtensions(context);
526
527             validate(context);
528
529             /*
530              * Check at this point that there are not any statements here that are not expected according to RFC.
531              */
532             for (final YangDomElement childDomElement : domElement.getChildren()) {
533                 final String childName = childDomElement.getName();
534
535                 if (optionalChildSingletonStatementNames.contains(childName) || optionalChildMultipleStatementNames
536                         .contains(childName) || mandatoryChildSingletonStatementNames.contains(
537                                 childName) || mandatoryChildMultipleStatementNames.contains(childName)) {
538                     /*
539                      * All ok, it is a statement that we are expecting and according to the RFC is fine to be here.
540                      */
541                 } else if (childName.contains(":")) {
542                     /*
543                      * That's ok as well, it is an extension statement, ignore.
544                      */
545                 } else {
546                     /*
547                      * Some other in-build YANG statement that should not be a child of this statement.
548                      */
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
552                                         .getName() + "'."));
553                     } else {
554                         context.addFinding(new Finding(childDomElement, ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT
555                                 .toString(), "'" + childName + "' is not part of the core YANG language."));
556                     }
557                 }
558             }
559         }
560
561         /*
562          * Indicate that processing of this statement, and all sub-statements, is finished.
563          */
564         subtreeProcessed(context);
565     }
566
567     /**
568      * Performs statement-specific validation. Statements sometimes override.
569      */
570     protected void validate(final ParserExecutionContext context) {
571         /*
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.
575          */
576         validateArgumentNotNullNotEmpty(context);
577     }
578
579     /**
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.
582      */
583     protected void subtreeProcessed(final ParserExecutionContext context) {
584     }
585
586     /**
587      * Extracts from the DOM tree all statements no matter what they are, except extensions
588      */
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,
593                         this);
594                 childStatement.process(context);
595             }
596         }
597     }
598
599     /**
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.
602      */
603     private void extractOptionalSingletonChildStatementsFromDom(final ParserExecutionContext context,
604             final List<String> optionalSingletonChildStatementNames) {
605
606         if (optionalSingletonChildStatementNames.isEmpty()) {
607             return;
608         }
609
610         final Set<String> namesOfStatementsAlreadyExtracted = new HashSet<>();
611
612         for (final YangDomElement childDomElement : domElement.getChildren()) {
613             final String domElementName = childDomElement.getName();
614
615             if (optionalSingletonChildStatementNames.contains(domElementName)) {
616
617                 if (!namesOfStatementsAlreadyExtracted.contains(domElementName)) {
618                     namesOfStatementsAlreadyExtracted.add(domElementName);
619                     final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context,
620                             childDomElement, this);
621                     childStatement.process(context);
622                 } else {
623                     /*
624                      * The same statement exists more than once, but is only allowed once. We generate the
625                      * finding on the parent.
626                      */
627                     context.addFinding(new Finding(domElement, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT.toString(),
628                             "Child statement '" + domElementName + "' cannot exist more than once under '" + domElement
629                                     .getName() + "'."));
630                 }
631             }
632         }
633     }
634
635     /**
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).
638      */
639     private void extractOptionalMultipleChildStatementsFromDom(final ParserExecutionContext context,
640             final List<String> optionalMultipleChildStatementNames) {
641
642         if (optionalMultipleChildStatementNames.isEmpty()) {
643             return;
644         }
645
646         for (final YangDomElement childDomElement : domElement.getChildren()) {
647             final String domElementNode = childDomElement.getName();
648
649             if (optionalMultipleChildStatementNames.contains(domElementNode)) {
650                 final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context, childDomElement,
651                         this);
652                 childStatement.process(context);
653             }
654         }
655     }
656
657     /**
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.
660      */
661     private void extractMandatorySingletonChildStatementsFromDom(final ParserExecutionContext context,
662             final List<String> mandatorySingletonChildStatementNames) {
663
664         if (mandatorySingletonChildStatementNames.isEmpty()) {
665             return;
666         }
667
668         final Set<String> namesOfStatementsYetToExtract = new HashSet<>(mandatorySingletonChildStatementNames);
669
670         for (final YangDomElement childDomElement : domElement.getChildren()) {
671             final String domElementName = childDomElement.getName();
672
673             if (mandatorySingletonChildStatementNames.contains(domElementName)) {
674
675                 if (namesOfStatementsYetToExtract.contains(domElementName)) {
676                     namesOfStatementsYetToExtract.remove(domElementName);
677                     final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context,
678                             childDomElement, this);
679                     childStatement.process(context);
680                 } else {
681                     /*
682                      * The same statement exists more than once, but is only allowed once. We generate the
683                      * finding on the parent.
684                      */
685                     context.addFinding(new Finding(domElement, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT.toString(),
686                             "Child statement '" + domElementName + "' cannot exist more than once under '" + domElement
687                                     .getName() + "'."));
688                 }
689             }
690         }
691
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() + "'."));
696             }
697         }
698     }
699
700     /**
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.
703      */
704     private void extractMandatoryMultipleChildStatementsFromDom(final ParserExecutionContext context,
705             final List<String> mandatoryMultipleChildStatementNames) {
706
707         if (mandatoryMultipleChildStatementNames.isEmpty()) {
708             return;
709         }
710
711         final Set<String> namesOfStatementsYetToExtract = new HashSet<>(mandatoryMultipleChildStatementNames);
712
713         for (final YangDomElement childDomElement : domElement.getChildren()) {
714             final String domElementNode = childDomElement.getName();
715
716             if (mandatoryMultipleChildStatementNames.contains(domElementNode)) {
717                 namesOfStatementsYetToExtract.remove(domElementNode);
718                 final AbstractStatement childStatement = StatementFactory.createYangCoreStatement(context, childDomElement,
719                         this);
720                 childStatement.process(context);
721             }
722         }
723
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
728                                 .getName() + "'."));
729             }
730         }
731     }
732
733     /**
734      * Extract any extension statement.
735      */
736     private void extractChildExtensions(final ParserExecutionContext context) {
737
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);
744                 }
745             }
746         }
747     }
748
749     /**
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
752      * our life easier.
753      */
754     public void assignEffectiveNamespaceToStatementTree(final String namespace) {
755         effectiveNamespace = namespace;
756         for (final AbstractStatement child : children) {
757             child.assignEffectiveNamespaceToStatementTree(namespace);
758         }
759     }
760
761     /**
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
764      * merged in.
765      */
766     public final String getEffectiveNamespace() {
767         return effectiveNamespace;
768     }
769
770     /**
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.
773      */
774     public void assignEffectiveConformanceTypeToStatementTree(final ConformanceType conformanceType) {
775         effectiveConformanceType = conformanceType;
776         for (final AbstractStatement child : children) {
777             child.assignEffectiveConformanceTypeToStatementTree(conformanceType);
778         }
779     }
780
781     /**
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.
785      */
786     public ConformanceType getEffectiveConformanceType() {
787         return effectiveConformanceType;
788     }
789
790     /**
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.
793      */
794     public void assignEffectiveConfigToStatementTree(final boolean configValueOfParent) {
795
796         /*
797          * Config value may switch.
798          */
799         boolean configValueToUse = configValueOfParent;
800
801         final YConfig yConfig = getChild(CY.STMT_CONFIG);
802         if (yConfig != null && configValueToUse == true) {
803             /*
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.
807              */
808             configValueToUse = yConfig.isConfigTrue();
809         }
810
811         /*
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.
815          */
816         if (is(CY.STMT_ACTION) || is(CY.STMT_RPC) || is(CY.STMT_NOTIFICATION)) {
817             configValueToUse = false;
818         }
819
820         effectiveConfig = configValueToUse;
821
822         for (final AbstractStatement child : children) {
823             child.assignEffectiveConfigToStatementTree(configValueToUse);
824         }
825     }
826
827     /**
828      * Returns whether this statement has at least one child instance of the specified statement.
829      */
830     public boolean hasAtLeastOneChildOf(final StatementModuleAndName statementModuleAndName) {
831         for (final AbstractStatement child : children) {
832             if (child.getStatementModuleAndName().equals(statementModuleAndName)) {
833                 return true;
834             }
835         }
836         return false;
837     }
838
839     /**
840      * Returns all children of this statement of the specified module and statement name.
841      */
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);
848             }
849         }
850         return result;
851     }
852
853     /**
854      * Returns all children of the specified statement module/names.
855      */
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);
863             }
864         }
865         return result;
866     }
867
868     /**
869      * Returns the first child of this statement of the specified module and statement name, or null if not found.
870      */
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)) {
875                 return (T) child;
876             }
877         }
878         return null;
879     }
880
881     /**
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.
885      */
886     public void cloneFrom(final AbstractStatement orig) {
887
888         copyPropertiesFrom(orig);
889
890         /*
891          * Clone the children. This will also clone extensions.
892          */
893         for (final AbstractStatement childToClone : orig.children) {
894             final AbstractStatement clonedStatement = StatementFactory.cloneYangStatement(childToClone, this);
895             clonedStatement.cloneFrom(childToClone);
896         }
897     }
898
899     /**
900      * Copies the statement properties from the supplied statement into this statement.
901      */
902     public void copyPropertiesFrom(final AbstractStatement orig) {
903
904         /*
905          * Clone-over the contents of any app data.
906          */
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
911                 } else {
912                     this.setCustomAppData(origEntry.getKey(), origEntry.getValue());
913                 }
914             }
915         }
916
917         this.effectiveConformanceType = orig.effectiveConformanceType;
918         this.effectiveConfig = orig.effectiveConfig;
919         this.effectiveNamespace = orig.effectiveNamespace;
920         this.effectiveStatus = orig.effectiveStatus;
921     }
922
923     /**
924      * All existing children of the same type of statement are removed, and replaced by the supplied statement.
925      */
926     public <T extends AbstractStatement> void replaceChildrenWith(final T replaceWith) {
927         removeAllChildrenOfStatement(replaceWith.getStatementModuleAndName());
928         addChild(replaceWith);
929     }
930
931     /**
932      * All existing children of the same statement are removed, and replaced by the supplied statements.
933      */
934     public <T extends AbstractStatement> void replaceChildrenWith(final List<T> replaceWith) {
935         if (!replaceWith.isEmpty()) {
936             removeAllChildrenOfStatement(replaceWith.get(0).getStatementModuleAndName());
937         }
938         addChildren(replaceWith);
939     }
940
941     /**
942      * All existing children of the supplied type of statement are removed
943      */
944     @SuppressWarnings("unchecked")
945     private <T extends AbstractStatement> void removeAllChildrenOfStatement(
946             final StatementModuleAndName statementModuleAndName) {
947         for (final T child : (List<T>) getChildren(statementModuleAndName)) {
948             removeChild(child);
949         }
950     }
951
952     /**
953      * Replace one specific child statement with another.
954      */
955     public <T extends AbstractStatement> void replaceChild(final T toBeReplaced, final T replaceWith) {
956         removeChild(toBeReplaced);
957         addChild(replaceWith);
958     }
959
960     /**
961      * Replace one specific child statement with another at the exact same place in the statement tree.
962      */
963     public <T extends AbstractStatement> void replaceChildInPlace(final T toBeReplaced, final T replaceWith) {
964         final int oldPos = removeChild(toBeReplaced);
965         addChild(replaceWith, oldPos);
966     }
967
968     /**
969      * Removes a number of statements as children from this statement.
970      */
971     public <T extends AbstractStatement> void removeChildren(final List<T> statementsToRemove) {
972         for (final T statementToRemove : statementsToRemove) {
973             removeChild(statementToRemove);
974         }
975     }
976
977     /**
978      * Removes a specific child statement. Returns the index position at which the child was placed in the statement tree.
979      */
980     public <T extends AbstractStatement> int removeChild(final T statementToRemove) {
981
982         final int indexOf = this.children.indexOf(statementToRemove);
983
984         /*
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.)
987          */
988         statementToRemove.parent = null;
989         this.children.remove(statementToRemove);
990
991         return indexOf;
992     }
993
994     /**
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
997      * re-parented).
998      */
999     public <T extends AbstractStatement> void addChildren(final List<T> statementsToAdd) {
1000         for (final T statementToAdd : statementsToAdd) {
1001             addChild(statementToAdd);
1002         }
1003     }
1004
1005     /**
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).
1008      */
1009     public <T extends AbstractStatement> void addChild(final T statementToAdd) {
1010         addChild(statementToAdd, this.children.size());
1011     }
1012
1013     /**
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).
1017      */
1018     public <T extends AbstractStatement> void addChild(final T statementToAdd, final int pos) {
1019         /*
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.
1024          */
1025         if (statementToAdd.parent != null) {
1026             statementToAdd.parent.children.remove(statementToAdd);
1027         }
1028         this.children.add(pos, statementToAdd);
1029         statementToAdd.parent = this;
1030         statementToAdd.modelRoot = this.modelRoot;
1031     }
1032
1033     /**
1034      * A result of TRUE means validation succeeded (all ok).
1035      */
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);
1044     }
1045
1046     /**
1047      * A result of TRUE means validation succeeded (all ok).
1048      */
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);
1057     }
1058
1059     /**
1060      * A result of TRUE means validation succeeded (all ok).
1061      */
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.");
1065     }
1066
1067     /**
1068      * A result of TRUE means validation succeeded (all ok).
1069      */
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.");
1073     }
1074
1075     /**
1076      * A result of TRUE means validation succeeded (all ok).
1077      */
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.");
1081     }
1082
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);
1087             return false;
1088         }
1089         return true;
1090     }
1091
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);
1096             return false;
1097         }
1098         return true;
1099     }
1100
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);
1105             return false;
1106         }
1107         return true;
1108     }
1109
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);
1114             return false;
1115         }
1116         return true;
1117     }
1118
1119     protected void validateIsYangIdentifier(final ParserExecutionContext context, final String valueToCheck) {
1120
1121         if (valueToCheck == null || valueToCheck.isEmpty()) {
1122             return;
1123         }
1124
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.");
1129         }
1130     }
1131
1132     protected void validateIsYangIdentifierReference(final ParserExecutionContext context, final String valueToCheck) {
1133
1134         if (valueToCheck == null || valueToCheck.isEmpty()) {
1135             return;
1136         }
1137
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.");
1142         }
1143     }
1144
1145     private void issueFinding(final ParserExecutionContext context, final ParserFindingType findingType,
1146             final String message) {
1147         context.addFinding(new Finding(this, findingType, message));
1148     }
1149
1150     @Override
1151     public String toString() {
1152         return domElement.toString();
1153     }
1154 }