Remove AbstractStatementSupport.createEmptyDeclared()
[yangtools.git] / yang / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / stmt / uses / UsesStatementSupport.java
index 7a9769310784182fece33c95a215a18ab5990a23..4efba3e7f256745f93c1e5cd36d7cb7ea13a9f46 100644 (file)
@@ -7,25 +7,45 @@
  */
 package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.uses;
 
-import com.google.common.base.Verify;
-import com.google.common.collect.ImmutableSet;
+import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.YangVersion;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 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.RefineEffectiveStatement;
 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.SchemaNodeIdentifier.Descendant;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.UsesEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
-import org.opendaylight.yangtools.yang.parser.rfc7950.namespace.ChildSchemaNodeNamespace;
+import org.opendaylight.yangtools.yang.model.ri.stmt.DeclaredStatements;
+import org.opendaylight.yangtools.yang.model.spi.meta.EffectiveStatementMixins;
 import org.opendaylight.yangtools.yang.parser.rfc7950.reactor.YangValidationBundles;
+import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.refine.RefineEffectiveStatementImpl;
+import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.refine.RefineTargetNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.GroupingNamespace;
+import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractQNameStatementSupport;
 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;
@@ -60,7 +80,7 @@ public final class UsesStatementSupport
     private static final UsesStatementSupport INSTANCE = new UsesStatementSupport();
 
     private UsesStatementSupport() {
-        super(YangStmtMapping.USES);
+        super(YangStmtMapping.USES, StatementPolicy.exactReplica());
     }
 
     public static UsesStatementSupport getInstance() {
@@ -80,7 +100,7 @@ public final class UsesStatementSupport
         super.onFullDefinitionDeclared(usesNode);
 
         final ModelActionBuilder usesAction = usesNode.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL);
-        final QName groupingName = usesNode.getStatementArgument();
+        final QName groupingName = usesNode.argument();
 
         final Prerequisite<StmtContext<?, ?, ?>> sourceGroupingPre = usesAction.requiresCtx(usesNode,
                 GroupingNamespace.class, groupingName, ModelProcessingPhase.EFFECTIVE_MODEL);
@@ -99,30 +119,68 @@ public final class UsesStatementSupport
                 copyFromSourceToTarget(sourceGrpStmtCtx, targetNodeStmtCtx, usesNode);
                 resolveUsesNode(usesNode, targetNodeStmtCtx);
                 StmtContextUtils.validateIfFeatureAndWhenOnListKeys(usesNode);
+                usesNode.addToNs(SourceGroupingNamespace.class, Empty.getInstance(), sourceGrpStmtCtx);
             }
 
             @Override
             public void prerequisiteFailed(final Collection<? extends Prerequisite<?>> failed) {
-                InferenceException.throwIf(failed.contains(sourceGroupingPre),
-                        usesNode.getStatementSourceReference(), "Grouping '%s' was not resolved.", groupingName);
-                throw new InferenceException("Unknown error occurred.", usesNode.getStatementSourceReference());
+                InferenceException.throwIf(failed.contains(sourceGroupingPre), usesNode,
+                    "Grouping '%s' was not resolved.", groupingName);
+                throw new InferenceException("Unknown error occurred.", usesNode);
             }
         });
     }
 
     @Override
