Centralize substatement validators
[yangtools.git] / yang / yang-parser-spi / src / main / java / org / opendaylight / yangtools / yang / parser / spi / meta / StatementSupport.java
index 22e20df65cf0052dc093f4e90063cfcfe287576c..0db910ffaf15505e09b0fbd35900aad05b493ca3 100644 (file)
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.yangtools.yang.parser.spi.meta;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.annotations.Beta;
+import com.google.common.base.VerifyException;
+import java.util.Collection;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 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.EffectiveStmtCtx.Current;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 /**
  * Support for processing concrete YANG statement.
  *
  * <p>
- * This interface is intended to be implemented by developers, which want to
- * introduce support of statement to parser. Consider subclassing
- * {@link AbstractStatementSupport} for easier implementation of this interface.
+ * This interface is intended to be implemented by developers, which want to introduce support of statement to parser.
+ * Consider subclassing {@link AbstractStatementSupport} for easier implementation of this interface.
  *
- * @param <A>
- *            Argument type
- * @param <D>
- *            Declared Statement representation
- * @param <E>
- *            Effective Statement representation
+ * @param <A> Argument type
+ * @param <D> Declared Statement representation
+ * @param <E> Effective Statement representation
  */
-public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
-        extends StatementDefinition, StatementFactory<A, D, E> {
+public abstract class StatementSupport<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
+        implements StatementDefinition, StatementFactory<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 a {@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>) EqualSemantics.CONTEXT_INDEPENDENT;
+        }
+
+        /**
+         * Return a {@link StatementPolicy} for {@link CopyPolicy#EXACT_REPLICA}.
+         *
+         * @param <A> Argument type
+         * @param <D> Declared Statement representation
+         * @return Exact-replica policy
+         */
+        @SuppressWarnings("unchecked")
+        public static final <A, D extends DeclaredStatement<A>> @NonNull StatementPolicy<A, D> exactReplica() {
+            return (StatementPolicy<A, D>) EqualSemantics.EXACT_REPLICA;
+        }
+
+        /**
+         * Return a {@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 a {@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 a {@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 Equality-based 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);
+        }
+
+        /**
+         * Return a {@link StatementPolicy} for {@link CopyPolicy#DECLARED_COPY}, always performing a copy operation.
+         *
+         * @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> alwaysCopyDeclared() {
+            return (StatementPolicy<A, D>) EqualSemantics.ALWAYS_COPY;
+        }
+
+        abstract boolean canReuseCurrent(@NonNull Current<A, D> copy, @NonNull Current<A, D> current,
+            @NonNull Collection<? extends EffectiveStatement<?, ?>> substatements);
+
+        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> {
+            static final @NonNull EqualSemantics<?, ?> ALWAYS_COPY =
+                new EqualSemantics<>((copy, stmt, substatements) -> false);
+            static final @NonNull EqualSemantics<?, ?> CONTEXT_INDEPENDENT =
+                new EqualSemantics<>(CopyPolicy.CONTEXT_INDEPENDENT, (copy, stmt, substatements) -> true);
+            static final @NonNull EqualSemantics<?, ?> EXACT_REPLICA =
+                new EqualSemantics<>(CopyPolicy.EXACT_REPLICA, (copy, stmt, substatements) -> true);
+
+            private final @NonNull StatementEquality<A, D> equality;
+
+            private EqualSemantics(final CopyPolicy copyPolicy, final StatementEquality<A, D> equality) {
+                super(copyPolicy);
+                this.equality = requireNonNull(equality);
+            }
+
+            EqualSemantics(final StatementEquality<A, D> equality) {
+                this(CopyPolicy.DECLARED_COPY, 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 def;
+    private final @NonNull CopyPolicy copyPolicy;
+
+    @Beta
+    protected StatementSupport(final StatementSupport<A, D, E> delegate) {
+        checkArgument(delegate != this);
+        this.def = delegate.def;
+        this.policy = delegate.policy;
+        this.copyPolicy = delegate.copyPolicy;
+    }
+
+    @Beta
+    protected StatementSupport(final StatementDefinition publicDefinition, final StatementPolicy<A, D> policy) {
+        checkArgument(publicDefinition != this);
+        this.def = requireNonNull(publicDefinition);
+        this.policy = requireNonNull(policy);
+        this.copyPolicy = policy.copyPolicy;
+    }
 
     /**
      * Returns public statement definition, which will be present in built statements.
@@ -47,7 +224,52 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      *
      * @return public statement definition, which will be present in built statements.
      */
