Do not force materialization when not needed 54/94654/10
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 18 Jan 2021 06:56:56 +0000 (07:56 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 18 Jan 2021 21:23:50 +0000 (22:23 +0100)
When an InferredStatementContext was not forced to materialize
during inference, i.e. there was no movement in its substatements,
we can potentially reuse the same EffectiveStatement instance.

Refactor applyCopyPolicy() to provide the statement-specific
facility to decide which way the semantics goes. Based on this
feedback, the InferredStatementContext is improved to skip
instantiations when instructed to do so by the support.

AbstractStatementSupport is retrofitted to provide this functionality
through EffectiveComparator and its subclasses. Backwards compatibility
is maintained via choosing a conservative comparator based on
copy policy.

KeyStatementSupport is converted to take advantage of these
facilities.

JIRA: YANGTOOLS-1195
Change-Id: Idcea43f5ee121598eba324bac2a2edc70b11eaaa
Signed-off-by: miroslav.kovac <miroslav.kovac@pantheon.tech>
Signed-off-by: Michal Banik <michal.banik@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/InferredStatementContext.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/BaseStatementSupport.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/key/KeyStatementSupport.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/AbstractStatementSupport.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/ForwardingStatementSupport.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StatementFactory.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StatementSupport.java

index fb691e3c319ed460947c4be3b48cc7d6528fdcbd..d6c83d1157f1059f37b2de4a3f270f42f2a0a3d7 100644 (file)
@@ -13,12 +13,16 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.base.VerifyException;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Streams;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
@@ -36,6 +40,7 @@ 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.NamespaceBehaviour.OnDemandSchemaTreeStorageNode;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StatementFactory;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
@@ -178,6 +183,76 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, E extend
         return new InferredStatementContext<>(this, newParent);
     }
 