-    public UsesStatement createDeclared(final StmtContext<QName, UsesStatement, ?> ctx) {
-        return new UsesStatementImpl(ctx);
+    protected SubstatementValidator getSubstatementValidator() {
+        return SUBSTATEMENT_VALIDATOR;
     }
 
     @Override
-    public UsesEffectiveStatement createEffective(final StmtContext<QName, UsesStatement, UsesEffectiveStatement> ctx) {
-        return new UsesEffectiveStatementImpl(ctx);
+    protected UsesStatement createDeclared(final StmtContext<QName, UsesStatement, ?> ctx,
+            final ImmutableList<? extends DeclaredStatement<?>> substatements) {
+        return DeclaredStatements.createUses(ctx.getRawArgument(), ctx.getArgument(), substatements);
     }
 
     @Override
-    protected SubstatementValidator getSubstatementValidator() {
-        return SUBSTATEMENT_VALIDATOR;
+    protected UsesEffectiveStatement createEffective(final Current<QName, UsesStatement> stmt,
+            final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
+        final EffectiveStatement<?, ?> source =
+            verifyNotNull(stmt.getFromNamespace(SourceGroupingNamespace.class, Empty.getInstance())).buildEffective();
+        verify(source instanceof GroupingDefinition, "Unexpected source %s", source);
+        final GroupingDefinition sourceGrouping = (GroupingDefinition) source;
+
+        final int flags = EffectiveStatementMixins.historyAndStatusFlags(stmt.history(), substatements);
+        final QName argument = stmt.getArgument();
+        final UsesStatement declared = stmt.declared();
+
+        if (substatements.isEmpty()) {
+            return argument.equals(declared.argument())
+                ? new EmptyLocalUsesEffectiveStatement(declared, sourceGrouping, flags)
+                        : new SimpleCopiedUsesEffectiveStatement(declared, argument, sourceGrouping, flags);
+        }
+
+        if (declared.argument().equals(argument)) {
+            return new RegularLocalUsesEffectiveStatement(declared, sourceGrouping, flags, substatements);
+        }
+        if (findFirstStatement(substatements, RefineEffectiveStatement.class) == null) {
+            return new SimpleCopiedUsesEffectiveStatement(declared, argument, sourceGrouping, flags, substatements);
+        }
+        return new FullCopiedUsesEffectiveStatement(declared, argument, sourceGrouping, flags, substatements);
+    }
+
+    static @NonNull ImmutableMap<Descendant, SchemaNode> indexRefines(
+            final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
+        final Map<Descendant, SchemaNode> refines = new LinkedHashMap<>();
+
+        for (EffectiveStatement<?, ?> effectiveStatement : substatements) {
+            if (effectiveStatement instanceof RefineEffectiveStatementImpl) {
+                final RefineEffectiveStatementImpl refineStmt = (RefineEffectiveStatementImpl) effectiveStatement;
+                refines.put(refineStmt.argument(), refineStmt.getRefineTargetNode());
+            }
+        }
+
+        return ImmutableMap.copyOf(refines);
     }
 
     /**
@@ -137,6 +195,8 @@ public final class UsesStatementSupport
      * @throws SourceException
      *             instance of SourceException
      */
+    @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
+            justification = "https://github.com/spotbugs/spotbugs/issues/811")
     private static void copyFromSourceToTarget(final Mutable<?, ?, ?> sourceGrpStmtCtx,
             final StatementContextBase<?, ?, ?> targetCtx,
             final Mutable<QName, UsesStatement, UsesEffectiveStatement> usesNode) {
@@ -146,38 +206,55 @@ public final class UsesStatementSupport
         final QNameModule newQNameModule = getNewQNameModule(targetCtx, sourceGrpStmtCtx);
 
         for (final Mutable<?, ?, ?> original : declared) {
-            if (original.isSupportedByFeatures()) {
-                copyStatement(original, targetCtx, newQNameModule, buffer);
+            if (original.isSupportedByFeatures() && shouldCopy(original)) {
+                original.copyAsChildOf(targetCtx, CopyType.ADDED_BY_USES, newQNameModule).ifPresent(buffer::add);
             }
         }
 
         for (final Mutable<?, ?, ?> original : effective) {
-            copyStatement(original, targetCtx, newQNameModule, buffer);
+            if (shouldCopy(original)) {
+                original.copyAsChildOf(targetCtx, CopyType.ADDED_BY_USES, newQNameModule).ifPresent(buffer::add);
+            }
         }
 
         targetCtx.addEffectiveSubstatements(buffer);
         usesNode.addAsEffectOfStatement(buffer);
     }
 
-    // FIXME: YANGTOOLS-652: this map looks very much like InferredStatementContext.REUSED_DEF_SET
-    private static final ImmutableSet<YangStmtMapping> TOP_REUSED_DEF_SET = ImmutableSet.of(
-        YangStmtMapping.TYPE,
-        YangStmtMapping.TYPEDEF);
-
-    private static void copyStatement(final Mutable<?, ?, ?> original,
-            final StatementContextBase<?, ?, ?> targetCtx, final QNameModule targetModule,
-            final Collection<Mutable<?, ?, ?>> buffer) {
-        final StatementDefinition def = original.getPublicDefinition();
-        if (TOP_REUSED_DEF_SET.contains(def)) {
-            buffer.add(original);
-            return;
-        }
-        // Do not propagate uses, as their effects have been accounted for in effective statements
-        // FIXME: YANGTOOLS-652: this check is different from InferredStatementContext. Why is that? We should express
-        //                       a common condition in our own implementation of copyAsChildOf()
-        if (!YangStmtMapping.USES.equals(def)) {
-            original.copyAsChildOf(targetCtx, CopyType.ADDED_BY_USES, targetModule).ifPresent(buffer::add);
+    private static boolean shouldCopy(final StmtContext<?, ?, ?> stmt) {
+        // https://tools.ietf.org/html/rfc7950#section-7.13:
+        //
+        //        The effect of a "uses" reference to a grouping is that the nodes
+        //        defined by the grouping are copied into the current schema tree and
+        //        are then updated according to the "refine" and "augment" statements.
+        //
+        // This means that the statement that is about to be copied (and can be subjected to buildEffective() I think)
+        // is actually a SchemaTreeEffectiveStatement
+        if (SchemaTreeEffectiveStatement.class.isAssignableFrom(
+                stmt.publicDefinition().getEffectiveRepresentationClass())) {
+            return true;
         }
+
+        // As per https://tools.ietf.org/html/rfc7950#section-7.13.2:
+        //
+        //        o  Any node can get refined extensions, if the extension allows
+        //           refinement.  See Section 7.19 for details.
+        //
+        // and https://tools.ietf.org/html/rfc7950#section-7.19:
+        //
+        //        An extension can allow refinement (see Section 7.13.2) and deviations
+        //        (Section 7.20.3.2), but the mechanism for how this is defined is
+        //        outside the scope of this specification.
+        //
+        // This is actively used out there (tailf-common.yang's tailf:action), which is incorrect, though. They do
+        // publish a bunch of metadata (through tailf-meta-extension.yang), but fail to publish a key aspect of the
+        // statement: it attaches to schema tree namespace (just as RFC7950 action does). Such an extension would
+        // automatically result in the extension being picked up by the above check and everybody would live happily
+        // ever after.
+        //
+        // We do not live in that world yet, hence we do the following and keep our fingers crossed.
+        // FIXME: YANGTOOLS-403: this should not be necessary once we implement the above (although tests will complain)
+        return StmtContextUtils.isUnknownStatement(stmt);
     }
 
     private static QNameModule getNewQNameModule(final StmtContext<?, ?, ?> targetCtx,
@@ -185,12 +262,12 @@ public final class UsesStatementSupport
         if (targetCtx.getParentContext() == null) {
             return targetCtx.getFromNamespace(ModuleCtxToModuleQName.class, targetCtx);
         }
-        if (targetCtx.getPublicDefinition() == YangStmtMapping.AUGMENT) {
+        if (targetCtx.publicDefinition() == YangStmtMapping.AUGMENT) {
             return StmtContextUtils.getRootModuleQName(targetCtx);
         }
 
-        final Object targetStmtArgument = targetCtx.getStatementArgument();
-        final Object sourceStmtArgument = stmtContext.getStatementArgument();
+        final Object targetStmtArgument = targetCtx.argument();
+        final Object sourceStmtArgument = stmtContext.argument();
         if (targetStmtArgument instanceof QName && sourceStmtArgument instanceof QName) {
             return ((QName) targetStmtArgument).getModule();
         }
@@ -198,11 +275,12 @@ public final class UsesStatementSupport
         return null;
     }
 
+    @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
+            justification = "https://github.com/spotbugs/spotbugs/issues/811")
     private static void resolveUsesNode(final Mutable<QName, UsesStatement, UsesEffectiveStatement> usesNode,
             final StmtContext<?, ?, ?> targetNodeStmtCtx) {
         for (final Mutable<?, ?, ?> subStmtCtx : usesNode.mutableDeclaredSubstatements()) {
-            if (StmtContextUtils.producesDeclared(subStmtCtx, RefineStatement.class)
-                    && areFeaturesSupported(subStmtCtx)) {
+            if (subStmtCtx.producesDeclared(RefineStatement.class) && areFeaturesSupported(subStmtCtx)) {
                 performRefine(subStmtCtx, targetNodeStmtCtx);
             }
         }
@@ -212,33 +290,40 @@ public final class UsesStatementSupport
         /*
          * In case of Yang 1.1, checks whether features are supported.
          */
-        return !YangVersion.VERSION_1_1.equals(subStmtCtx.getRootVersion()) || subStmtCtx.isSupportedByFeatures();
+        return !YangVersion.VERSION_1_1.equals(subStmtCtx.yangVersion()) || subStmtCtx.isSupportedByFeatures();
     }
 
     private static void performRefine(final Mutable<?, ?, ?> subStmtCtx, final StmtContext<?, ?, ?> usesParentCtx) {
-        final Object refineArgument = subStmtCtx.getStatementArgument();
-        InferenceException.throwIf(!(refineArgument instanceof SchemaNodeIdentifier),
-            subStmtCtx.getStatementSourceReference(),
+        final Object refineArgument = subStmtCtx.argument();
+        InferenceException.throwIf(!(refineArgument instanceof SchemaNodeIdentifier), subStmtCtx,
             "Invalid refine argument %s. It must be instance of SchemaNodeIdentifier.", refineArgument);
 
-        final Optional<StmtContext<?, ?, ?>> optRefineTargetCtx = ChildSchemaNodeNamespace.findNode(
+        final Optional<StmtContext<?, ?, ?>> optRefineTargetCtx = SchemaTreeNamespace.findNode(
             usesParentCtx, (SchemaNodeIdentifier) refineArgument);
-        InferenceException.throwIf(!optRefineTargetCtx.isPresent(), subStmtCtx.getStatementSourceReference(),
-            "Refine target node %s not found.", refineArgument);
+        InferenceException.throwIf(!optRefineTargetCtx.isPresent(), subStmtCtx, "Refine target node %s not found.",
+            refineArgument);
 
+        // FIXME: This communicates the looked-up target node to RefineStatementSupport.buildEffective(). We should do
+        //        this trick through a shared namespace or similar reactor-agnostic meeting place. It really feels like
+        //        an inference action RefineStatementSupport should be doing.
         final StmtContext<?, ?, ?> refineTargetNodeCtx = optRefineTargetCtx.get();
         if (StmtContextUtils.isUnknownStatement(refineTargetNodeCtx)) {
             LOG.trace("Refine node '{}' in uses '{}' has target node unknown statement '{}'. "
-                + "Refine has been skipped. At line: {}", subStmtCtx.getStatementArgument(),
-                subStmtCtx.coerceParentContext().getStatementArgument(),
-                refineTargetNodeCtx.getStatementArgument(), subStmtCtx.getStatementSourceReference());
-            subStmtCtx.addAsEffectOfStatement(refineTargetNodeCtx);
-            return;
+                + "Refine has been skipped. At line: {}", subStmtCtx.argument(),
+                subStmtCtx.coerceParentContext().argument(), refineTargetNodeCtx.argument(),
+                subStmtCtx.sourceReference());
+        } else {
+            verify(refineTargetNodeCtx instanceof StatementContextBase);
+            addOrReplaceNodes(subStmtCtx, (StatementContextBase<?, ?, ?>) refineTargetNodeCtx);
         }
 
-        Verify.verify(refineTargetNodeCtx instanceof StatementContextBase);
-        addOrReplaceNodes(subStmtCtx, (StatementContextBase<?, ?, ?>) refineTargetNodeCtx);
-        subStmtCtx.addAsEffectOfStatement(refineTargetNodeCtx);
+        // Target is a prerequisite for the 'refine', hence if the target is not supported, the refine is not supported
+        // as well. Otherwise add a pointer to the target into refine's local namespace.
+        if (refineTargetNodeCtx.isSupportedToBuildEffective()) {
+            subStmtCtx.addToNs(RefineTargetNamespace.class, Empty.getInstance(), refineTargetNodeCtx);
+        } else {
+            subStmtCtx.setIsSupportedToBuildEffective(false);
+        }
     }
 
     private static void addOrReplaceNodes(final Mutable<?, ?, ?> subStmtCtx,
@@ -253,23 +338,21 @@ public final class UsesStatementSupport
     private static void addOrReplaceNode(final Mutable<?, ?, ?> refineSubstatementCtx,
             final StatementContextBase<?, ?, ?> refineTargetNodeCtx) {
 
-        final StatementDefinition refineSubstatementDef = refineSubstatementCtx.getPublicDefinition();
+        final StatementDefinition refineSubstatementDef = refineSubstatementCtx.publicDefinition();
 
         SourceException.throwIf(!isSupportedRefineTarget(refineSubstatementCtx, refineTargetNodeCtx),
-                refineSubstatementCtx.getStatementSourceReference(),
+                refineSubstatementCtx,
                 "Error in module '%s' in the refine of uses '%s': can not perform refine of '%s' for the target '%s'.",
-                refineSubstatementCtx.getRoot().getStatementArgument(),
-                refineSubstatementCtx.coerceParentContext().getStatementArgument(),
-                refineSubstatementCtx.getPublicDefinition(), refineTargetNodeCtx.getPublicDefinition());
+                refineSubstatementCtx.getRoot().rawArgument(), refineSubstatementCtx.coerceParentContext().argument(),
+                refineSubstatementCtx.publicDefinition(), refineTargetNodeCtx.publicDefinition());
 
-        if (isAllowedToAddByRefine(refineSubstatementDef)) {
-            refineTargetNodeCtx.addEffectiveSubstatement(refineSubstatementCtx);
-        } else {
+        if (!isAllowedToAddByRefine(refineSubstatementDef)) {
             refineTargetNodeCtx.removeStatementFromEffectiveSubstatements(refineSubstatementDef);
-            refineTargetNodeCtx.addEffectiveSubstatement(refineSubstatementCtx);
         }
+        refineTargetNodeCtx.addEffectiveSubstatement(refineSubstatementCtx.replicaAsChildOf(refineTargetNodeCtx));
     }
 
+    // FIXME: clarify this and inline into single caller
     private static boolean isAllowedToAddByRefine(final StatementDefinition publicDefinition) {
         return YangStmtMapping.MUST.equals(publicDefinition);
     }
@@ -279,16 +362,16 @@ public final class UsesStatementSupport
                 ValidationBundlesNamespace.class, ValidationBundleType.SUPPORTED_REFINE_SUBSTATEMENTS);
 
         return supportedRefineSubstatements == null || supportedRefineSubstatements.isEmpty()
-                || supportedRefineSubstatements.contains(refineSubstatementCtx.getPublicDefinition())
+                || supportedRefineSubstatements.contains(refineSubstatementCtx.publicDefinition())
                 || StmtContextUtils.isUnknownStatement(refineSubstatementCtx);
     }
 
     private static boolean isSupportedRefineTarget(final StmtContext<?, ?, ?> refineSubstatementCtx,
             final StmtContext<?, ?, ?> refineTargetNodeCtx) {
         final Collection<?> supportedRefineTargets = YangValidationBundles.SUPPORTED_REFINE_TARGETS.get(
-            refineSubstatementCtx.getPublicDefinition());
+            refineSubstatementCtx.publicDefinition());
 
         return supportedRefineTargets == null || supportedRefineTargets.isEmpty()
-                || supportedRefineTargets.contains(refineTargetNodeCtx.getPublicDefinition());
+                || supportedRefineTargets.contains(refineTargetNodeCtx.publicDefinition());
     }
 }