-    @NonNull StatementDefinition getPublicView();
+    public final @NonNull StatementDefinition getPublicView() {
+        return def;
+    }
+
+    /**
+     * Return this statement's {@link CopyPolicy}. This is a static value, reflecting how this statement reacts to being
+     * replicated to a different context, without reflecting on behaviour of potential substatements, which would come
+     * into play in something like:
+     *
+     * <pre>
+     *   <code>
+     *     module foo {
+     *       namespace foo;
+     *       prefix foo;
+     *
+     *       extension note {
+     *         argument string {
+     *           type string {
+     *             length 1..max;
+     *           }
+     *         }
+     *         description "Can be used in description/reference statements to attach additional notes";
+     *       }
+     *
+     *       description "A nice module extending description statement semantics" {
+     *         foo:note "We can now attach description/reference a note.";
+     *         foo:note "Also another note";
+     *       }
+     *     }
+     *   </code>
+     * </pre>
+     *
+     * <p>
+     * In this scenario, it is the reactor's job to figure out what to do (like talking to substatements).
+     *
+     * @return This statement's copy policy
+     */
+    public final @NonNull CopyPolicy copyPolicy() {
+        return copyPolicy;
+    }
+
+    @Override
+    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);
+    }
 
     /**
      * Parses textual representation of argument in object representation.
@@ -57,20 +279,18 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      * @return Parsed value
      * @throws SourceException when an inconsistency is detected.
      */
-    A parseArgumentValue(StmtContext<?, ?, ?> ctx, String value);
+    public abstract A parseArgumentValue(StmtContext<?, ?, ?> ctx, String value);
 
     /**
-     * Adapts the argument value to match a new module.
+     * Adapts the argument value to match a new module. Default implementation returns original value stored in context,
+     * which is appropriate for most implementations.
      *
-     * @param ctx
-     *            Context, which may be used to access source-specific
-     *            namespaces required for parsing.
-     * @param targetModule
-     *            Target module, may not be null.
-     * @return Adapted argument value. The default implementation returns original value stored in context.
+     * @param ctx Context, which may be used to access source-specific namespaces required for parsing.
+     * @param targetModule Target module, may not be null.
+     * @return Adapted argument value.
      */
