Remove unused constructor
[yangtools.git] / yang / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / StatementContextBase.java
index 899345574ab9f99a4d4ccbd137ba599788e9861b..65656d4efa5ba85c3e4541b6cf033756f4170547 100644 (file)
@@ -8,12 +8,13 @@
 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.VerifyException;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultimap;
@@ -30,7 +31,6 @@ import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.stream.Stream;
 import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
 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;
@@ -40,10 +40,12 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ImplicitParentAwareStatementSupport;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase.ExecutionOrder;
 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;
@@ -63,7 +65,7 @@ import org.slf4j.LoggerFactory;
  * @param <E> Effective Statement representation
  */
 public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
-        extends ReactorStmtCtx<A, D, E> {
+        extends ReactorStmtCtx<A, D, E> implements CopyHistory {
     /**
      * Event listener when an item is added to model namespace.
      */
@@ -94,47 +96,82 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     private static final Logger LOG = LoggerFactory.getLogger(StatementContextBase.class);
 
-    private final CopyHistory copyHistory;
+    //
+    // {@link CopyHistory} encoded as a single byte. We still have 4 bits unused.
+    //
+    private static final byte COPY_LAST_TYPE_MASK        = 0x03;
+    private static final byte COPY_ADDED_BY_USES         = 0x04;
+    private static final byte COPY_ADDED_BY_AUGMENTATION = 0x08;
+    private static final byte COPY_ORIGINAL              = 0x00;
+
+    private final byte copyHistory;
+
+    static {
+        final int copyTypes = CopyType.values().length;
+        // This implies CopyType.ordinal() is <= COPY_TYPE_MASK
+        verify(copyTypes == COPY_LAST_TYPE_MASK + 1, "Unexpected %s CopyType values", copyTypes);
+    }
+
     // Note: this field can strictly be derived in InferredStatementContext, but it forms the basis of many of our
     //       operations, hence we want to keep it close by.
     private final @NonNull StatementDefinitionContext<A, D, E> definition;
 
+    // TODO: consider keying by Byte equivalent of ExecutionOrder
     private Multimap<ModelProcessingPhase, OnPhaseFinished> phaseListeners = ImmutableMultimap.of();
     private Multimap<ModelProcessingPhase, ContextMutation> phaseMutation = ImmutableMultimap.of();
 
     private List<StmtContext<?, ?, ?>> effectOfStatement = ImmutableList.of();
 
-    private @Nullable ModelProcessingPhase completedPhase;
+    /**
+     * {@link ModelProcessingPhase.ExecutionOrder} value of current {@link ModelProcessingPhase} of this statement.
+     */
+    private byte executionOrder;
 
     // Copy constructor used by subclasses to implement reparent()
     StatementContextBase(final StatementContextBase<A, D, E> original) {
         super(original);
         this.copyHistory = original.copyHistory;
         this.definition = original.definition;
-        this.completedPhase = original.completedPhase;
+        this.executionOrder = original.executionOrder;
     }
 
     StatementContextBase(final StatementDefinitionContext<A, D, E> def) {
         this.definition = requireNonNull(def);
-        this.copyHistory = CopyHistory.original();
+        this.copyHistory = COPY_ORIGINAL;
     }
 
-    StatementContextBase(final StatementDefinitionContext<A, D, E> def, final CopyHistory copyHistory) {
+    StatementContextBase(final StatementDefinitionContext<A, D, E> def, final CopyType copyType) {
         this.definition = requireNonNull(def);
-        this.copyHistory = requireNonNull(copyHistory);
+        this.copyHistory = (byte) copyFlags(copyType);
     }
 
-    @Override
-    public Collection<? extends StmtContext<?, ?, ?>> getEffectOfStatement() {
-        return effectOfStatement;
+    StatementContextBase(final StatementContextBase<A, D, E> prototype, final CopyType copyType) {
+        this.definition = prototype.definition;
+        this.copyHistory = (byte) (copyFlags(copyType) | prototype.copyHistory & ~COPY_LAST_TYPE_MASK);
     }
 
-    @Override
-    public void addAsEffectOfStatement(final StmtContext<?, ?, ?> ctx) {
-        if (effectOfStatement.isEmpty()) {
-            effectOfStatement = new ArrayList<>(1);
+    private static int copyFlags(final CopyType copyType) {
+        return historyFlags(copyType) | copyType.ordinal();
+    }
+
+    private static byte historyFlags(final CopyType copyType) {
+        switch (copyType) {
+            case ADDED_BY_AUGMENTATION:
+                return COPY_ADDED_BY_AUGMENTATION;
+            case ADDED_BY_USES:
+                return COPY_ADDED_BY_USES;
+            case ADDED_BY_USES_AUGMENTATION:
+                return COPY_ADDED_BY_AUGMENTATION | COPY_ADDED_BY_USES;
+            case ORIGINAL:
+                return COPY_ORIGINAL;
+            default:
+                throw new VerifyException("Unhandled type " + copyType);
         }
-        effectOfStatement.add(ctx);
+    }
+
+    @Override
+    public Collection<? extends StmtContext<?, ?, ?>> getEffectOfStatement() {
+        return effectOfStatement;
     }
 
     @Override
@@ -149,20 +186,43 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         effectOfStatement.addAll(ctxs);
     }
 
+    //
+    // CopyHistory integration
+    //
+
     @Override
     public final CopyHistory history() {
-        return copyHistory;
+        return this;
     }
 
     @Override
-    public final ModelProcessingPhase getCompletedPhase() {
-        return completedPhase;
+    public final boolean isAddedByUses() {
+        return (copyHistory & COPY_ADDED_BY_USES) != 0;
+    }
+
+    @Override
+    public final boolean isAugmenting() {
+        return (copyHistory & COPY_ADDED_BY_AUGMENTATION) != 0;
+    }
+
+    @Override
+    public final CopyType getLastOperation() {
+        return CopyType.values()[copyHistory & COPY_LAST_TYPE_MASK];
+    }
+
+    //
+    // Inference completion tracking
+    //
+
+    @Override
+    final byte executionOrder() {
+        return executionOrder;
     }
 
     // FIXME: this should be propagated through a correct constructor
     @Deprecated
     final void setCompletedPhase(final ModelProcessingPhase completedPhase) {
-        this.completedPhase = completedPhase;
+        this.executionOrder = completedPhase.executionOrder();
     }
 
     @Override
@@ -264,10 +324,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
         final List<ReactorStmtCtx<?, ?, ?>> resized = beforeAddEffectiveStatement(effective, 1);
         final ReactorStmtCtx<?, ?, ?> stmt = (ReactorStmtCtx<?, ?, ?>) substatement;
-        final ModelProcessingPhase phase = completedPhase;
-        if (phase != null) {
-            ensureCompletedPhase(stmt, phase);
-        }
+        ensureCompletedExecution(stmt);
         resized.add(stmt);
         return resized;
     }
@@ -293,12 +350,11 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     final List<ReactorStmtCtx<?, ?, ?>> addEffectiveSubstatementsImpl(final List<ReactorStmtCtx<?, ?, ?>> effective,
             final Collection<? extends Mutable<?, ?, ?>> statements) {
         final List<ReactorStmtCtx<?, ?, ?>> resized = beforeAddEffectiveStatement(effective, statements.size());
-        final Collection<? extends StatementContextBase<?, ?, ?>> casted =
-            (Collection<? extends StatementContextBase<?, ?, ?>>) statements;
-        final ModelProcessingPhase phase = completedPhase;
-        if (phase != null) {
-            for (StatementContextBase<?, ?, ?> stmt : casted) {
-                ensureCompletedPhase(stmt, phase);
+        final Collection<? extends ReactorStmtCtx<?, ?, ?>> casted =
+            (Collection<? extends ReactorStmtCtx<?, ?, ?>>) statements;
+        if (executionOrder != ExecutionOrder.NULL) {
+            for (ReactorStmtCtx<?, ?, ?> stmt : casted) {
+                ensureCompletedExecution(stmt, executionOrder);
             }
         }
 
@@ -311,17 +367,20 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     // exposed for InferredStatementContext only
     final void ensureCompletedPhase(final Mutable<?, ?, ?> stmt) {
         verifyStatement(stmt);
-        final ModelProcessingPhase phase = completedPhase;
-        if (phase != null) {
-            ensureCompletedPhase((ReactorStmtCtx<?, ?, ?>) stmt, phase);
+        ensureCompletedExecution((ReactorStmtCtx<?, ?, ?>) stmt);
+    }
+
+    // Make sure target statement has transitioned at least to our phase (if we have one). This method is just before we
+    // take allow a statement to become our substatement. This is needed to ensure that every statement tree does not
+    // contain any statements which did not complete the same phase as the root statement.
+    private void ensureCompletedExecution(final ReactorStmtCtx<?, ?, ?> stmt) {
+        if (executionOrder != ExecutionOrder.NULL) {
+            ensureCompletedExecution(stmt, executionOrder);
         }
     }
 
-    // Make sure target statement has transitioned at least to specified phase. This method is just before we take
-    // allow a statement to become our substatement. This is needed to ensure that every statement tree does not contain
-    // any statements which did not complete the same phase as the root statement.
-    private static void ensureCompletedPhase(final ReactorStmtCtx<?, ?, ?> stmt, final ModelProcessingPhase phase) {
-        verify(stmt.tryToCompletePhase(phase), "Statement %s cannot complete phase %s", stmt, phase);
+    private static void ensureCompletedExecution(final ReactorStmtCtx<?, ?, ?> stmt, final byte executionOrder) {
+        verify(stmt.tryToCompletePhase(executionOrder), "Statement %s cannot complete phase %s", stmt, executionOrder);
     }
 
     private static void verifyStatement(final Mutable<?, ?, ?> stmt) {
@@ -330,8 +389,10 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     private List<ReactorStmtCtx<?, ?, ?>> beforeAddEffectiveStatement(final List<ReactorStmtCtx<?, ?, ?>> effective,
             final int toAdd) {
-        // We cannot allow statement to be further mutated
-        verify(completedPhase != ModelProcessingPhase.EFFECTIVE_MODEL, "Cannot modify finished statement at %s",
+        // We cannot allow statement to be further mutated.
+        // TODO: we really want to say 'not NULL and not at or after EFFECTIVE_MODEL here. This will matter if we have
+        //       a phase after EFFECTIVE_MODEL
+        verify(executionOrder != ExecutionOrder.EFFECTIVE_MODEL, "Cannot modify finished statement at %s",
             sourceReference());
         return beforeAddEffectiveStatementUnsafe(effective, toAdd);
     }
@@ -346,42 +407,66 @@ 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;
     }
 
-    abstract Stream<? extends StmtContext<?, ?, ?>> streamDeclared();
+    @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<?, ?, ?>> streamEffective();
+    /**
+     * Return a stream of declared statements which can be built into an {@link EffectiveStatement}, as per
+     * {@link StmtContext#buildEffective()} contract.
+     *
+     * @return Stream of supported declared statements.
+     */
+    // FIXME: we really want to unify this with streamEffective(), under its name
+    abstract Stream<? extends @NonNull StmtContext<?, ?, ?>> streamDeclared();
+
+    /**
+     * Return a stream of inferred statements which can be built into an {@link EffectiveStatement}, as per
+     * {@link StmtContext#buildEffective()} contract.
+     *
+     * @return Stream of supported effective statements.
+     */
+    // FIXME: this method is currently a misnomer, but unifying with streamDeclared() would make this accurate again
+    abstract Stream<? extends @NonNull StmtContext<?, ?, ?>> streamEffective();
 
     @Override
-    final boolean doTryToCompletePhase(final ModelProcessingPhase phase) {
-        final boolean finished = phaseMutation.isEmpty() ? true : runMutations(phase);
-        if (completeChildren(phase) && finished) {
-            onPhaseCompleted(phase);
+    final boolean doTryToCompletePhase(final byte targetOrder) {
+        final boolean finished = phaseMutation.isEmpty() ? true : runMutations(targetOrder);
+        if (completeChildren(targetOrder) && finished) {
+            onPhaseCompleted(targetOrder);
             return true;
         }
         return false;
     }
 
-    private boolean completeChildren(final ModelProcessingPhase phase) {
+    private boolean completeChildren(final byte targetOrder) {
         boolean finished = true;
         for (final StatementContextBase<?, ?, ?> child : mutableDeclaredSubstatements()) {
-            finished &= child.tryToCompletePhase(phase);
+            finished &= child.tryToCompletePhase(targetOrder);
         }
         for (final ReactorStmtCtx<?, ?, ?> child : effectiveChildrenToComplete()) {
-            finished &= child.tryToCompletePhase(phase);
+            finished &= child.tryToCompletePhase(targetOrder);
         }
         return finished;
     }
 
-    private boolean runMutations(final ModelProcessingPhase phase) {
+    private boolean runMutations(final byte targetOrder) {
+        final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(targetOrder));
         final Collection<ContextMutation> openMutations = phaseMutation.get(phase);
         return openMutations.isEmpty() ? true : runMutations(phase, openMutations);
     }
@@ -412,22 +497,73 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
-     * Occurs on end of {@link ModelProcessingPhase} of source parsing.
+     * Occurs on end of {@link ModelProcessingPhase} of source parsing. This method must not be called with
+     * {@code executionOrder} equal to {@link ExecutionOrder#NULL}.
      *
-     * @param phase
-     *            that was to be completed (finished)
-     * @throws SourceException
-     *             when an error occurred in source parsing
+     * @param phase that was to be completed (finished)
+     * @throws SourceException when an error occurred in source parsing
      */
-    private void onPhaseCompleted(final ModelProcessingPhase phase) {
-        completedPhase = phase;
+    private void onPhaseCompleted(final byte completedOrder) {
+        executionOrder = completedOrder;
+        if (completedOrder == ExecutionOrder.EFFECTIVE_MODEL) {
+            // We have completed effective model, substatements are guaranteed not to change
+            summarizeSubstatementPolicy();
+        }
 
+        final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(completedOrder));
         final Collection<OnPhaseFinished> listeners = phaseListeners.get(phase);
         if (!listeners.isEmpty()) {
             runPhaseListeners(phase, listeners);
         }
     }
 
+    private void summarizeSubstatementPolicy() {
+        if (definition().support().copyPolicy() == CopyPolicy.EXACT_REPLICA || noSensitiveSubstatements()) {
+            setAllSubstatementsContextIndependent();
+        }
+    }
+
+    /**
+     * Determine whether any substatements are copy-sensitive as determined by {@link StatementSupport#copyPolicy()}.
+     * Only {@link CopyPolicy#CONTEXT_INDEPENDENT}, {@link CopyPolicy#EXACT_REPLICA} and {@link CopyPolicy#IGNORE} are
+     * copy-insensitive. Note that statements which are not {@link StmtContext#isSupportedToBuildEffective()} are all
+     * considered copy-insensitive.
+     *
+     * <p>
+     * Implementations are expected to call {@link #noSensitiveSubstatements()} to actually traverse substatement sets.
+     *
+     * @return True if no substatements require copy-sensitive handling
+     */
+    abstract boolean noSensitiveSubstatements();
+
+    /**
+     * Determine whether any of the provided substatements are context-sensitive for purposes of implementing
+     * {@link #noSensitiveSubstatements()}.
+     *
+     * @param substatements Substatements to check
+     * @return True if no substatements require context-sensitive handling
+     */
+    static boolean noSensitiveSubstatements(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
+        for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
+            if (stmt.isSupportedToBuildEffective()) {
+                if (!stmt.allSubstatementsContextIndependent()) {
+                    // This is a recursive property
+                    return false;
+                }
+
+                switch (stmt.definition().support().copyPolicy()) {
+                    case CONTEXT_INDEPENDENT:
+                    case EXACT_REPLICA:
+                    case IGNORE:
+                        break;
+                    default:
+                        return false;
+                }
+            }
+        }
+        return true;
+    }
+
     private void runPhaseListeners(final ModelProcessingPhase phase, final Collection<OnPhaseFinished> listeners) {
         final Iterator<OnPhaseFinished> listener = listeners.iterator();
         while (listener.hasNext()) {
@@ -541,10 +677,10 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      * @throws NullPointerException if any of the arguments is null
      */
     void addPhaseCompletedListener(final ModelProcessingPhase phase, final OnPhaseFinished listener) {
-        checkNotNull(phase, "Statement context processing phase cannot be null at: %s", sourceReference());
-        checkNotNull(listener, "Statement context phase listener cannot be null at: %s", sourceReference());
+        requireNonNull(phase, "Statement context processing phase cannot be null");
+        requireNonNull(listener, "Statement context phase listener cannot be null");
 
-        ModelProcessingPhase finishedPhase = completedPhase;
+        ModelProcessingPhase finishedPhase = ModelProcessingPhase.ofExecutionOrder(executionOrder);
         while (finishedPhase != null) {
             if (phase.equals(finishedPhase)) {
                 listener.phaseFinished(this, finishedPhase);
@@ -565,12 +701,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      * @throws IllegalStateException when the mutation was registered after phase was completed
      */
     final void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) {
-        ModelProcessingPhase finishedPhase = completedPhase;
-        while (finishedPhase != null) {
-            checkState(!phase.equals(finishedPhase), "Mutation registered after phase was completed at: %s",
-                sourceReference());
-            finishedPhase = finishedPhase.getPreviousPhase();
-        }
+        checkState(executionOrder < phase.executionOrder(), "Mutation registered after phase was completed at: %s",
+            sourceReference());
 
         if (phaseMutation.isEmpty()) {
             phaseMutation = newMultimap();
@@ -595,28 +727,27 @@ 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.applyCopyPolicy(this, parent, type, targetModule);
+        final CopyPolicy policy = support.copyPolicy();
         switch (policy) {
+            case EXACT_REPLICA:
+                return replicaAsChildOf(parent);
             case CONTEXT_INDEPENDENT:
-                if (hasEmptySubstatements()) {
-                    // This statement is context-independent and has no substatements -- hence it can be freely shared.
-                    return Optional.of(replicaAsChildOf(parent));
+                if (allSubstatementsContextIndependent()) {
+                    return replicaAsChildOf(parent);
                 }
-                // ascertaining substatements could be quite costly, let's just fall through to declared copy and deal
-                // shortcut it when we build the statements.
+
                 // fall through
             case DECLARED_COPY:
-                // FIXME: YANGTOOLS-694: this is still to eager, we really want to copy as a lazily-instantiated
-                //                       context, so that we can support building an effective statement without copying
-                //                       anything -- we will typically end up not being inferred against. In that case,
-                //                       this slim context should end up dealing with differences at buildContext()
-                //                       time. This is a YANGTOOLS-1067 prerequisite (which will deal with what can and
-                //                       cannot be shared across instances).
-                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:
@@ -624,6 +755,26 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         }
     }
 
+    @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;
+        }
+
+        parent.ensureCompletedPhase(copy);
+        return canReuseCurrent(copy) ? this : copy;
+    }
+
+    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();
+    }
+
     @Override
     public final Mutable<?, ?, ?> childCopyOf(final StmtContext<?, ?, ?> stmt, final CopyType type,
             final QNameModule targetModule) {
@@ -670,13 +821,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     @Override
-    public final ReactorStmtCtx<A, D, E> replicaAsChildOf(final Mutable<?, ?, ?> parent) {
-        checkArgument(parent instanceof StatementContextBase, "Unsupported parent %s", parent);
-        return replicaAsChildOf((StatementContextBase<?, ?, ?>) parent);
-    }
-
-    final @NonNull ReplicaStatementContext<A, D, E> replicaAsChildOf(final StatementContextBase<?, ?, ?> stmt) {
-        return new ReplicaStatementContext<>(stmt, this);
+    final ReplicaStatementContext<A, D, E> replicaAsChildOf(final StatementContextBase<?, ?, ?> parent) {
+        return new ReplicaStatementContext<>(parent, this);
     }
 
     private static void checkEffectiveModelCompleted(final StmtContext<?, ?, ?> stmt) {
@@ -686,6 +832,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     @Beta
+    // FIXME: this information should be exposed as a well-known Namespace
     public final boolean hasImplicitParentSupport() {
         return definition.getFactory() instanceof ImplicitParentAwareStatementSupport;
     }