From 6ca178b9c67bda1aa2603d5f25ce276efdbbd928 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Fri, 27 Nov 2020 18:44:26 +0100 Subject: [PATCH] Split out AugmentInferenceAction We will need to specialize augment inference to do the right thing in all circumstances. This patch splits it out into a standalone class, so it is clearer what is being used where. JIRA: YANGTOOLS-1190 Change-Id: Ied0ed3bd12c9639a45435c8f0ab615df844012d6 Signed-off-by: Robert Varga --- .../AbstractAugmentStatementSupport.java | 258 +--------------- .../stmt/augment/AugmentInferenceAction.java | 289 ++++++++++++++++++ 2 files changed, 293 insertions(+), 254 deletions(-) create mode 100644 yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AugmentInferenceAction.java diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AbstractAugmentStatementSupport.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AbstractAugmentStatementSupport.java index 9966b17432..825a9a1774 100644 --- a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AbstractAugmentStatementSupport.java +++ b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AbstractAugmentStatementSupport.java @@ -9,17 +9,10 @@ package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.augment; import static com.google.common.base.Verify.verify; -import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.Objects; -import java.util.Optional; import java.util.regex.Pattern; -import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode; import org.opendaylight.yangtools.yang.model.api.Status; import org.opendaylight.yangtools.yang.model.api.YangStmtMapping; @@ -28,39 +21,28 @@ import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement; import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition; import org.opendaylight.yangtools.yang.model.api.stmt.AugmentEffectiveStatement; import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement; -import org.opendaylight.yangtools.yang.model.api.stmt.DataDefinitionStatement; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Descendant; import org.opendaylight.yangtools.yang.model.api.stmt.StatusEffectiveStatement; -import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement; import org.opendaylight.yangtools.yang.model.api.stmt.WhenEffectiveStatement; import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.ArgumentUtils; import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.BaseStatementSupport; import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.EffectiveStatementMixins.EffectiveStatementWithFlags.FlagsBuilder; import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.SubstatementIndexingException; import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace; -import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType; import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current; -import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder; -import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceAction; -import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceContext; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.Prerequisite; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils; import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; -import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace; -import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType; import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; abstract class AbstractAugmentStatementSupport extends BaseStatementSupport { - private static final Logger LOG = LoggerFactory.getLogger(AbstractAugmentStatementSupport.class); private static final Pattern PATH_REL_PATTERN1 = Pattern.compile("\\.\\.?\\s*/(.+)"); private static final Pattern PATH_REL_PATTERN2 = Pattern.compile("//.*"); @@ -113,56 +95,7 @@ abstract class AbstractAugmentStatementSupport final Prerequisite>> target = augmentAction.mutatesEffectiveCtxPath( getSearchRoot(augmentNode), SchemaTreeNamespace.class, augmentNode.getArgument().getNodeIdentifiers()); - augmentAction.apply(new InferenceAction() { - @Override - public void apply(final InferenceContext ctx) { - final StatementContextBase augmentTargetCtx = - (StatementContextBase) target.resolve(ctx); - if (!isSupportedAugmentTarget(augmentTargetCtx) - || StmtContextUtils.isInExtensionBody(augmentTargetCtx)) { - augmentNode.setIsSupportedToBuildEffective(false); - return; - } - - // We are targeting a context which is creating implicit nodes. In order to keep things consistent, - // we will need to circle back when creating effective statements. - if (augmentTargetCtx.hasImplicitParentSupport()) { - augmentNode.addToNs(AugmentImplicitHandlingNamespace.class, augmentNode, augmentTargetCtx); - } - - final StatementContextBase augmentSourceCtx = (StatementContextBase) augmentNode; - // FIXME: this is a workaround for models which augment a node which is added via an extension - // which we do not handle. This needs to be reworked in terms of unknown schema nodes. - try { - copyFromSourceToTarget(augmentSourceCtx, augmentTargetCtx); - augmentTargetCtx.addEffectiveSubstatement(augmentSourceCtx); - } catch (final SourceException e) { - LOG.warn("Failed to add augmentation {} defined at {}", augmentTargetCtx.sourceReference(), - augmentSourceCtx.sourceReference(), e); - } - } - - @Override - public void prerequisiteFailed(final Collection> failed) { - /* - * Do not fail, if it is an uses-augment to an unknown node. - */ - if (YangStmtMapping.USES == augmentNode.coerceParentContext().publicDefinition()) { - final SchemaNodeIdentifier augmentArg = augmentNode.getArgument(); - final Optional> targetNode = SchemaTreeNamespace.findNode( - getSearchRoot(augmentNode), augmentArg); - if (targetNode.isPresent() && StmtContextUtils.isUnknownStatement(targetNode.get())) { - augmentNode.setIsSupportedToBuildEffective(false); - LOG.warn("Uses-augment to unknown node {}. Augmentation has not been performed. At line: {}", - augmentArg, augmentNode.sourceReference()); - return; - } - } - - throw new InferenceException(augmentNode.sourceReference(), "Augment target '%s' not found", - augmentNode.argument()); - } - }); + augmentAction.apply(new AugmentInferenceAction(this, augmentNode, target)); } @Override @@ -208,7 +141,9 @@ abstract class AbstractAugmentStatementSupport } } - private static StmtContext getSearchRoot(final StmtContext augmentContext) { + abstract boolean allowsMandatory(StmtContext ctx); + + static StmtContext getSearchRoot(final StmtContext augmentContext) { // Augment is in uses - we need to augment instantiated nodes in parent. final StmtContext parent = augmentContext.coerceParentContext(); if (YangStmtMapping.USES == parent.publicDefinition()) { @@ -217,192 +152,7 @@ abstract class AbstractAugmentStatementSupport return parent; } - final void copyFromSourceToTarget(final StatementContextBase sourceCtx, - final StatementContextBase targetCtx) { - final CopyType typeOfCopy = sourceCtx.coerceParentContext().producesDeclared(UsesStatement.class) - ? CopyType.ADDED_BY_USES_AUGMENTATION : CopyType.ADDED_BY_AUGMENTATION; - /* - * Since Yang 1.1, if an augmentation is made conditional with a - * "when" statement, it is allowed to add mandatory nodes. - */ - final boolean skipCheckOfMandatoryNodes = allowsMandatory(sourceCtx); - final boolean unsupported = !sourceCtx.isSupportedByFeatures(); - - final Collection> declared = sourceCtx.mutableDeclaredSubstatements(); - final Collection> effective = sourceCtx.mutableEffectiveSubstatements(); - final Collection> buffer = new ArrayList<>(declared.size() + effective.size()); - - for (final Mutable originalStmtCtx : declared) { - copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes, - unsupported || !originalStmtCtx.isSupportedByFeatures()); - } - for (final Mutable originalStmtCtx : effective) { - copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes, unsupported); - } - - targetCtx.addEffectiveSubstatements(buffer); - } - - abstract boolean allowsMandatory(StmtContext ctx); - static boolean hasWhenSubstatement(final StmtContext ctx) { return ctx.hasSubstatement(WhenEffectiveStatement.class); } - - private static void copyStatement(final Mutable original, final StatementContextBase target, - final CopyType typeOfCopy, final Collection> buffer, - final boolean skipCheckOfMandatoryNodes, final boolean unsupported) { - // We always copy statements, but if either the source statement or the augmentation which causes it are not - // supported to build we also mark the target as such. - if (needToCopyByAugment(original)) { - validateNodeCanBeCopiedByAugment(original, target, typeOfCopy, skipCheckOfMandatoryNodes); - - final Mutable copy = target.childCopyOf(original, typeOfCopy); - if (unsupported) { - copy.setIsSupportedToBuildEffective(false); - } - buffer.add(copy); - } else if (isReusedByAugment(original) && !unsupported) { - buffer.add(original); - } - } - - private static void validateNodeCanBeCopiedByAugment(final StmtContext sourceCtx, - final StatementContextBase targetCtx, final CopyType typeOfCopy, - final boolean skipCheckOfMandatoryNodes) { - if (!skipCheckOfMandatoryNodes && typeOfCopy == CopyType.ADDED_BY_AUGMENTATION - && requireCheckOfMandatoryNodes(sourceCtx, targetCtx)) { - checkForMandatoryNodes(sourceCtx); - } - - // Data definition statements must not collide on their namespace - if (sourceCtx.producesDeclared(DataDefinitionStatement.class)) { - for (final StmtContext subStatement : targetCtx.allSubstatements()) { - if (subStatement.producesDeclared(DataDefinitionStatement.class)) { - InferenceException.throwIf(Objects.equals(sourceCtx.argument(), subStatement.argument()), - sourceCtx.sourceReference(), - "An augment cannot add node named '%s' because this name is already used in target", - sourceCtx.rawArgument()); - } - } - } - } - - private static void checkForMandatoryNodes(final StmtContext sourceCtx) { - if (StmtContextUtils.isNonPresenceContainer(sourceCtx)) { - /* - * We need to iterate over both declared and effective sub-statements, - * because a mandatory node can be: - * a) declared in augment body - * b) added to augment body also via uses of a grouping and - * such sub-statements are stored in effective sub-statements collection. - */ - sourceCtx.allSubstatementsStream().forEach(AbstractAugmentStatementSupport::checkForMandatoryNodes); - } - - InferenceException.throwIf(StmtContextUtils.isMandatoryNode(sourceCtx), sourceCtx.sourceReference(), - "An augment cannot add node '%s' because it is mandatory and in module different than target", - sourceCtx.rawArgument()); - } - - private static boolean requireCheckOfMandatoryNodes(final StmtContext sourceCtx, - Mutable targetCtx) { - /* - * If the statement argument is not QName, it cannot be mandatory - * statement, therefore return false and skip mandatory nodes validation - */ - final Object arg = sourceCtx.argument(); - if (!(arg instanceof QName)) { - return false; - } - final QName sourceStmtQName = (QName) arg; - - // RootStatementContext, for example - final Mutable root = targetCtx.getRoot(); - do { - final Object targetArg = targetCtx.argument(); - verify(targetArg instanceof QName, "Argument of augment target statement must be QName, not %s", targetArg); - final QName targetStmtQName = (QName) targetArg; - /* - * If target is from another module, return true and perform mandatory nodes validation - */ - if (!targetStmtQName.getModule().equals(sourceStmtQName.getModule())) { - return true; - } - - /* - * If target or one of the target's ancestors from the same namespace - * is a presence container - * or is non-mandatory choice - * or is non-mandatory list - * return false and skip mandatory nodes validation, because these nodes - * are not mandatory node containers according to RFC 6020 section 3.1. - */ - if (StmtContextUtils.isPresenceContainer(targetCtx) - || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.CHOICE) - || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.LIST)) { - return false; - } - - // This could be an augmentation stacked on top of a previous augmentation from the same module, which is - // conditional -- in which case we do not run further checks - if (targetCtx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) { - final Optional> optPrevCopy = targetCtx.getPreviousCopyCtx(); - if (optPrevCopy.isPresent()) { - final StmtContext original = optPrevCopy.get(); - final Object origArg = original.getArgument(); - Verify.verify(origArg instanceof QName, "Unexpected statement argument %s", origArg); - - if (sourceStmtQName.getModule().equals(((QName) origArg).getModule()) - && hasWhenSubstatement(getParentAugmentation(original))) { - return false; - } - } - } - } while ((targetCtx = targetCtx.getParentContext()) != root); - - /* - * All target node's parents belong to the same module as source node, - * therefore return false and skip mandatory nodes validation. - */ - return false; - } - - private static StmtContext getParentAugmentation(final StmtContext child) { - StmtContext parent = Verify.verifyNotNull(child.getParentContext(), "Child %s has not parent", child); - while (parent.publicDefinition() != YangStmtMapping.AUGMENT) { - parent = Verify.verifyNotNull(parent.getParentContext(), "Failed to find augmentation parent of %s", child); - } - return parent; - } - - private static final ImmutableSet NOCOPY_DEF_SET = ImmutableSet.of(YangStmtMapping.USES, - YangStmtMapping.WHEN, YangStmtMapping.DESCRIPTION, YangStmtMapping.REFERENCE, YangStmtMapping.STATUS); - - private static boolean needToCopyByAugment(final StmtContext stmtContext) { - return !NOCOPY_DEF_SET.contains(stmtContext.publicDefinition()); - } - - private static final ImmutableSet REUSED_DEF_SET = ImmutableSet.of(YangStmtMapping.TYPEDEF); - - private static boolean isReusedByAugment(final StmtContext stmtContext) { - return REUSED_DEF_SET.contains(stmtContext.publicDefinition()); - } - - static boolean isSupportedAugmentTarget(final StmtContext substatementCtx) { - /* - * :TODO Substatement must be allowed augment target type e.g. - * Container, etc... and must not be for example grouping, identity etc. - * It is problem in case when more than one substatements have the same - * QName, for example Grouping and Container are siblings and they have - * the same QName. We must find the Container and the Grouping must be - * ignored as disallowed augment target. - */ - final Collection allowedAugmentTargets = substatementCtx.getFromNamespace( - ValidationBundlesNamespace.class, ValidationBundleType.SUPPORTED_AUGMENT_TARGETS); - - // if no allowed target is returned we consider all targets allowed - return allowedAugmentTargets == null || allowedAugmentTargets.isEmpty() - || allowedAugmentTargets.contains(substatementCtx.publicDefinition()); - } } diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AugmentInferenceAction.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AugmentInferenceAction.java new file mode 100644 index 0000000000..7c41e85d89 --- /dev/null +++ b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/augment/AugmentInferenceAction.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.augment; + +import static com.google.common.base.Verify.verify; +import static com.google.common.base.Verify.verifyNotNull; +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.YangStmtMapping; +import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.AugmentEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.DataDefinitionStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; +import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement; +import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace; +import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType; +import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException; +import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceAction; +import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.Prerequisite; +import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable; +import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils; +import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; +import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace; +import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType; +import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Inference action, split out of {@link AbstractAugmentStatementSupport} for clarity and potential specialization. + */ +final class AugmentInferenceAction implements InferenceAction { + private static final Logger LOG = LoggerFactory.getLogger(AugmentInferenceAction.class); + private static final ImmutableSet NOCOPY_DEF_SET = ImmutableSet.of(YangStmtMapping.USES, + YangStmtMapping.WHEN, YangStmtMapping.DESCRIPTION, YangStmtMapping.REFERENCE, YangStmtMapping.STATUS); + private static final ImmutableSet REUSED_DEF_SET = ImmutableSet.of(YangStmtMapping.TYPEDEF); + + private final Mutable augmentNode; + private final Prerequisite>> target; + private final AbstractAugmentStatementSupport statementSupport; + + AugmentInferenceAction(final AbstractAugmentStatementSupport statementSupport, + final Mutable augmentNode, + final Prerequisite>> target) { + this.statementSupport = requireNonNull(statementSupport); + this.augmentNode = requireNonNull(augmentNode); + this.target = requireNonNull(target); + } + + @Override + public void apply(final InferenceContext ctx) { + final StatementContextBase augmentTargetCtx = (StatementContextBase) target.resolve(ctx); + if (!isSupportedAugmentTarget(augmentTargetCtx) + || StmtContextUtils.isInExtensionBody(augmentTargetCtx)) { + augmentNode.setIsSupportedToBuildEffective(false); + return; + } + + // We are targeting a context which is creating implicit nodes. In order to keep things consistent, + // we will need to circle back when creating effective statements. + if (augmentTargetCtx.hasImplicitParentSupport()) { + augmentNode.addToNs(AugmentImplicitHandlingNamespace.class, augmentNode, augmentTargetCtx); + } + + final StatementContextBase augmentSourceCtx = (StatementContextBase) augmentNode; + // FIXME: this is a workaround for models which augment a node which is added via an extension + // which we do not handle. This needs to be reworked in terms of unknown schema nodes. + try { + copyFromSourceToTarget(augmentSourceCtx, augmentTargetCtx); + augmentTargetCtx.addEffectiveSubstatement(augmentSourceCtx); + } catch (final SourceException e) { + LOG.warn("Failed to add augmentation {} defined at {}", augmentTargetCtx.sourceReference(), + augmentSourceCtx.sourceReference(), e); + } + } + + @Override + public void prerequisiteFailed(final Collection> failed) { + /* + * Do not fail, if it is an uses-augment to an unknown node. + */ + if (YangStmtMapping.USES == augmentNode.coerceParentContext().publicDefinition()) { + final SchemaNodeIdentifier augmentArg = augmentNode.getArgument(); + final Optional> targetNode = SchemaTreeNamespace.findNode( + AbstractAugmentStatementSupport.getSearchRoot(augmentNode), augmentArg); + if (targetNode.isPresent() && StmtContextUtils.isUnknownStatement(targetNode.get())) { + augmentNode.setIsSupportedToBuildEffective(false); + LOG.warn("Uses-augment to unknown node {}. Augmentation has not been performed. At line: {}", + augmentArg, augmentNode.sourceReference()); + return; + } + } + + throw new InferenceException(augmentNode.sourceReference(), "Augment target '%s' not found", + augmentNode.argument()); + } + + private void copyFromSourceToTarget(final StatementContextBase sourceCtx, + final StatementContextBase targetCtx) { + final CopyType typeOfCopy = sourceCtx.coerceParentContext().producesDeclared(UsesStatement.class) + ? CopyType.ADDED_BY_USES_AUGMENTATION : CopyType.ADDED_BY_AUGMENTATION; + /* + * Since Yang 1.1, if an augmentation is made conditional with a + * "when" statement, it is allowed to add mandatory nodes. + */ + final boolean skipCheckOfMandatoryNodes = statementSupport.allowsMandatory(sourceCtx); + final boolean unsupported = !sourceCtx.isSupportedByFeatures(); + + final Collection> declared = sourceCtx.mutableDeclaredSubstatements(); + final Collection> effective = sourceCtx.mutableEffectiveSubstatements(); + final Collection> buffer = new ArrayList<>(declared.size() + effective.size()); + + for (final Mutable originalStmtCtx : declared) { + copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes, + unsupported || !originalStmtCtx.isSupportedByFeatures()); + } + for (final Mutable originalStmtCtx : effective) { + copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes, unsupported); + } + + targetCtx.addEffectiveSubstatements(buffer); + } + + private static void copyStatement(final Mutable original, final StatementContextBase target, + final CopyType typeOfCopy, final Collection> buffer, + final boolean skipCheckOfMandatoryNodes, final boolean unsupported) { + // We always copy statements, but if either the source statement or the augmentation which causes it are not + // supported to build we also mark the target as such. + if (needToCopyByAugment(original)) { + validateNodeCanBeCopiedByAugment(original, target, typeOfCopy, skipCheckOfMandatoryNodes); + + final Mutable copy = target.childCopyOf(original, typeOfCopy); + if (unsupported) { + copy.setIsSupportedToBuildEffective(false); + } + buffer.add(copy); + } else if (isReusedByAugment(original) && !unsupported) { + buffer.add(original); + } + } + + private static void validateNodeCanBeCopiedByAugment(final StmtContext sourceCtx, + final StatementContextBase targetCtx, final CopyType typeOfCopy, + final boolean skipCheckOfMandatoryNodes) { + if (!skipCheckOfMandatoryNodes && typeOfCopy == CopyType.ADDED_BY_AUGMENTATION + && requireCheckOfMandatoryNodes(sourceCtx, targetCtx)) { + checkForMandatoryNodes(sourceCtx); + } + + // Data definition statements must not collide on their namespace + if (sourceCtx.producesDeclared(DataDefinitionStatement.class)) { + for (final StmtContext subStatement : targetCtx.allSubstatements()) { + if (subStatement.producesDeclared(DataDefinitionStatement.class)) { + InferenceException.throwIf(Objects.equals(sourceCtx.argument(), subStatement.argument()), + sourceCtx.sourceReference(), + "An augment cannot add node named '%s' because this name is already used in target", + sourceCtx.rawArgument()); + } + } + } + } + + private static void checkForMandatoryNodes(final StmtContext sourceCtx) { + if (StmtContextUtils.isNonPresenceContainer(sourceCtx)) { + /* + * We need to iterate over both declared and effective sub-statements, + * because a mandatory node can be: + * a) declared in augment body + * b) added to augment body also via uses of a grouping and + * such sub-statements are stored in effective sub-statements collection. + */ + sourceCtx.allSubstatementsStream().forEach(AugmentInferenceAction::checkForMandatoryNodes); + } + + InferenceException.throwIf(StmtContextUtils.isMandatoryNode(sourceCtx), sourceCtx.sourceReference(), + "An augment cannot add node '%s' because it is mandatory and in module different than target", + sourceCtx.rawArgument()); + } + + private static boolean requireCheckOfMandatoryNodes(final StmtContext sourceCtx, + Mutable targetCtx) { + /* + * If the statement argument is not QName, it cannot be mandatory + * statement, therefore return false and skip mandatory nodes validation + */ + final Object arg = sourceCtx.argument(); + if (!(arg instanceof QName)) { + return false; + } + final QName sourceStmtQName = (QName) arg; + + // RootStatementContext, for example + final Mutable root = targetCtx.getRoot(); + do { + final Object targetArg = targetCtx.argument(); + verify(targetArg instanceof QName, "Argument of augment target statement must be QName, not %s", targetArg); + final QName targetStmtQName = (QName) targetArg; + /* + * If target is from another module, return true and perform mandatory nodes validation + */ + if (!targetStmtQName.getModule().equals(sourceStmtQName.getModule())) { + return true; + } + + /* + * If target or one of the target's ancestors from the same namespace + * is a presence container + * or is non-mandatory choice + * or is non-mandatory list + * return false and skip mandatory nodes validation, because these nodes + * are not mandatory node containers according to RFC 6020 section 3.1. + */ + if (StmtContextUtils.isPresenceContainer(targetCtx) + || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.CHOICE) + || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.LIST)) { + return false; + } + + // This could be an augmentation stacked on top of a previous augmentation from the same module, which is + // conditional -- in which case we do not run further checks + if (targetCtx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) { + final Optional> optPrevCopy = targetCtx.getPreviousCopyCtx(); + if (optPrevCopy.isPresent()) { + final StmtContext original = optPrevCopy.get(); + final Object origArg = original.getArgument(); + verify(origArg instanceof QName, "Unexpected statement argument %s", origArg); + + if (sourceStmtQName.getModule().equals(((QName) origArg).getModule()) + && AbstractAugmentStatementSupport.hasWhenSubstatement(getParentAugmentation(original))) { + return false; + } + } + } + } while ((targetCtx = targetCtx.getParentContext()) != root); + + /* + * All target node's parents belong to the same module as source node, + * therefore return false and skip mandatory nodes validation. + */ + return false; + } + + private static StmtContext getParentAugmentation(final StmtContext child) { + StmtContext parent = verifyNotNull(child.getParentContext(), "Child %s has not parent", child); + while (parent.publicDefinition() != YangStmtMapping.AUGMENT) { + parent = verifyNotNull(parent.getParentContext(), "Failed to find augmentation parent of %s", child); + } + return parent; + } + + private static boolean needToCopyByAugment(final StmtContext stmtContext) { + return !NOCOPY_DEF_SET.contains(stmtContext.publicDefinition()); + } + + private static boolean isReusedByAugment(final StmtContext stmtContext) { + return REUSED_DEF_SET.contains(stmtContext.publicDefinition()); + } + + private static boolean isSupportedAugmentTarget(final StmtContext substatementCtx) { + /* + * :TODO Substatement must be allowed augment target type e.g. + * Container, etc... and must not be for example grouping, identity etc. + * It is problem in case when more than one substatements have the same + * QName, for example Grouping and Container are siblings and they have + * the same QName. We must find the Container and the Grouping must be + * ignored as disallowed augment target. + */ + final Collection allowedAugmentTargets = substatementCtx.getFromNamespace( + ValidationBundlesNamespace.class, ValidationBundleType.SUPPORTED_AUGMENT_TARGETS); + + // if no allowed target is returned we consider all targets allowed + return allowedAugmentTargets == null || allowedAugmentTargets.isEmpty() + || allowedAugmentTargets.contains(substatementCtx.publicDefinition()); + } +} -- 2.36.6