BUG-4688: Add flexible match support to NamespaceStorageSupport
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / StatementContextBase.java
index 2d1c74533334c900cfffc86d8ccf123d4ea83985..d009eb78dba486b89a922155f69c16b5e6729a4c 100644 (file)
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.EnumMap;
 import java.util.EventListener;
 import java.util.Iterator;
+import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Set;
 import javax.annotation.Nonnull;
@@ -40,42 +41,46 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
 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.NamespaceBehaviour;
+import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceKeyCriterion;
 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.StmtContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
+import org.opendaylight.yangtools.yang.parser.spi.source.ImplicitSubstatement;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace.SupportedFeatures;
-import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.ValueAddedListener;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.KeyedValueAddedListener;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.PredicateValueAddedListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
-        extends NamespaceStorageSupport implements StmtContext.Mutable<A, D, E> {
+        extends NamespaceStorageSupport implements Mutable<A, D, E> {
     /**
-     * event listener when an item is added to model namespace.
+     * Event listener when an item is added to model namespace.
      */
     interface OnNamespaceItemAdded extends EventListener {
         /**
-         * @throws SourceException
+         * Invoked whenever a new item is added to a namespace.
          */
         void namespaceItemAdded(StatementContextBase<?, ?, ?> context, Class<?> namespace, Object key, Object value);
     }
 
     /**
-     * event listener when a parsing {@link ModelProcessingPhase} is completed.
+     * Event listener when a parsing {@link ModelProcessingPhase} is completed.
      */
     interface OnPhaseFinished extends EventListener {
         /**
-         * @throws SourceException
+         * Invoked whenever a processing phase has finished.
          */
         boolean phaseFinished(StatementContextBase<?, ?, ?> context, ModelProcessingPhase phase);
     }
 
     /**
-     * interface for all mutations within an {@link ModelActionBuilder.InferenceAction}.
+     * Interface for all mutations within an {@link ModelActionBuilder.InferenceAction}.
      */
     interface ContextMutation {
 
@@ -225,6 +230,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     public abstract StatementContextBase<?, ?, ?> getParentContext();
 
     /**
+     * Returns the model root for this statement.
+     *
      * @return root context of statement
      */
     @Nonnull
@@ -232,6 +239,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     public abstract RootStatementContext<?, ?, ?> getRoot();
 
     /**
+     * Returns the origin of the statement.
+     *
      * @return origin of statement
      */
     @Nonnull
@@ -241,6 +250,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
+     * Returns a reference to statement source.
+     *
      * @return reference of statement source
      */
     @Nonnull
@@ -281,7 +292,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return Collections.unmodifiableCollection(effective);
     }
 
-    public void removeStatementsFromEffectiveSubstatements(final Collection<? extends StmtContext<?, ?, ?>> substatements) {
+    public void removeStatementsFromEffectiveSubstatements(
+            final Collection<? extends StmtContext<?, ?, ?>> substatements) {
         if (!effective.isEmpty()) {
             effective.removeAll(substatements);
             shrinkEffective();
@@ -311,11 +323,12 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
-     * Removes a statement context from the effective substatements
-     * based on its statement definition (i.e statement keyword) and raw (in String form) statement argument.
-     * The statement context is removed only if both statement definition and statement argument match with
-     * one of the effective substatements' statement definition and argument.
+     * Removes a statement context from the effective substatements based on its statement definition (i.e statement
+     * keyword) and raw (in String form) statement argument. The statement context is removed only if both statement
+     * definition and statement argument match with one of the effective substatements' statement definition
+     * and argument.
      *
+     * <p>
      * If the statementArg parameter is null, the statement context is removed based only on its statement definition.
      *
      * @param statementDef statement definition of the statement context to remove
@@ -343,7 +356,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
-     * adds effective statement to collection of substatements
+     * Adds an effective statement to collection of substatements.
      *
      * @param substatement substatement
      * @throws IllegalStateException
@@ -357,7 +370,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
-     * adds effective statement to collection of substatements
+     * Adds an effective statement to collection of substatements.
      *
      * @param substatements substatements
      * @throws IllegalStateException
@@ -395,17 +408,19 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      * @param argument statement argument
      * @return A new substatement
      */
-    public final <CA, CD extends DeclaredStatement<CA>, CE extends EffectiveStatement<CA, CD>> StatementContextBase<CA, CD, CE> createSubstatement(
-            final int offset, final StatementDefinitionContext<CA, CD, CE> def, final StatementSourceReference ref,
-            final String argument) {
+    @SuppressWarnings("checkstyle:methodTypeParameterName")
+    public final <CA, CD extends DeclaredStatement<CA>, CE extends EffectiveStatement<CA, CD>>
+            StatementContextBase<CA, CD, CE> createSubstatement(final int offset,
+                    final StatementDefinitionContext<CA, CD, CE> def, final StatementSourceReference ref,
+                    final String argument) {
         final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
         Preconditions.checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL,
                 "Declared statement cannot be added in effective phase at: %s", getStatementSourceReference());
 
-        final Optional<StatementContextBase<?, ?, ?>> implicitStatement = definition.beforeSubStatementCreated(this,
-            offset, def, ref, argument);
-        if (implicitStatement.isPresent()) {
-            return implicitStatement.get().createSubstatement(offset, def, ref, argument);
+        final Optional<StatementSupport<?, ?, ?>> implicitParent = definition.getImplicitParentFor(def.getPublicView());
+        if (implicitParent.isPresent()) {
+            return createImplicitParent(offset, implicitParent.get(), ref, argument).createSubstatement(offset, def,
+                    ref, argument);
         }
 
         final StatementContextBase<CA, CD, CE> ret = new SubstatementContext<>(this, def, ref, argument);
@@ -414,6 +429,12 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return ret;
     }
 
+    private StatementContextBase<?, ?, ?> createImplicitParent(final int offset,
+            final StatementSupport<?, ?, ?> implicitParent, final StatementSourceReference ref, final String argument) {
+        final StatementDefinitionContext<?, ?, ?> def = new StatementDefinitionContext<>(implicitParent);
+        return createSubstatement(offset, def, ImplicitSubstatement.of(ref), argument);
+    }
+
     /**
      * Lookup substatement by its offset in this statement.
      *
@@ -524,15 +545,14 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     /**
      * Ends declared section of current node.
-     *
-     * @param ref
-     * @throws SourceException
      */
     void endDeclared(final StatementSourceReference ref, final ModelProcessingPhase phase) {
         definition().onDeclarationFinished(this, phase);
     }
 
     /**
+     * Return the context in which this statement was defined.
+     *
      * @return statement definition
      */
     protected final StatementDefinitionContext<A, D, E> definition() {
@@ -550,8 +570,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         // definition().onNamespaceElementAdded(this, type, key, value);
     }
 
-    <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceItemAddedAction(final Class<N> type, final K key,
-            final OnNamespaceItemAdded listener) throws SourceException {
+    final <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceItemAddedAction(final Class<N> type, final K key,
+            final OnNamespaceItemAdded listener) {
         final Object potential = getFromNamespace(type, key);
         if (potential != null) {
             LOG.trace("Listener on {} key {} satisfied immediately", type, key);
@@ -559,19 +579,68 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
             return;
         }
 
-        final NamespaceBehaviour<K, V, N> behaviour = getBehaviourRegistry().getNamespaceBehaviour(type);
-        Preconditions.checkArgument(behaviour instanceof NamespaceBehaviourWithListeners,
-            "Namespace {} does not support listeners", type);
-
-        final NamespaceBehaviourWithListeners<K, V, N> casted = (NamespaceBehaviourWithListeners<K, V, N>) behaviour;
-        casted.addValueListener(new ValueAddedListener<K>(this, key) {
+        getBehaviour(type).addListener(new KeyedValueAddedListener<K>(this, key) {
             @Override
-            void onValueAdded(final Object key, final Object value) {
+            void onValueAdded(final Object value) {
                 listener.namespaceItemAdded(StatementContextBase.this, type, key, value);
             }
         });
     }
 
+    final <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceItemAddedAction(final Class<N> type,
+            final ModelProcessingPhase phase, final NamespaceKeyCriterion<K> criterion,
+            final OnNamespaceItemAdded listener) {
+        final Optional<Entry<K, V>> existing = getFromNamespace(type, criterion);
+        if (existing.isPresent()) {
+            final Entry<K, V> entry = existing.get();
+            LOG.debug("Listener on {} criterion {} found a pre-existing match: {}", type, criterion, entry);
+            waitForPhase(entry.getValue(), type, phase, criterion, listener);
+            return;
+        }
+
+        final NamespaceBehaviourWithListeners<K, V, N> behaviour = getBehaviour(type);
+        behaviour.addListener(new PredicateValueAddedListener<K, V>(this) {
+            @Override
+            boolean onValueAdded(final K key, final V value) {
+                if (criterion.match(key)) {
+                    LOG.debug("Listener on {} criterion {} matched added key {}", type, criterion, key);
+                    waitForPhase(value, type, phase, criterion, listener);
+                    return true;
+                }
+
+                return false;
+            }
+        });
+    }
+
+    final <K, V, N extends IdentifierNamespace<K, V>> void selectMatch(final Class<N> type,
+            final NamespaceKeyCriterion<K> criterion, final OnNamespaceItemAdded listener) {
+        final Optional<Entry<K, V>> optMatch = getFromNamespace(type, criterion);
+        Preconditions.checkState(optMatch.isPresent(),
+            "Failed to find a match for criterion %s in namespace %s node %s", criterion, type, this);
+        final Entry<K, V> match = optMatch.get();
+        listener.namespaceItemAdded(StatementContextBase.this, type, match.getKey(), match.getValue());
+    }
+
+    final <K, V, N extends IdentifierNamespace<K, V>> void waitForPhase(final Object value, final Class<N> type,
+            final ModelProcessingPhase phase, final NamespaceKeyCriterion<K> criterion,
+            final OnNamespaceItemAdded listener) {
+        ((StatementContextBase<?, ? ,?>) value).addPhaseCompletedListener(phase,
+            (context, completedPhase) -> {
+                selectMatch(type, criterion, listener);
+                return true;
+            });
+    }
+
+    private <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> getBehaviour(
+            final Class<N> type) {
+        final NamespaceBehaviour<K, V, N> behaviour = getBehaviourRegistry().getNamespaceBehaviour(type);
+        Preconditions.checkArgument(behaviour instanceof NamespaceBehaviourWithListeners,
+            "Namespace %s does not support listeners", type);
+
+        return (NamespaceBehaviourWithListeners<K, V, N>) behaviour;
+    }
+
     /**
      * See {@link StatementSupport#getPublicView()}.
      */
@@ -591,12 +660,14 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
-     * adds {@link OnPhaseFinished} listener for a {@link ModelProcessingPhase} end
+     * Adds {@link OnPhaseFinished} listener for a {@link ModelProcessingPhase} end. If the base has already completed
+     * the listener is notified immediately.
      *
-     * @throws SourceException
+     * @param phase requested completion phase
+     * @param listener listener to invoke
+     * @throws NullPointerException if any of the arguments is null
      */
     void addPhaseCompletedListener(final ModelProcessingPhase phase, final OnPhaseFinished listener) {
-
         Preconditions.checkNotNull(phase, "Statement context processing phase cannot be null at: %s",
                 getStatementSourceReference());
         Preconditions.checkNotNull(listener, "Statement context phase listener cannot be null at: %s",
@@ -618,7 +689,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
-     * adds {@link ContextMutation} to {@link ModelProcessingPhase}
+     * Adds a {@link ContextMutation} to a {@link ModelProcessingPhase}.
      *
      * @throws IllegalStateException
      *             when the mutation was registered after phase was completed
@@ -626,10 +697,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) {
         ModelProcessingPhase finishedPhase = completedPhase;
         while (finishedPhase != null) {
-            if (phase.equals(finishedPhase)) {
-                throw new IllegalStateException("Mutation registered after phase was completed at: "  +
-                        getStatementSourceReference());
-            }
+            Preconditions.checkState(!phase.equals(finishedPhase),
+                "Mutation registered after phase was completed at: %s", getStatementSourceReference());
             finishedPhase = finishedPhase.getPreviousPhase();
         }