Fix InvalidRangeConstraintException serializability
[yangtools.git] / yang / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / StatementContextBase.java
index 899345574ab9f99a4d4ccbd137ba599788e9861b..fc83c28ed34a989744d3357356fc9f4dc0f73439 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;
@@ -129,14 +130,6 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return effectOfStatement;
     }
 
-    @Override
-    public void addAsEffectOfStatement(final StmtContext<?, ?, ?> ctx) {
-        if (effectOfStatement.isEmpty()) {
-            effectOfStatement = new ArrayList<>(1);
-        }
-        effectOfStatement.add(ctx);
-    }
-
     @Override
     public void addAsEffectOfStatement(final Collection<? extends StmtContext<?, ?, ?>> ctxs) {
         if (ctxs.isEmpty()) {
@@ -293,11 +286,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 Collection<? extends ReactorStmtCtx<?, ?, ?>> casted =
+            (Collection<? extends ReactorStmtCtx<?, ?, ?>>) statements;
         final ModelProcessingPhase phase = completedPhase;
         if (phase != null) {
-            for (StatementContextBase<?, ?, ?> stmt : casted) {
+            for (ReactorStmtCtx<?, ?, ?> stmt : casted) {
                 ensureCompletedPhase(stmt, phase);
             }
         }
@@ -346,19 +339,42 @@ 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);
+    }
 
-    abstract Stream<? extends StmtContext<?, ?, ?>> streamEffective();
+    // 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());
+    }
+
+    /**
+     * 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) {
@@ -421,6 +437,9 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      */
     private void onPhaseCompleted(final ModelProcessingPhase phase) {
         completedPhase = phase;
+        if (phase == ModelProcessingPhase.EFFECTIVE_MODEL) {
+            summarizeSubstatementPolicy();
+        }
 
         final Collection<OnPhaseFinished> listeners = phaseListeners.get(phase);
         if (!listeners.isEmpty()) {
@@ -428,6 +447,53 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         }
     }
 
+    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()) {
@@ -595,28 +661,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 +689,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 +755,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) {