cab50b3ff1e4f6061939346eb6e003850080862a
[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.schema;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Set;
28
29 import org.oran.smo.yangtools.parser.ParserExecutionContext;
30 import org.oran.smo.yangtools.parser.findings.Finding;
31 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
32 import org.oran.smo.yangtools.parser.model.ConformanceType;
33 import org.oran.smo.yangtools.parser.model.ModuleIdentity;
34 import org.oran.smo.yangtools.parser.model.YangModel;
35 import org.oran.smo.yangtools.parser.model.resolvers.Helper;
36 import org.oran.smo.yangtools.parser.model.statements.AbstractStatement;
37 import org.oran.smo.yangtools.parser.model.statements.StatementModuleAndName;
38 import org.oran.smo.yangtools.parser.model.statements.yang.CY;
39 import org.oran.smo.yangtools.parser.model.statements.yang.YCase;
40 import org.oran.smo.yangtools.parser.model.statements.yang.YChoice;
41 import org.oran.smo.yangtools.parser.model.statements.yang.YFeature;
42 import org.oran.smo.yangtools.parser.model.statements.yang.YIfFeature;
43 import org.oran.smo.yangtools.parser.model.statements.yang.YInput;
44 import org.oran.smo.yangtools.parser.model.statements.yang.YModule;
45 import org.oran.smo.yangtools.parser.model.statements.yang.YOutput;
46 import org.oran.smo.yangtools.parser.model.statements.yang.YStatus;
47 import org.oran.smo.yangtools.parser.model.statements.yang.YSubmodule;
48 import org.oran.smo.yangtools.parser.model.statements.yang.YIfFeature.Token;
49 import org.oran.smo.yangtools.parser.model.util.YangFeature;
50 import org.oran.smo.yangtools.parser.model.yangdom.YangDomElement;
51 import org.oran.smo.yangtools.parser.util.QNameHelper;
52
53 /**
54  * Processes the schema.
55  *
56  * @author Mark Hollmann
57  */
58 public abstract class SchemaProcessor {
59
60     // =================================== SUBMODULE HANDLING =====================================
61
62     public static void resolveSubmodules(final Schema schema) {
63
64         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
65             if (yangModelFile.getYangModelRoot().isSubmodule()) {
66                 mergeInSubmodule(schema, yangModelFile);
67             }
68         }
69     }
70
71     /**
72      * Merges the content of submodules into their owning modules. This makes processing later on
73      * considerably easier (e.g. much easier to find the target node for an augment or deviation).
74      * <p/>
75      * Only the statement tree is manipulated, the DOM tree remains unchanged.
76      */
77     private static void mergeInSubmodule(final Schema schema, final YangModel yangModel) {
78
79         final YSubmodule submodule = yangModel.getYangModelRoot().getSubmodule();
80
81         final String belongsToModuleName = submodule.getBelongsToValue();
82         if (belongsToModuleName == null) {
83             // No need for a finding, an invalid syntax finding would have previously issued.
84             return;
85         }
86
87         final List<YangModel> moduleYangFiles = schema.getModuleRegistry().byModuleName(belongsToModuleName);
88         if (moduleYangFiles.size() != 1) {
89             // No need to record a finding - would have been caught previously.
90             return;
91         }
92
93         /*
94          * We are basically taking all the schema nodes and re-house them into the module. Note: The prefix-resolver
95          * of all of these statements will not be adjusted, i.e. they will keep the prefix resolver of the submodule.
96          * This is to handle a situation where the prefixes declared are different between those in the module and those
97          * in the submodule(s) - which is frequently the case.
98          */
99         final YModule owningModule = moduleYangFiles.get(0).getYangModelRoot().getModule();
100         if (owningModule == null) {
101             // submodule owned by submodule, which is wrong; no need to record a finding - would have been caught previously.
102             return;
103         }
104
105         owningModule.addChildren(submodule.getAnydata());
106         owningModule.addChildren(submodule.getAnyxmls());
107         owningModule.addChildren(submodule.getAugments());
108         owningModule.addChildren(submodule.getChoices());
109         owningModule.addChildren(submodule.getContainers());
110         owningModule.addChildren(submodule.getDeviations());
111         owningModule.addChildren(submodule.getExtensions());
112         owningModule.addChildren(submodule.getFeatures());
113         owningModule.addChildren(submodule.getGroupings());
114         owningModule.addChildren(submodule.getIdentities());
115         owningModule.addChildren(submodule.getLeafLists());
116         owningModule.addChildren(submodule.getLeafs());
117         owningModule.addChildren(submodule.getLists());
118         owningModule.addChildren(submodule.getNotifications());
119         owningModule.addChildren(submodule.getRpcs());
120         owningModule.addChildren(submodule.getTypedefs());
121         owningModule.addChildren(submodule.getUses());
122     }
123
124     // =================================== CASE HANDLING =====================================
125
126     @SuppressWarnings("unchecked")
127     public static void fixupOmittedCaseStatements(final Schema schema) {
128         final List<YChoice> allChoices = (List<YChoice>) Helper.findStatementsInSchema(CY.STMT_CHOICE, schema);
129         for (final YChoice oneChoice : allChoices) {
130             injectCaseForShorthandedStatements(oneChoice);
131         }
132     }
133
134     private static final Set<StatementModuleAndName> POSSIBLY_SHORTHANDED_STATEMENTS = new HashSet<>(Arrays.asList(
135             CY.STMT_ANYDATA, CY.STMT_ANYXML, CY.STMT_CHOICE, CY.STMT_CONTAINER, CY.STMT_LEAF_LIST, CY.STMT_LEAF,
136             CY.STMT_LIST));
137
138     /**
139      * Injects a 'case' statement above any data node-defining statement found directly under
140      * a 'choice'. In Yang, this is referred-to as "shorthand". Example:
141      * <p/>
142      * choice my-choice {
143      * container option1 { ... }
144      * container option2 { ... }
145      * }
146      * <p/>
147      * Usage of shorthand causes problems, as schema node paths referring to data nodes within
148      * the choice must also use the identity of the (omitted) case statement. To simplify
149      * navigation later on, we inject these missing case statements here. The above example is
150      * effectively modified to:
151      * <p/>
152      * choice my-choice {
153      * case option1 {
154      * container option1 { ... }
155      * }
156      * case option2 {
157      * container option2 { ... }
158      * }
159      * }
160      * <p/>
161      * Both the DOM tree and the statement tree are adjusted. The artificial 'case' statement
162      * will be given the same name and line number as the statement defining the data node.
163      * <p/>
164      * The parent statement is usually a 'choice', but could be an 'augment' or 'grouping',
165      * which inject into a 'choice'.
166      */
167     public static void injectCaseForShorthandedStatements(final AbstractStatement parentStatement) {
168
169         final List<AbstractStatement> shorthandedStatements = parentStatement.getChildren(POSSIBLY_SHORTHANDED_STATEMENTS);
170
171         for (final AbstractStatement shorthandedStatement : shorthandedStatements) {
172             /*
173              * The identifier of the case statement is equal to the identifier of the short-handed statement.
174              */
175             final String caseIdentifier = shorthandedStatement.getStatementIdentifier();
176             /*
177              * Create new DOM element for the 'case' statement first. It will sit below the parent
178              * statement and has no children (yet). As line number, we will give it the line number
179              * of the short-handed statement.
180              */
181             final YangDomElement caseDomElement = new YangDomElement(CY.CASE, caseIdentifier, parentStatement
182                     .getDomElement(), shorthandedStatement.getDomElement().getLineNumber());
183             /*
184              * Now create the 'case' statement under the parent statement, and then move
185              * the short-handed statement under the 'case'. That cleans up the statement tree.
186              */
187             final YCase newCase = new YCase(parentStatement, caseDomElement);
188             newCase.addChild(shorthandedStatement);
189             /*
190              * We still need to clean up the DOM tree - we do something very similar here,
191              * basically re-parenting the short-handed DOM element under the 'case' DOM element.
192              */
193             caseDomElement.reparent(shorthandedStatement.getDomElement());
194
195             /*
196              * The properties need to be copied over.
197              */
198             newCase.copyPropertiesFrom(shorthandedStatement);
199
200             /*
201              * Any 'if-feature' statements that sit under the shorthand statement also need to
202              * be cloned to make the newly-created case just as much dependent on those.
203              */
204             final List<YIfFeature> ifFeaturesOfShorthand = shorthandedStatement.getChildren(CY.STMT_IF_FEATURE);
205             for (final YIfFeature ifFeature : ifFeaturesOfShorthand) {
206                 final YIfFeature clonedIfFeature = new YIfFeature(newCase, ifFeature.getDomElement());
207                 clonedIfFeature.cloneFrom(ifFeature);
208             }
209
210             Helper.addGeneralInfoAppData(newCase, "originally omitted 'case' statement inserted for readability.");
211         }
212     }
213
214     // =================================== INPUT / OUTPUT HANDLING =====================================
215
216     public static void fixupMissingInputOutputStatements(final Schema schema) {
217         fixupMissingInputOutputStatements(schema, CY.STMT_ACTION);
218         fixupMissingInputOutputStatements(schema, CY.STMT_RPC);
219     }
220
221     /**
222      * Some RPCs or actions do not use any input or output. However, it is still possible for data nodes to
223      * be augmented-into RPCs and actions. For this to work, the targetNode path will list the input/output
224      * statement. We simplify our processing later on if we inject any missing input/output statements now.
225      */
226     @SuppressWarnings("unchecked")
227     private static <T extends AbstractStatement> void fixupMissingInputOutputStatements(final Schema schema,
228             final StatementModuleAndName actionOrRpcClazz) {
229
230         final List<T> statements = (List<T>) Helper.findStatementsInSchema(actionOrRpcClazz, schema);
231         for (final T actionOrRpc : statements) {
232
233             if (actionOrRpc.getChild(CY.STMT_INPUT) == null) {
234                 /*
235                  * Create new DOM element for the 'input' statement. It will sit below the 'action' (or 'rpc')
236                  * statement and has no children. As line number, we will give it the line number of the parent.
237                  */
238                 final YangDomElement inputDomElement = new YangDomElement(CY.INPUT, null, actionOrRpc.getDomElement(),
239                         actionOrRpc.getDomElement().getLineNumber());
240                 /*
241                  * And create the statement.
242                  */
243                 final YInput yInput = new YInput(actionOrRpc, inputDomElement);
244                 /*
245                  * Copy over properties as well.
246                  */
247                 yInput.copyPropertiesFrom(actionOrRpc);
248             }
249
250             /*
251              * Exact same now for the output.
252              */
253             if (actionOrRpc.getChild(CY.STMT_OUTPUT) == null) {
254                 final YangDomElement outputDomElement = new YangDomElement(CY.OUTPUT, null, actionOrRpc.getDomElement(),
255                         actionOrRpc.getDomElement().getLineNumber());
256                 final YOutput yOutput = new YOutput(actionOrRpc, outputDomElement);
257                 yOutput.copyPropertiesFrom(actionOrRpc);
258             }
259         }
260     }
261
262     // =================================== DATA NODE HANDLING FOR IMPORT-ONLY MODULES =====================================
263
264     public static void removeProtocolAccessibleObjects(final Schema schema) {
265         /*
266          * According to RFC, any statement that "implements any protocol-accessible objects" cannot remain in the
267          * module, so we delete those. Such statements may have been augmented into a lower part of the tree, so we
268          * need to navigate down the tree.
269          */
270         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
271             removeProtocolAccessibleObjects(yangModelFile.getYangModelRoot().getModuleOrSubmodule());
272         }
273     }
274
275     private static final Set<StatementModuleAndName> PROTOCOL_ACCESSIBLE_STATEMENTS = new HashSet<>(Arrays.asList(
276             CY.STMT_ANYDATA, CY.STMT_ANYXML, CY.STMT_AUGMENT, CY.STMT_CHOICE, CY.STMT_CONTAINER, CY.STMT_DEVIATION,
277             CY.STMT_LEAF, CY.STMT_LEAF_LIST, CY.STMT_LIST, CY.STMT_NOTIFICATION, CY.STMT_RPC, CY.STMT_USES));
278
279     private static void removeProtocolAccessibleObjects(final AbstractStatement statement) {
280
281         final List<AbstractStatement> children = statement.getNonExtensionChildStatements();
282         for (final AbstractStatement child : children) {
283             if (child.getEffectiveConformanceType() == ConformanceType.IMPORT && PROTOCOL_ACCESSIBLE_STATEMENTS.contains(
284                     child.getStatementModuleAndName())) {
285                 statement.removeChild(child);
286             } else if (child.definesSchemaNode()) {
287                 removeProtocolAccessibleObjects(child);
288             }
289         }
290     }
291
292     // =================================== IF-FEATURE HANDLING =====================================
293
294     public static void removeDataNodesNotSatisfyingIfFeature(final ParserExecutionContext context, final Schema schema) {
295
296         /*
297          * There is a special case that requires some up-front logic: it is possible for
298          * a feature itself to be constrained by if-feature. This does not mean that the
299          * feature shall be removed. From the RFC:
300          *
301          * "In order for a server to support a feature that is dependent on any other
302          * features (i.e., the feature has one or more "if-feature" substatements), the
303          * server MUST also support all the dependent features."
304          *
305          * Consider this:
306          *
307          *   feature feature-abc;
308          *   feature feature-def;
309          *   feature feature-xyz {
310          *       if-feature "feature-abc and feature-def";
311          *   }
312          *
313          * What this means is that xyz can only be supported if both abc and def are
314          * supported. The client that has supplied the YANG library, however, could have
315          * gotten this wrong. So we need to check for that.
316          */
317         checkFeaturesConstrainedByIfFeatures(context, schema);
318
319         /*
320          * Now that this is done, remove statements as required...
321          */
322         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
323             removeFromModule(context, schema, yangModelFile);
324         }
325     }
326
327     private static void removeFromModule(final ParserExecutionContext context, final Schema schema,
328             final YangModel yangModelFile) {
329
330         final AbstractStatement moduleOrSubmodule = yangModelFile.getYangModelRoot().getModuleOrSubmodule();
331
332         /*
333          * We go through the statement tree and check each statement whether it's
334          * if-feature is fulfilled or not.
335          */
336         handleStatementPossiblyConstrainedByIfFeature(context, schema, moduleOrSubmodule);
337     }
338
339     private static void handleStatementPossiblyConstrainedByIfFeature(final ParserExecutionContext context,
340             final Schema schema, final AbstractStatement parent) {
341
342         final List<AbstractStatement> children = new ArrayList<>(parent.getChildStatements());          // deep copy required
343         for (final AbstractStatement child : children) {
344             if (!child.is(CY.STMT_FEATURE) && !ifFeaturesUnderStatementAreSatisfied(context, schema, child)) {
345                 /*
346                  * Remove the statement and keep a note of it!
347                  */
348                 addChildRemovedDueToIfFeatureAppData(parent, child.getDomElement().getNameValue());
349                 if (child.getStatementIdentifier() != null && !child.getStatementIdentifier().isEmpty()) {
350                     addRemovedChildIdentifierDueToIfFeature(parent, child.getStatementIdentifier());
351                 }
352
353                 parent.removeChild(child);
354             } else {
355                 /*
356                  * Go into the sub-tree.
357                  */
358                 handleStatementPossiblyConstrainedByIfFeature(context, schema, child);
359             }
360         }
361     }
362
363     private static void checkFeaturesConstrainedByIfFeatures(final ParserExecutionContext context, final Schema schema) {
364
365         for (final YangModel yangModel : schema.getModuleRegistry().getAllYangModels()) {
366
367             final List<YFeature> featuresInYam = yangModel.getYangModelRoot().getModuleOrSubmodule().getChildren(
368                     CY.STMT_FEATURE);
369
370             for (final YFeature feature : featuresInYam) {
371                 if (ifFeaturesUnderStatementAreSatisfied(context, schema, feature)) {
372                     continue;
373                 }
374
375                 /*
376                  * Some if-feature is not satisfied. This means that the feature itself MUST NOT
377                  * be supported. Better check!
378                  */
379                 final String namespace = feature.getDomElement().getYangModel().getYangModelRoot().getNamespace();
380                 final String moduleName = feature.getDomElement().getYangModel().getYangModelRoot().getOwningSchema()
381                         .getModuleNamespaceResolver().getModuleForNamespace(namespace);
382
383                 final YangFeature yangFeature = new YangFeature(namespace, moduleName, feature.getFeatureName());
384                 if (context.getSupportedFeatures().contains(yangFeature)) {
385                     context.addFinding(new Finding(feature, ParserFindingType.P086_FEATURE_CANNOT_BE_SUPPORTED,
386                             "Feature '" + feature
387                                     .getFeatureName() + "' has been supplied as supported, but it's 'if-feature' statement evaluates to false."));
388                 }
389             }
390         }
391     }
392
393     private static boolean ifFeaturesUnderStatementAreSatisfied(final ParserExecutionContext context, final Schema schema,
394             final AbstractStatement statement) {
395
396         if (!statement.hasAtLeastOneChildOf(CY.STMT_IF_FEATURE)) {
397             return true;
398         }
399
400         final List<YIfFeature> ifFeatures = statement.getChildren(CY.STMT_IF_FEATURE);
401
402         /*
403          * There can be multiple if-feature statements under a statement. They all must
404          * be true for the overall result to be true.
405          */
406         for (final YIfFeature ifFeature : ifFeatures) {
407             if (!ifFeatureIsSatisfied(context, schema, ifFeature)) {
408                 return false;
409             }
410         }
411
412         return true;
413     }
414
415     private static boolean ifFeatureIsSatisfied(final ParserExecutionContext context, final Schema schema,
416             final YIfFeature ifFeature) {
417
418         final List<Token> tokens = ifFeature.getTokens();
419
420         /*
421          * If the tokens are not valid the logic further below where we simplify the
422          * expression until we get a result will throw errors, so catch this problem
423          * here.
424          */
425         if (!ifFeature.areTokensValid(context, tokens)) {
426             return false;
427         }
428
429         if (tokens.size() == 1) {
430             /*
431              * Simple if-feature statement, just referring to a single feature, easy.
432              */
433             return isFeatureSupported(context, schema, ifFeature, tokens.get(0).name);
434         }
435
436         /*
437          * More than one token in the if-feature. This means we need to apply boolean
438          * logic to the string We build a logical expression first, and then simplify it
439          * bit-by-bit.
440          */
441         final List<Operand> expression = buildExpressionFromTokens(context, schema, ifFeature, tokens);
442
443         final Operand result = simplify(expression);
444         return result == Operand.TRUE;
445     }
446
447     private static boolean isFeatureSupported(final ParserExecutionContext context, final Schema schema,
448             final YIfFeature ifFeature, final String possiblyPrefixedFeatureName) {
449
450         final String featureName = QNameHelper.extractName(possiblyPrefixedFeatureName);
451         String namespace = null;
452
453         if (QNameHelper.hasPrefix(possiblyPrefixedFeatureName)) {
454             final String prefix = QNameHelper.extractPrefix(possiblyPrefixedFeatureName);
455             final ModuleIdentity moduleIdentityForPrefix = ifFeature.getPrefixResolver().getModuleForPrefix(prefix);
456             if (moduleIdentityForPrefix == null) {
457                 context.addFinding(new Finding(ifFeature, ParserFindingType.P033_UNRESOLVEABLE_PREFIX,
458                         "Unresolvable prefix '" + prefix + "'."));
459                 return false;
460             }
461
462             final YangModel yangModel = schema.getModuleRegistry().find(moduleIdentityForPrefix);
463             if (yangModel == null) {
464                 context.addFinding(new Finding(ifFeature, ParserFindingType.P034_UNRESOLVABLE_IMPORT,
465                         "Cannot find '" + moduleIdentityForPrefix + "' in input."));
466                 return false;
467             }
468
469             namespace = yangModel.getYangModelRoot().getNamespace();
470         } else {
471             /*
472              * No prefix, so refers to same module in which it was defined.
473              */
474             namespace = ifFeature.getDomElement().getYangModel().getYangModelRoot().getNamespace();
475         }
476
477         final String moduleName = ifFeature.getDomElement().getYangModel().getYangModelRoot().getOwningSchema()
478                 .getModuleNamespaceResolver().getModuleForNamespace(namespace);
479
480         final YangFeature yangFeature = new YangFeature(namespace, moduleName, featureName);
481         return context.getSupportedFeatures().contains(yangFeature);
482     }
483
484     // -------------------------- All the expression handling stuff here -------------------------------
485
486     private enum Operand {
487         NOT,
488         OR,
489         AND,
490         TRUE,
491         FALSE,
492         LEFT_PARENTHESIS,
493         RIGHT_PARENTHESIS;
494     }
495
496     private static List<Operand> buildExpressionFromTokens(final ParserExecutionContext context, final Schema schema,
497             final YIfFeature ifFeature, final List<Token> tokens) {
498
499         final List<Operand> result = new ArrayList<>(tokens.size());
500
501         for (final Token token : tokens) {
502
503             switch (token.type) {
504                 case NOT:
505                     result.add(Operand.NOT);
506                     break;
507                 case AND:
508                     result.add(Operand.AND);
509                     break;
510                 case OR:
511                     result.add(Operand.OR);
512                     break;
513                 case LEFT_PARENTHESIS:
514                     result.add(Operand.LEFT_PARENTHESIS);
515                     break;
516                 case RIGHT_PARENTHESIS:
517                     result.add(Operand.RIGHT_PARENTHESIS);
518                     break;
519                 default:
520                     if (isFeatureSupported(context, schema, ifFeature, token.name)) {
521                         result.add(Operand.TRUE);
522                     } else {
523                         result.add(Operand.FALSE);
524                     }
525             }
526         }
527
528         return result;
529     }
530
531     /**
532      * A bit of magic here. We keep simplifying the expression until there are no
533      * more operators left.
534      */
535     private static Operand simplify(final List<Operand> input) {
536
537         final List<Operand> result = new ArrayList<>(input);
538
539         /*
540          * The RFC gives as precedence: parenthesis, not, and, or. So that is the order in which we resolve things.
541          *
542          * To simplify the list, we simplify all sub-expressions (those in parenthesis) first.
543          */
544         while (result.contains(Operand.LEFT_PARENTHESIS)) {
545             /*
546              * Find first occurrence of LEFT, and last occurrence or RIGHT. The contents of
547              * that will be simplified and replaces the sub-expression.
548              */
549             final int indexOfLeft = result.indexOf(Operand.LEFT_PARENTHESIS);
550             int parenthesisCount = 1;
551             int indexOfRight = indexOfLeft;
552
553             while (parenthesisCount > 0) {
554                 indexOfRight++;
555                 if (result.get(indexOfRight) == Operand.LEFT_PARENTHESIS) {
556                     parenthesisCount++;
557                 } else if (result.get(indexOfRight) == Operand.RIGHT_PARENTHESIS) {
558                     parenthesisCount--;
559                 }
560             }
561
562             /*
563              * Get the content that is between the parenthesis. That content will be
564              * simplified in a moment and replaces the sub-expression.
565              */
566             final List<Operand> sublist = new ArrayList<>(result.subList(indexOfLeft + 1, indexOfRight));
567             final Operand replaceSubExpressionWith = simplify(sublist);
568
569             /*
570              * Remove the sub-expression and replace with the result of the simplification.
571              */
572             final int nrOfElementsToRemove = indexOfRight - indexOfLeft + 1;
573             for (int i = 0; i < nrOfElementsToRemove; ++i) {
574                 result.remove(indexOfLeft);
575             }
576             result.add(indexOfLeft, replaceSubExpressionWith);
577         }
578
579         /*
580          * Next precedence is "NOT"
581          */
582         while (result.contains(Operand.NOT)) {
583
584             final int indexOfNot = result.indexOf(Operand.NOT);
585
586             /*
587              * We simply remove the 'NOT', and flip the next element.
588              */
589             result.remove(indexOfNot);
590             result.set(indexOfNot, result.get(indexOfNot) == Operand.TRUE ? Operand.FALSE : Operand.TRUE);
591         }
592
593         /*
594          * Next precedence is "AND"
595          */
596         while (result.contains(Operand.AND)) {
597
598             final int indexOfAnd = result.indexOf(Operand.AND);
599
600             /*
601              * We logically AND together the elements before and after. Then we remove all
602              * three elements and replace with the ANDed result.
603              */
604             final boolean resultOfAnd = (result.get(indexOfAnd - 1) == Operand.TRUE) && (result.get(
605                     indexOfAnd + 1) == Operand.TRUE);
606
607             result.remove(indexOfAnd - 1);
608             result.remove(indexOfAnd - 1);
609             result.remove(indexOfAnd - 1);
610             result.add(indexOfAnd - 1, resultOfAnd ? Operand.TRUE : Operand.FALSE);
611         }
612
613         /*
614          * And finally "OR"
615          */
616         while (result.contains(Operand.OR)) {
617
618             final int indexOfOr = result.indexOf(Operand.OR);
619
620             /*
621              * We logically OR together the elements before and after. Then we remove all
622              * three elements and replace with the ORed result.
623              */
624             final boolean resultOfOr = (result.get(indexOfOr - 1) == Operand.TRUE) || (result.get(
625                     indexOfOr + 1) == Operand.TRUE);
626
627             result.remove(indexOfOr - 1);
628             result.remove(indexOfOr - 1);
629             result.remove(indexOfOr - 1);
630             result.add(indexOfOr - 1, resultOfOr ? Operand.TRUE : Operand.FALSE);
631         }
632
633         /*
634          * At this point, there can only be a single entry left in the list.
635          */
636
637         return result.get(0);
638     }
639
640     private static final String IF_FEATURE_INFO = "IF_FEATURE_INFO";
641     private static final String IF_FEATURE_REMOVED_CHILD_IDENTIFIERS = "REMOVED_CHILD_IDENTIFIERS";
642
643     private static void addChildRemovedDueToIfFeatureAppData(final AbstractStatement parentStatement,
644             final String nameOfRemovedChild) {
645         Helper.addAppDataListInfo(parentStatement, IF_FEATURE_INFO,
646                 "Child statement " + nameOfRemovedChild + " removed as it's if-feature condition evaluated to false.");
647     }
648
649     public static List<String> getChildRemovedDueToIfFeatureAppDataForStatement(final AbstractStatement statement) {
650         return Helper.getAppDataListInfo(statement, IF_FEATURE_INFO);
651     }
652
653     private static void addRemovedChildIdentifierDueToIfFeature(final AbstractStatement statement,
654             final String identifier) {
655         Helper.addAppDataListInfo(statement, IF_FEATURE_REMOVED_CHILD_IDENTIFIERS, identifier);
656     }
657
658     public static List<String> getRemovedChildIdentifiersDueToIfFeatureForStatement(final AbstractStatement statement) {
659         return Helper.getAppDataListInfo(statement, IF_FEATURE_REMOVED_CHILD_IDENTIFIERS);
660     }
661
662     // =================================== FINDING HANDLING ON UNUSED SCHEMA NODES =====================================
663
664     /**
665      * Certain parts of the YAMs may not make it into the final schema:
666      *
667      * - Unused typedefs
668      * - Unused groupings
669      *
670      * Also, the schema may be tweaked such that original schema nodes are not longer referenced when we do the following:
671      *
672      * - deviate replace
673      * - uses refine
674      * - if-feature evaluating to false
675      *
676      * So what we do is we go through the final schema, and jump back to the DOM nodes and record which of these are still
677      * referenced; whatever is not referenced will then be looked-at in terms of findings and the findings removed. This
678      * cuts down on "noise"
679      */
680     public static void removeFindingsOnUnusedSchemaNodes(final ParserExecutionContext context, final Schema schema) {
681
682         final Set<YangDomElement> usedDomNodes = new HashSet<>(500000, 0.75f);
683
684         /*
685          * Collect the DOM nodes first.
686          */
687         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
688             collectUsedDomNodes(yangModelFile, usedDomNodes);
689         }
690
691         /*
692          * Now go through the DOM and have a look.
693          */
694         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
695             removeFindingsFromUnusedDomNodes(context, yangModelFile, usedDomNodes);
696         }
697     }
698
699     private static void collectUsedDomNodes(final YangModel yangModelFile, final Set<YangDomElement> usedDomNodes) {
700
701         /*
702          * Start off with the YAM root, and then work recursively down.
703          */
704         final AbstractStatement moduleOrSubmodule = yangModelFile.getYangModelRoot().getModuleOrSubmodule();
705         usedDomNodes.add(moduleOrSubmodule.getDomElement());
706
707         collectUsedDomNodes(usedDomNodes, moduleOrSubmodule);
708     }
709
710     private final static Set<StatementModuleAndName> CHILDREN_OF_STATEMENTS_TO_IGNORE = new HashSet<>(Arrays.asList(
711             CY.STMT_GROUPING, CY.STMT_TYPEDEF));
712
713     private static void collectUsedDomNodes(final Set<YangDomElement> usedDomNodes,
714             final AbstractStatement parentStatement) {
715
716         for (final AbstractStatement child : parentStatement.getChildStatements()) {
717
718             usedDomNodes.add(child.getDomElement());
719
720             /*
721              * Certain statements we happily ignore when we encounter them in the schema. They
722              * would have been resolved / merged-in by this stage and have no further role to
723              * play in the schema..
724              */
725             if (CHILDREN_OF_STATEMENTS_TO_IGNORE.contains(child.getStatementModuleAndName())) {
726                 continue;
727             }
728
729             collectUsedDomNodes(usedDomNodes, child);
730         }
731     }
732
733     private static void removeFindingsFromUnusedDomNodes(final ParserExecutionContext context,
734             final YangModel yangModelFile, final Set<YangDomElement> usedDomNodes) {
735         final YangDomElement moduleOrSubmodule = yangModelFile.getYangModelRoot().getModuleOrSubmodule().getDomElement();
736         removeFindingsFromUnusedDomNodes(context, usedDomNodes, moduleOrSubmodule);
737     }
738
739     private static void removeFindingsFromUnusedDomNodes(final ParserExecutionContext context,
740             final Set<YangDomElement> usedDomNodes, final YangDomElement parentDomElement) {
741
742         for (final YangDomElement child : parentDomElement.getChildren()) {
743
744             if (!usedDomNodes.contains(child)) {
745                 /*
746                  * We have encountered a DOM node that is not referenced by the statement tree. Remove any findings on it.
747                  */
748                 context.getFindingsManager().removeFindingsOnYangDomElement(child);
749             }
750
751             removeFindingsFromUnusedDomNodes(context, usedDomNodes, child);
752         }
753     }
754
755     // =================================== STATUS HANDLING =====================================
756
757     /**
758      * Each statement will be assigned the effective status.
759      */
760     public static void assignStatus(final Schema schema) {
761         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
762             final String implicitStatus = YStatus.CURRENT;
763             assignStatus(yangModelFile.getYangModelRoot().getModuleOrSubmodule(), implicitStatus);
764         }
765     }
766
767     private static void assignStatus(final AbstractStatement statement, final String statusOfParent) {
768
769         String statusToAssign = statusOfParent;
770
771         /*
772          * If there is a status statement that makes the status "more severe"
773          * we use that instead. If the status is less restrictive we ignore that.
774          */
775         final YStatus statusChild = statement.getChild(CY.STMT_STATUS);
776         if (statusChild != null) {
777             if (statusChild.isObsolete()) {
778                 statusToAssign = YStatus.OBSOLETE;
779             } else if (statusChild.isDeprecated() && statusOfParent.equals(YStatus.CURRENT)) {
780                 statusToAssign = YStatus.DEPRECATED;
781             }
782         }
783
784         statement.setEffectiveStatus(statusToAssign);
785
786         for (final AbstractStatement child : statement.getChildStatements()) {
787             assignStatus(child, statusToAssign);
788         }
789     }
790
791     // =================================== NAMESPACE HANDLING =====================================
792
793     /**
794      * Each statement within a YAM will get the namespace of the module.
795      */
796     public static void assignEffectiveNamespaces(final Schema schema) {
797         for (final YangModel yangModel : schema.getModuleRegistry().getAllYangModels()) {
798             final String namespace = yangModel.getYangModelRoot().getNamespace();
799             yangModel.getYangModelRoot().assignEffectiveNamespaceToStatementTree(namespace);
800         }
801     }
802
803     // =================================== CONFORMANCE TYPE HANDLING =====================================
804
805     /**
806      * The ConformanceType is applied to all statements within a YAM.
807      */
808     public static void assignEffectiveConformanceType(final Schema schema) {
809         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
810             final ConformanceType conformanceType = yangModelFile.getConformanceType();
811             yangModelFile.getYangModelRoot().assignEffectiveConformanceTypeToStatementTree(conformanceType);
812         }
813     }
814
815     /**
816      * The effective config is applied to all statements within a YAM.
817      */
818     public static void assignConfig(final Schema schema) {
819         for (final YangModel yangModelFile : schema.getModuleRegistry().getAllYangModels()) {
820             yangModelFile.getYangModelRoot().assignEffectiveConfigToStatementTree(true);
821         }
822     }
823 }