Bump byte-buddy to 1.15.2
[yangtools.git] / model / yang-model-api / src / main / java / org / opendaylight / yangtools / yang / model / api / stmt / IfFeatureExpr.java
1 /*
2  * Copyright (c) 2019 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.yangtools.yang.model.api.stmt;
9
10 import static com.google.common.base.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.base.VerifyException;
15 import com.google.common.collect.ImmutableSet;
16 import java.util.Arrays;
17 import java.util.HashSet;
18 import java.util.Set;
19 import java.util.function.Function;
20 import java.util.function.Predicate;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.opendaylight.yangtools.concepts.Immutable;
23 import org.opendaylight.yangtools.yang.common.QName;
24
25 /**
26  * A resolved {@code if-feature} expression, implementing a {@link Predicate}. Internal representation is that of
27  * a tree of expressions, optimized for memory usage. {@link #negate()} performs an efficient logical negation without
28  * relying on default predicate methods. Other Predicate methods, like {@link #and(Predicate)} are not optimized in
29  * this implementation.
30  *
31  * <p>
32  * The set of features referenced in this expression is available through {@link #getReferencedFeatures()}.
33  */
34 public abstract sealed class IfFeatureExpr implements Immutable, Predicate<FeatureSet> {
35     private abstract static sealed class Single extends IfFeatureExpr {
36         final @NonNull QName qname;
37
38         Single(final QName qname) {
39             this.qname = requireNonNull(qname);
40         }
41
42         @Override
43         public final ImmutableSet<QName> getReferencedFeatures() {
44             return ImmutableSet.of(qname);
45         }
46
47         @Override
48         public final int hashCode() {
49             return qname.hashCode();
50         }
51
52         @Override
53         final void addQNames(final Set<QName> set) {
54             set.add(qname);
55         }
56
57         @Override
58         public final boolean equals(final Object obj) {
59             return this == obj || getClass().isInstance(obj) && qname.equals(((Single) obj).qname);
60         }
61     }
62
63     // We are using arrays to hold our components to save a wee bit of space. The arrays originate from Sets retaining
64     // insertion order of Lists, each component is guaranteed to be unique, in definition order, not appearing multiple
65     // times
66     private abstract static sealed class AbstractArray<T> extends IfFeatureExpr {
67         final T[] array;
68
69         AbstractArray(final T[] array) {
70             this.array = requireNonNull(array);
71             verify(array.length > 1);
72         }
73
74         @Override
75         public final int hashCode() {
76             return Arrays.hashCode(array);
77         }
78
79         @Override
80         public final boolean equals(final Object obj) {
81             return this == obj || getClass().isInstance(obj)
82                     && Arrays.deepEquals(array, ((AbstractArray<?>) obj).array);
83         }
84
85         abstract String infix();
86     }
87
88     private abstract static sealed class Complex extends AbstractArray<IfFeatureExpr> {
89         Complex(final IfFeatureExpr[] array) {
90             super(array);
91         }
92
93         @Override
94         public final Set<QName> getReferencedFeatures() {
95             final var ret = new HashSet<QName>();
96             addQNames(ret);
97             return ret;
98         }
99
100         @Override
101         final void addQNames(final Set<QName> set) {
102             for (var expr : array) {
103                 expr.addQNames(set);
104             }
105         }
106
107         final IfFeatureExpr[] negateExprs() {
108             final var ret = new IfFeatureExpr[array.length];
109             for (int i = 0; i < array.length; i++) {
110                 ret[i] = verifyNotNull(array[i].negate());
111             }
112             return ret;
113         }
114
115         @Override
116         public final String toString() {
117             final var sb = new StringBuilder("(").append(array[0]);
118             final var sep = infix();
119             for (int i = 1; i < array.length; ++i) {
120                 sb.append(sep).append(array[i]);
121             }
122             return sb.append(')').toString();
123         }
124     }
125
126     private abstract static sealed class Compound extends AbstractArray<QName> {
127         Compound(final QName[] qnames) {
128             super(qnames);
129         }
130
131         @Override
132         public final ImmutableSet<QName> getReferencedFeatures() {
133             return ImmutableSet.copyOf(array);
134         }
135
136         @Override
137         final void addQNames(final Set<QName> set) {
138             set.addAll(Arrays.asList(array));
139         }
140
141         @Override
142         public final String toString() {
143             final var sb = new StringBuilder();
144             if (negated()) {
145                 sb.append("not ");
146             }
147
148             sb.append("(\"").append(array[0]).append('"');
149             final var sep = infix();
150             for (int i = 1; i < array.length; ++i) {
151                 sb.append(sep).append('"').append(array[i]).append('"');
152             }
153             return sb.append(')').toString();
154         }
155
156         abstract boolean negated();
157     }
158
159     private static final class Absent extends Single {
160         Absent(final QName qname) {
161             super(qname);
162         }
163
164         @Override
165         public IfFeatureExpr negate() {
166             return isPresent(qname);
167         }
168
169         @Override
170         public boolean test(final FeatureSet supportedFeatures) {
171             return !supportedFeatures.contains(qname);
172         }
173
174         @Override
175         public String toString() {
176             return "not \"" + qname + '"';
177         }
178     }
179
180     private static final class Present extends Single {
181         Present(final QName qname) {
182             super(qname);
183         }
184
185         @Override
186         public IfFeatureExpr negate() {
187             return new Absent(qname);
188         }
189
190         @Override
191         public boolean test(final FeatureSet supportedFeatures) {
192             return supportedFeatures.contains(qname);
193         }
194
195         @Override
196         public String toString() {
197             return "\"" + qname + '"';
198         }
199     }
200
201     private static final class AllExprs extends Complex {
202         AllExprs(final IfFeatureExpr[] exprs) {
203             super(exprs);
204         }
205
206         @Override
207         public IfFeatureExpr negate() {
208             return new AnyExpr(negateExprs());
209         }
210
211         @Override
212         public boolean test(final FeatureSet supportedFeatures) {
213             for (var expr : array) {
214                 if (!expr.test(supportedFeatures)) {
215                     return false;
216                 }
217             }
218             return true;
219         }
220
221         @Override
222         String infix() {
223             return " and ";
224         }
225     }
226
227     private static final class AnyExpr extends Complex {
228         AnyExpr(final IfFeatureExpr[] exprs) {
229             super(exprs);
230         }
231
232         @Override
233         public IfFeatureExpr negate() {
234             return new AllExprs(negateExprs());
235         }
236
237         @Override
238         public boolean test(final FeatureSet supportedFeatures) {
239             for (var expr : array) {
240                 if (expr.test(supportedFeatures)) {
241                     return true;
242                 }
243             }
244             return false;
245         }
246
247         @Override
248         String infix() {
249             return " or ";
250         }
251     }
252
253     private abstract static sealed class AbstractAll extends Compound {
254         AbstractAll(final QName[] qnames) {
255             super(qnames);
256         }
257
258         @Override
259         public final boolean test(final FeatureSet supportedFeatures) {
260             final boolean neg = negated();
261             for (var qname : array) {
262                 if (supportedFeatures.contains(qname) == neg) {
263                     return false;
264                 }
265             }
266             return true;
267         }
268
269         @Override
270         final String infix() {
271             return " and ";
272         }
273     }
274
275     private static final class All extends AbstractAll {
276         All(final QName[] qnames) {
277             super(qnames);
278         }
279
280         @Override
281         public IfFeatureExpr negate() {
282             return new NotAll(array);
283         }
284
285         @Override
286         boolean negated() {
287             return false;
288         }
289     }
290
291     private static final class NotAll extends AbstractAll {
292         NotAll(final QName[] qnames) {
293             super(qnames);
294         }
295
296         @Override
297         public IfFeatureExpr negate() {
298             return new All(array);
299         }
300
301         @Override
302         boolean negated() {
303             return true;
304         }
305     }
306
307     private abstract static sealed class AbstractAny extends Compound {
308         AbstractAny(final QName[] qnames) {
309             super(qnames);
310         }
311
312         @Override
313         public final boolean test(final FeatureSet supportedFeatures) {
314             for (var qname : array) {
315                 if (supportedFeatures.contains(qname)) {
316                     return !negated();
317                 }
318             }
319             return negated();
320         }
321
322         @Override
323         final String infix() {
324             return " or ";
325         }
326     }
327
328     private static final class Any extends AbstractAny {
329         Any(final QName[] array) {
330             super(array);
331         }
332
333         @Override
334         public IfFeatureExpr negate() {
335             return new NotAny(array);
336         }
337
338         @Override
339         boolean negated() {
340             return false;
341         }
342     }
343
344     private static final class NotAny extends AbstractAny {
345         NotAny(final QName[] qnames) {
346             super(qnames);
347         }
348
349         @Override
350         public IfFeatureExpr negate() {
351             return new Any(array);
352         }
353
354         @Override
355         boolean negated() {
356             return true;
357         }
358     }
359
360     /**
361      * Construct an assertion that a feature is present in the set passed to {@link #test(FeatureSet)}.
362      *
363      * @param qname Feature QName
364      * @return An expression
365      * @throws NullPointerException if {@code qname} is {@code null}
366      */
367     public static final @NonNull IfFeatureExpr isPresent(final QName qname) {
368         return new Present(qname);
369     }
370
371     /**
372      * Construct a intersection (logical {@code AND}) expression of specified expressions.
373      *
374      * @param exprs Constituent expressions
375      * @return An expression
376      * @throws NullPointerException if {@code exprs} or any of its members is {@code null}
377      * @throws IllegalArgumentException if {@code exprs} is empty
378      */
379     public static final @NonNull IfFeatureExpr and(final Set<IfFeatureExpr> exprs) {
380         return compose(exprs, All::new, NotAny::new, AllExprs::new);
381     }
382
383     /**
384      * Construct a union (logical {@code OR}) expression of specified expressions.
385      *
386      * @param exprs Constituent expressions
387      * @return An expression
388      * @throws NullPointerException if {@code exprs} or any of its members is {@code null}
389      * @throws IllegalArgumentException if {@code exprs} is empty
390      */
391     public static final @NonNull IfFeatureExpr or(final Set<IfFeatureExpr> exprs) {
392         return compose(exprs, Any::new, NotAll::new, AnyExpr::new);
393     }
394
395     /**
396      * Returns the set of all {@code feature}s referenced by this expression. Each feature is identified by its QName.
397      *
398      * @return The set of referenced features. Mutability of the returned Set and order of features is undefined.
399      */
400     public abstract @NonNull Set<QName> getReferencedFeatures();
401
402     @Override
403     public abstract @NonNull IfFeatureExpr negate();
404
405     @Override
406     public abstract boolean test(FeatureSet supportedFeatures);
407
408     @Override
409     public abstract int hashCode();
410
411     @Override
412     public abstract boolean equals(Object obj);
413
414     @Override
415     public abstract String toString();
416
417     /**
418      * Add QNames referenced by this expression into a target set.
419      *
420      * @param set The set to fill
421      * @throws NullPointerException if {@code set} is {@code null}
422      */
423     abstract void addQNames(@NonNull Set<QName> set);
424
425     private static @NonNull IfFeatureExpr compose(final Set<IfFeatureExpr> exprs,
426             final Function<QName[], @NonNull Compound> allPresent,
427             final Function<QName[], @NonNull Compound> allAbsent,
428             final Function<IfFeatureExpr[], @NonNull Complex> mixed) {
429         return switch (exprs.size()) {
430             case 0 -> throw new IllegalArgumentException("Expressions may not be empty");
431             case 1 -> requireNonNull(exprs.iterator().next());
432             default -> {
433                 // Heavy lifting is needed: determine if whether this is all-positive or all-negative expressions
434                 boolean negative = false;
435                 boolean positive = false;
436                 for (var expr : exprs) {
437                     switch (expr) {
438                         case Present present -> {
439                             positive = true;
440                         }
441                         case Absent absent -> {
442                             negative = true;
443                         }
444                         case AbstractArray<?> array -> {
445                             negative = positive = true;
446                         }
447                     }
448                 }
449
450                 // Not nice: we have both negative and positive expressions
451                 verify(negative || positive, "Unresolved expressions %s", exprs);
452                 if (positive == negative) {
453                     yield mixed.apply(exprs.toArray(new IfFeatureExpr[0]));
454                 }
455
456                 // All expressions are either positive or negative, hence we can combine them efficiently
457                 final var qnames = exprs.stream()
458                     .map(expr -> switch (expr) {
459                         case Single single -> single.qname;
460                         case AbstractArray<?> array -> throw new VerifyException("Unexpected expression " + array);
461                     })
462                     .collect(ImmutableSet.toImmutableSet())
463                     .toArray(new QName[0]);
464                 yield positive ? allPresent.apply(qnames) : allAbsent.apply(qnames);
465             }
466         };
467     }
468 }