BUG-7052: Move qnameFromArgument to StmtContextUtils
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / UsesStatementImpl.java
index a5872bd614f440a02497893ed86210e1cd7359f7..860fd2d1033537dc4b90dca5c111ef404de9f7be 100644 (file)
@@ -7,16 +7,23 @@
  */
 package org.opendaylight.yangtools.yang.parser.stmt.rfc6020;
 
-import static org.opendaylight.yangtools.yang.parser.spi.SubstatementValidator.MAX;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Set;
+import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.YangVersion;
+import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
 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.AugmentStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.DescriptionStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.IfFeatureStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ReferenceStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
 import org.opendaylight.yangtools.yang.model.api.stmt.StatusStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.WhenStatement;
@@ -24,30 +31,35 @@ import org.opendaylight.yangtools.yang.parser.spi.GroupingNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.SubstatementValidator;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractDeclaredStatement;
 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.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.ModuleCtxToModuleQName;
 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.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.UsesEffectiveStatementImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-
 public class UsesStatementImpl extends AbstractDeclaredStatement<QName> implements UsesStatement {
-    private static final SubstatementValidator SUBSTATEMENT_VALIDATOR = SubstatementValidator.builder(Rfc6020Mapping
+    private static final SubstatementValidator SUBSTATEMENT_VALIDATOR = SubstatementValidator.builder(YangStmtMapping
             .USES)
-            .add(Rfc6020Mapping.AUGMENT, 0, MAX)
-            .add(Rfc6020Mapping.DESCRIPTION, 0, 1)
-            .add(Rfc6020Mapping.IF_FEATURE, 0, MAX)
-            .add(Rfc6020Mapping.REFINE, 0, MAX)
-            .add(Rfc6020Mapping.REFERENCE, 0, 1)
-            .add(Rfc6020Mapping.STATUS, 0, 1)
-            .add(Rfc6020Mapping.WHEN, 0, 1)
+            .addAny(YangStmtMapping.AUGMENT)
+            .addOptional(YangStmtMapping.DESCRIPTION)
+            .addAny(YangStmtMapping.IF_FEATURE)
+            .addAny(YangStmtMapping.REFINE)
+            .addOptional(YangStmtMapping.REFERENCE)
+            .addOptional(YangStmtMapping.STATUS)
+            .addOptional(YangStmtMapping.WHEN)
             .build();
 
     private static final Logger LOG = LoggerFactory.getLogger(UsesStatementImpl.class);
@@ -60,25 +72,27 @@ public class UsesStatementImpl extends AbstractDeclaredStatement<QName> implemen
             AbstractStatementSupport<QName, UsesStatement, EffectiveStatement<QName, UsesStatement>> {
 
         public Definition() {
-            super(Rfc6020Mapping.USES);
+            super(YangStmtMapping.USES);
         }
 
         @Override
         public QName parseArgumentValue(final StmtContext<?, ?, ?> ctx, final String value) {
-            return Utils.qNameFromArgument(ctx, value);
+            return StmtContextUtils.qnameFromArgument(ctx, value);
         }
 
         @Override
         public void onFullDefinitionDeclared(
-                final StmtContext.Mutable<QName, UsesStatement, EffectiveStatement<QName, UsesStatement>> usesNode)
-                throws SourceException {
-            SUBSTATEMENT_VALIDATOR.validate(usesNode);
+                final Mutable<QName, UsesStatement, EffectiveStatement<QName, UsesStatement>> usesNode) {
+            if (!usesNode.isSupportedByFeatures()) {
+                return;
+            }
+            super.onFullDefinitionDeclared(usesNode);
 
             if (StmtContextUtils.isInExtensionBody(usesNode)) {
                 return;
             }
 
-            ModelActionBuilder usesAction = usesNode.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL);
+            final ModelActionBuilder usesAction = usesNode.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL);
             final QName groupingName = usesNode.getStatementArgument();
 
             final Prerequisite<StmtContext<?, ?, ?>> sourceGroupingPre = usesAction.requiresCtx(usesNode,
@@ -89,14 +103,15 @@ public class UsesStatementImpl extends AbstractDeclaredStatement<QName> implemen
             usesAction.apply(new InferenceAction() {
 
                 @Override
-                public void apply() {
-                    StatementContextBase<?, ?, ?> targetNodeStmtCtx = (StatementContextBase<?, ?, ?>) targetNodePre.get();
-                    StatementContextBase<?, ?, ?> sourceGrpStmtCtx = (StatementContextBase<?, ?, ?>) sourceGroupingPre.get();
+                public void apply(final InferenceContext ctx) {
+                    final StatementContextBase<?, ?, ?> targetNodeStmtCtx = (StatementContextBase<?, ?, ?>) targetNodePre.resolve(ctx);
+                    final StatementContextBase<?, ?, ?> sourceGrpStmtCtx = (StatementContextBase<?, ?, ?>) sourceGroupingPre.resolve(ctx);
 
                     try {
-                        GroupingUtils.copyFromSourceToTarget(sourceGrpStmtCtx, targetNodeStmtCtx, usesNode);
-                        GroupingUtils.resolveUsesNode(usesNode, targetNodeStmtCtx);
-                    } catch (SourceException e) {
+                        copyFromSourceToTarget(sourceGrpStmtCtx, targetNodeStmtCtx, usesNode);
+                        resolveUsesNode(usesNode, targetNodeStmtCtx);
+                        StmtContextUtils.validateIfFeatureAndWhenOnListKeys(usesNode);
+                    } catch (final SourceException e) {
                         LOG.warn(e.getMessage(), e);
                         throw e;
                     }
@@ -105,7 +120,7 @@ public class UsesStatementImpl extends AbstractDeclaredStatement<QName> implemen
                 @Override
                 public void prerequisiteFailed(final Collection<? extends Prerequisite<?>> failed) {
                     InferenceException.throwIf(failed.contains(sourceGroupingPre),
-                        usesNode.getStatementSourceReference(), "Grouping '%s' was not resolved.", groupingName);
+                            usesNode.getStatementSourceReference(), "Grouping '%s' was not resolved.", groupingName);
                     throw new InferenceException("Unknown error occurred.", usesNode.getStatementSourceReference());
                 }
             });
@@ -122,8 +137,14 @@ public class UsesStatementImpl extends AbstractDeclaredStatement<QName> implemen
             return new UsesEffectiveStatementImpl(ctx);
         }
 
+        @Override
+        protected SubstatementValidator getSubstatementValidator() {
+            return SUBSTATEMENT_VALIDATOR;
+        }
+
     }
 
+    @Nonnull
     @Override
     public QName getName() {
         return argument();
@@ -134,6 +155,7 @@ public class UsesStatementImpl extends AbstractDeclaredStatement<QName> implemen
         return firstDeclared(WhenStatement.class);
     }
 
+    @Nonnull
     @Override
     public Collection<? extends IfFeatureStatement> getIfFeatures() {
         return allDeclared(IfFeatureStatement.class);
@@ -154,13 +176,208 @@ public class UsesStatementImpl extends AbstractDeclaredStatement<QName> implemen
         return firstDeclared(ReferenceStatement.class);
     }
 
+    @Nonnull
     @Override
     public Collection<? extends AugmentStatement> getAugments() {
         return allDeclared(AugmentStatement.class);
     }
 
+    @Nonnull
     @Override
     public Collection<? extends RefineStatement> getRefines() {
         return allDeclared(RefineStatement.class);
     }
+
+    /**
+     * @param sourceGrpStmtCtx
+     *            source grouping statement context
+     * @param targetCtx
+     *            target context
+     * @param usesNode
+     *            uses node
+     * @throws SourceException
+     *             instance of SourceException
+     */
+    private static void copyFromSourceToTarget(final Mutable<?, ?, ?> sourceGrpStmtCtx,
+            final StatementContextBase<?, ?, ?> targetCtx,
+            final StmtContext.Mutable<QName, UsesStatement, EffectiveStatement<QName, UsesStatement>> usesNode) {
+        final Collection<? extends Mutable<?, ?, ?>> declared = sourceGrpStmtCtx.mutableDeclaredSubstatements();
+        final Collection<? extends Mutable<?, ?, ?>> effective = sourceGrpStmtCtx.mutableEffectiveSubstatements();
+        final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(declared.size() + effective.size());
+        final QNameModule newQNameModule = getNewQNameModule(targetCtx, sourceGrpStmtCtx);
+
+        for (final Mutable<?, ?, ?> original : declared) {
+            if (original.isSupportedByFeatures()) {
+                copyStatement(original, targetCtx, newQNameModule, buffer);
+            }
+        }
+
+        for (final Mutable<?, ?, ?> original : effective) {
+            copyStatement(original, targetCtx, newQNameModule, buffer);
+        }
+
+        targetCtx.addEffectiveSubstatements(buffer);
+        usesNode.addAsEffectOfStatement(buffer);
+    }
+
+    private static void copyStatement(final Mutable<?, ?, ?> original,
+            final StatementContextBase<?, ?, ?> targetCtx, final QNameModule targetModule,
+            final Collection<Mutable<?, ?, ?>> buffer) {
+        if (needToCopyByUses(original)) {
+            final Mutable<?, ?, ?> copy = original.createCopy(targetModule, targetCtx, CopyType.ADDED_BY_USES);
+            buffer.add(copy);
+        } else if (isReusedByUsesOnTop(original)) {
+            buffer.add(original);
+        }
+    }
+
+    private static final Set<YangStmtMapping> TOP_REUSED_DEF_SET = ImmutableSet.of(
+        YangStmtMapping.TYPE,
+        YangStmtMapping.TYPEDEF);
+
+    private static boolean isReusedByUsesOnTop(final StmtContext<?, ?, ?> stmtContext) {
+        return TOP_REUSED_DEF_SET.contains(stmtContext.getPublicDefinition());
+    }
+
+    private static final Set<YangStmtMapping> NOCOPY_FROM_GROUPING_SET = ImmutableSet.of(
+        YangStmtMapping.DESCRIPTION,
+        YangStmtMapping.REFERENCE,
+        YangStmtMapping.STATUS);
+    private static final Set<YangStmtMapping> REUSED_DEF_SET = ImmutableSet.of(
+        YangStmtMapping.TYPE,
+        YangStmtMapping.TYPEDEF,
+        YangStmtMapping.USES);
+
+    public static boolean needToCopyByUses(final StmtContext<?, ?, ?> stmtContext) {
+        final StatementDefinition def = stmtContext.getPublicDefinition();
+        if (REUSED_DEF_SET.contains(def)) {
+            LOG.debug("Will reuse {} statement {}", def, stmtContext);
+            return false;
+        }
+        if (NOCOPY_FROM_GROUPING_SET.contains(def)) {
+            return !YangStmtMapping.GROUPING.equals(stmtContext.getParentContext().getPublicDefinition());
+        }
+
+        LOG.debug("Will copy {} statement {}", def, stmtContext);
+        return true;
+    }
+
+    public static void resolveUsesNode(
+            final Mutable<QName, UsesStatement, EffectiveStatement<QName, UsesStatement>> usesNode,
+            final StatementContextBase<?, ?, ?> targetNodeStmtCtx) {
+        for (final Mutable<?, ?, ?> subStmtCtx : usesNode.mutableDeclaredSubstatements()) {
+            if (StmtContextUtils.producesDeclared(subStmtCtx, RefineStatement.class)
+                    && areFeaturesSupported(subStmtCtx)) {
+                performRefine(subStmtCtx, targetNodeStmtCtx);
+            }
+        }
+    }
+
+    private static boolean areFeaturesSupported(final Mutable<?, ?, ?> subStmtCtx) {
+        /*
+         * In case of Yang 1.1, checks whether features are supported.
+         */
+        return !YangVersion.VERSION_1_1.equals(subStmtCtx.getRootVersion()) || subStmtCtx.isSupportedByFeatures();
+    }
+
+    private static void performRefine(final Mutable<?, ?, ?> subStmtCtx,
+            final StatementContextBase<?, ?, ?> usesParentCtx) {
+
+        final Object refineArgument = subStmtCtx.getStatementArgument();
+        InferenceException.throwIf(!(refineArgument instanceof SchemaNodeIdentifier),
+            subStmtCtx.getStatementSourceReference(),
+            "Invalid refine argument %s. It must be instance of SchemaNodeIdentifier.", refineArgument);
+
+        final SchemaNodeIdentifier refineTargetNodeIdentifier = (SchemaNodeIdentifier) refineArgument;
+        final StatementContextBase<?, ?, ?> refineTargetNodeCtx = Utils.findNode(usesParentCtx,
+                refineTargetNodeIdentifier);
+
+        InferenceException.throwIfNull(refineTargetNodeCtx, subStmtCtx.getStatementSourceReference(),
+            "Refine target node %s not found.", refineTargetNodeIdentifier);
+
+        if (StmtContextUtils.isUnknownStatement(refineTargetNodeCtx)) {
+            LOG.debug(
+                    "Refine node '{}' in uses '{}' has target node unknown statement '{}'. Refine has been skipped. At line: {}",
+                    subStmtCtx.getStatementArgument(), subStmtCtx.getParentContext().getStatementArgument(),
+                    refineTargetNodeCtx.getStatementArgument(), subStmtCtx.getStatementSourceReference());
+            subStmtCtx.addAsEffectOfStatement(refineTargetNodeCtx);
+            return;
+        }
+
+        addOrReplaceNodes(subStmtCtx, refineTargetNodeCtx);
+        subStmtCtx.addAsEffectOfStatement(refineTargetNodeCtx);
+    }
+
+    private static void addOrReplaceNodes(final Mutable<?, ?, ?> subStmtCtx,
+            final StatementContextBase<?, ?, ?> refineTargetNodeCtx) {
+        for (final Mutable<?, ?, ?> refineSubstatementCtx : subStmtCtx.mutableDeclaredSubstatements()) {
+            if (isSupportedRefineSubstatement(refineSubstatementCtx)) {
+                addOrReplaceNode(refineSubstatementCtx, refineTargetNodeCtx);
+            }
+        }
+    }
+
+    private static void addOrReplaceNode(final Mutable<?, ?, ?> refineSubstatementCtx,
+            final StatementContextBase<?, ?, ?> refineTargetNodeCtx) {
+
+        final StatementDefinition refineSubstatementDef = refineSubstatementCtx.getPublicDefinition();
+
+        SourceException.throwIf(!isSupportedRefineTarget(refineSubstatementCtx, refineTargetNodeCtx),
+                refineSubstatementCtx.getStatementSourceReference(),
+                "Error in module '%s' in the refine of uses '%s': can not perform refine of '%s' for the target '%s'.",
+                refineSubstatementCtx.getRoot().getStatementArgument(), refineSubstatementCtx.getParentContext()
+                        .getStatementArgument(), refineSubstatementCtx.getPublicDefinition(), refineTargetNodeCtx
+                        .getPublicDefinition());
+
+        if (isAllowedToAddByRefine(refineSubstatementDef)) {
+            refineTargetNodeCtx.addEffectiveSubstatement(refineSubstatementCtx);
+        } else {
+            refineTargetNodeCtx.removeStatementFromEffectiveSubstatements(refineSubstatementDef);
+            refineTargetNodeCtx.addEffectiveSubstatement(refineSubstatementCtx);
+        }
+    }
+
+    private static final Set<YangStmtMapping> ALLOWED_TO_ADD_BY_REFINE_DEF_SET = ImmutableSet.of(YangStmtMapping.MUST);
+
+    private static boolean isAllowedToAddByRefine(final StatementDefinition publicDefinition) {
+        return ALLOWED_TO_ADD_BY_REFINE_DEF_SET.contains(publicDefinition);
+    }
+
+    private static boolean isSupportedRefineSubstatement(final StmtContext<?, ?, ?> refineSubstatementCtx) {
+        final Collection<?> supportedRefineSubstatements = refineSubstatementCtx.getFromNamespace(
+                ValidationBundlesNamespace.class, ValidationBundleType.SUPPORTED_REFINE_SUBSTATEMENTS);
+
+        return supportedRefineSubstatements == null || supportedRefineSubstatements.isEmpty()
+                || supportedRefineSubstatements.contains(refineSubstatementCtx.getPublicDefinition())
+                || StmtContextUtils.isUnknownStatement(refineSubstatementCtx);
+    }
+
+    private static boolean isSupportedRefineTarget(final StmtContext<?, ?, ?> refineSubstatementCtx,
+            final StmtContext<?, ?, ?> refineTargetNodeCtx) {
+        final Collection<?> supportedRefineTargets = YangValidationBundles.SUPPORTED_REFINE_TARGETS
+                .get(refineSubstatementCtx.getPublicDefinition());
+
+        return supportedRefineTargets == null || supportedRefineTargets.isEmpty()
+                || supportedRefineTargets.contains(refineTargetNodeCtx.getPublicDefinition());
+    }
+
+
+    private static QNameModule getNewQNameModule(final StmtContext<?, ?, ?> targetCtx,
+            final StmtContext<?, ?, ?> stmtContext) {
+        if (targetCtx.isRootContext()) {
+            return targetCtx.getFromNamespace(ModuleCtxToModuleQName.class, targetCtx);
+        }
+        if (targetCtx.getPublicDefinition() == YangStmtMapping.AUGMENT) {
+            return StmtContextUtils.getRootModuleQName(targetCtx);
+        }
+
+        final Object targetStmtArgument = targetCtx.getStatementArgument();
+        final Object sourceStmtArgument = stmtContext.getStatementArgument();
+        if (targetStmtArgument instanceof QName && sourceStmtArgument instanceof QName) {
+            return ((QName) targetStmtArgument).getModule();
+        }
+
+        return null;
+    }
+
 }