Add Binding/DOM Query language adapter 93/88193/27
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 1 Mar 2020 12:14:00 +0000 (13:14 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 16 Apr 2020 16:19:03 +0000 (18:19 +0200)
Provide implementation of binding language translation on top
of its DOM counterpart.

On the DOM layer the expression can be transmitted, hence it gives
the possibility to move the execution to storage backend, thus
reducing app/backend data interchange volume.

Change-Id: I51c8ec7e34c2485f62aeb5bdbe35fe1507cabaa9
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
18 files changed:
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/AbstractValueMatchBuilder.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultComparableMatchBuilder.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultDescendantQueryBuilder.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultMatchBuilderPath.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQuery.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQueryFactory.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultStringMatchBuilder.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatch.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatchBuilder.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/LambdaDecoder.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/OSGiQueryFactory.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderState.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderTest.java [new file with mode: 0644]
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQuery.java [new file with mode: 0644]
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryLike.java [new file with mode: 0644]
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryPredicate.java [new file with mode: 0644]
dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/query/DOMQueryEvaluator.java [new file with mode: 0644]

diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java
new file mode 100644 (file)
index 0000000..490aed3
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.binding.api.query;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+@Beta
+public interface QueryExecutor {
+
+    <T extends DataObject> ListenableFuture<? extends QueryResult<T>> executeQuery(QueryExpression<T> query);
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/AbstractValueMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/AbstractValueMatchBuilder.java
new file mode 100644 (file)
index 0000000..a6eae4f
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.query.ValueMatch;
+import org.opendaylight.mdsal.binding.api.query.ValueMatchBuilder;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Exists;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.ValueEquals;
+import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+abstract class AbstractValueMatchBuilder<T extends DataObject, V> implements ValueMatchBuilder<T, V> {
+    private final QueryBuilderState builder;
+    private final InstanceIdentifier<T> select;
+    private final BoundMethod method;
+
+    AbstractValueMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier<T> select,
+            final BoundMethod method) {
+        this.builder = requireNonNull(builder);
+        this.select = requireNonNull(select);
+        this.method = requireNonNull(method);
+    }
+
+    @Override
+    public final ValueMatch<T> nonNull() {
+        return withPredicate(new Exists(relativePath()));
+    }
+
+    @Override
+    public final ValueMatch<T> valueEquals(final V value) {
+        return withPredicate(new ValueEquals<>(relativePath(), value));
+    }
+
+    final YangInstanceIdentifier relativePath() {
+        return method.parentPath.node(((DataSchemaNode) method.methodCodec.getSchema()).getQName());
+    }
+
+    final @NonNull ValueMatch<T> withPredicate(final DOMQueryPredicate predicate) {
+        // FIXME: this does not quite take value codec into account :(
+        builder.addPredicate(predicate);
+        return new DefaultValueMatch<>(builder, select);
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultComparableMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultComparableMatchBuilder.java
new file mode 100644 (file)
index 0000000..7565edf
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import org.opendaylight.mdsal.binding.api.query.ComparableMatchBuilder;
+import org.opendaylight.mdsal.binding.api.query.ValueMatch;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.GreaterThan;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.GreaterThanOrEqual;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.LessThan;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.LessThanOrEqual;
+import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+final class DefaultComparableMatchBuilder<T extends DataObject, V extends Comparable<V>>
+        extends AbstractValueMatchBuilder<T, V> implements ComparableMatchBuilder<T, V> {
+    DefaultComparableMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier<T> select,
+            final BoundMethod method) {
+        super(builder, select, method);
+    }
+
+    @Override
+    public ValueMatch<T> lessThan(final V value) {
+        return withPredicate(new LessThan<>(relativePath(), value));
+    }
+
+    @Override
+    public ValueMatch<T> lessThanOrEqual(final V value) {
+        return withPredicate(new LessThanOrEqual<>(relativePath(), value));
+    }
+
+    @Override
+    public ValueMatch<T> greaterThan(final V value) {
+        return withPredicate(new GreaterThan<>(relativePath(), value));
+    }
+
+    @Override
+    public ValueMatch<T> greaterThanOrEqual(final V value) {
+        return withPredicate(new GreaterThanOrEqual<>(relativePath(), value));
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultDescendantQueryBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultDescendantQueryBuilder.java
new file mode 100644 (file)
index 0000000..a26ff8d
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import org.opendaylight.mdsal.binding.api.query.DescendantQueryBuilder;
+import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryStructureException;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yangtools.yang.binding.ChoiceIn;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.InstanceIdentifierBuilder;
+
+final class DefaultDescendantQueryBuilder<R extends DataObject, T extends DataObject>
+        implements DescendantQueryBuilder<T> {
+    private final InstanceIdentifierBuilder<T> childPath;
+    private final QueryBuilderState builder;
+
+    DefaultDescendantQueryBuilder(final BindingCodecTree codec, final InstanceIdentifier<T> rootPath) {
+        this.builder = new QueryBuilderState(codec, rootPath);
+        this.childPath = rootPath.builder();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <N extends ChildOf<? super T>> DescendantQueryBuilder<N> extractChild(final Class<N> childClass) {
+        childPath.child(childClass);
+        return (DefaultDescendantQueryBuilder<R, N>) this;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <C extends ChoiceIn<? super T> & DataObject, N extends ChildOf<? super C>>
+            DescendantQueryBuilder<N> extractChild(final Class<C> caseClass, final Class<N> childClass) {
+        childPath.child(caseClass, childClass);
+        return (DefaultDescendantQueryBuilder<R, N>) this;
+    }
+
+    @Override
+    public MatchBuilderPath<T, T> matching() {
+        final InstanceIdentifier<T> selectPath = childPath.build();
+        builder.setSelectPath(selectPath);
+        return new DefaultMatchBuilderPath<>(builder, selectPath, childPath);
+    }
+
+    @Override
+    public QueryExpression<T> build() throws QueryStructureException {
+        builder.setSelectPath(childPath.build());
+        return builder.buildQuery();
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultMatchBuilderPath.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultMatchBuilderPath.java
new file mode 100644 (file)
index 0000000..28de9e1
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.query.ComparableMatchBuilder;
+import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath;
+import org.opendaylight.mdsal.binding.api.query.StringMatchBuilder;
+import org.opendaylight.mdsal.binding.api.query.ValueMatchBuilder;
+import org.opendaylight.yangtools.yang.binding.BaseIdentity;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yangtools.yang.binding.ChoiceIn;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.InstanceIdentifierBuilder;
+import org.opendaylight.yangtools.yang.binding.TypeObject;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+
+final class DefaultMatchBuilderPath<O extends DataObject, T extends DataObject> implements MatchBuilderPath<O, T> {
+    private final QueryBuilderState builder;
+    private final InstanceIdentifier<O> select;
+    private final InstanceIdentifierBuilder<T> target;
+
+    DefaultMatchBuilderPath(final QueryBuilderState builder, final InstanceIdentifier<O> select,
+            final InstanceIdentifierBuilder<T> target) {
+        this.builder = requireNonNull(builder);
+        this.select = requireNonNull(select);
+        this.target = requireNonNull(target);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <N extends ChildOf<? super T>> MatchBuilderPath<O, N> childObject(final Class<N> childClass) {
+        target.child(childClass);
+        return (MatchBuilderPath<O, N>) this;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <C extends ChoiceIn<? super T> & DataObject, N extends ChildOf<? super C>>
+            MatchBuilderPath<O, N> extractChild(final Class<C> caseClass, final Class<N> childClass) {
+        target.child(caseClass, childClass);
+        return (MatchBuilderPath<O, N>) this;
+    }
+
+    @Override
+    public ValueMatchBuilder<O, Empty> leaf(final EmptyLeafReference<T> methodRef) {
+        return defaultFor(methodRef);
+    }
+
+    @Override
+    public StringMatchBuilder<O> leaf(final StringLeafReference<T> methodRef) {
+        return new DefaultStringMatchBuilder<>(builder, select, builder.bindMethod(target.build(), methodRef));
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Byte> leaf(final Int8LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Short> leaf(final Int16LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Integer> leaf(final Int32LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Long> leaf(final Int64LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Uint8> leaf(final Uint8LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Uint16> leaf(final Uint16LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Uint32> leaf(final Uint32LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public ComparableMatchBuilder<O, Uint64> leaf(final Uint64LeafReference<T> methodRef) {
+        return comparableFor(methodRef);
+    }
+
+    @Override
+    public <I extends BaseIdentity> ValueMatchBuilder<O, I> leaf(final IdentityLeafReference<T, I> methodRef) {
+        return defaultFor(methodRef);
+    }
+
+    @Override
+    public <C extends TypeObject> ValueMatchBuilder<O, C> leaf(final TypeObjectLeafReference<T, C> methodRef) {
+        return defaultFor(methodRef);
+    }
+
+    private <F> @NonNull ValueMatchBuilder<O, F> defaultFor(final LeafReference<T, F> ref) {
+        return new DefaultValueMatchBuilder<>(builder, select, builder.bindMethod(target.build(), ref));
+    }
+
+    private <F extends Comparable<F>> @NonNull ComparableMatchBuilder<O, F> comparableFor(
+            final LeafReference<T, F> ref) {
+        return new DefaultComparableMatchBuilder<>(builder, select, builder.bindMethod(target.build(), ref));
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQuery.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQuery.java
new file mode 100644 (file)
index 0000000..8036153
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.dom.api.query.DOMQuery;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryLike;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+final class DefaultQuery<T extends DataObject> implements QueryExpression<T>, DOMQueryLike {
+    private final @NonNull DOMQuery domQuery;
+
+    DefaultQuery(final DOMQuery domQuery) {
+        this.domQuery = requireNonNull(domQuery);
+    }
+
+    @Override
+    public DOMQuery asDOMQuery() {
+        return domQuery;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this).add("dom", domQuery).toString();
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQueryFactory.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQueryFactory.java
new file mode 100644 (file)
index 0000000..e3cee0d
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import java.util.ServiceLoader;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.kohsuke.MetaInfServices;
+import org.opendaylight.mdsal.binding.api.query.DescendantQueryBuilder;
+import org.opendaylight.mdsal.binding.api.query.QueryFactory;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+@Beta
+@MetaInfServices
+@Singleton
+public final class DefaultQueryFactory implements QueryFactory {
+    private final BindingCodecTree codec;
+
+    public DefaultQueryFactory() {
+        this(ServiceLoader.load(BindingCodecTree.class).findFirst().orElseThrow());
+    }
+
+    @Inject
+    public DefaultQueryFactory(final BindingCodecTree codec) {
+        this.codec = requireNonNull(codec);
+    }
+
+    @Override
+    public <T extends DataObject> DescendantQueryBuilder<T> querySubtree(final InstanceIdentifier<T> rootPath) {
+        return new DefaultDescendantQueryBuilder<>(codec, rootPath);
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultStringMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultStringMatchBuilder.java
new file mode 100644 (file)
index 0000000..00e042d
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import java.util.regex.Pattern;
+import org.opendaylight.mdsal.binding.api.query.StringMatchBuilder;
+import org.opendaylight.mdsal.binding.api.query.ValueMatch;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Contains;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.EndsWith;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.MatchesPattern;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.StartsWith;
+import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+final class DefaultStringMatchBuilder<T extends DataObject> extends AbstractValueMatchBuilder<T, String>
+        implements StringMatchBuilder<T> {
+    DefaultStringMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier<T> select,
+            final BoundMethod method) {
+        super(builder, select, method);
+    }
+
+    @Override
+    public ValueMatch<T> startsWith(final String str) {
+        return withPredicate(new StartsWith(relativePath(), str));
+    }
+
+    @Override
+    public ValueMatch<T> endsWith(final String str) {
+        return withPredicate(new EndsWith(relativePath(), str));
+    }
+
+    @Override
+    public ValueMatch<T> contains(final String str) {
+        return withPredicate(new Contains(relativePath(), str));
+    }
+
+    @Override
+    public ValueMatch<T> matchesPattern(final Pattern pattern) {
+        return withPredicate(new MatchesPattern(relativePath(), pattern));
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatch.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatch.java
new file mode 100644 (file)
index 0000000..1152fb6
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import static java.util.Objects.requireNonNull;
+
+import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.ValueMatch;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+final class DefaultValueMatch<T extends DataObject> implements ValueMatch<T> {
+    private final QueryBuilderState builder;
+    private final InstanceIdentifier<T> select;
+
+    DefaultValueMatch(final QueryBuilderState builder, final InstanceIdentifier<T> select) {
+        this.builder = requireNonNull(builder);
+        this.select = requireNonNull(select);
+    }
+
+    @Override
+    public MatchBuilderPath<T, T> and() {
+        return new DefaultMatchBuilderPath<>(builder, select, select.builder());
+    }
+
+    @Override
+    public QueryExpression<T> build() {
+        return builder.buildQuery();
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatchBuilder.java
new file mode 100644 (file)
index 0000000..bf4902f
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+final class DefaultValueMatchBuilder<T extends DataObject, V> extends AbstractValueMatchBuilder<T, V> {
+    DefaultValueMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier<T> select,
+            final BoundMethod method) {
+        super(builder, select, method);
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/LambdaDecoder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/LambdaDecoder.java
new file mode 100644 (file)
index 0000000..18fc7b5
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import java.lang.invoke.SerializedLambda;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath.LeafReference;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Utility class for forcing decoding lambda instances to the method being invoked. The theory here is that
+ * {@code MatchBuilderPath.leaf()} methods are expected to be used with {@code method references}, which are converted
+ * to {@link LeafReference} lambdas.
+ *
+ * <p>
+ * We then assume runtime is following guidance around {@link SerializedLambda}, thus Serializable lambdas have a
+ * {@code writeReplace()} method and that it produces {@link SerializedLambda} -- which we use to get the information
+ * about what the lambda does at least in the single case we support.
+ *
+ * <p>
+ * An alternative approach to cracking the lambda would be to generate a dynamic proxy implementation of the base
+ * DataObject (we have the Class to do that), back it by a invocation handler which throws a private RuntimeException
+ * subclass containing the name of the invoked method. We then would invoke the lambda on such a proxy and intercept
+ * the exception raised. This unfortunately has multiple downsides:
+ * <ul>
+ *   <li>it requires a properly-managed ClassLoader (or pollutes original classloader with the proxy class)</li>
+ *   <li>it makes it appear we support something else than method references, which we do not</li>
+ *   <li>it creates additional implementation of the interface, bringing the system-wide total to 3, which can hurt
+ *       JIT's decisions</li>
+ * </ul>
+ */
+final class LambdaDecoder {
+    static final class LambdaTarget implements Immutable {
+        final String targetClass;
+        final String targetMethod;
+
+        LambdaTarget(final String targetClass, final String targetMethod) {
+            this.targetClass = requireNonNull(targetClass);
+            this.targetMethod = requireNonNull(targetMethod);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this).add("class", targetClass).add("method", targetMethod).toString();
+        }
+    }
+
+    private static final LoadingCache<Class<?>, Method> REPLACE_CACHE = CacheBuilder.newBuilder()
+            .weakKeys().weakValues().build(new CacheLoader<Class<?>, Method>() {
+                @Override
+                public Method load(final Class<?> key) throws PrivilegedActionException {
+                    return AccessController.doPrivileged((PrivilegedExceptionAction<Method>) () -> {
+                        final Method method = key.getDeclaredMethod("writeReplace");
+                        method.setAccessible(true);
+                        return method;
+                    });
+                }
+            });
+    private static final LoadingCache<LeafReference<?, ?>, LambdaTarget> LAMBDA_CACHE = CacheBuilder.newBuilder()
+            .weakKeys().build(new CacheLoader<LeafReference<?, ?>, LambdaTarget>() {
+                @Override
+                public LambdaTarget load(final LeafReference<?, ?> ref) throws Exception {
+                    final Object replaced = REPLACE_CACHE.get(ref.getClass()).invoke(ref);
+                    verify(replaced instanceof SerializedLambda, "Unexpected replaced object %s", replaced);
+                    final SerializedLambda serialized = (SerializedLambda) replaced;
+                    return new LambdaTarget(serialized.getImplClass().replace('/', '.'),
+                        serialized.getImplMethodName());
+                }
+            });
+
+    private LambdaDecoder() {
+        // Hidden on purpose
+    }
+
+    static LambdaTarget resolveLambda(final LeafReference<?, ?> lambda) {
+        return LAMBDA_CACHE.getUnchecked(lambda);
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/OSGiQueryFactory.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/OSGiQueryFactory.java
new file mode 100644 (file)
index 0000000..6e57fd0
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.mdsal.binding.api.query.DescendantQueryBuilder;
+import org.opendaylight.mdsal.binding.api.query.QueryFactory;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Beta
+@Component(immediate = true)
+public final class OSGiQueryFactory implements QueryFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(OSGiQueryFactory.class);
+
+    @Reference
+    BindingCodecTree codec;
+
+    private DefaultQueryFactory delegate;
+
+    @Override
+    public <T extends DataObject> DescendantQueryBuilder<T> querySubtree(final InstanceIdentifier<T> rootPath) {
+        return delegate.querySubtree(rootPath);
+    }
+
+    @Activate
+    void activate() {
+        delegate = new DefaultQueryFactory(codec);
+        LOG.info("Binding Query activated");
+    }
+
+    @Deactivate
+    void deactivate() {
+        delegate = null;
+        LOG.info("Binding Query deactivated");
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderState.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderState.java
new file mode 100644 (file)
index 0000000..7686c61
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath.LeafReference;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryStructureException;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
+import org.opendaylight.mdsal.binding.dom.codec.spi.BindingSchemaMapping;
+import org.opendaylight.mdsal.dom.api.query.DOMQuery;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
+import org.opendaylight.mdsal.query.binding.adapter.LambdaDecoder.LambdaTarget;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
+
+final class QueryBuilderState {
+    static final class BoundMethod implements Immutable {
+        final @NonNull YangInstanceIdentifier parentPath;
+        final @NonNull BindingCodecTreeNode methodCodec;
+
+        BoundMethod(final YangInstanceIdentifier parentPath, final BindingCodecTreeNode methodCodec) {
+            this.parentPath = requireNonNull(parentPath);
+            this.methodCodec = requireNonNull(methodCodec);
+        }
+    }
+
+    private final BindingCodecTree codec;
+
+    private final List<DOMQueryPredicate> predicates = new ArrayList<>();
+    private final YangInstanceIdentifier root;
+    private YangInstanceIdentifier absoluteSelect;
+    private YangInstanceIdentifier relativeSelect;
+
+    QueryBuilderState(final BindingCodecTree codec, final InstanceIdentifier<?> root) {
+        this.codec = requireNonNull(codec);
+        this.root = fromBinding(root);
+    }
+
+    void setSelectPath(final @NonNull InstanceIdentifier<?> selectPath) {
+        checkState(root != null, "Root path has not been set yet");
+        checkState(relativeSelect == null, "Select path has already been set to %s", relativeSelect);
+
+        absoluteSelect = fromBinding(selectPath);
+        relativeSelect = absoluteSelect.relativeTo(root)
+                .orElseThrow(() -> new IllegalStateException(root + " is not an ancestor of " + absoluteSelect));
+    }
+
+    @NonNull BoundMethod bindMethod(final @NonNull InstanceIdentifier<?> bindingPath,
+            final @NonNull LeafReference<?, ?> ref) {
+        // Verify bindingPath, which will give us something to fish in
+        final BindingDataObjectCodecTreeNode<?> targetCodec = codec.getSubtreeCodec(bindingPath);
+        checkState(targetCodec != null, "Failed to find codec for %s", bindingPath);
+
+        final WithStatus targetSchema = targetCodec.getSchema();
+        verify(targetSchema instanceof DataNodeContainer, "Unexpected target schema %s", targetSchema);
+
+        final LambdaTarget targetLeaf = LambdaDecoder.resolveLambda(ref);
+        verify(targetLeaf.targetClass.equals(bindingPath.getTargetType().getName()), "Mismatched target %s and path %s",
+            targetLeaf, bindingPath);
+        final DataSchemaNode child = findChild((DataNodeContainer) targetSchema, targetLeaf.targetMethod);
+        final YangInstanceIdentifier absTarget = fromBinding(bindingPath);
+        final YangInstanceIdentifier relTarget = absTarget.relativeTo(absoluteSelect)
+                .orElseThrow(() -> new IllegalStateException(absoluteSelect + " is not an ancestor of " + absTarget));
+
+        return new BoundMethod(relTarget, targetCodec.yangPathArgumentChild(new NodeIdentifier(child.getQName())));
+    }
+
+    void addPredicate(final DOMQueryPredicate predicate) {
+        predicates.add(requireNonNull(predicate));
+    }
+
+    <T extends DataObject> @NonNull QueryExpression<T> buildQuery() {
+        return new DefaultQuery<>(new DOMQuery(root, relativeSelect, predicates));
+    }
+
+    private @NonNull YangInstanceIdentifier fromBinding(final InstanceIdentifier<?> bindingId) {
+        return codec.getInstanceIdentifierCodec().fromBinding(bindingId);
+    }
+
+    private static DataSchemaNode findChild(final DataNodeContainer parent, final String methodName) {
+        for (DataSchemaNode child : parent.getChildNodes()) {
+            if (methodName.equals(BindingSchemaMapping.getGetterMethodName(child))) {
+                return child;
+            }
+        }
+        throw new QueryStructureException("Failed to find schema matching " + methodName + " in " + parent);
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderTest.java b/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderTest.java
new file mode 100644 (file)
index 0000000..23d9b0c
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.query.binding.adapter;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.binding.runtime.spi.BindingRuntimeHelpers;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryFactory;
+import org.opendaylight.mdsal.binding.api.query.QueryStructureException;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
+import org.opendaylight.mdsal.binding.dom.codec.impl.DefaultBindingCodecTreeFactory;
+import org.opendaylight.mdsal.binding.generator.impl.DefaultBindingRuntimeGenerator;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.Top;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.top.level.list.NestedList;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class QueryBuilderTest {
+    private static BindingCodecTree CODEC;
+
+    private final QueryFactory factory = new DefaultQueryFactory(CODEC);
+
+    @BeforeClass
+    public static void beforeClass() {
+        CODEC = new DefaultBindingCodecTreeFactory().create(BindingRuntimeHelpers.createRuntimeContext(
+            new DefaultBindingRuntimeGenerator()));
+    }
+
+    @Test
+    public void bar() throws QueryStructureException {
+        final QueryExpression<TopLevelList> query = factory.querySubtree(InstanceIdentifier.builder(Top.class).build())
+                .extractChild(TopLevelList.class)
+                .matching()
+                    .childObject(NestedList.class)
+                    .leaf(NestedList::getName).contains("foo")
+                    .and().leaf(TopLevelList::getName).valueEquals("bar")
+                .build();
+//
+//        // Execution start
+//        final Result<TopLevelList> result = query.getResult();
+//        // Execution fetch
+//        final TopLevelList value = result.getValue();
+    }
+}
diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQuery.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQuery.java
new file mode 100644 (file)
index 0000000..81cc44e
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.dom.api.query;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+@Beta
+public final class DOMQuery implements Immutable {
+    private final @NonNull YangInstanceIdentifier root;
+    // Note: relative to root
+    private final @NonNull YangInstanceIdentifier select;
+    private final @NonNull ImmutableList<DOMQueryPredicate> predicates;
+
+    public DOMQuery(final YangInstanceIdentifier root, final YangInstanceIdentifier select,
+            final List<? extends DOMQueryPredicate> predicates) {
+        this.root = requireNonNull(root);
+        this.select = requireNonNull(select);
+        this.predicates = ImmutableList.copyOf(predicates);
+    }
+
+    public @NonNull YangInstanceIdentifier getRoot() {
+        return root;
+    }
+
+    public @NonNull YangInstanceIdentifier getSelect() {
+        return select;
+    }
+
+    public @NonNull List<? extends DOMQueryPredicate> getPredicates() {
+        return predicates;
+    }
+}
diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryLike.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryLike.java
new file mode 100644 (file)
index 0000000..c0913ac
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.dom.api.query;
+
+import com.google.common.annotations.Beta;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Trait for objects which can be formulated in terms of a {@link DOMQuery}.
+ */
+@Beta
+@NonNullByDefault
+public interface DOMQueryLike extends Immutable {
+    /**
+     * Return a {@link DOMQuery} view of this object.
+     *
+     * @return A DOMQuery
+     */
+    DOMQuery asDOMQuery();
+}
diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryPredicate.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryPredicate.java
new file mode 100644 (file)
index 0000000..735126c
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.dom.api.query;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+@Beta
+public abstract class DOMQueryPredicate implements Immutable, Predicate<Object> {
+    abstract static class AbstractValueDOMQueryPredicate<T> extends DOMQueryPredicate {
+        private final @NonNull T value;
+
+        AbstractValueDOMQueryPredicate(final YangInstanceIdentifier relativePath, final T value) {
+            super(relativePath);
+            this.value = requireNonNull(value);
+        }
+
+        final @NonNull T value() {
+            return value;
+        }
+
+        @Override
+        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+            return helper.add("value", value);
+        }
+    }
+
+    abstract static class AbstractComparableDOMQueryPredicate<T extends Comparable<T>>
+            extends AbstractValueDOMQueryPredicate<T> {
+        AbstractComparableDOMQueryPredicate(final YangInstanceIdentifier relativePath, final T value) {
+            super(relativePath, value);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public final boolean test(final Object data) {
+            return data != null && test(value().compareTo((T) data));
+        }
+
+        abstract boolean test(int valueToData);
+    }
+
+    abstract static class AbstractStringDOMQueryPredicate extends AbstractValueDOMQueryPredicate<String> {
+        AbstractStringDOMQueryPredicate(final YangInstanceIdentifier relativePath, final String value) {
+            super(relativePath, value);
+        }
+
+        @Override
+        public final boolean test(final Object data) {
+            return data instanceof String && test((String) data);
+        }
+
+        abstract boolean test(@NonNull String str);
+    }
+
+    public static final class Exists extends DOMQueryPredicate {
+        public Exists(final YangInstanceIdentifier relativePath) {
+            super(relativePath);
+        }
+
+        @Override
+        public boolean test(final Object data) {
+            return data != null;
+        }
+    }
+
+    public static final class ValueEquals<T> extends AbstractValueDOMQueryPredicate<T> {
+        public ValueEquals(final YangInstanceIdentifier relativePath, final T value) {
+            super(relativePath, value);
+        }
+
+        @Override
+        public boolean test(final Object data) {
+            return value().equals(data);
+        }
+    }
+
+    public static final class GreaterThan<T extends Comparable<T>> extends AbstractComparableDOMQueryPredicate<T> {
+        public GreaterThan(final YangInstanceIdentifier relativePath, final T value) {
+            super(relativePath, value);
+        }
+
+        @Override
+        boolean test(final int valueToData) {
+            return valueToData <= 0;
+        }
+    }
+
+    public static final class GreaterThanOrEqual<T extends Comparable<T>>
+            extends AbstractComparableDOMQueryPredicate<T> {
+        public GreaterThanOrEqual(final YangInstanceIdentifier relativePath, final T value) {
+            super(relativePath, value);
+        }
+
+        @Override
+        boolean test(final int valueToData) {
+            return valueToData < 0;
+        }
+    }
+
+    public static final class LessThan<T extends Comparable<T>> extends AbstractComparableDOMQueryPredicate<T> {
+        public LessThan(final YangInstanceIdentifier relativePath, final T value) {
+            super(relativePath, value);
+        }
+
+        @Override
+        boolean test(final int valueToData) {
+            return valueToData >= 0;
+        }
+    }
+
+    public static final class LessThanOrEqual<T extends Comparable<T>> extends AbstractComparableDOMQueryPredicate<T> {
+        public LessThanOrEqual(final YangInstanceIdentifier relativePath, final T value) {
+            super(relativePath, value);
+        }
+
+        @Override
+        boolean test(final int valueToData) {
+            return valueToData > 0;
+        }
+    }
+
+    public static final class StartsWith extends AbstractStringDOMQueryPredicate {
+        public StartsWith(final YangInstanceIdentifier relativePath, final String str) {
+            super(relativePath, str);
+        }
+
+        @Override
+        boolean test(final String str) {
+            return str.startsWith(value());
+        }
+    }
+
+    public static final class EndsWith extends AbstractStringDOMQueryPredicate {
+        public EndsWith(final YangInstanceIdentifier relativePath, final String str) {
+            super(relativePath, str);
+        }
+
+        @Override
+        boolean test(final String str) {
+            return str.endsWith(value());
+        }
+    }
+
+    public static final class Contains extends AbstractStringDOMQueryPredicate {
+        public Contains(final YangInstanceIdentifier relativePath, final String str) {
+            super(relativePath, str);
+        }
+
+        @Override
+        boolean test(final String str) {
+            return str.contains(value());
+        }
+    }
+
+    public static final class MatchesPattern extends DOMQueryPredicate {
+        private final Pattern pattern;
+
+        public MatchesPattern(final YangInstanceIdentifier relativePath, final Pattern pattern) {
+            super(relativePath);
+            this.pattern = requireNonNull(pattern);
+        }
+
+        @Override
+        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+            return helper.add("pattern", pattern);
+        }
+
+        @Override
+        public boolean test(final Object data) {
+            return data instanceof CharSequence && pattern.matcher((CharSequence) data).matches();
+        }
+    }
+
+    private final @NonNull YangInstanceIdentifier relativePath;
+
+    DOMQueryPredicate(final YangInstanceIdentifier relativePath) {
+        this.relativePath = requireNonNull(relativePath);
+    }
+
+    public final @NonNull YangInstanceIdentifier getPath() {
+        return relativePath;
+    }
+
+    @Override
+    public abstract boolean test(@Nullable Object data);
+
+    @Override
+    public String toString() {
+        return addToStringAttributes(MoreObjects.toStringHelper(this).add("path", relativePath)).toString();
+    }
+
+    ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+        return helper;
+    }
+}
diff --git a/dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/query/DOMQueryEvaluator.java b/dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/query/DOMQueryEvaluator.java
new file mode 100644 (file)
index 0000000..d7a9dde
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.mdsal.dom.spi.query;
+
+import com.google.common.collect.ImmutableList;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import org.opendaylight.mdsal.dom.api.query.DOMQuery;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+
+public final class DOMQueryEvaluator {
+    private DOMQueryEvaluator() {
+
+    }
+
+    public static List<? extends Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> evaluate(final DOMQuery query,
+            final NormalizedNode<?, ?> root) {
+        final YangInstanceIdentifier path = query.getSelect();
+        return path.isEmpty() ? evalSingle(root, query)
+                : evalPath(new ArrayDeque<>(path.getPathArguments()), root, query);
+    }
+
+    private static List<? extends Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> evalPath(
+            final ArrayDeque<PathArgument> remaining, final NormalizedNode<?,?> data, final DOMQuery query) {
+        final List<Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> result = new ArrayList<>();
+        evalPath(result, query.getRoot(), remaining, data, query);
+        return result;
+    }
+
+    private static void evalPath(final List<Entry<YangInstanceIdentifier, NormalizedNode<?,?>>> result,
+            final YangInstanceIdentifier path, final ArrayDeque<PathArgument> remaining,
+            final NormalizedNode<?, ?> data, final DOMQuery query) {
+        final PathArgument next = remaining.poll();
+        if (next == null) {
+            if (matches(data, query)) {
+                result.add(new SimpleImmutableEntry<>(query.getRoot(), data));
+            }
+            return;
+        }
+
+        // TODO: this is probably insufficient
+        NormalizedNodes.findNode(data, next)
+            .ifPresent(child -> evalPath(result, path.node(next), remaining, child, query));
+        remaining.push(next);
+    }
+
+    private static List<? extends Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> evalSingle(
+            final NormalizedNode<?, ?> data, final DOMQuery query) {
+        return matches(data, query) ? ImmutableList.of()
+                : ImmutableList.of(new SimpleImmutableEntry<>(query.getRoot(), data));
+    }
+
+    private static boolean matches(final NormalizedNode<?, ?> data, final DOMQuery query) {
+        for (DOMQueryPredicate pred : query.getPredicates()) {
+            if (!pred.test(data)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}