Refactor DOMQueryPredicate
[mdsal.git] / dom / mdsal-dom-api / src / main / java / org / opendaylight / mdsal / dom / api / query / DOMQueryPredicate.java
index 80db6f34420efd8bced760979e9a1007a30e057e..fd6266131b1ccaa5c58e69d2e360ebee10d6bf82 100644 (file)
@@ -11,243 +11,470 @@ 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 com.google.common.collect.ImmutableList;
+import java.util.Iterator;
 import java.util.function.Predicate;
 import java.util.regex.Pattern;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
+/**
+ * A single {@link DOMQuery} predicate. It is composed of a relative path and a match. The relative path needs to be
+ * expanded using usual wildcard rules, i.e. NodeIdentifier being used as a 'match all' identifier. For all candidate
+ * nodes selected by the relative path, the corresponding match needs to be invoked.
+ */
 @Beta
-public abstract class DOMQueryPredicate implements Immutable, Predicate<NormalizedNode<?, ?>> {
-    abstract static class AbstractLeafDOMQueryPredicate extends DOMQueryPredicate {
-        AbstractLeafDOMQueryPredicate(final YangInstanceIdentifier relativePath) {
-            super(relativePath);
+@NonNullByDefault
+public final class DOMQueryPredicate implements Immutable {
+    /**
+     * A single match. The primary entrypoint is {@link #test(NormalizedNode)}, but during composition instances may
+     * be combined in a manner similar to {@link Predicate}.
+     */
+    public abstract static class Match {
+        Match() {
+            // Hidden on purpose
         }
 
-        @Override
-        public final boolean test(final NormalizedNode<?, ?> data) {
-            return testValue(data instanceof LeafNode ? ((LeafNode<?>) data).getValue() : null);
+        public static final Match exists() {
+            return MatchExists.INSTACE;
         }
 
-        abstract boolean testValue(Object data);
-    }
+        public static final <T extends Comparable<T>> Match greaterThan(final T value) {
+            return new MatchGreaterThan<>(value);
+        }
 
-    abstract static class AbstractValueDOMQueryPredicate<T> extends AbstractLeafDOMQueryPredicate {
-        private final @NonNull T value;
+        public static final <T extends Comparable<T>> Match greaterThanOrEqual(final T value) {
+            return new MatchGreaterThanOrEqual<>(value);
+        }
 
-        AbstractValueDOMQueryPredicate(final YangInstanceIdentifier relativePath, final T value) {
-            super(relativePath);
-            this.value = requireNonNull(value);
+        public static final <T extends Comparable<T>> Match lessThan(final T value) {
+            return new MatchLessThan<>(value);
         }
 
-        final @NonNull T value() {
-            return value;
+        public static final <T extends Comparable<T>> Match lessThanOrEqual(final T value) {
+            return new MatchLessThanOrEqual<>(value);
+        }
+
+        public static final Match stringMatches(final Pattern pattern) {
+            return new MatchStringMatches(pattern);
+        }
+
+        public static final Match stringStartsWith(final String str) {
+            return new MatchStringStartsWith(str);
+        }
+
+        public static final Match stringEndsWith(final String str) {
+            return new MatchStringEndsWith(str);
+        }
+
+        public static final Match stringContains(final String str) {
+            return new MatchStringContains(str);
+        }
+
+        public static final <V> Match valueEquals(final V value) {
+            return new MatchValueEquals<>(value);
+        }
+
+        /**
+         * Return a {@link Match} which tests the opposite of this match.
+         *
+         * @return Negated match.
+         */
+        public Match negate() {
+            return new MatchNot(this);
+        }
+
+        public Match and(final Match other) {
+            return new MatchAll(ImmutableList.of(this, other));
+        }
+
+        public Match or(final Match other) {
+            return new MatchAny(ImmutableList.of(this, other));
+        }
+
+        public abstract boolean test(@Nullable NormalizedNode<?, ?> data);
+
+        final void appendTo(final StringBuilder sb) {
+            sb.append(op()).append('(');
+            appendArgument(sb);
+            sb.append(')');
+        }
+
+        void appendArgument(final StringBuilder sb) {
+            // No-op by default
         }
 
+        abstract String op();
+
         @Override
-        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
-            return helper.add("value", value);
+        public final String toString() {
+            final var sb = new StringBuilder();
+            appendTo(sb);
+            return sb.toString();
         }
     }
 
-    abstract static class AbstractComparableDOMQueryPredicate<T extends Comparable<T>>
-            extends AbstractValueDOMQueryPredicate<T> {
-        AbstractComparableDOMQueryPredicate(final YangInstanceIdentifier relativePath, final T value) {
-            super(relativePath, value);
+    private static final class MatchAll extends CompositeMatch {
+        MatchAll(final ImmutableList<Match> components) {
+            super(components);
         }
 
         @Override
-        @SuppressWarnings("unchecked")
-        public final boolean testValue(final Object data) {
-            return data != null && test(value().compareTo((T) data));
+        public MatchAll and(final Match other) {
+            return new MatchAll(newComponents(other));
         }
 
-        abstract boolean test(int valueToData);
+        @Override
+        public boolean test(final @Nullable NormalizedNode<?, ?> data) {
+            for (Match component : components()) {
+                if (!component.test(data)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        String op() {
+            return "allOf";
+        }
     }
 
-    abstract static class AbstractStringDOMQueryPredicate extends AbstractValueDOMQueryPredicate<String> {
-        AbstractStringDOMQueryPredicate(final YangInstanceIdentifier relativePath, final String value) {
-            super(relativePath, value);
+    private static final class MatchAny extends CompositeMatch {
+        MatchAny(final ImmutableList<Match> components) {
+            super(components);
         }
 
         @Override
-        public final boolean testValue(final Object data) {
-            return data instanceof String && test((String) data);
+        public MatchAny or(final Match other) {
+            return new MatchAny(newComponents(other));
         }
 
-        abstract boolean test(@NonNull String str);
+        @Override
+        public boolean test(final @Nullable NormalizedNode<?, ?> data) {
+            for (Match component : components()) {
+                if (component.test(data)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        String op() {
+            return "anyOf";
+        }
     }
 
-    public static final class Exists extends DOMQueryPredicate {
-        public Exists(final YangInstanceIdentifier relativePath) {
-            super(relativePath);
+    private static final class MatchExists extends Match {
+        static final MatchExists INSTACE = new MatchExists();
+
+        private MatchExists() {
+            // Hidden on purpose
         }
 
         @Override
-        public boolean test(final NormalizedNode<?, ?> data) {
+        public boolean test(final @Nullable NormalizedNode<?, ?> data) {
             return data != null;
         }
+
+        @Override
+        String op() {
+            return "exists";
+        }
     }
 
-    public static final class Not extends DOMQueryPredicate {
-        private final DOMQueryPredicate predicate;
+    private static final class MatchNot extends Match {
+        private final Match match;
 
-        Not(final DOMQueryPredicate predicate) {
-            super(predicate.relativePath);
-            this.predicate = predicate;
+        MatchNot(final Match match) {
+            this.match = requireNonNull(match);
         }
 
-        public @NonNull DOMQueryPredicate predicate() {
-            return predicate;
+        @Override
+        public Match negate() {
+            return match;
+        }
+
+        @Override
+        public boolean test(final @Nullable NormalizedNode<?, ?> data) {
+            return !match.test(data);
         }
 
         @Override
-        public DOMQueryPredicate negate() {
-            return predicate;
+        String op() {
+            return "not";
         }
 
         @Override
-        public boolean test(final NormalizedNode<?, ?> data) {
-            return !predicate.test(data);
+        void appendArgument(final StringBuilder sb) {
+            match.appendTo(sb);
         }
     }
 
-    public static final class ValueEquals<T> extends AbstractValueDOMQueryPredicate<T> {
-        public ValueEquals(final YangInstanceIdentifier relativePath, final T value) {
-            super(relativePath, value);
+    private static final class MatchValueEquals<T> extends AbstractMatchValue<T> {
+        MatchValueEquals(final T value) {
+            super(value);
         }
 
         @Override
-        public boolean testValue(final Object data) {
+        String op() {
+            return "eq";
+        }
+
+        @Override
+        boolean testValue(final @Nullable 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);
+    private static final class MatchStringContains extends AbstractMatchString {
+        MatchStringContains(final String value) {
+            super(value);
+        }
+
+        @Override
+        String op() {
+            return "contains";
+        }
+
+        @Override
+        boolean testString(final String str) {
+            return str.contains(value());
+        }
+    }
+
+    private static final class MatchStringMatches extends AbstractMatch {
+        private final Pattern pattern;
+
+        MatchStringMatches(final Pattern pattern) {
+            this.pattern = requireNonNull(pattern);
+        }
+
+        @Override
+        String op() {
+            return "matches";
+        }
+
+        @Override
+        void appendArgument(final StringBuilder sb) {
+            sb.append(pattern);
+        }
+
+        @Override
+        boolean testValue(final @Nullable Object data) {
+            return data instanceof CharSequence && pattern.matcher((CharSequence) data).matches();
+        }
+    }
+
+    private static final class MatchStringStartsWith extends AbstractMatchString {
+        MatchStringStartsWith(final String value) {
+            super(value);
+        }
+
+        @Override
+        String op() {
+            return "startsWith";
+        }
+
+        @Override
+        boolean testString(final String str) {
+            return str.startsWith(value());
+        }
+    }
+
+    private static final class MatchStringEndsWith extends AbstractMatchString {
+        MatchStringEndsWith(final String value) {
+            super(value);
+        }
+
+        @Override
+        String op() {
+            return "endsWith";
+        }
+
+        @Override
+        boolean testString(final String str) {
+            return str.endsWith(value());
+        }
+    }
+
+    private static final class MatchGreaterThan<T extends Comparable<T>> extends AbstractMatchComparable<T> {
+        MatchGreaterThan(final T value) {
+            super(value);
         }
 
         @Override
-        boolean test(final int valueToData) {
+        String op() {
+            return "gt";
+        }
+
+        @Override
+        boolean testCompare(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);
+    private static final class MatchGreaterThanOrEqual<T extends Comparable<T>> extends AbstractMatchComparable<T> {
+        MatchGreaterThanOrEqual(final T value) {
+            super(value);
         }
 
         @Override
-        boolean test(final int valueToData) {
+        String op() {
+            return "gte";
+        }
+
+        @Override
+        boolean testCompare(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);
+    private static final class MatchLessThan<T extends Comparable<T>> extends AbstractMatchComparable<T> {
+        MatchLessThan(final T value) {
+            super(value);
+        }
+
+        @Override
+        String op() {
+            return "lt";
         }
 
         @Override
-        boolean test(final int valueToData) {
+        boolean testCompare(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);
+    private static final class MatchLessThanOrEqual<T extends Comparable<T>> extends AbstractMatchComparable<T> {
+        MatchLessThanOrEqual(final T value) {
+            super(value);
         }
 
         @Override
-        boolean test(final int valueToData) {
+        String op() {
+            return "lte";
+        }
+
+        @Override
+        boolean testCompare(final int valueToData) {
             return valueToData > 0;
         }
     }
 
-    public static final class StartsWith extends AbstractStringDOMQueryPredicate {
-        public StartsWith(final YangInstanceIdentifier relativePath, final String str) {
-            super(relativePath, str);
+    private abstract static class CompositeMatch extends Match {
+        private final ImmutableList<Match> components;
+
+        CompositeMatch(final ImmutableList<Match> components) {
+            this.components = requireNonNull(components);
+        }
+
+        final ImmutableList<Match> components() {
+            return components;
+        }
+
+        final ImmutableList<Match> newComponents(final Match nextComponent) {
+            return ImmutableList.<Match>builderWithExpectedSize(components.size() + 1)
+                .addAll(components)
+                .add(nextComponent)
+                .build();
         }
 
         @Override
-        boolean test(final String str) {
-            return str.startsWith(value());
+        final void appendArgument(final StringBuilder sb) {
+            final Iterator<Match> it = components.iterator();
+            sb.append(it.next());
+            while (it.hasNext()) {
+                sb.append(", ").append(it.next());
+            }
         }
     }
 
-    public static final class EndsWith extends AbstractStringDOMQueryPredicate {
-        public EndsWith(final YangInstanceIdentifier relativePath, final String str) {
-            super(relativePath, str);
+    private abstract static class AbstractMatch extends Match {
+        AbstractMatch() {
+            // Hidden on purpose
         }
 
         @Override
-        boolean test(final String str) {
-            return str.endsWith(value());
+        public final boolean test(final @Nullable NormalizedNode<?, ?> data) {
+            return data instanceof LeafNode ? testValue(((LeafNode<?>) data).getValue()) : testValue(null);
         }
+
+        abstract boolean testValue(@Nullable Object data);
     }
 
-    public static final class Contains extends AbstractStringDOMQueryPredicate {
-        public Contains(final YangInstanceIdentifier relativePath, final String str) {
-            super(relativePath, str);
+    private abstract static class AbstractMatchComparable<T extends Comparable<T>> extends AbstractMatchValue<T> {
+        AbstractMatchComparable(final T value) {
+            super(value);
         }
 
         @Override
-        boolean test(final String str) {
-            return str.contains(value());
+        @SuppressWarnings("unchecked")
+        final boolean testValue(final @Nullable Object data) {
+            return data != null && testCompare(value().compareTo((T) data));
         }
-    }
 
-    public static final class MatchesPattern extends AbstractLeafDOMQueryPredicate {
-        private final Pattern pattern;
+        abstract boolean testCompare(int valueToData);
+    }
 
-        public MatchesPattern(final YangInstanceIdentifier relativePath, final Pattern pattern) {
-            super(relativePath);
-            this.pattern = requireNonNull(pattern);
+    private abstract static class AbstractMatchString extends AbstractMatchValue<String> {
+        AbstractMatchString(final String value) {
+            super(value);
         }
 
         @Override
-        public boolean testValue(final Object data) {
-            return data instanceof CharSequence && pattern.matcher((CharSequence) data).matches();
+        final boolean testValue(final @Nullable Object data) {
+            return data instanceof String && testString((String) data);
+        }
+
+        abstract boolean testString(String str);
+    }
+
+    private abstract static class AbstractMatchValue<T> extends AbstractMatch {
+        private final @NonNull T value;
+
+        AbstractMatchValue(final T value) {
+            this.value = requireNonNull(value);
+        }
+
+        final @NonNull T value() {
+            return value;
         }
 
         @Override
-        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
-            return helper.add("pattern", pattern);
+        final void appendArgument(final StringBuilder sb) {
+            sb.append(value);
         }
     }
 
-    private final @NonNull YangInstanceIdentifier relativePath;
+    private final YangInstanceIdentifier relativePath;
+    private final Match match;
 
-    DOMQueryPredicate(final YangInstanceIdentifier relativePath) {
+    private DOMQueryPredicate(final YangInstanceIdentifier relativePath, final Match match) {
         this.relativePath = requireNonNull(relativePath);
+        this.match = requireNonNull(match);
     }
 
-    public final @NonNull YangInstanceIdentifier getPath() {
-        return relativePath;
+    public static DOMQueryPredicate of(final YangInstanceIdentifier relativePath, final Match match) {
+        return new DOMQueryPredicate(relativePath, match);
     }
 
-    @Override
-    public @NonNull DOMQueryPredicate negate() {
-        return new Not(this);
+    public YangInstanceIdentifier relativePath() {
+        return relativePath;
     }
 
-    @Override
-    public abstract boolean test(@Nullable NormalizedNode<?, ?> data);
+    public Match match() {
+        return match;
+    }
 
     @Override
     public String toString() {
-        return addToStringAttributes(MoreObjects.toStringHelper(this).add("path", relativePath)).toString();
-    }
-
-    ToStringHelper addToStringAttributes(final ToStringHelper helper) {
-        return helper;
+        return MoreObjects.toStringHelper(this).add("path", relativePath).add("match", match).toString();
     }
 }