BUG-4688: Add flexible match support to NamespaceStorageSupport 70/64570/11
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 20 Oct 2017 12:08:59 +0000 (14:08 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 24 Oct 2017 10:55:55 +0000 (12:55 +0200)
This patch adds the support for flexible match of namespace keys
based on filter/reduce mechanics -- which are required to consistently
support revisionless and semantic imports without reliance on
magic constants in public interfaces.

In order to support required mechanics, we need to fix
the ValueAddedListener implementation and their lifecycle and introduce
a predicate-based match.

Change-Id: Icb22850e44de05d59820e1974e204dc1541e1f76
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ModifierImpl.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/NamespaceBehaviourWithListeners.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/NamespaceStorageSupport.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SimpleNamespaceContext.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SourceSpecificContext.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/VirtualNamespaceContext.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/ModelActionBuilder.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/NamespaceBehaviour.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/NamespaceKeyCriterion.java [new file with mode: 0644]

index 7818069762582118de7e8de584c5a3aad087cf8b..bfa3aa16221086a3149d4fd9c6327a964caaf7c7 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 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.NamespaceKeyCriterion;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
@@ -89,7 +90,6 @@ final class ModifierImpl implements ModelActionBuilder {
         actionApplied = true;
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     private <K, C extends StmtContext<?,?,?>, N extends StatementNamespace<K, ?, ?>> AbstractPrerequisite<C>
             requiresCtxImpl(final StmtContext<?, ?, ?> context, final Class<N> namespace, final K key,
                     final ModelProcessingPhase phase)  {
@@ -97,7 +97,18 @@ final class ModifierImpl implements ModelActionBuilder {
 
         AddedToNamespace<C> addedToNs = new AddedToNamespace<>(phase);
         addReq(addedToNs);
-        contextImpl(context).onNamespaceItemAddedAction((Class) namespace, key, addedToNs);
+        contextImpl(context).onNamespaceItemAddedAction(namespace, key, addedToNs);
+        return addedToNs;
+    }
+
+    private <K, C extends StmtContext<?,?,?>, N extends StatementNamespace<K, ?, ?>> AbstractPrerequisite<C>
+            requiresCtxImpl(final StmtContext<?, ?, ?> context, final Class<N> namespace,
+                    final NamespaceKeyCriterion<K> criterion, final ModelProcessingPhase phase)  {
+        checkNotRegistered();
+
+        AddedToNamespace<C> addedToNs = new AddedToNamespace<>(phase);
+        addReq(addedToNs);
+        contextImpl(context).onNamespaceItemAddedAction(namespace, phase, criterion, addedToNs);
         return addedToNs;
     }
 
@@ -165,6 +176,14 @@ final class ModifierImpl implements ModelActionBuilder {
         return requiresCtxImpl(context, namespace, key, phase);
     }
 
+    @Nonnull
+    @Override
+    public <K, N extends StatementNamespace<K, ?, ?>> Prerequisite<StmtContext<?, ?, ?>> requiresCtx(
+            final StmtContext<?, ?, ?> context, final Class<N> namespace, final NamespaceKeyCriterion<K> criterion,
+            final ModelProcessingPhase phase) {
+        return requiresCtxImpl(context, namespace, criterion, phase);
+    }
+
     @Nonnull
     @Override
     public <D extends DeclaredStatement<?>> Prerequisite<D> requiresDeclared(
index adba2b6572f7cf81952626eafbb2e8d51bdf7a48..9aa6ecc41e2717bb5802412023cf4d2535aa2c5a 100644 (file)
@@ -7,10 +7,13 @@
  */
 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 
+import static java.util.Objects.requireNonNull;
+
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
 
@@ -19,25 +22,45 @@ abstract class NamespaceBehaviourWithListeners<K, V, N extends IdentifierNamespa
 
     abstract static class ValueAddedListener<K> {
         private final NamespaceStorageNode ctxNode;
-        private final K key;
 
-        ValueAddedListener(final NamespaceStorageNode contextNode, final K key) {
-            this.ctxNode = contextNode;
-            this.key = key;
+        ValueAddedListener(final NamespaceStorageNode contextNode) {
+            this.ctxNode = requireNonNull(contextNode);
         }
 
-        NamespaceStorageNode getCtxNode() {
+        final NamespaceStorageNode getCtxNode() {
             return ctxNode;
         }
 
+    }
+
+    abstract static class KeyedValueAddedListener<K> extends ValueAddedListener<K> {
+        private final K key;
+
+        KeyedValueAddedListener(final NamespaceStorageNode contextNode, final K key) {
+            super(contextNode);
+            this.key = requireNonNull(key);
+        }
+
+        final K getKey() {
+            return key;
+        }
+
         final <V> boolean isRequestedValue(final NamespaceBehaviour<K, ? , ?> behavior,
                 final NamespaceStorageNode storage, final V value) {
-            return value == behavior.getFrom(ctxNode, key);
+            return value == behavior.getFrom(getCtxNode(), key);
         }
 
         abstract void onValueAdded(Object value);
     }
 
+    abstract static class PredicateValueAddedListener<K, V> extends ValueAddedListener<K> {
+        PredicateValueAddedListener(final NamespaceStorageNode contextNode) {
+            super(contextNode);
+        }
+
+        abstract boolean onValueAdded(@Nonnull K key, @Nonnull V value);
+    }
+
     protected final NamespaceBehaviour<K, V, N> delegate;
     private final List<VirtualNamespaceContext<?, V, ?, K>> derivedNamespaces = new ArrayList<>();
 
@@ -46,22 +69,24 @@ abstract class NamespaceBehaviourWithListeners<K, V, N extends IdentifierNamespa
         this.delegate = delegate;
     }
 
-    abstract void addListener(K key, ValueAddedListener<K> listener);
+    abstract void addListener(KeyedValueAddedListener<K> listener);
+
+    abstract void addListener(PredicateValueAddedListener<K, V> listener);
 
     @Override
     public abstract void addTo(NamespaceStorageNode storage, K key, V value);
 
     protected void notifyListeners(final NamespaceStorageNode storage,
-            final Iterator<ValueAddedListener<K>> keyListeners, final V value) {
-        List<ValueAddedListener<K>> toNotify = new ArrayList<>();
+            final Iterator<? extends KeyedValueAddedListener<K>> keyListeners, final V value) {
+        List<KeyedValueAddedListener<K>> toNotify = new ArrayList<>();
         while (keyListeners.hasNext()) {
-            final ValueAddedListener<K> listener = keyListeners.next();
+            final KeyedValueAddedListener<K> listener = keyListeners.next();
             if (listener.isRequestedValue(this, storage, value)) {
                 keyListeners.remove();
                 toNotify.add(listener);
             }
         }
-        for (ValueAddedListener<K> listener : toNotify) {
+        for (KeyedValueAddedListener<K> listener : toNotify) {
             listener.onValueAdded(value);
         }
     }
index 0a97b80aadeef3910da87a90f33f41dcfa321808..889dcc67212c8aeac8991b28150035aa31941c39 100644 (file)
@@ -11,6 +11,7 @@ import com.google.common.collect.ImmutableMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
@@ -18,6 +19,7 @@ import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.NamespaceStorageNode;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.Registry;
+import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceKeyCriterion;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceNotAvailableException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
@@ -52,11 +54,16 @@ abstract class NamespaceStorageSupport implements NamespaceStorageNode {
     }
 
     @Nonnull
-    public final <K,V, KT extends K, N extends IdentifierNamespace<K, V>> V getFromNamespace(final Class<N> type,
+    public final <K, V, KT extends K, N extends IdentifierNamespace<K, V>> V getFromNamespace(final Class<N> type,
             final KT key) throws NamespaceNotAvailableException {
         return getBehaviourRegistry().getNamespaceBehaviour(type).getFrom(this, key);
     }
 
+    public final <K, V, N extends IdentifierNamespace<K, V>> Optional<Entry<K, V>> getFromNamespace(
+            final Class<N> type, final NamespaceKeyCriterion<K> criterion) {
+        return getBehaviourRegistry().getNamespaceBehaviour(type).getFrom(this, criterion);
+    }
+
     public final <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> getAllFromNamespace(final Class<N> type) {
         return getBehaviourRegistry().getNamespaceBehaviour(type).getAllFrom(this);
     }
index e59e5e606f4cdb602bf939832d3a1f9d6c2215da..b335df09f66fc38cc1817156cac83721362d8f46 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
@@ -17,25 +18,36 @@ final class SimpleNamespaceContext<K, V, N extends IdentifierNamespace<K, V>>
         extends NamespaceBehaviourWithListeners<K, V, N> {
 
     // FIXME: Change this to Multimap, once issue with modules is resolved.
-    private final List<ValueAddedListener<K>> listeners = new ArrayList<>();
+    private final List<KeyedValueAddedListener<K>> listeners = new ArrayList<>();
+
+    private final Collection<PredicateValueAddedListener<K, V>> predicateListeners = new ArrayList<>();
 
     SimpleNamespaceContext(final NamespaceBehaviour<K, V, N> delegate) {
         super(delegate);
     }
 
     @Override
-    void addListener(final K key, final ValueAddedListener<K> listener) {
+    void addListener(final KeyedValueAddedListener<K> listener) {
         listeners.add(listener);
     }
 
-    private Iterator<ValueAddedListener<K>> getMutableListeners(final K key) {
-        return listeners.iterator();
+    @Override
+    void addListener(final PredicateValueAddedListener<K, V> listener) {
+        predicateListeners.add(listener);
     }
 
     @Override
     public void addTo(final NamespaceStorageNode storage, final K key, final V value) {
         delegate.addTo(storage, key, value);
-        notifyListeners(storage, getMutableListeners(key), value);
+        notifyListeners(storage, listeners.iterator(), value);
+
+        final Iterator<PredicateValueAddedListener<K, V>> it = predicateListeners.iterator();
+        while (it.hasNext()) {
+            if (it.next().onValueAdded(key, value)) {
+                it.remove();
+            }
+        }
+
         notifyDerivedNamespaces(storage, key, value);
     }
 }
index ff54ac473b0fd525632a16c6857c044b3c748a8d..c02909a7a1ae38b138947655a0a912705228c194 100644 (file)
@@ -46,8 +46,8 @@ 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.BelongsToModuleContext;
 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleCtx;
-import org.opendaylight.yangtools.yang.parser.spi.source.ImportPrefixToModuleCtx;
 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToNamespace;
+import org.opendaylight.yangtools.yang.parser.spi.source.ImportPrefixToModuleCtx;
 import org.opendaylight.yangtools.yang.parser.spi.source.ImportedModuleContext;
 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
@@ -314,6 +314,7 @@ public class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeh
     private static boolean tryToProgress(final Collection<ModifierImpl> currentPhaseModifiers) {
         boolean hasProgressed = false;
 
+        // Try making forward progress ...
         final Iterator<ModifierImpl> modifier = currentPhaseModifiers.iterator();
         while (modifier.hasNext()) {
             if (modifier.next().tryApply()) {
index 1807b101de3833d82ee1f62e73722a249acea9cc..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,6 +41,7 @@ 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;
@@ -50,7 +52,8 @@ 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;
 
@@ -567,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);
@@ -576,12 +579,7 @@ 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 %s does not support listeners", type);
-
-        final NamespaceBehaviourWithListeners<K, V, N> casted = (NamespaceBehaviourWithListeners<K, V, N>) behaviour;
-        casted.addListener(key, new ValueAddedListener<K>(this, key) {
+        getBehaviour(type).addListener(new KeyedValueAddedListener<K>(this, key) {
             @Override
             void onValueAdded(final Object value) {
                 listener.namespaceItemAdded(StatementContextBase.this, type, key, value);
@@ -589,6 +587,60 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         });
     }
 
+    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()}.
      */
index 77693e8dc5583eceb251b3c761020994a965fc21..2df2fe5d36bd5f40f01c061f5f7a6a59f1ec6a71 100644 (file)
@@ -15,7 +15,7 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.DerivedNamespaceBehaviour
 final class VirtualNamespaceContext<K, V, N extends IdentifierNamespace<K, V>, D>
         extends NamespaceBehaviourWithListeners<K, V, N> {
 
-    private final Multimap<D, ValueAddedListener<K>> listeners = HashMultimap.create();
+    private final Multimap<D, KeyedValueAddedListener<K>> listeners = HashMultimap.create();
     private final DerivedNamespaceBehaviour<K, V, D, N, ?> derivedDelegate;
 
     VirtualNamespaceContext(final DerivedNamespaceBehaviour<K, V, D, N, ?> delegate) {
@@ -24,8 +24,13 @@ final class VirtualNamespaceContext<K, V, N extends IdentifierNamespace<K, V>, D
     }
 
     @Override
-    void addListener(final K key, final ValueAddedListener<K> listener) {
-        listeners.put(derivedDelegate.getSignificantKey(key), listener);
+    void addListener(final KeyedValueAddedListener<K> listener) {
+        listeners.put(derivedDelegate.getSignificantKey(listener.getKey()), listener);
+    }
+
+    @Override
+    void addListener(final PredicateValueAddedListener<K, V> listener) {
+        throw new UnsupportedOperationException("Virtual namespaces support only exact lookups");
     }
 
     void addedToSourceNamespace(final NamespaceStorageNode storage, final D key, final V value) {
index eb6337a83948921cb16fc3b0fcbf66685d099a19..465e7fd4e9ba749752e0aacf7c529370b719bde9 100644 (file)
@@ -156,6 +156,10 @@ public interface ModelActionBuilder {
     @Nonnull <K, N extends StatementNamespace<K, ?, ?>> Prerequisite<StmtContext<?, ?, ?>> requiresCtx(
         StmtContext<?, ?, ?> context, Class<N> namespace, K key, ModelProcessingPhase phase);
 
+    @Nonnull <K, N extends StatementNamespace<K, ?, ?>> Prerequisite<StmtContext<?, ?, ?>> requiresCtx(
+            StmtContext<?, ?, ?> context, Class<N> namespace, NamespaceKeyCriterion<K> criterion,
+            ModelProcessingPhase phase);
+
     default @Nonnull <T extends Mutable<?, ?, ?>> Prerequisite<T> mutatesEffectiveCtx(final T stmt) {
         return mutatesCtx(stmt, EFFECTIVE_MODEL);
     }
index 0d88c3ef3e4848858a0350406e6421b892dfe856..9fcba740e922ea0920e52723921756d0a3841471 100644 (file)
@@ -9,7 +9,10 @@ package org.opendaylight.yangtools.yang.parser.spi.meta;
 
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.base.Verify;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Identifiable;
@@ -152,6 +155,41 @@ public abstract class NamespaceBehaviour<K, V, N extends IdentifierNamespace<K,
      */
     public abstract V getFrom(NamespaceStorageNode storage, K key);
 
+    /**
+     * Returns the key/value mapping best matching specified criterion.
+     *
+     * @param storage namespace storage
+     * @param criterion selection criterion
+     * @return Selected mapping, if available.
+     */
+    public final Optional<Entry<K, V>> getFrom(final NamespaceStorageNode storage,
+            final NamespaceKeyCriterion<K> criterion) {
+        final Map<K, V> mappings = getAllFrom(storage);
+        if (mappings == null) {
+            return Optional.empty();
+        }
+
+        Entry<K, V> match = null;
+        for (Entry<K, V> entry : mappings.entrySet()) {
+            final K key = entry.getKey();
+            if (criterion.match(key)) {
+                if (match != null) {
+                    final K selected = criterion.select(match.getKey(), key);
+                    if (selected.equals(match.getKey())) {
+                        continue;
+                    }
+
+                    Verify.verify(selected == key, "Criterion %s selected invalid key %s from candidates [%s %s]",
+                            selected, match.getKey(), key);
+                }
+
+                match = entry;
+            }
+        }
+
+        return Optional.ofNullable(match);
+    }
+
     /**
      * Returns all values of a keys of param class from model namespace storage.
      *
diff --git a/yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/NamespaceKeyCriterion.java b/yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/NamespaceKeyCriterion.java
new file mode 100644 (file)
index 0000000..7276095
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * 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 com.google.common.annotations.Beta;
+import javax.annotation.Nonnull;
+
+/**
+ * Namespace key matching criterion.
+ *
+ * @param <K> Key type
+ *
+ * @author Robert Varga
+ */
+@Beta
+public abstract class NamespaceKeyCriterion<K> {
+    /**
+     * Match a key against this criterion.
+     *
+     * @param key Key to be matched
+     * @return True if the key matches this criterion, false otherwise.
+     */
+    public abstract boolean match(@Nonnull K key);
+
+    /**
+     * Select the better match from two candidate keys.
+     *
+     * @param first First key
+     * @param second Second key
+     * @return Selected key, must be either first or second key, by identity.
+     */
+    public abstract K select(@Nonnull K first, @Nonnull K second);
+
+    @Override
+    public abstract String toString();
+
+    @Override
+    public final int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public final boolean equals(final Object obj) {
+        return super.equals(obj);
+    }
+}