-    default A adaptArgumentValue(final StmtContext<A, D, E> ctx, final QNameModule targetModule) {
-        return ctx.getStatementArgument();
+    public A adaptArgumentValue(final @NonNull StmtContext<A, D, E> ctx, final @NonNull QNameModule targetModule) {
+        return ctx.argument();
     }
 
     /**
@@ -79,10 +299,11 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      * accessible via {@link StmtContext#getParentContext()}. One such use is populating the parent's namespaces to
      * allow it to locate this child statement.
      *
-     * @param stmt
-     *            Context of added statement. No substatements are available.
+     * @param stmt Context of added statement. No substatements are available.
      */
-    void onStatementAdded(StmtContext.Mutable<A, D, E> stmt);
+    public void onStatementAdded(final @NonNull Mutable<A, D, E> stmt) {
+        // NOOP for most implementations
+    }
 
     /**
      * Invoked when statement is closed during {@link ModelProcessingPhase#SOURCE_PRE_LINKAGE} phase, only substatements
@@ -90,12 +311,13 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      *
      * <p>
      * Implementation may use method to perform actions on this event or register modification action using
-     * {@link StmtContext.Mutable#newInferenceAction(ModelProcessingPhase)}.
+     * {@link Mutable#newInferenceAction(ModelProcessingPhase)}.
      *
-     * @param stmt
-     *            Context of added statement.
+     * @param stmt Context of added statement.
      */
-    void onPreLinkageDeclared(StmtContext.Mutable<A, D, E> stmt);
+    public void onPreLinkageDeclared(final @NonNull Mutable<A, D, E> stmt) {
+        // NOOP for most implementations
+    }
 
     /**
      * Invoked when statement is closed during {@link ModelProcessingPhase#SOURCE_LINKAGE} phase, only substatements
@@ -103,14 +325,14 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      *
      * <p>
      * Implementation may use method to perform actions on this event or register modification action using
-     * {@link StmtContext.Mutable#newInferenceAction(ModelProcessingPhase)}.
+     * {@link Mutable#newInferenceAction(ModelProcessingPhase)}.
      *
-     * @param stmt
-     *            Context of added statement.
-     * @throws SourceException
-     *             when an inconsistency is detected.
+     * @param stmt Context of added statement.
+     * @throws SourceException when an inconsistency is detected.
      */
-    void onLinkageDeclared(StmtContext.Mutable<A, D, E> stmt);
+    public void onLinkageDeclared(final @NonNull Mutable<A, D, E> stmt) {
+        // NOOP for most implementations
+    }
 
     /**
      * Invoked when statement is closed during {@link ModelProcessingPhase#STATEMENT_DEFINITION} phase,
@@ -118,15 +340,14 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      *
      * <p>
      * Implementation may use method to perform actions on this event or register modification action using
-     * {@link StmtContext.Mutable#newInferenceAction(ModelProcessingPhase)}.
+     * {@link Mutable#newInferenceAction(ModelProcessingPhase)}.
      *
-     * @param stmt
-     *            Context of added statement. Argument and statement parent is
-     *            accessible.
-     * @throws SourceException
-     *             when an inconsistency is detected.
+     * @param stmt Context of added statement. Argument and statement parent is accessible.
+     * @throws SourceException when an inconsistency is detected.
      */
-    void onStatementDefinitionDeclared(StmtContext.Mutable<A, D, E> stmt);
+    public void onStatementDefinitionDeclared(final Mutable<A, D, E> stmt) {
+        // NOOP for most implementations
+    }
 
     /**
      * Invoked when statement is closed during {@link ModelProcessingPhase#FULL_DECLARATION} phase,
@@ -134,64 +355,67 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      *
      * <p>
      * Implementation may use method to perform actions on this event or register modification action using
-     * {@link StmtContext.Mutable#newInferenceAction(ModelProcessingPhase)}.
+     * {@link Mutable#newInferenceAction(ModelProcessingPhase)}.
      *
-     * @param stmt
-     *            Context of added statement. Argument and statement parent is
-     *            accessible.
-     * @throws SourceException
-     *             when an inconsistency is detected.
+     * @param stmt Context of added statement. Argument and statement parent is accessible.
+     * @throws SourceException when an inconsistency is detected.
      */
-    void onFullDefinitionDeclared(StmtContext.Mutable<A, D, E> stmt);
+    public void onFullDefinitionDeclared(final Mutable<A, D, E> stmt) {
+        final SubstatementValidator validator = substatementValidator();
+        if (validator != null) {
+            validator.validate(stmt);
+        }
+    }
+
+    /**
+     * Returns corresponding substatement validator of a statement support.
+     *
+     * @return substatement validator or null, if substatement validator is not defined
+     */
+    protected abstract @Nullable SubstatementValidator substatementValidator();
 
     /**
      * Returns true if this support has argument specific supports.
+     *
+     * @return true if this support has argument specific supports.
      */
-    boolean hasArgumentSpecificSupports();
+    public boolean hasArgumentSpecificSupports() {
+        // Most of statement supports don't have any argument specific supports, so return 'false'.
+        return false;
+    }
 
     /**
      * If this support has argument specific supports, the method returns support specific for given argument
      * (e.g. type statement support need to be specialized based on its argument), otherwise returns null.
      *
-     * @param argument
-     *            argument of statement
+     * @param argument argument of statement
      * @return statement support specific for supplied argument or null
      */
-    @Nullable StatementSupport<?, ?, ?> getSupportSpecificForArgument(String argument);
+    public @Nullable StatementSupport<?, ?, ?> getSupportSpecificForArgument(final String argument) {
+        // Most of statement supports don't have any argument specific supports, so return null.
+        return null;
+    }
 
     /**
-     * Given a raw string representation of an argument, try to use a shared representation.
+     * Given a raw string representation of an argument, try to use a shared representation. Default implementation
+     * does nothing.
      *
-     * @param rawArgument
-     *            Argument string
+     * @param rawArgument Argument string
      * @return A potentially-shard instance
      */
-    default String internArgument(final String rawArgument) {
+    public String internArgument(final String rawArgument) {
         return rawArgument;
     }
 
-    /**
-     * Returns unknown statement form of a regular YANG statement supplied as a parameter to the method.
-     *
-     * @param yangStmtDef
-     *            statement definition of a regular yang statement
-     * @return Optional of unknown statement form of a regular yang statement or
-     *         Optional.empty() if it is not supported by this statement support
-     */
-    default Optional<StatementSupport<?, ?, ?>> getUnknownStatementDefinitionOf(final StatementDefinition yangStmtDef) {
-        return Optional.empty();
-    }
-
     /**
      * Returns true if this statement support and all its substatements ignore if-feature statements (e.g. yang-data
      * extension defined in <a href="https://tools.ietf.org/html/rfc8040#section-8">RFC 8040</a>). Default
      * implementation returns false.
      *
-     * @return true if this statement support ignores if-feature statements,
-     *         otherwise false.
+     * @return true if this statement support ignores if-feature statements, otherwise false.
      */
     @Beta
-    default boolean isIgnoringIfFeatures() {
+    public boolean isIgnoringIfFeatures() {
         return false;
     }
 
@@ -204,27 +428,76 @@ public interface StatementSupport<A, D extends DeclaredStatement<A>, E extends E
      *         otherwise false.
      */
     @Beta
-    default boolean isIgnoringConfig() {
+    public boolean isIgnoringConfig() {
         return false;
     }
 
     @Override
-    default QName getStatementName() {
-        return getPublicView().getStatementName();
+    public final QName getStatementName() {
+        return def.getStatementName();
     }
 
     @Override
-    default @NonNull Optional<ArgumentDefinition> getArgumentDefinition() {
-        return getPublicView().getArgumentDefinition();
+    public final Optional<ArgumentDefinition> getArgumentDefinition() {
+        return def.getArgumentDefinition();
     }
 
     @Override
-    default Class<? extends DeclaredStatement<?>> getDeclaredRepresentationClass() {
-        return getPublicView().getDeclaredRepresentationClass();
+    // Non-final for compatible extensions
+    public Class<? extends DeclaredStatement<?>> getDeclaredRepresentationClass() {
+        return def.getDeclaredRepresentationClass();
     }
 
     @Override
-    default Class<? extends EffectiveStatement<?,?>> getEffectiveRepresentationClass() {
-        return getPublicView().getEffectiveRepresentationClass();
+    // Non-final for compatible extensions
+    public Class<? extends EffectiveStatement<?,?>> getEffectiveRepresentationClass() {
+        return def.getEffectiveRepresentationClass();
+    }
+
+    /**
+     * Statement context copy policy, indicating how should reactor handle statement copy operations. Every statement
+     * copied by the reactor is subject to this policy.
+     */
+    public enum CopyPolicy {
+        /**
+         * Reuse the source statement context in the new place, as it cannot be affected by any further operations. This
+         * implies that the semantics of the effective statement are not affected by any of its substatements. Each
+         * of the substatements is free to make its own policy.
+         *
+         * <p>
+         * This policy is typically used by static constant statements such as {@code description} or {@code length},
+         * where the baseline RFC7950 does not allow any impact. A {@code description} could hold an extension statement
+         * in which case this interaction would come into play. Normal YANG will see empty substatements, so the reactor
+         * will be free to complete reuse the context.
+         *
+         * <p>
+         * In case any substatement is of stronger policy, it is up to the reactor to handle correct handling of
+         * resulting subobjects.
+         */
+        // TODO: does this mean source must have transitioned to ModelProcessingPhase.EFFECTIVE_MODEL?
+        CONTEXT_INDEPENDENT,
+        /**
+         * Reuse the source statement context in the new place completely. This policy is more stringent than
+         * {@link #CONTEXT_INDEPENDENT} in that the statement is dependent on circumstances of its original definition
+         * and any copy operation must replicate it exactly as is. This implies ignoring the usual policy of its
+         * substatements. A typical example of such a statement is {@code type}.
+         */
+        EXACT_REPLICA,
+        /**
+         * Create a copy sharing declared instance, but otherwise having a separate disconnected lifecycle.
+         */
+        // TODO: will the copy transition to ModelProcessingPhase.FULL_DECLARATION or which phase?
+        DECLARED_COPY,
+        /**
+         * Reject any attempt to copy this statement. This is useful for statements that are defined as top-level
+         * constructs, such as {@code contact}, {@code deviation} and similar.
+         */
+        REJECT,
+        /**
+         * Ignore this statement's existence for the purposes of the new place -- it is not impacted. This guidance
+         * is left here for completeness, as it can have justifiable uses (but I can't think of any). Any substatements
+         * need to be ignored, too.
+         */
+        IGNORE;
     }
 }