+    @Override
+    E createEffective(final StatementFactory<A, D, E> factory) {
+        // If we have not materialized we do not have a difference in effective substatements, hence we can forward
+        // towards the source of the statement.
+        return substatements == null ? tryToReusePrototype(factory) : super.createEffective(factory);
+    }
+
+    private @NonNull E tryToReusePrototype(final StatementFactory<A, D, E> factory) {
+        final E origEffective = prototype.buildEffective();
+        final Collection<? extends @NonNull EffectiveStatement<?, ?>> origSubstatements =
+            origEffective.effectiveSubstatements();
+
+        // First check if we can reuse the entire prototype
+        if (!factory.canReuseCurrent(this, prototype, origSubstatements)) {
+            // FIXME: YANGTOOLS-1067: an incremental improvement here is that we reuse statements that are not affected
+            //                        by us changing parent. For example: if our SchemaPath changed, but the namespace
+            //                        remained the same, 'key' statement should get reused.
+            // Fall back to full instantiation
+            return super.createEffective(factory);
+        }
+
+        // No substatements to deal with, we can freely reuse the original
+        if (origSubstatements.isEmpty()) {
+            LOG.debug("Reusing empty: {}", origEffective);
+            substatements = ImmutableList.of();
+            prototype.decRef();
+            return origEffective;
+        }
+
+        // We can reuse this statement let's see if all the statements agree
+        final List<Entry<Mutable<?, ?, ?>, Mutable<?, ?, ?>>> declared = prototype.streamDeclared()
+            .filter(StmtContext::isSupportedByFeatures)
+            .map(sub -> effectiveCopy((ReactorStmtCtx<?, ?, ?>) sub))
+            .filter(Objects::nonNull)
+            .collect(Collectors.toUnmodifiableList());
+        final List<Entry<Mutable<?, ?, ?>, Mutable<?, ?, ?>>> effective = prototype.streamEffective()
+            .map(sub -> effectiveCopy((ReactorStmtCtx<?, ?, ?>) sub))
+            .filter(Objects::nonNull)
+            .collect(Collectors.toUnmodifiableList());
+
+        // We no longer need the prototype's substatements, but we may need to retain ours
+        prototype.decRef();
+        if (haveRef()) {
+            substatements = Streams.concat(declared.stream(), effective.stream())
+                .map(Entry::getValue)
+                .collect(ImmutableList.toImmutableList());
+        } else {
+            // This should immediately get swept anyway. Should we use a poison object?
+            substatements = List.of();
+        }
+
+        if (allReused(declared) && allReused(effective)) {
+            LOG.debug("Reusing after substatement check: {}", origEffective);
+            return origEffective;
+        }
+
+        // Values are the effective copies, hence this efficienly deals with recursion.
+        return factory.createEffective(this, declared.stream().map(Entry::getValue),
+            effective.stream().map(Entry::getValue));
+    }
+
+    private static boolean allReused(final List<Entry<Mutable<?, ?, ?>, Mutable<?, ?, ?>>> entries) {
+        for (Entry<Mutable<?, ?, ?>, Mutable<?, ?, ?>> entry : entries) {
+            if (entry.getKey() != entry.getValue()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     boolean hasEmptySubstatements() {
         if (substatements == null) {
@@ -348,6 +423,16 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, E extend
         YangStmtMapping.TYPEDEF,
         YangStmtMapping.USES);
 
+    private Map.Entry<Mutable<?, ?, ?>, Mutable<?, ?, ?>> effectiveCopy(final ReactorStmtCtx<?, ?, ?> stmt) {
+        // FIXME: YANGTOOLS-652: formerly known as "isReusedByUses"
+        if (REUSED_DEF_SET.contains(stmt.definition().getPublicView())) {
+            return Map.entry(stmt, stmt.replicaAsChildOf(this));
+        }
+
+        final ReactorStmtCtx<?, ?, ?> effective = stmt.asEffectiveChildOf(this, childCopyType, targetModule);
+        return effective == null ? null : Map.entry(stmt, effective);
+    }
+
     private void copySubstatement(final Mutable<?, ?, ?> substatement, final Collection<Mutable<?, ?, ?>> buffer,
             final Map<StmtContext<?, ?, ?>, ReactorStmtCtx<?, ?, ?>> materializedSchemaTree) {
         final StatementDefinition def = substatement.publicDefinition();
@@ -376,7 +461,7 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, E extend
     }
 
     private Optional<? extends Mutable<?, ?, ?>> copySubstatement(final Mutable<?, ?, ?> substatement) {
-        // FIXME: YANGTOOLS-1195: this is not exactly what we want to do here, because we are deling with two different
+        // FIXME: YANGTOOLS-1195: this is not exactly what we want to do here, because we are dealing with two different
         //                        requests: copy for inference purposes (this method), while we also copy for purposes
         //                        of buildEffective() -- in which case we want to probably invoke asEffectiveChildOf()
         //                        or similar
index d8283e8882d0577ffa201b30a01b29153ddbe439..c8f3c02663412a980303eed305ff9c9204bdf88b 100644 (file)
@@ -19,6 +19,7 @@ import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 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.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
@@ -31,6 +32,7 @@ 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.UsesStatement;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+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;
@@ -306,6 +308,9 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
         // definition().onNamespaceElementAdded(this, type, key, value);
     }
 
+    abstract @Nullable ReactorStmtCtx<?, ?, ?> asEffectiveChildOf(StatementContextBase<?, ?, ?> parent, CopyType type,
+        QNameModule targetModule);
+
     //
     //
     // Statement build entry points -- both public and package-private.
@@ -605,6 +610,15 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
         }
     }
 
+    /**
+     * Return {@code true} if this context has an outstanding reference.
+     *
+     * @return True if this context has an outstanding reference.
+     */
+    final boolean haveRef() {
+        return refcount > REFCOUNT_NONE;
+    }
+
     private void lastDecRef() {
         if (noImplictRef()) {
             // We are no longer guarded by effective instance
index c870df6b4e0aab669d1f406397069cc9a2ba86e4..f93b39ed5307feb8a30ea612ec8259be8bf834c1 100644 (file)
@@ -112,6 +112,12 @@ final class ReplicaStatementContext<A, D extends DeclaredStatement<A>, E extends
         return source.copyAsChildOf(newParent, type, targetModule);
     }
 
+    @Override
+    ReactorStmtCtx<?, ?, ?> asEffectiveChildOf(final StatementContextBase<?, ?, ?> newParent, final CopyType type,
+            final QNameModule targetModule) {
+        return source.asEffectiveChildOf(newParent, type, targetModule);
+    }
+
     @Override
     StatementDefinitionContext<A, D, E> definition() {
         return source.definition();
index cd07b95b1cb51e30d3054050f5d6434d6234f96a..7232a0bc080b3707fcc0ca7c17563aa5bc8d5913 100644 (file)
@@ -44,6 +44,7 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceKeyCriterion;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StatementFactory;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport.CopyPolicy;
@@ -346,16 +347,25 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return effective.isEmpty() ? new ArrayList<>(toAdd) : effective;
     }
 
-
     @Override
     final E createEffective() {
-        final E result = definition.getFactory().createEffective(this, streamDeclared(), streamEffective());
+        final E result = createEffective(definition.getFactory());
         if (result instanceof MutableStatement) {
             getRoot().addMutableStmtToSeal((MutableStatement) result);
         }
         return result;
     }
 
+    @NonNull E createEffective(final StatementFactory<A, D, E> factory) {
+        return createEffective(factory, this);
+    }
+
+    // Creates EffectiveStatement through full materialization
+    static <A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>> @NonNull E createEffective(
+            final StatementFactory<A, D, E> factory, final StatementContextBase<A, D, E> ctx) {
+        return factory.createEffective(ctx, ctx.streamDeclared(), ctx.streamEffective());
+    }
+
     abstract Stream<? extends StmtContext<?, ?, ?>> streamDeclared();
 
     abstract Stream<? extends StmtContext<?, ?, ?>> streamEffective();
@@ -595,20 +605,25 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     public Optional<? extends Mutable<?, ?, ?>> copyAsChildOf(final Mutable<?, ?, ?> parent, final CopyType type,
             final QNameModule targetModule) {
         checkEffectiveModelCompleted(this);
+        return Optional.ofNullable(copyAsChildOfImpl(parent, type, targetModule));
+    }
 
+    private ReactorStmtCtx<A, D, E> copyAsChildOfImpl(final Mutable<?, ?, ?> parent, final CopyType type,
+            final QNameModule targetModule) {
         final StatementSupport<A, D, E> support = definition.support();
         final CopyPolicy policy = support.copyPolicy();
         switch (policy) {
             case CONTEXT_INDEPENDENT:
-                if (substatementsContextIndependent()) {
-                    return Optional.of(replicaAsChildOf(parent));
+                if (allSubstatementsContextIndependent()) {
+                    return replicaAsChildOf(parent);
                 }
 
                 // fall through
             case DECLARED_COPY:
-                return Optional.of(parent.childCopyOf(this, type, targetModule));
+                // FIXME: ugly cast
+                return (ReactorStmtCtx<A, D, E>) parent.childCopyOf(this, type, targetModule);
             case IGNORE:
-                return Optional.empty();
+                return null;
             case REJECT:
                 throw new IllegalStateException("Statement " + support.getPublicView() + " should never be copied");
             default:
@@ -616,35 +631,35 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         }
     }
 
-    private boolean substatementsContextIndependent() {
-        // FIXME: YANGTOOLS-1195: we really want to compute (and cache) the summary for substatements.
-        //
-        // For now we just check if there are any substatements, but we really want to ask:
-        //
-        //   Are all substatements (recursively) CONTEXT_INDEPENDENT as well?
-        //
-        // Which is something we want to compute once and store. This needs to be implemented.
-        return hasEmptySubstatements();
-    }
+    @Override
+    final ReactorStmtCtx<?, ?, ?> asEffectiveChildOf(final StatementContextBase<?, ?, ?> parent, final CopyType type,
+            final QNameModule targetModule) {
+        final ReactorStmtCtx<A, D, E> copy = copyAsChildOfImpl(parent, type, targetModule);
+        if (copy == null) {
+            // The statement fizzled, this should never happen, perhaps a verify()?
+            return null;
+        }
 
-    // FIXME: YANGTOOLS-1195: this method is unused, but should be called from InferredStatementContext at the very
-    //        least. It should return @NonNull -- either 'E' or EffectiveStmtCtx.Current'. Perhaps its arguments need
-    //        to be adjusted, too.
-    final void asEffectiveChildOf(final Mutable<?, ?, ?> parent, final CopyType type, final QNameModule targetModule) {
-        checkEffectiveModelCompleted(this);
+        parent.ensureCompletedPhase(copy);
+        return canReuseCurrent(copy) ? replicaAsChildOf(parent) : copy;
+    }
 
-        final StatementSupport<A, D, E> support = definition.support();
-        final StmtContext<?, ?, ?> effective = support.effectiveCopyOf(this, parent, type, targetModule);
-        if (effective == this) {
-            LOG.debug("Should reuse {}", this);
-            return;
-        }
+    private boolean canReuseCurrent(final ReactorStmtCtx<A, D, E> copy) {
+        // Defer to statement factory to see if we can reuse this object. If we can and have only context-independent
+        // substatements we can reuse the object. More complex cases are handled indirectly via the copy.
+        return definition.getFactory().canReuseCurrent(copy, this, buildEffective().effectiveSubstatements())
+            && allSubstatementsContextIndependent();
+    }
 
-        // FIXME: YANGTOOLS-1195: here is probably where we want to do some statement reuse: even if the parent is
-        //                        affected, some substatements may not -- in which case we want to reuse them. This
-        //                        probably needs to be a callout of some kind.
-        // FIXME: YANGTOOLS-1067: an incremental improvement to that is that if no substatements changed, we want to
-        //                        be reusing the entire List<EffectiveStatement> and pass that as substatements.
+    // FIXME: YANGTOOLS-1195: we really want to compute (and cache) the summary for substatements.
+    //
+    // For now we just check if there are any substatements, but we really want to ask:
+    //
+    //   Are all substatements (recursively) CONTEXT_INDEPENDENT as well?
+    //
+    // Which is something we want to compute once and store. This needs to be implemented.
+    private boolean allSubstatementsContextIndependent() {
+        return hasEmptySubstatements();
     }
 
     @Override
index e60164a92a0f817e5730bf09e847ba66ea536dd0..964cf3b6ce490b772d8b7650ff86c6a833ff858c 100644 (file)
@@ -37,11 +37,15 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 @Beta
 public abstract class BaseStatementSupport<A, D extends DeclaredStatement<A>,
         E extends EffectiveStatement<A, D>> extends AbstractStatementSupport<A, D, E> {
-
+    @Deprecated
     protected BaseStatementSupport(final StatementDefinition publicDefinition, final CopyPolicy copyPolicy) {
         super(publicDefinition, copyPolicy);
     }
 
+    protected BaseStatementSupport(final StatementDefinition publicDefinition, final StatementPolicy<A, D> policy) {
+        super(publicDefinition, policy);
+    }
+
     @Override
     public final D createDeclared(final StmtContext<A, D, ?> ctx) {
         final ImmutableList<? extends DeclaredStatement<?>> substatements = ctx.declaredSubstatements().stream()
index 27aa30050e60357ea414f7b5e285235c63dcc6bf..b33d565f56cf613c3aa87fa6cc1ec17d71535d1b 100644 (file)
@@ -54,13 +54,14 @@ public final class KeyStatementSupport
      */
     private static final Splitter KEY_ARG_SPLITTER = Splitter.on(SEP).omitEmptyStrings();
 
-    private static final SubstatementValidator SUBSTATEMENT_VALIDATOR = SubstatementValidator.builder(
-        YangStmtMapping.KEY)
-        .build();
+    private static final SubstatementValidator SUBSTATEMENT_VALIDATOR =
+        SubstatementValidator.builder(YangStmtMapping.KEY).build();
     private static final KeyStatementSupport INSTANCE = new KeyStatementSupport();
 
     private KeyStatementSupport() {
-        super(YangStmtMapping.KEY, CopyPolicy.DECLARED_COPY);
+        super(YangStmtMapping.KEY, StatementPolicy.copyDeclared(
+            // Identity comparison is sufficient because adaptArgumentValue() is careful about reuse.
+            (copy, current, substatements) -> copy.getArgument() == current.getArgument()));
     }
 
     public static KeyStatementSupport getInstance() {
@@ -97,8 +98,7 @@ public final class KeyStatementSupport
             }
         }
 
-        // This makes sure we reuse the collection when a grouping is
-        // instantiated in the same module
+        // This makes sure we reuse the collection when a grouping is instantiated in the same module.
         return replaced ? builder.build() : ctx.argument();
     }
 
index 0622463b8212cde180b5b7c2dfdcfef60eae35bc..f8ddbf3e0d3619f3d391776f325e787b1e502c4b 100644 (file)
@@ -12,13 +12,14 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.VerifyException;
+import java.util.Collection;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.concepts.Immutable;
 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.parser.spi.meta.StmtContext.Mutable;
+import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
 
 /**
  * Class providing necessary support for processing a YANG statement. This class is intended to be subclassed
@@ -30,17 +31,192 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
  */
 public abstract class AbstractStatementSupport<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
         implements StatementDefinition, StatementFactory<A, D, E>, StatementSupport<A, D, E> {
+    /**
+     * A baseline class for implementing the {@link StatementFactory#canReuseCurrent(Current, Current, Collection)}
+     * contract in a manner which is consistent with a statement's {@link CopyPolicy}.
+     *
+     * @param <A> Argument type
+     * @param <D> Declared Statement representation
+     */
+    public abstract static class StatementPolicy<A, D extends DeclaredStatement<A>> implements Immutable {
+        final @NonNull CopyPolicy copyPolicy;
+
+        StatementPolicy(final CopyPolicy copyPolicy) {
+            this.copyPolicy = requireNonNull(copyPolicy);
+        }
+
+        /**
+         * Return an {@link StatementPolicy} for {@link CopyPolicy#CONTEXT_INDEPENDENT}.
+         *
+         * @param <A> Argument type
+         * @param <D> Declared Statement representation
+         * @return Context-independent policy
+         */
+        @SuppressWarnings("unchecked")
+        public static final <A, D extends DeclaredStatement<A>> @NonNull StatementPolicy<A, D> contextIndependent() {
+            return (StatementPolicy<A, D>) AlwaysReuse.CONTEXT_INDEPENDENT;
+        }
+
+        /**
+         * Return an {@link StatementPolicy} for {@link CopyPolicy#IGNORE}.
+         *
+         * @param <A> Argument type
+         * @param <D> Declared Statement representation
+         * @return Ignoring policy
+         */
+        @SuppressWarnings("unchecked")
+        public static final <A, D extends DeclaredStatement<A>> @NonNull StatementPolicy<A, D> ignore() {
+            return (StatementPolicy<A, D>) AlwaysFail.IGNORE;
+        }
+
+        /**
+         * Return an {@link StatementPolicy} for {@link CopyPolicy#REJECT}.
+         *
+         * @param <A> Argument type
+         * @param <D> Declared Statement representation
+         * @return Rejecting statement policy
+         */
+        @SuppressWarnings("unchecked")
+        public static final <A, D extends DeclaredStatement<A>> @NonNull StatementPolicy<A, D> reject() {
+            return (StatementPolicy<A, D>) AlwaysFail.REJECT;
+        }
+
+        /**
+         * Return an {@link StatementPolicy} for {@link CopyPolicy#DECLARED_COPY}, deferring to a
+         * {@link StatementEquality} for individual decisions.
+         *
+         * @param <A> Argument type
+         * @param <D> Declared Statement representation
+         * @param equality {@link StatementEquality} to apply to effective statements
+         * @return Rejecting statement policy
+         */
+        public static final <A, D extends DeclaredStatement<A>> @NonNull StatementPolicy<A, D> copyDeclared(
+                final @NonNull StatementEquality<A, D> equality) {
+            return new EqualSemantics<>(equality);
+        }
+
+        abstract boolean canReuseCurrent(@NonNull Current<A, D> copy, @NonNull Current<A, D> current,
+            @NonNull Collection<? extends EffectiveStatement<?, ?>> substatements);
+
+        @Deprecated
+        @SuppressWarnings("unchecked")
+        static <A, D extends DeclaredStatement<A>> StatementPolicy<A, D> compat(final CopyPolicy copyPolicy) {
+            switch (copyPolicy) {
+                case CONTEXT_INDEPENDENT:
+                    return contextIndependent();
+                case DECLARED_COPY:
+                    return (StatementPolicy<A, D>) AlwaysCopy.DECLARED_COPY;
+                case IGNORE:
+                    return ignore();
+                case REJECT:
+                    return reject();
+                default:
+                    throw new IllegalStateException("Unsupported policy " + copyPolicy);
+            }
+        }
+
+        private static final class AlwaysCopy<A, D extends DeclaredStatement<A>> extends StatementPolicy<A, D> {
+            @Deprecated
+            static final @NonNull AlwaysCopy<?, ?> DECLARED_COPY = new AlwaysCopy<>(CopyPolicy.DECLARED_COPY);
+
+            AlwaysCopy(final CopyPolicy copyPolicy) {
+                super(copyPolicy);
+            }
+
+            @Override
+            boolean canReuseCurrent(final Current<A, D> copy, final Current<A, D> current,
+                    final Collection<? extends EffectiveStatement<?, ?>> substatements) {
+                return false;
+            }
+        }
+
+        private static final class AlwaysReuse<A, D extends DeclaredStatement<A>> extends StatementPolicy<A, D> {
+            static final @NonNull AlwaysReuse<?, ?> CONTEXT_INDEPENDENT =
+                new AlwaysReuse<>(CopyPolicy.CONTEXT_INDEPENDENT);
+
+            private AlwaysReuse(final CopyPolicy copyPolicy) {
+                super(copyPolicy);
+            }
 
+            @Override
+            boolean canReuseCurrent(final Current<A, D> copy, final Current<A, D> current,
+                    final Collection<? extends EffectiveStatement<?, ?>> substatements) {
+                return true;
+            }
+        }
+
+        private static final class AlwaysFail<A, D extends DeclaredStatement<A>> extends StatementPolicy<A, D> {
+            static final @NonNull AlwaysFail<?, ?> IGNORE = new AlwaysFail<>(CopyPolicy.IGNORE);
+            static final @NonNull AlwaysFail<?, ?> REJECT = new AlwaysFail<>(CopyPolicy.REJECT);
+
+            private AlwaysFail(final CopyPolicy copyPolicy) {
+                super(copyPolicy);
+            }
+
+            @Override
+            boolean canReuseCurrent(final Current<A, D> copy, final Current<A, D> current,
+                    final Collection<? extends EffectiveStatement<?, ?>> substatements) {
+                throw new VerifyException("This implementation should never be invoked");
+            }
+        }
+
+        private static final class EqualSemantics<A, D extends DeclaredStatement<A>> extends StatementPolicy<A, D> {
+            private final @NonNull StatementEquality<A, D> equality;
+
+            EqualSemantics(final @NonNull StatementEquality<A, D> equality) {
+                super(CopyPolicy.DECLARED_COPY);
+                this.equality = requireNonNull(equality);
+            }
+
+            @Override
+            boolean canReuseCurrent(final Current<A, D> copy, final Current<A, D> current,
+                    final Collection<? extends EffectiveStatement<?, ?>> substatements) {
+                return equality.canReuseCurrent(copy, current, substatements);
+            }
+        }
+    }
+
+    /**
+     * Abstract base class for comparators associated with statements with a {@link CopyPolicy#DECLARED_COPY} copy
+     * policy.
+     *
+     * @param <A> Argument type
+     * @param <D> Declared Statement representation
+     */
+    @FunctionalInterface
+    public interface StatementEquality<A, D extends DeclaredStatement<A>> {
+        /**
+         * Determine whether {@code current} statement has the same semantics as the provided copy. See the contract
+         * specification of {@link StatementFactory#canReuseCurrent(Current, Current, Collection)}.
+         *
+         * @param copy Copy of current effective context
+         * @param current Current effective context
+         * @param substatements Current effective substatements
+         * @return True if {@code current} can be reused in place of {@code copy}, false if the copy needs to be used.
+         */
+        boolean canReuseCurrent(@NonNull Current<A, D> copy, @NonNull Current<A, D> current,
+            @NonNull Collection<? extends EffectiveStatement<?, ?>> substatements);
+    }
+
+    private final @NonNull StatementPolicy<A, D> policy;
     private final @NonNull StatementDefinition type;
     private final @NonNull CopyPolicy copyPolicy;
 
     @Beta
-    protected AbstractStatementSupport(final StatementDefinition publicDefinition, final CopyPolicy copyPolicy) {
+    protected AbstractStatementSupport(final StatementDefinition publicDefinition, final StatementPolicy<A, D> policy) {
         this.type = requireNonNull(publicDefinition);
-        this.copyPolicy = requireNonNull(copyPolicy);
+        this.policy = requireNonNull(policy);
+        this.copyPolicy = policy.copyPolicy;
         checkArgument(publicDefinition != this);
     }
 
+    @Beta
+    @Deprecated
+    // FIXME: remove this constructor
+    protected AbstractStatementSupport(final StatementDefinition publicDefinition, final CopyPolicy copyPolicy) {
+        this(publicDefinition, StatementPolicy.compat(copyPolicy));
+    }
+
     @Override
     public final StatementDefinition getPublicView() {
         return type;
@@ -52,18 +228,9 @@ public abstract class AbstractStatementSupport<A, D extends DeclaredStatement<A>
     }
 
     @Override
-    public final StmtContext<?, ?, ?> effectiveCopyOf(final StmtContext<?, ?, ?> stmt, final Mutable<?, ?, ?> parent,
-            final CopyType copyType, final QNameModule targetModule) {
-        switch (copyPolicy) {
-            case CONTEXT_INDEPENDENT:
-                return stmt;
-            case DECLARED_COPY:
-                // FIXME: YANGTOOLS-1195: this is too harsh, we need to make a callout to subclass methods so they
-                //                        actually examine the differences.
-                return parent.childCopyOf(stmt, copyType, targetModule);
-            default:
-                throw new VerifyException("Attempted to apply " + copyPolicy);
-        }
+    public final boolean canReuseCurrent(final Current<A, D> copy, final Current<A, D> current,
+             final Collection<? extends EffectiveStatement<?, ?>> substatements) {
+        return policy.canReuseCurrent(copy, current, substatements);
     }
 
     @Override
index 827a97bdc9488cab6ecce9e1d46f64ff70e463b0..0d15f1451aea98c63489027962bb242d655a0c3f 100644 (file)
@@ -9,8 +9,8 @@ package org.opendaylight.yangtools.yang.parser.spi.meta;
 
 import com.google.common.annotations.Beta;
 import com.google.common.collect.ForwardingObject;
+import java.util.Collection;
 import java.util.stream.Stream;
-import org.opendaylight.yangtools.yang.common.QNameModule;
 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;
@@ -97,8 +97,8 @@ public abstract class ForwardingStatementSupport<A, D extends DeclaredStatement<
     }
 
     @Override
-    public StmtContext<?, ?, ?> effectiveCopyOf(final StmtContext<?, ?, ?> stmt, final Mutable<?, ?, ?> parent,
-            final CopyType copyType, final QNameModule targetModule) {
-        return delegate().effectiveCopyOf(stmt, parent, copyType, targetModule);
+    public boolean canReuseCurrent(final Current<A, D> copy, final Current<A, D> current,
+            final Collection<? extends EffectiveStatement<?, ?>> substatements) {
+        return delegate().canReuseCurrent(copy, current, substatements);
     }
 }
index f929529c63277cefa8ac569d23af7d34606800e4..793c0e3534fb00b4cf3b97be41942d57486c5646 100644 (file)
@@ -7,10 +7,12 @@
  */
 package org.opendaylight.yangtools.yang.parser.spi.meta;
 
+import java.util.Collection;
 import java.util.stream.Stream;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
 
 /**
  * An entity capable of creating {@link DeclaredStatement} and {@link EffectiveStatement} instances for a particular
@@ -38,4 +40,22 @@ public interface StatementFactory<A, D extends DeclaredStatement<A>, E extends E
     @NonNull E createEffective(EffectiveStmtCtx.@NonNull Current<A, D> stmt,
         Stream<? extends StmtContext<?, ?, ?>> declaredSubstatements,
         Stream<? extends StmtContext<?, ?, ?>> effectiveSubstatements);
+
+    /**
+     * Determine reactor copy behaviour of a statement instance. Implementations classes are required to determine
+     * their operations with regard to their statements being replicated into different contexts -- potentially sharing
+     * instantiations.
+     *
+     * <p>
+     * Implementations are examine {@code copy} as to whether it would result in the same semantics as {@code current}
+     * does, provided that {@code current}'s {@code substatements} are properly propagated.
+     *
+     * @param copy Copy of current effective context
+     * @param current Current effective context
+     * @param substatements Current effective substatements
+     * @return True if the differences between {@code copy} and {@code current} do not affect this statement's effective
+     *         semantics.
+     */
+    boolean canReuseCurrent(@NonNull Current<A, D> copy, @NonNull Current<A, D> current,
+        @NonNull Collection<? extends EffectiveStatement<?, ?>> substatements);
 }
index 419c28c60d216e644a1a0acdf1ba84614e6573ab..05e5f47f4c8e0348ca9e72c50049eead268e3614 100644 (file)
@@ -17,7 +17,6 @@ import org.opendaylight.yangtools.yang.model.api.meta.ArgumentDefinition;
 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.parser.spi.meta.StmtContext.Mutable;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 /**
@@ -175,34 +174,6 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      */
     @NonNull CopyPolicy copyPolicy();
 
-    /**
-     * Determine reactor copy behaviour of a statement instance. Statement support classes are required to determine
-     * their operations with regard to their statements being replicated into different contexts, so that
-     * {@link Mutable} instances are not created when it is evident they are superfluous.
-     *
-     * <p>
-     * The copy operation has three intrinsic parts:
-     * <ul>
-     *   <li>target {@code parent}, i.e. new parent statement for the copy. This determines things like default value
-     *       of the {@code config} statement and similar</li>
-     *   <li>copy operation type</li>
-     *   <li>{@code target module}, which defines the default namespace for the statement copy. This might not be always
-     *       present, in which case the namespace is retained from the source. As an example, {@code uses} changes
-     *       the default namespace to parent's namespace, whereas {@code augment} does not.</li>
-     * </ul>
-     *
-     * <p>
-     * Implementations should return the context to use -- returning {@code stmt} if there is no change or a copy of it.
-     *
-     * @param stmt Context of statement to be copied statement
-     * @param parent Parent statement context
-     * @param copyType Type of copy being performed
-     * @param targetModule Target module, if present
-     * @return StmtContext holding the effective state
-     */
-    @NonNull StmtContext<?, ?, ?> effectiveCopyOf(StmtContext<?, ?, ?> stmt, Mutable<?, ?, ?> parent, CopyType copyType,
-        @Nullable QNameModule targetModule);
-
     /**
      * Given a raw string representation of an argument, try to use a shared representation.
      *
@@ -272,8 +243,7 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
 
     /**
      * Statement context copy policy, indicating how should reactor handle statement copy operations. Every statement
-     * copied by the reactor is subject to policy check done by
-     * {@link StatementSupport#effectiveCopyOf(StmtContext, Mutable, CopyType, QNameModule)}.
+     * copied by the reactor is subject to this policy.
      */
     enum CopyPolicy {
         /**