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();
}
}
import java.util.List;
import java.util.Optional;
import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
-import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Not;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Match;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
// Utility class
}
- static boolean matches(final NormalizedNode<?, ?> data, final List<? extends DOMQueryPredicate> predicates) {
+ static boolean matchesAll(final NormalizedNode<?, ?> data, final List<? extends DOMQueryPredicate> predicates) {
// TODO: it would be nice if predicates were somehow structured -- can we perhaps sort them by their
// InstanceIdentifier? If the predicates are sharing a common subpath. Hence if we can guarantee
// predicates are in a certain order, we would not end up in subsequent re-lookups of the same node.
// So now, dealing with implementations: YangInstanceIdentifier.getLastPathArgument() is always cheap.
// If its parent is YangInstanceIdentifier.ROOT (i.e. isEmpty() == true), we are dealing with a last-step
// lookup -- in which case we forgo iteration:
- final YangInstanceIdentifier path = pred.getPath();
+ final YangInstanceIdentifier path = pred.relativePath();
if (path.coerceParent().isEmpty()) {
- if (pred instanceof Not) {
- if (matchesChild(((Not) pred).predicate(), data, path.getLastPathArgument())) {
- return false;
- }
- } else if (!matchesChild(pred, data, path.getLastPathArgument())) {
+ if (!matchesChild(pred.match(), data, path.getLastPathArgument())) {
return false;
}
continue;
pathArgs.addAll(path.getPathArguments());
// The stage is set, we now have to deal with potential negation.
- if (pred instanceof Not) {
- if (matchesAny(((Not) pred).predicate(), data, pathArgs)) {
- return false;
- }
- } else if (!matchesAny(pred, data, pathArgs)) {
+ if (!matchesAny(pred.match(), data, pathArgs)) {
return false;
}
return true;
}
- private static boolean matchesAny(final DOMQueryPredicate pred, final NormalizedNode<?, ?> data,
+ private static boolean matchesAny(final Match match, final NormalizedNode<?, ?> data,
final Deque<PathArgument> pathArgs) {
// Guaranteed to have at least one item
final PathArgument pathArg = pathArgs.pop();
// Ultimate item -- reuse lookup & match
if (pathArgs.isEmpty()) {
pathArgs.push(pathArg);
- return matchesChild(pred, data, pathArg);
+ return matchesChild(match, data, pathArg);
}
final Optional<NormalizedNode<?, ?>> direct = NormalizedNodes.getDirectChild(data, pathArg);
if (direct.isPresent()) {
- final boolean ret = matchesAny(pred, direct.orElseThrow(), pathArgs);
+ final boolean ret = matchesAny(match, direct.orElseThrow(), pathArgs);
pathArgs.push(pathArg);
return ret;
}
// We may be dealing with a wildcard here. NodeIdentifier is a final class, hence this is as fast as it gets.
if (pathArg instanceof NodeIdentifier && data instanceof MapNode) {
for (MapEntryNode child : ((MapNode) data).getValue()) {
- if (matchesAny(pred, child, pathArgs)) {
+ if (matchesAny(match, child, pathArgs)) {
pathArgs.push(pathArg);
return true;
}
return false;
}
- private static boolean matchesChild(final DOMQueryPredicate pred, final NormalizedNode<?, ?> data,
+ private static boolean matchesChild(final Match match, final NormalizedNode<?, ?> data,
final PathArgument pathArg) {
// Try the direct approach...
final Optional<NormalizedNode<?, ?>> direct = NormalizedNodes.getDirectChild(data, pathArg);
if (direct.isPresent()) {
- return pred.test(direct.orElseThrow());
+ return match.test(direct.orElseThrow());
}
// We may be dealing with a wildcard here. NodeIdentifier is a final class, hence this is as fast as it gets.
if (pathArg instanceof NodeIdentifier && data instanceof MapNode) {
for (MapEntryNode child : ((MapNode) data).getValue()) {
- if (pred.test(child)) {
+ if (match.test(child)) {
return true;
}
}
}
- return false;
+ return match.test(null);
}
}