X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-parser-rfc7950%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fparser%2Frfc7950%2Fstmt%2Faugment%2FAbstractAugmentStatementSupport.java;h=293534f7bd1d3d09ef506fb94b82c998de9b5c57;hb=7fd1abd03d792989653a05052e723b1516a9e554;hp=dff3296dd511af6f3fa8d609e3539a817810065d;hpb=801a53da5a59219883bb1141d2e8191c11b65f47;p=yangtools.git 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 dff3296dd5..293534f7bd 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 @@ -7,80 +7,88 @@ */ package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.augment; -import com.google.common.base.Verify; -import com.google.common.collect.ImmutableSet; -import java.util.ArrayList; -import java.util.Collection; +import static com.google.common.base.Verify.verify; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +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.common.YangVersion; +import org.opendaylight.yangtools.yang.common.Empty; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Status; import org.opendaylight.yangtools.yang.model.api.YangStmtMapping; +import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement; 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.UsesStatement; -import org.opendaylight.yangtools.yang.model.api.stmt.WhenStatement; -import org.opendaylight.yangtools.yang.parser.rfc7950.namespace.ChildSchemaNodeNamespace; +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.WhenEffectiveStatement; +import org.opendaylight.yangtools.yang.model.ri.stmt.DeclaredStatements; +import org.opendaylight.yangtools.yang.model.spi.meta.SubstatementIndexingException; import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.ArgumentUtils; +import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.EffectiveStatementMixins.EffectiveStatementWithFlags.FlagsBuilder; +import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace; import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport; -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.EffectiveStmtCtx.Current; 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.source.StmtOrderingNamespace; -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 AbstractStatementSupport> { - private static final Logger LOG = LoggerFactory.getLogger(AbstractAugmentStatementSupport.class); +abstract class AbstractAugmentStatementSupport + extends AbstractStatementSupport { private static final Pattern PATH_REL_PATTERN1 = Pattern.compile("\\.\\.?\\s*/(.+)"); private static final Pattern PATH_REL_PATTERN2 = Pattern.compile("//.*"); AbstractAugmentStatementSupport() { - super(YangStmtMapping.AUGMENT); + super(YangStmtMapping.AUGMENT, StatementPolicy.copyDeclared( + (copy, current, substatements) -> + copy.getArgument().equals(current.getArgument()) + && copy.moduleName().getModule().equals(current.moduleName().getModule()) + && Objects.equals(copy.original(), current.original()) + )); } @Override public final SchemaNodeIdentifier parseArgumentValue(final StmtContext ctx, final String value) { SourceException.throwIf(PATH_REL_PATTERN1.matcher(value).matches() - || PATH_REL_PATTERN2.matcher(value).matches(), ctx.getStatementSourceReference(), - "Augment argument \'%s\' is not valid, it can be only absolute path; or descendant if used in uses", - value); - - return ArgumentUtils.nodeIdentifierFromPath(ctx, value); - } - - @Override - public final AugmentStatement createDeclared(final StmtContext ctx) { - return new AugmentStatementImpl(ctx); - } - - @Override - public final EffectiveStatement createEffective( - final StmtContext> ctx) { - return new AugmentEffectiveStatementImpl(ctx); + || PATH_REL_PATTERN2.matcher(value).matches(), ctx, + "Augment argument \'%s\' is not valid, it can be only absolute path; or descendant if used in uses", value); + + // As per: + // https://tools.ietf.org/html/rfc6020#section-7.15 + // https://tools.ietf.org/html/rfc7950#section-7.17 + // + // The argument is either Absolute or Descendant based on whether the statement is declared within a 'uses' + // statement. The mechanics differs wildly between the two cases, so let's start by ensuring our argument + // is in the correct domain. + final SchemaNodeIdentifier result = ArgumentUtils.nodeIdentifierFromPath(ctx, value); + final StatementDefinition parent = ctx.coerceParentContext().publicDefinition(); + if (parent == YangStmtMapping.USES) { + SourceException.throwIf(result instanceof Absolute, ctx, + "Absolute schema node identifier is not allowed when used within a uses statement"); + } else { + SourceException.throwIf(result instanceof Descendant, ctx, + "Descendant schema node identifier is not allowed when used outside of a uses statement"); + } + return result; } @Override - public final void onFullDefinitionDeclared(final Mutable> augmentNode) { + public final void onFullDefinitionDeclared( + final Mutable augmentNode) { if (!augmentNode.isSupportedByFeatures()) { - return; + // We need this augment node to be present, but it should not escape to effective world + augmentNode.setIsSupportedToBuildEffective(false); } super.onFullDefinitionDeclared(augmentNode); @@ -89,295 +97,68 @@ abstract class AbstractAugmentStatementSupport extends AbstractStatementSupport< return; } - final ModelActionBuilder augmentAction = augmentNode.newInferenceAction( - ModelProcessingPhase.EFFECTIVE_MODEL); - final Prerequisite>> sourceCtxPrereq = - augmentAction.requiresCtx(augmentNode, ModelProcessingPhase.EFFECTIVE_MODEL); - final Prerequisite>> target = - augmentAction.mutatesEffectiveCtxPath(getSearchRoot(augmentNode), - ChildSchemaNodeNamespace.class, augmentNode.coerceStatementArgument().getPathFromRoot()); - - 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); - updateAugmentOrder(augmentSourceCtx); - } catch (final SourceException e) { - LOG.warn("Failed to add augmentation {} defined at {}", - augmentTargetCtx.getStatementSourceReference(), - augmentSourceCtx.getStatementSourceReference(), e); - } - } - - private void updateAugmentOrder(final StatementContextBase augmentSourceCtx) { - Integer currentOrder = augmentSourceCtx.getFromNamespace(StmtOrderingNamespace.class, - YangStmtMapping.AUGMENT); - if (currentOrder == null) { - currentOrder = 1; - } else { - currentOrder++; - } - - augmentSourceCtx.addToNs(StmtOrderingNamespace.class, YangStmtMapping.AUGMENT, currentOrder); - } - - @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().getPublicDefinition()) { - final SchemaNodeIdentifier augmentArg = augmentNode.coerceStatementArgument(); - final Optional> targetNode = ChildSchemaNodeNamespace.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.getStatementSourceReference()); - return; - } - } - - throw new InferenceException(augmentNode.getStatementSourceReference(), - "Augment target '%s' not found", augmentNode.getStatementArgument()); - } - }); - } - - private 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.getPublicDefinition()) { - return parent.getParentContext(); - } - return parent; - } - - static void copyFromSourceToTarget(final StatementContextBase sourceCtx, - final StatementContextBase targetCtx) { - final CopyType typeOfCopy = UsesStatement.class.equals(sourceCtx.coerceParentContext().getPublicDefinition() - .getDeclaredRepresentationClass()) ? 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 = YangVersion.VERSION_1_1.equals(sourceCtx.getRootVersion()) - && isConditionalAugmentStmt(sourceCtx); - - final Collection> declared = sourceCtx.mutableDeclaredSubstatements(); - final Collection> effective = sourceCtx.mutableEffectiveSubstatements(); - final Collection> buffer = new ArrayList<>(declared.size() + effective.size()); - - for (final Mutable originalStmtCtx : declared) { - if (originalStmtCtx.isSupportedByFeatures()) { - copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes); - } - } - for (final Mutable originalStmtCtx : effective) { - copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes); - } - - targetCtx.addEffectiveSubstatements(buffer); - } + final ModelActionBuilder augmentAction = augmentNode.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL); + augmentAction.requiresCtx(augmentNode, ModelProcessingPhase.EFFECTIVE_MODEL); + final Prerequisite>> target = augmentAction.mutatesEffectiveCtxPath( + getSearchRoot(augmentNode), SchemaTreeNamespace.class, augmentNode.getArgument().getNodeIdentifiers()); - /** - * Checks whether supplied statement context is conditional augment - * statement. - * - * @param ctx - * statement context to be checked - * - * @return true if supplied statement context is conditional augment - * statement, otherwise false - */ - private static boolean isConditionalAugmentStmt(final StmtContext ctx) { - return ctx.getPublicDefinition() == YangStmtMapping.AUGMENT && hasWhenSubstatement(ctx); + augmentAction.apply(new AugmentInferenceAction(this, augmentNode, target)); } - private static boolean hasWhenSubstatement(final StmtContext ctx) { - return StmtContextUtils.findFirstSubstatement(ctx, WhenStatement.class) != null; + @Override + protected final AugmentStatement createDeclared(final StmtContext ctx, + final ImmutableList> substatements) { + return DeclaredStatements.createAugment(ctx.getRawArgument(), ctx.getArgument(), substatements); } - private static void copyStatement(final Mutable original, final StatementContextBase target, - final CopyType typeOfCopy, final Collection> buffer, - final boolean skipCheckOfMandatoryNodes) { - if (needToCopyByAugment(original)) { - validateNodeCanBeCopiedByAugment(original, target, typeOfCopy, skipCheckOfMandatoryNodes); - - buffer.add(target.childCopyOf(original, typeOfCopy)); - } else if (isReusedByAugment(original)) { - buffer.add(original); - } + @Override + protected final AugmentStatement createEmptyDeclared( + final StmtContext ctx) { + return DeclaredStatements.createAugment(ctx.getRawArgument(), ctx.getArgument()); } - private static void validateNodeCanBeCopiedByAugment(final StmtContext sourceCtx, - final StatementContextBase targetCtx, final CopyType typeOfCopy, - final boolean skipCheckOfMandatoryNodes) { - - if (WhenStatement.class.equals(sourceCtx.getPublicDefinition().getDeclaredRepresentationClass())) { - return; - } - - if (!skipCheckOfMandatoryNodes && typeOfCopy == CopyType.ADDED_BY_AUGMENTATION - && requireCheckOfMandatoryNodes(sourceCtx, targetCtx)) { - checkForMandatoryNodes(sourceCtx); - } - - // Data definition statements must not collide on their namespace - if (DataDefinitionStatement.class.isAssignableFrom( - sourceCtx.getPublicDefinition().getDeclaredRepresentationClass())) { - for (final StmtContext subStatement : targetCtx.allSubstatements()) { - if (DataDefinitionStatement.class.isAssignableFrom( - subStatement.getPublicDefinition().getDeclaredRepresentationClass())) { - - InferenceException.throwIf( - Objects.equals(sourceCtx.getStatementArgument(), subStatement.getStatementArgument()), - sourceCtx.getStatementSourceReference(), - "An augment cannot add node named '%s' because this name is already used in target", - sourceCtx.rawStatementArgument()); - } - } - } + @Override + protected final List> statementsToBuild( + final Current stmt, + final List> substatements) { + // Pick up the marker left by onFullDefinitionDeclared() inference action. If it is present we need to pass our + // children through target's implicit wrapping. + final StatementContextBase implicitDef = stmt.getFromNamespace(AugmentImplicitHandlingNamespace.class, + Empty.getInstance()); + return implicitDef == null ? substatements : Lists.transform(substatements, subCtx -> { + verify(subCtx instanceof StatementContextBase); + return implicitDef.wrapWithImplicit((StatementContextBase) subCtx); + }); } - 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); + @Override + protected final AugmentEffectiveStatement createEffective( + final Current stmt, + final ImmutableList> substatements) { + final int flags = new FlagsBuilder() + .setStatus(findFirstArgument(substatements, StatusEffectiveStatement.class, Status.CURRENT)) + .toFlags(); + + try { + return new AugmentEffectiveStatementImpl(stmt.declared(), stmt.getArgument(), flags, + stmt.moduleName().getModule(), substatements, (AugmentationSchemaNode) stmt.original()); + } catch (SubstatementIndexingException e) { + throw new SourceException(e.getMessage(), stmt, e); } - - InferenceException.throwIf(StmtContextUtils.isMandatoryNode(sourceCtx), - sourceCtx.getStatementSourceReference(), - "An augment cannot add node '%s' because it is mandatory and in module different than target", - sourceCtx.rawStatementArgument()); } - 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.getStatementArgument(); - if (!(arg instanceof QName)) { - return false; - } - final QName sourceStmtQName = (QName) arg; + abstract boolean allowsMandatory(StmtContext ctx); - // RootStatementContext, for example - final Mutable root = targetCtx.getRoot(); - do { - final Object targetArg = targetCtx.getStatementArgument(); - Verify.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.coerceStatementArgument(); - 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.getPublicDefinition() != YangStmtMapping.AUGMENT) { - parent = Verify.verifyNotNull(parent.getParentContext(), "Failed to find augmentation parent of %s", child); + 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()) { + return parent.getParentContext(); } 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.getPublicDefinition()); - } - - private static final ImmutableSet REUSED_DEF_SET = ImmutableSet.of(YangStmtMapping.TYPEDEF); - - private static boolean isReusedByAugment(final StmtContext stmtContext) { - return REUSED_DEF_SET.contains(stmtContext.getPublicDefinition()); - } - - 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.getPublicDefinition()); + static boolean hasWhenSubstatement(final StmtContext ctx) { + return ctx.hasSubstatement(WhenEffectiveStatement.class); } }