Fix mdsal-dom-spi dependencies
[mdsal.git] / dom / mdsal-dom-api / src / main / java / org / opendaylight / mdsal / dom / api / query / DOMQueryPredicate.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.dom.api.query;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.MoreObjects;
14 import com.google.common.collect.ImmutableList;
15 import java.util.Iterator;
16 import java.util.function.Predicate;
17 import java.util.regex.Pattern;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.concepts.Immutable;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
25
26 /**
27  * A single {@link DOMQuery} predicate. It is composed of a relative path and a match. The relative path needs to be
28  * expanded using usual wildcard rules, i.e. NodeIdentifier being used as a 'match all' identifier. For all candidate
29  * nodes selected by the relative path, the corresponding match needs to be invoked.
30  */
31 @Beta
32 @NonNullByDefault
33 public final class DOMQueryPredicate implements Immutable {
34     /**
35      * A single match. The primary entrypoint is {@link #test(NormalizedNode)}, but during composition instances may
36      * be combined in a manner similar to {@link Predicate}.
37      */
38     public abstract static class Match {
39         Match() {
40             // Hidden on purpose
41         }
42
43         public static final Match exists() {
44             return MatchExists.INSTACE;
45         }
46
47         public static final <T extends Comparable<T>> Match greaterThan(final T value) {
48             return new MatchGreaterThan<>(value);
49         }
50
51         public static final <T extends Comparable<T>> Match greaterThanOrEqual(final T value) {
52             return new MatchGreaterThanOrEqual<>(value);
53         }
54
55         public static final <T extends Comparable<T>> Match lessThan(final T value) {
56             return new MatchLessThan<>(value);
57         }
58
59         public static final <T extends Comparable<T>> Match lessThanOrEqual(final T value) {
60             return new MatchLessThanOrEqual<>(value);
61         }
62
63         public static final Match stringMatches(final Pattern pattern) {
64             return new MatchStringMatches(pattern);
65         }
66
67         public static final Match stringStartsWith(final String str) {
68             return new MatchStringStartsWith(str);
69         }
70
71         public static final Match stringEndsWith(final String str) {
72             return new MatchStringEndsWith(str);
73         }
74
75         public static final Match stringContains(final String str) {
76             return new MatchStringContains(str);
77         }
78
79         public static final <V> Match valueEquals(final V value) {
80             return new MatchValueEquals<>(value);
81         }
82
83         /**
84          * Return a {@link Match} which tests the opposite of this match.
85          *
86          * @return Negated match.
87          */
88         public Match negate() {
89             return new MatchNot(this);
90         }
91
92         public Match and(final Match other) {
93             return new MatchAll(ImmutableList.of(this, other));
94         }
95
96         public Match or(final Match other) {
97             return new MatchAny(ImmutableList.of(this, other));
98         }
99
100         public abstract boolean test(@Nullable NormalizedNode data);
101
102         final void appendTo(final StringBuilder sb) {
103             sb.append(op()).append('(');
104             appendArgument(sb);
105             sb.append(')');
106         }
107
108         void appendArgument(final StringBuilder sb) {
109             // No-op by default
110         }
111
112         abstract String op();
113
114         @Override
115         public final String toString() {
116             final var sb = new StringBuilder();
117             appendTo(sb);
118             return sb.toString();
119         }
120     }
121
122     private static final class MatchAll extends CompositeMatch {
123         MatchAll(final ImmutableList<Match> components) {
124             super(components);
125         }
126
127         @Override
128         public MatchAll and(final Match other) {
129             return new MatchAll(newComponents(other));
130         }
131
132         @Override
133         public boolean test(final @Nullable NormalizedNode data) {
134             for (Match component : components()) {
135                 if (!component.test(data)) {
136                     return false;
137                 }
138             }
139             return true;
140         }
141
142         @Override
143         String op() {
144             return "allOf";
145         }
146     }
147
148     private static final class MatchAny extends CompositeMatch {
149         MatchAny(final ImmutableList<Match> components) {
150             super(components);
151         }
152
153         @Override
154         public MatchAny or(final Match other) {
155             return new MatchAny(newComponents(other));
156         }
157
158         @Override
159         public boolean test(final @Nullable NormalizedNode data) {
160             for (Match component : components()) {
161                 if (component.test(data)) {
162                     return true;
163                 }
164             }
165             return false;
166         }
167
168         @Override
169         String op() {
170             return "anyOf";
171         }
172     }
173
174     private static final class MatchExists extends Match {
175         static final MatchExists INSTACE = new MatchExists();
176
177         private MatchExists() {
178             // Hidden on purpose
179         }
180
181         @Override
182         public boolean test(final @Nullable NormalizedNode data) {
183             return data != null;
184         }
185
186         @Override
187         String op() {
188             return "exists";
189         }
190     }
191
192     private static final class MatchNot extends Match {
193         private final Match match;
194
195         MatchNot(final Match match) {
196             this.match = requireNonNull(match);
197         }
198
199         @Override
200         public Match negate() {
201             return match;
202         }
203
204         @Override
205         public boolean test(final @Nullable NormalizedNode data) {
206             return !match.test(data);
207         }
208
209         @Override
210         String op() {
211             return "not";
212         }
213
214         @Override
215         void appendArgument(final StringBuilder sb) {
216             match.appendTo(sb);
217         }
218     }
219
220     private static final class MatchValueEquals<T> extends AbstractMatchValue<T> {
221         MatchValueEquals(final T value) {
222             super(value);
223         }
224
225         @Override
226         String op() {
227             return "eq";
228         }
229
230         @Override
231         boolean testValue(final @Nullable Object data) {
232             return value().equals(data);
233         }
234     }
235
236     private static final class MatchStringContains extends AbstractMatchString {
237         MatchStringContains(final String value) {
238             super(value);
239         }
240
241         @Override
242         String op() {
243             return "contains";
244         }
245
246         @Override
247         boolean testString(final String str) {
248             return str.contains(value());
249         }
250     }
251
252     private static final class MatchStringMatches extends AbstractMatch {
253         private final Pattern pattern;
254
255         MatchStringMatches(final Pattern pattern) {
256             this.pattern = requireNonNull(pattern);
257         }
258
259         @Override
260         String op() {
261             return "matches";
262         }
263
264         @Override
265         void appendArgument(final StringBuilder sb) {
266             sb.append(pattern);
267         }
268
269         @Override
270         boolean testValue(final @Nullable Object data) {
271             return data instanceof CharSequence && pattern.matcher((CharSequence) data).matches();
272         }
273     }
274
275     private static final class MatchStringStartsWith extends AbstractMatchString {
276         MatchStringStartsWith(final String value) {
277             super(value);
278         }
279
280         @Override
281         String op() {
282             return "startsWith";
283         }
284
285         @Override
286         boolean testString(final String str) {
287             return str.startsWith(value());
288         }
289     }
290
291     private static final class MatchStringEndsWith extends AbstractMatchString {
292         MatchStringEndsWith(final String value) {
293             super(value);
294         }
295
296         @Override
297         String op() {
298             return "endsWith";
299         }
300
301         @Override
302         boolean testString(final String str) {
303             return str.endsWith(value());
304         }
305     }
306
307     private static final class MatchGreaterThan<T extends Comparable<T>> extends AbstractMatchComparable<T> {
308         MatchGreaterThan(final T value) {
309             super(value);
310         }
311
312         @Override
313         String op() {
314             return "gt";
315         }
316
317         @Override
318         boolean testCompare(final int valueToData) {
319             return valueToData < 0;
320         }
321     }
322
323     private static final class MatchGreaterThanOrEqual<T extends Comparable<T>> extends AbstractMatchComparable<T> {
324         MatchGreaterThanOrEqual(final T value) {
325             super(value);
326         }
327
328         @Override
329         String op() {
330             return "gte";
331         }
332
333         @Override
334         boolean testCompare(final int valueToData) {
335             return valueToData <= 0;
336         }
337     }
338
339     private static final class MatchLessThan<T extends Comparable<T>> extends AbstractMatchComparable<T> {
340         MatchLessThan(final T value) {
341             super(value);
342         }
343
344         @Override
345         String op() {
346             return "lt";
347         }
348
349         @Override
350         boolean testCompare(final int valueToData) {
351             return valueToData > 0;
352         }
353     }
354
355     private static final class MatchLessThanOrEqual<T extends Comparable<T>> extends AbstractMatchComparable<T> {
356         MatchLessThanOrEqual(final T value) {
357             super(value);
358         }
359
360         @Override
361         String op() {
362             return "lte";
363         }
364
365         @Override
366         boolean testCompare(final int valueToData) {
367             return valueToData >= 0;
368         }
369     }
370
371     private abstract static class CompositeMatch extends Match {
372         private final ImmutableList<Match> components;
373
374         CompositeMatch(final ImmutableList<Match> components) {
375             this.components = requireNonNull(components);
376         }
377
378         final ImmutableList<Match> components() {
379             return components;
380         }
381
382         final ImmutableList<Match> newComponents(final Match nextComponent) {
383             return ImmutableList.<Match>builderWithExpectedSize(components.size() + 1)
384                 .addAll(components)
385                 .add(nextComponent)
386                 .build();
387         }
388
389         @Override
390         final void appendArgument(final StringBuilder sb) {
391             final Iterator<Match> it = components.iterator();
392             sb.append(it.next());
393             while (it.hasNext()) {
394                 sb.append(", ").append(it.next());
395             }
396         }
397     }
398
399     private abstract static class AbstractMatch extends Match {
400         AbstractMatch() {
401             // Hidden on purpose
402         }
403
404         @Override
405         public final boolean test(final @Nullable NormalizedNode data) {
406             return data instanceof LeafNode ? testValue(((LeafNode<?>) data).body()) : testValue(null);
407         }
408
409         abstract boolean testValue(@Nullable Object data);
410     }
411
412     private abstract static class AbstractMatchComparable<T extends Comparable<T>> extends AbstractMatchValue<T> {
413         AbstractMatchComparable(final T value) {
414             super(value);
415         }
416
417         @Override
418         @SuppressWarnings("unchecked")
419         final boolean testValue(final @Nullable Object data) {
420             return data != null && testCompare(value().compareTo((T) data));
421         }
422
423         /**
424          * Evaluate the result of {@code value().compareTo(data)}. Note this result is an inversion of what the match
425          * may be named.
426          *
427          * @param valueToData {@link Comparable#compareTo(Object)} result
428          * @return True if the result indicates a match
429          */
430         abstract boolean testCompare(int valueToData);
431     }
432
433     private abstract static class AbstractMatchString extends AbstractMatchValue<String> {
434         AbstractMatchString(final String value) {
435             super(value);
436         }
437
438         @Override
439         final boolean testValue(final @Nullable Object data) {
440             return data instanceof String && testString((String) data);
441         }
442
443         abstract boolean testString(String str);
444     }
445
446     private abstract static class AbstractMatchValue<T> extends AbstractMatch {
447         private final @NonNull T value;
448
449         AbstractMatchValue(final T value) {
450             this.value = requireNonNull(value);
451         }
452
453         final @NonNull T value() {
454             return value;
455         }
456
457         @Override
458         final void appendArgument(final StringBuilder sb) {
459             sb.append(value);
460         }
461     }
462
463     private final YangInstanceIdentifier relativePath;
464     private final Match match;
465
466     private DOMQueryPredicate(final YangInstanceIdentifier relativePath, final Match match) {
467         this.relativePath = requireNonNull(relativePath);
468         this.match = requireNonNull(match);
469     }
470
471     public static DOMQueryPredicate of(final YangInstanceIdentifier relativePath, final Match match) {
472         return new DOMQueryPredicate(relativePath, match);
473     }
474
475     public YangInstanceIdentifier relativePath() {
476         return relativePath;
477     }
478
479     public Match match() {
480         return match;
481     }
482
483     @Override
484     public String toString() {
485         return MoreObjects.toStringHelper(this).add("path", relativePath).add("match", match).toString();
486     }
487 }