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.resolvers;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Objects;
29 import java.util.stream.Collectors;
31 import org.oran.smo.yangtools.parser.ParserExecutionContext;
32 import org.oran.smo.yangtools.parser.findings.Finding;
33 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
34 import org.oran.smo.yangtools.parser.model.schema.Schema;
35 import org.oran.smo.yangtools.parser.model.statements.AbstractStatement;
36 import org.oran.smo.yangtools.parser.model.statements.ExtensionStatement;
37 import org.oran.smo.yangtools.parser.model.statements.StatementFactory;
38 import org.oran.smo.yangtools.parser.model.statements.StatementModuleAndName;
39 import org.oran.smo.yangtools.parser.model.statements.ExtensionStatement.MaxCardinality;
40 import org.oran.smo.yangtools.parser.model.statements.yang.CY;
41 import org.oran.smo.yangtools.parser.model.statements.yang.YDeviate;
42 import org.oran.smo.yangtools.parser.model.statements.yang.YDeviate.DeviateType;
43 import org.oran.smo.yangtools.parser.model.statements.yang.YDeviation;
44 import org.oran.smo.yangtools.parser.model.statements.yang.YMaxElements;
45 import org.oran.smo.yangtools.parser.model.statements.yang.YMinElements;
46 import org.oran.smo.yangtools.parser.model.statements.yang.YType;
47 import org.oran.smo.yangtools.parser.model.util.StringHelper;
48 import org.oran.smo.yangtools.parser.util.QNameHelper;
51 * Resolves 'deviation' statements.
53 * @author Mark Hollmann
55 public abstract class DeviationResolver {
58 * A note on how properties and statements are handled. Some properties for data nodes *always* exist,
59 * even though their corresponding statements may not be listed in the YAM. For example, a data node
60 * will always have a 'config' property, which may or may not be explicitly set by means of a statement
61 * in the YAM. Technically, those properties cannot be ADDED or DELETED vi a deviate, only REPLACED.
62 * This is not clear in the spec; but was clarified as part of a discussion on the netmod mailing list:
64 * "I think that config, mandatory, type, max-elements, min-elements cannot be added or
65 * deleted, only replaced, because they always exist."
67 * However, most YAM designers are not aware of this, and will use a deviate add/remove for these
68 * properties. To avoid noise we are lenient in the code and will allow any of deviate
69 * add/replace/remove for these properties.
71 private static final Set<StatementModuleAndName> PROPERTIES_THAT_ALWAYS_EXIST = new HashSet<>(Arrays.asList(
72 CY.STMT_CONFIG, CY.STMT_MANDATORY, CY.STMT_TYPE, CY.STMT_MAX_ELEMENTS, CY.STMT_MIN_ELEMENTS));
75 * Resolves all deviates by applying the deviate operation against the respective target nodes.
77 public static void resolveDeviates(final ParserExecutionContext context, final Schema schema) {
79 @SuppressWarnings("unchecked") final List<YDeviation> allDeviations = (List<YDeviation>) Helper
80 .findStatementsAtModuleRootInSchema(CY.STMT_DEVIATION, schema);
83 * This is done in a certain order, to facilitate multiple deviations affecting
84 * each other. E.g., a deviation may replace a property added by another deviation.
86 for (final YDeviation deviation : allDeviations) {
87 resolveDeviation(context, deviation, YDeviate.DeviateType.ADD, schema);
89 for (final YDeviation deviation : allDeviations) {
90 resolveDeviation(context, deviation, YDeviate.DeviateType.REPLACE, schema);
92 for (final YDeviation deviation : allDeviations) {
93 resolveDeviation(context, deviation, YDeviate.DeviateType.DELETE, schema);
95 for (final YDeviation deviation : allDeviations) {
96 resolveDeviation(context, deviation, YDeviate.DeviateType.NOT_SUPPORTED, schema);
100 private static void resolveDeviation(final ParserExecutionContext context, final YDeviation deviation,
101 final DeviateType deviateTypeToProcess, final Schema schema) {
103 final String deviationTargetNode = deviation.getDeviationTargetNode();
104 if (deviationTargetNode.isEmpty() || !deviationTargetNode.startsWith("/")) {
106 * Pointless trying to resolve the path. No point issuing a finding either, a
107 * P015_INVALID_SYNTAX_IN_DOCUMENT would have been issued already.
112 final AbstractStatement deviationTarget = findTargetSchemaNode(context, deviation, schema);
113 if (deviationTarget == null) {
115 * It is possible that the deviation is trying to remove parts of the tree that
116 * have already been removed by a different deviation. If that's the case, we
119 if (deviateTypeToProcess == DeviateType.NOT_SUPPORTED && deviationRefersToPreviouslyRemovedSchemaNode(context,
120 deviation, schema)) {
123 context.addFinding(new Finding(deviation, ParserFindingType.P054_UNRESOLVABLE_PATH,
124 "Path to schema node '" + deviation
125 .getDeviationTargetNode() + "', part of 'deviation' statement, cannot be resolved."));
131 for (final YDeviate deviate : deviation.getDeviates()) {
132 if (deviateTypeToProcess == deviate.getDeviateType()) {
133 handleDeviate(context, deviate, deviationTarget);
138 private static void handleDeviate(final ParserExecutionContext context, final YDeviate deviate,
139 final AbstractStatement deviationTarget) {
141 if (deviate.getDeviateType() == DeviateType.ADD || deviate.getDeviateType() == DeviateType.REPLACE) {
143 * Run a check first to see whether the statements underneath the 'deviate add/replace' are
144 * actually allowed as children of the statement that is the target of the deviation. The RFC
145 * says that handling of extensions is not defined; we assume that any extension listed under
146 * a deviate add/replace is meant to be handled such.
148 final List<String> statementsAllowedAsChild = deviationTarget.getStatementsAllowedAsChild();
149 boolean illegalChildFound = false;
151 for (final AbstractStatement childOfDeviate : deviate.getChildStatements()) {
152 if (!childOfDeviate.isExtension() && !statementsAllowedAsChild.contains(childOfDeviate.getDomElement()
154 illegalChildFound = true;
155 context.addFinding(new Finding(childOfDeviate, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT,
156 "Statement '" + childOfDeviate
157 .getStatementName() + "' not a valid child statement of deviated statement '" + deviationTarget
158 .getStatementName()));
160 if (childOfDeviate.isExtension() && !((ExtensionStatement) childOfDeviate).canBeChildOf(deviationTarget
161 .getStatementModuleAndName())) {
162 illegalChildFound = true;
163 context.addFinding(new Finding(childOfDeviate, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT,
164 "Extension '" + childOfDeviate
165 .getStatementName() + "' not a valid child statement of deviated statement '" + deviationTarget
166 .getStatementName()));
170 if (illegalChildFound) {
171 addDeviateInfo(deviate, "'deviate' has not been applied due to findings.");
177 * Now manipulate the tree in accordance with the deviate operation.
179 switch (deviate.getDeviateType()) {
181 handleDeviateNotSupported(deviationTarget, deviate);
184 handleDeviateAdd(context, deviationTarget, deviate);
187 handleDeviateDelete(context, deviationTarget, deviate);
190 handleDeviateReplace(context, deviationTarget, deviate);
194 addDeviateInfo(deviate, "'deviate' has been applied.");
197 private static void handleDeviateDelete(final ParserExecutionContext context, final AbstractStatement deviationTarget,
198 final YDeviate deviate) {
202 * The argument "delete" deletes properties from the target node. The
203 * properties to delete are identified by substatements to the "delete"
204 * statement. The substatement’s keyword MUST match a corresponding
205 * keyword in the target node, and the argument’s string MUST be equal
206 * to the corresponding keyword’s argument string in the target node.
210 * So we need to get the child statements of the "deviate delete" statement,
211 * and try to match these up against the child statements of the deviated statement.
212 * "Match up" means the statement type must be the exact same, and the statements
213 * value must be the exact same.
215 for (final AbstractStatement childOfDeviate : deviate.getChildStatements()) {
216 deleteStatementFromUnderDeviatedStatement(context, deviationTarget, childOfDeviate, deviate);
220 private static void deleteStatementFromUnderDeviatedStatement(final ParserExecutionContext context,
221 final AbstractStatement deviationTarget, final AbstractStatement statementToDelete, final YDeviate deviate) {
223 boolean matchFound = false;
224 final List<? extends AbstractStatement> statementsOfTypeUnderDeviationTarget = deviationTarget.getChildren(
225 statementToDelete.getStatementModuleAndName());
227 for (final AbstractStatement childOfDeviationTarget : statementsOfTypeUnderDeviationTarget) {
228 if (statementValuesAreSame(statementToDelete, childOfDeviationTarget)) {
230 * We do a check first to see how many instances of the statement would be left after we delete the statement.
231 * If 0, and the statement is mandatory, that's a finding. We ignore extensions, as extensions (by definition)
232 * can never be mandatory.
234 final int newOccurenceCount = statementsOfTypeUnderDeviationTarget.size() - 1;
235 if (newOccurenceCount == 0 && !statementToDelete.isExtension()) {
236 final String statementName = statementToDelete.getDomElement().getName();
237 if (deviationTarget.getMandatorySingleChildStatementNames().contains(statementName) || deviationTarget
238 .getMandatoryMultipleChildStatementNames().contains(statementName)) {
239 context.addFinding(new Finding(statementToDelete,
240 ParserFindingType.P166_DEVIATE_RESULTS_IN_CHILD_CARDINALITY_VIOLATION,
241 "Cannot 'deviate delete' this statement as at least a single '" + statementToDelete
242 .getStatementName() + "' statement is required under deviated statement '" + deviationTarget
243 .getStatementName() + "'."));
248 if (statementHasBeenAddedInByDeviateAdd(childOfDeviationTarget)) {
249 context.addFinding(new Finding(statementToDelete,
250 ParserFindingType.P165_DEVIATE_DELETE_OF_DEVIATED_STATEMENT, "Deletes a '" + statementToDelete
251 .getStatementName() + "' statement that was previously added by a separate 'deviate add' operation."));
252 } else if (statementHasBeenAddedInByDeviateReplace(childOfDeviationTarget)) {
253 context.addFinding(new Finding(statementToDelete,
254 ParserFindingType.P165_DEVIATE_DELETE_OF_DEVIATED_STATEMENT, "Deletes a '" + statementToDelete
255 .getStatementName() + "' statement that was previously replaced by a separate 'deviate replace' operation."));
258 addDeviationHistory(deviationTarget, DeviationHistory.delete(childOfDeviationTarget, statementToDelete));
259 addDeviateInfo(deviationTarget, "statement " + childOfDeviationTarget.getDomElement()
260 .getNameValue() + " in " + StringHelper.getModuleLineString(
261 childOfDeviationTarget) + " deleted by 'deviate delete' in " + StringHelper
262 .getModuleLineString(statementToDelete));
264 deviationTarget.removeChild(childOfDeviationTarget);
272 context.addFinding(new Finding(statementToDelete, ParserFindingType.P161_INVALID_DEVIATE_OPERATION,
273 "Cannot 'deviate delete' this statement as the statement does not exist under the deviated statement."));
277 private static boolean statementValuesAreSame(final AbstractStatement statement1, final AbstractStatement statement2) {
278 final String value1 = statement1.getDomElement().getValue();
279 final String value2 = statement2.getDomElement().getValue();
280 return Objects.equals(value1, value2);
283 private static void handleDeviateAdd(final ParserExecutionContext context, final AbstractStatement deviationTarget,
284 final YDeviate deviate) {
288 * "The argument "add" adds properties to the target node. The
289 * properties to add are identified by substatements to the "deviate"
290 * statement. If a property can only appear once, the property MUST NOT
291 * exist in the target node."
295 * The following can only exists once, hence MUST NOT already exist under the deviated schema node.
297 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_CONFIG);
298 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MANDATORY);
299 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MAX_ELEMENTS);
300 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MIN_ELEMENTS);
301 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_TYPE);
302 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_UNITS);
303 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_DEFAULT);
305 * The following can exist more than once, hence there can be statements under the deviated statement already.
307 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MUST);
308 addUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_UNIQUE);
311 * Also make sure to deviate-in extensions. No special handling applies to these, as the
312 * RFC stipulates this to be extension-specific.
314 handleDeviateAddForExtensions(context, deviationTarget, deviate);
317 private static void handleDeviateAddForExtensions(final ParserExecutionContext context,
318 final AbstractStatement deviatedStatement, final YDeviate deviate) {
320 * For extensions we don't know upfront what the exact module/statement names are.
321 * We must figure these out first, before we start adding these:
323 final Set<StatementModuleAndName> extensionModulesAndNames = deviate.getExtensionChildStatements().stream().map(
324 ext -> ext.getStatementModuleAndName()).collect(Collectors.toSet());
326 extensionModulesAndNames.forEach(sman -> addUnderDeviatedStatement(context, deviatedStatement, deviate, sman));
330 * Given a statement type, verifies that the deviated statement does not already have as child this
331 * statement, and assuming that this is not the case, adds it / them.
333 * Note: Will gracefully handle a 'deviate add' of properties, i.e. allow these, although according
334 * to RFC these should only ever be replaced.
336 private static <T extends AbstractStatement> void addUnderDeviatedStatement(final ParserExecutionContext context,
337 final AbstractStatement deviationTarget, final YDeviate deviate, final StatementModuleAndName soughtChildType) {
340 * Get the instances of the statement type.
342 final List<T> statementsOfTypeUnderDeviate = deviate.getChildren(soughtChildType);
343 final List<T> statementsOfTypeUnderTargetStatement = deviationTarget.getChildren(soughtChildType);
345 if (statementsOfTypeUnderDeviate.isEmpty()) {
350 * Figure out whether there can be multiple instances of the statement under the target.
352 final AbstractStatement oneChildUnderDeviate = deviate.getChild(soughtChildType);
353 boolean canHaveMultiple;
355 if (oneChildUnderDeviate.isExtension()) {
356 canHaveMultiple = ((ExtensionStatement) oneChildUnderDeviate)
357 .getMaxCardinalityUnderParent() == MaxCardinality.MULTIPLE;
359 final String coreStatementName = soughtChildType.getStatementName();
360 canHaveMultiple = deviationTarget.getMandatoryMultipleChildStatementNames().contains(
361 coreStatementName) || deviationTarget.getOptionalMultipleChildStatementNames().contains(
365 if (!statementsOfTypeUnderTargetStatement.isEmpty() && !canHaveMultiple) {
367 * A statement of this type already exists under the target, and there cannot be more than one.
369 for (final AbstractStatement statementUnderDeviate : statementsOfTypeUnderDeviate) {
370 context.addFinding(new Finding(statementUnderDeviate, ParserFindingType.P161_INVALID_DEVIATE_OPERATION,
371 "Cannot 'deviate add' statement '" + statementUnderDeviate
372 .getStatementName() + "' as it already exists under the deviated statement. Use a 'deviate replace' instead."));
378 * Check whether the max cardinality would be violated by adding the statements.
380 final int newCount = statementsOfTypeUnderDeviate.size() + statementsOfTypeUnderTargetStatement.size();
381 if (newCount > 1 && !canHaveMultiple) {
382 context.addFinding(new Finding(statementsOfTypeUnderDeviate.get(0),
383 ParserFindingType.P166_DEVIATE_RESULTS_IN_CHILD_CARDINALITY_VIOLATION,
384 "Cannot 'deviate add' this statement as at most one '" + statementsOfTypeUnderDeviate.get(0)
385 .getStatementName() + "' statement is allowed under under '" + deviationTarget
386 .getStatementName() + "'."));
391 * Good - either a statement of this type does not already exist under the deviation target, or
392 * this fact doesn't matter. Then simply clone it so that it ends up under the deviated statement.
394 * Really, cloning is not necessary - we could simply move the statement. However, if there is
395 * downstream tooling that visualizes the schema it would be nice to retain the original content
396 * of the deviation so that a user can see it. Also useful for debugging.
398 for (final T statementUnderDeviate : statementsOfTypeUnderDeviate) {
399 final T clonedStatementPlacedUnderDeviationTarget = StatementFactory.cloneYangStatement(statementUnderDeviate,
401 clonedStatementPlacedUnderDeviationTarget.cloneFrom(statementUnderDeviate);
403 addDeviationHistory(clonedStatementPlacedUnderDeviationTarget, DeviationHistory.add(statementUnderDeviate));
404 addDeviateInfo(clonedStatementPlacedUnderDeviationTarget,
405 "statement added by 'deviate add' in module " + StringHelper.getModuleLineString(
406 statementUnderDeviate));
410 private static void handleDeviateReplace(final ParserExecutionContext context, final AbstractStatement deviationTarget,
411 final YDeviate deviate) {
415 * The argument "replace" replaces properties of the target node. The
416 * properties to replace are identified by substatements to the
417 * "deviate" statement. The properties to replace MUST exist in the
422 * Before we do the actual replacement, we check for shrinking of boundaries.
424 checkForNarrowedMinMaxElements(context, deviationTarget.getChild(CY.STMT_MIN_ELEMENTS), deviate.getMinElements(),
425 deviationTarget.getChild(CY.STMT_MAX_ELEMENTS), deviate.getMaxElements());
427 * ...and check for data type replacements...
429 checkForReplacedDataType(context, deviationTarget.getChild(CY.STMT_TYPE), deviate.getType());
431 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_CONFIG);
432 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_DEFAULT);
433 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MANDATORY);
434 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MAX_ELEMENTS);
435 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MIN_ELEMENTS);
436 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_MUST);
437 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_TYPE);
438 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_UNIQUE);
439 replaceUnderDeviatedStatement(context, deviationTarget, deviate, CY.STMT_UNITS);
441 handleReplaceDeviateForExtensions(context, deviationTarget, deviate);
444 private static void handleReplaceDeviateForExtensions(final ParserExecutionContext context,
445 final AbstractStatement deviatedStatement, final YDeviate deviate) {
447 * For extensions we don't know upfront what the exact module/statement names are.
448 * We must figure these out first, before we start replacing these:
450 final Set<StatementModuleAndName> extensionModulesAndNames = deviate.getExtensionChildStatements().stream().map(
451 ext -> ext.getStatementModuleAndName()).collect(Collectors.toSet());
453 extensionModulesAndNames.forEach(sman -> replaceUnderDeviatedStatement(context, deviatedStatement, deviate, sman));
456 private static void checkForReplacedDataType(final ParserExecutionContext context, final YType oldType,
457 final YType newType) {
459 if (newType == null) {
463 final Set<String> oldDataTypes = new HashSet<>();
464 final Set<String> newDataTypes = new HashSet<>();
466 collectDataTypes(oldDataTypes, oldType);
467 collectDataTypes(newDataTypes, newType);
469 if (!oldDataTypes.equals(newDataTypes)) {
470 context.addFinding(new Finding(newType, ParserFindingType.P057_DATA_TYPE_CHANGED,
471 "Data type has changed from " + oldDataTypes.toString() + " to " + newDataTypes.toString() + "."));
475 private static void collectDataTypes(final Set<String> dataTypes, final YType type) {
476 if (type.getDataType().equals("union")) {
477 for (final YType unionMemberType : type.getTypes()) {
478 collectDataTypes(dataTypes, unionMemberType);
481 dataTypes.add(type.getDataType());
485 private static void checkForNarrowedMinMaxElements(final ParserExecutionContext context,
486 final YMinElements oldMinElements, final YMinElements newMinElements, final YMaxElements oldMaxElements,
487 final YMaxElements newMaxElements) {
489 final long oldMinValue = oldMinElements == null ? 0L : oldMinElements.getMinValue();
490 final long newMinValue = newMinElements == null ? 0L : newMinElements.getMinValue();
492 if (newMinValue > oldMinValue) {
493 context.addFinding(new Finding(newMinElements, ParserFindingType.P056_CONSTRAINT_NARROWED,
494 "Replacement raises min-elements boundary from '" + oldMinValue + "' to '" + newMinValue + "'."));
497 final boolean oldMaxIsUnbounded = oldMaxElements == null ? true : oldMaxElements.isUnbounded();
498 final boolean newMaxIsUnbounded = newMaxElements == null ? true : newMaxElements.isUnbounded();
499 final long oldMaxValue = oldMaxIsUnbounded ? 0L : oldMaxElements.getMaxValue();
500 final long newMaxValue = newMaxIsUnbounded ? 0L : newMaxElements.getMaxValue();
502 if (oldMaxIsUnbounded && !newMaxIsUnbounded) {
503 context.addFinding(new Finding(newMaxElements, ParserFindingType.P056_CONSTRAINT_NARROWED,
504 "Replacement lowers max-elements boundary from 'unbounded' to '" + newMaxValue + "'."));
505 } else if (!oldMaxIsUnbounded && !newMaxIsUnbounded && newMaxValue < oldMaxValue) {
506 context.addFinding(new Finding(newMaxElements, ParserFindingType.P056_CONSTRAINT_NARROWED,
507 "Replacement lowers max-elements boundary from '" + oldMaxValue + "' to '" + newMaxValue + "'."));
512 * Given a statement type, verifies that the deviated statement *does* have as child this
513 * statement, and assuming that this is the case, removes the original child and replaces
514 * it with the deviate's child. Note: exception is made for properties that *always* exist.
516 @SuppressWarnings("unchecked")
517 private static <T extends AbstractStatement> void replaceUnderDeviatedStatement(final ParserExecutionContext context,
518 final AbstractStatement deviationTarget, final YDeviate deviate, final StatementModuleAndName soughtChildType) {
521 * Establish whether an instance of the supplied class exists under the "deviate" and/or under the deviated statement.
523 final List<T> statementsOfTypeUnderDeviate = deviate.getChildren(soughtChildType);
524 final List<T> statementsOfTypeUnderDeviationTarget = deviationTarget.getChildren(soughtChildType);
526 if (statementsOfTypeUnderDeviate.isEmpty()) {
530 final StatementModuleAndName statementModuleAndName = statementsOfTypeUnderDeviate.get(0)
531 .getStatementModuleAndName();
533 if (statementsOfTypeUnderDeviationTarget.isEmpty() && !PROPERTIES_THAT_ALWAYS_EXIST.contains(
534 statementModuleAndName)) {
535 context.addFinding(new Finding(statementsOfTypeUnderDeviate.get(0),
536 ParserFindingType.P161_INVALID_DEVIATE_OPERATION,
537 "Cannot 'deviate replace' statement '" + statementsOfTypeUnderDeviate.get(0)
538 .getStatementName() + "' as it does not exist under the deviated statement."));
543 * Check whether cardinality would be violated by doing the replace.
545 final int newOccurencesOfStatementType = statementsOfTypeUnderDeviate.size();
546 if (statementModuleAndName.isYangCoreStatement() && newOccurencesOfStatementType > 1) {
547 if (!deviationTarget.getMandatoryMultipleChildStatementNames().contains(statementModuleAndName
548 .getStatementName()) && !deviationTarget.getOptionalMultipleChildStatementNames().contains(
549 statementModuleAndName.getStatementName())) {
550 context.addFinding(new Finding(statementsOfTypeUnderDeviate.get(0),
551 ParserFindingType.P166_DEVIATE_RESULTS_IN_CHILD_CARDINALITY_VIOLATION,
552 "Cannot 'deviate replace' this statement as at most one '" + statementModuleAndName
553 .getStatementName() + "' statement is allowed under '" + deviationTarget
554 .getStatementName() + "'."));
560 * Exists in both, so remove the original statement(s), and add the statements under deviate. We do a
561 * quick check first, though, to see whether the statements that are being replaced likewise have been
562 * deviated-in previously.
564 boolean replacingPreviouslyReplaced = false;
565 boolean replacingPreviouslyAdded = false;
567 for (final T oneReplacedStatement : statementsOfTypeUnderDeviationTarget) {
568 if (statementHasBeenAddedInByDeviateReplace(oneReplacedStatement)) {
569 replacingPreviouslyReplaced = true;
570 } else if (statementHasBeenAddedInByDeviateAdd(oneReplacedStatement)) {
571 replacingPreviouslyAdded = true;
574 addDeviateInfo(deviationTarget, "Previous statement/property " + oneReplacedStatement.getDomElement()
575 .getNameValue() + " in " + StringHelper.getModuleLineString(
576 deviationTarget) + " replaced by 'deviate replace' in " + StringHelper.getModuleLineString(
580 deviationTarget.removeChildren(statementsOfTypeUnderDeviationTarget);
582 for (final T statementUnderDeviate : statementsOfTypeUnderDeviate) {
584 if (replacingPreviouslyReplaced) {
585 context.addFinding(new Finding(statementUnderDeviate,
586 ParserFindingType.P163_AMBIGUOUS_DEVIATE_REPLACE_OF_SAME_STATEMENT,
587 "Replaces another '" + statementUnderDeviate
588 .getStatementName() + "' statement that has replaced the original statement by a separate 'deviate replace' operation."));
589 } else if (replacingPreviouslyAdded) {
590 context.addFinding(new Finding(statementUnderDeviate,
591 ParserFindingType.P164_DEVIATE_REPLACE_OF_DEVIATE_ADDED_STATEMENT,
592 "Replaces another '" + statementUnderDeviate
593 .getStatementName() + "' statement that has been previously added by a separate 'deviate add' operation."));
596 final T clonedStatementPlacedUnderDeviationTarget = StatementFactory.cloneYangStatement(statementUnderDeviate,
598 clonedStatementPlacedUnderDeviationTarget.cloneFrom(statementUnderDeviate);
600 addDeviationHistory(clonedStatementPlacedUnderDeviationTarget, DeviationHistory.replace(
601 (List<AbstractStatement>) statementsOfTypeUnderDeviationTarget, statementUnderDeviate));
602 addDeviateInfo(clonedStatementPlacedUnderDeviationTarget,
603 "Replacement for previous statement or (possibly implicit) property, replaced by 'deviate replace' in " + StringHelper
604 .getModuleLineString(statementUnderDeviate));
608 private static void handleDeviateNotSupported(final AbstractStatement deviationTarget, final YDeviate deviate) {
611 * We add some additional information to the parent that will help the user to make sense of
612 * what has happened. We also keep a record of the statement that we have removed. This is useful
613 * later on if there is another statement referring to the deleted statement, because then we can
614 * be smart about it and not issue findings.
616 addDeviationHistory(deviationTarget.getParentStatement(), DeviationHistory.notSupported(deviationTarget, deviate));
617 addDeviateInfo(deviationTarget.getParentStatement(), "Statement " + deviationTarget.getDomElement()
618 .getNameValue() + " in " + StringHelper.getModuleLineString(
619 deviationTarget) + " marked as 'not-supported' by deviation in " + StringHelper.getModuleLineString(
620 deviate) + " and thus removed.");
623 * That's easy - the schema node (the found statement) is not supported, so simply remove it.
625 deviationTarget.getParentStatement().removeChild(deviationTarget);
629 * Finds the schema node that is being deviated.
631 private static AbstractStatement findTargetSchemaNode(final ParserExecutionContext context, final YDeviation deviation,
632 final Schema schema) {
634 final AbstractStatement targetSchemaNode = Helper.findSchemaNode(context, deviation, deviation
635 .getDeviationTargetNode(), schema);
636 if (targetSchemaNode == null) {
641 * Check whether they both sit inside the very same module
643 if (deviation.getDomElement().getYangModel() == targetSchemaNode.getDomElement().getYangModel()) {
644 context.addFinding(new Finding(deviation, ParserFindingType.P162_DEVIATION_TARGET_NODE_IN_SAME_MODULE,
645 "Both 'deviation' and it's target node sit in the same (sub-)module."));
648 return targetSchemaNode;
652 * We try to figure out whether a 'deviate not-supported' refers to something in the
653 * tree that has previously been removed. That's a bit involved, but well possible...
655 private static boolean deviationRefersToPreviouslyRemovedSchemaNode(final ParserExecutionContext context,
656 final YDeviation deviation, final Schema schema) {
658 final String deviationTargetNode = deviation.getDeviationTargetNode();
659 if (deviationTargetNode.isEmpty() || !deviationTargetNode.startsWith("/")) {
661 * Pointless trying to resolve the path. No point issuing a finding either, a
662 * P015_INVALID_SYNTAX_IN_DOCUMENT would have been issued already.
668 * We split the path, and get the starting point for navigation.
670 final String[] identifierParts = Helper.getIdentifierParts(true, deviationTargetNode);
671 AbstractStatement traversalStatement = Helper.findStartingSchemaNode(context, deviation, true, identifierParts[0],
673 if (traversalStatement == null) {
678 * Go down the tree until we get stuck...
680 for (final String possiblyPrefixedIdentifier : identifierParts) {
681 final AbstractStatement foundChildStatement = Helper.findChildSchemaNode(context, traversalStatement,
682 possiblyPrefixedIdentifier, deviation);
683 if (foundChildStatement == null) {
685 * OK, so this is the point where we could not follow the path anymore. We extract the
686 * identifier that we were actually looking for, and check whether such an identifier
687 * has been marked as previously not-supported.
689 final String unprefixedIdentifierSought = QNameHelper.extractName(possiblyPrefixedIdentifier);
691 final List<DeviationHistory> removedStatementsHistory = getDeviationHistory(traversalStatement).stream()
692 .filter(dh -> dh.deviateType == DeviateType.NOT_SUPPORTED).collect(Collectors.toList());
693 for (final DeviationHistory history : removedStatementsHistory) {
694 if (unprefixedIdentifierSought.equals(history.deviatedStatements.get(0).getStatementIdentifier())) {
700 * There is nothing in the history, so truly not found.
705 traversalStatement = foundChildStatement;
709 * We really should never ever be getting here.
714 private static final String DEVIATION_INFO = "DEVIATION_INFO";
716 private static void addDeviateInfo(final AbstractStatement statement, final String info) {
717 Helper.addAppDataListInfo(statement, DEVIATION_INFO, info);
720 public static List<String> getDeviationInfosForStatement(final AbstractStatement statement) {
721 return Helper.getAppDataListInfo(statement, DEVIATION_INFO);
724 private static final String DEVIATION_HISTORY = "DEVIATION_HISTORY";
726 private static void addDeviationHistory(final AbstractStatement onStatement, final DeviationHistory deviationHistory) {
727 Helper.addAppDataListInfo(onStatement, DEVIATION_HISTORY, deviationHistory);
730 public static List<DeviationHistory> getDeviationHistory(final AbstractStatement onStatement) {
731 return Helper.getAppDataListInfo(onStatement, DEVIATION_HISTORY);
734 private static boolean statementHasBeenAddedInByDeviateAdd(final AbstractStatement statement) {
735 final List<DeviationHistory> deviationHistory = getDeviationHistory(statement);
736 return deviationHistory.size() > 0 && deviationHistory.get(0).deviateType == DeviateType.ADD;
739 private static boolean statementHasBeenAddedInByDeviateReplace(final AbstractStatement statement) {
740 final List<DeviationHistory> deviationHistory = getDeviationHistory(statement);
741 return deviationHistory.size() > 0 && deviationHistory.get(0).deviateType == DeviateType.REPLACE;
744 public static class DeviationHistory {
746 static DeviationHistory notSupported(final AbstractStatement notSupportedChild, final YDeviate yDeviate) {
747 return new DeviationHistory(YDeviate.DeviateType.NOT_SUPPORTED, Collections.singletonList(notSupportedChild),
751 static DeviationHistory delete(final AbstractStatement deletedChild, final AbstractStatement deviatingStatement) {
752 return new DeviationHistory(YDeviate.DeviateType.DELETE, Collections.singletonList(deletedChild),
756 static DeviationHistory add(final AbstractStatement deviatingStatement) {
757 return new DeviationHistory(YDeviate.DeviateType.ADD, Collections.<AbstractStatement> emptyList(),
761 static DeviationHistory replace(final List<AbstractStatement> replacedStatements,
762 final AbstractStatement deviatingStatement) {
763 return new DeviationHistory(YDeviate.DeviateType.REPLACE, replacedStatements, deviatingStatement);
767 * The type of deviation applied.
769 public final YDeviate.DeviateType deviateType;
772 * The statement that was deviated. This will be:
774 * not-supported: The single statement (which will be a data node) that was removed.
775 * delete: The single statement (typically a property) that was removed.
776 * add: empty, no content.
777 * replace: The statements (0..n) that were replaced.
779 * that were added, removed, or were changed. If the statements were
780 * removed, the statement will not have a parent anymore.
782 public final List<AbstractStatement> deviatedStatements;
785 * The statement that performed the deviation, i.e. the 'deviate' statement itself,
786 * or a child statement of 'deviate'.
788 public final AbstractStatement deviatingStatement;
790 private DeviationHistory(final DeviateType deviateType, final List<AbstractStatement> deviatedStatements,
791 final AbstractStatement deviatingStatement) {
792 this.deviateType = deviateType;
793 this.deviatedStatements = deviatedStatements;
794 this.deviatingStatement = deviatingStatement;