2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.model.api.stmt;
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;
14 import com.google.common.annotations.Beta;
15 import com.google.common.collect.ImmutableSet;
16 import java.util.Arrays;
17 import java.util.HashSet;
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;
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.
32 * The set of features referenced in this expression is available through {@link #getReferencedFeatures()}.
34 * @author Robert Varga
37 public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<QName>> {
38 private abstract static sealed class Single extends IfFeatureExpr {
41 Single(final QName qname) {
42 this.qname = requireNonNull(qname);
46 public final ImmutableSet<QName> getReferencedFeatures() {
47 return ImmutableSet.of(qname);
51 public final int hashCode() {
52 return qname.hashCode();
56 final void addQNames(final Set<QName> set) {
61 public final boolean equals(final Object obj) {
62 return this == obj || getClass().isInstance(obj) && qname.equals(((Single) obj).qname);
66 // We are using arrays to hold our components to save a wee bit of space. The arrays originate from Sets retaining
67 // insertion order of Lists, each component is guaranteed to be unique, in definition order, not appearing multiple
69 private abstract static sealed class AbstractArray<T> extends IfFeatureExpr {
72 AbstractArray(final T[] array) {
73 this.array = requireNonNull(array);
74 verify(array.length > 1);
78 public final int hashCode() {
79 return Arrays.hashCode(array);
83 public final boolean equals(final Object obj) {
84 return this == obj || getClass().isInstance(obj)
85 && Arrays.deepEquals(array, ((AbstractArray<?>) obj).array);
88 abstract String infix();
91 private abstract static sealed class Complex extends AbstractArray<IfFeatureExpr> {
92 Complex(final IfFeatureExpr[] array) {
97 public final Set<QName> getReferencedFeatures() {
98 final Set<QName> ret = new HashSet<>();
104 final void addQNames(final Set<QName> set) {
105 for (IfFeatureExpr expr : array) {
110 final IfFeatureExpr[] negateExprs() {
111 final IfFeatureExpr[] ret = new IfFeatureExpr[array.length];
112 for (int i = 0; i < array.length; i++) {
113 ret[i] = verifyNotNull(array[i].negate());
119 public final String toString() {
120 final StringBuilder sb = new StringBuilder("(");
122 final String sep = infix();
123 for (int i = 1; i < array.length; ++i) {
124 sb.append(sep).append(array[i]);
126 return sb.append(')').toString();
130 private abstract static sealed class Compound extends AbstractArray<QName> {
131 Compound(final QName[] qnames) {
136 public final ImmutableSet<QName> getReferencedFeatures() {
137 return ImmutableSet.copyOf(array);
141 final void addQNames(final Set<QName> set) {
142 set.addAll(Arrays.asList(array));
146 public final String toString() {
147 final StringBuilder sb = new StringBuilder();
152 sb.append("(\"").append(array[0]).append('"');
153 final String sep = infix();
154 for (int i = 1; i < array.length; ++i) {
155 sb.append(sep).append('"').append(array[i]).append('"');
157 return sb.append(')').toString();
160 abstract boolean negated();
163 private static final class Absent extends Single {
164 Absent(final QName qname) {
169 public IfFeatureExpr negate() {
170 return isPresent(qname);
174 public boolean test(final Set<QName> supportedFeatures) {
175 return !supportedFeatures.contains(qname);
179 public String toString() {
180 return "not \"" + qname + '"';
184 private static final class Present extends Single {
185 Present(final QName qname) {
190 public IfFeatureExpr negate() {
191 return new Absent(qname);
195 public boolean test(final Set<QName> supportedFeatures) {
196 return supportedFeatures.contains(qname);
200 public String toString() {
201 return "\"" + qname + '"';
205 private static final class AllExprs extends Complex {
206 AllExprs(final IfFeatureExpr[] exprs) {
211 public IfFeatureExpr negate() {
212 return new AnyExpr(negateExprs());
216 public boolean test(final Set<QName> supportedFeatures) {
217 for (IfFeatureExpr expr : array) {
218 if (!expr.test(supportedFeatures)) {
231 private static final class AnyExpr extends Complex {
232 AnyExpr(final IfFeatureExpr[] exprs) {
237 public IfFeatureExpr negate() {
238 return new AllExprs(negateExprs());
242 public boolean test(final Set<QName> supportedFeatures) {
243 for (IfFeatureExpr expr : array) {
244 if (expr.test(supportedFeatures)) {
257 private abstract static sealed class AbstractAll extends Compound {
258 AbstractAll(final QName[] qnames) {
263 public final boolean test(final Set<QName> supportedFeatures) {
264 final boolean neg = negated();
265 for (QName qname : array) {
266 if (supportedFeatures.contains(qname) == neg) {
274 final String infix() {
279 private static final class All extends AbstractAll {
280 All(final QName[] qnames) {
285 public IfFeatureExpr negate() {
286 return new NotAll(array);
295 private static final class NotAll extends AbstractAll {
296 NotAll(final QName[] qnames) {
301 public IfFeatureExpr negate() {
302 return new All(array);
311 private abstract static sealed class AbstractAny extends Compound {
312 AbstractAny(final QName[] qnames) {
317 public final boolean test(final Set<QName> supportedFeatures) {
318 for (QName qname : array) {
319 if (supportedFeatures.contains(qname)) {
327 final String infix() {
332 private static final class Any extends AbstractAny {
333 Any(final QName[] array) {
338 public IfFeatureExpr negate() {
339 return new NotAny(array);
348 private static final class NotAny extends AbstractAny {
349 NotAny(final QName[] qnames) {
354 public IfFeatureExpr negate() {
355 return new Any(array);
365 * Construct an assertion that a feature is present in the set passed to {@link #test(Set)}.
367 * @param qname Feature QName
368 * @return An expression
369 * @throws NullPointerException if {@code qname} is null
371 public static final @NonNull IfFeatureExpr isPresent(final QName qname) {
372 return new Present(qname);
376 * Construct a intersection (logical {@code AND}) expression of specified expressions.
378 * @param exprs Constituent expressions
379 * @return An expression
380 * @throws NullPointerException if {@code exprs} or any of its members is null
381 * @throws IllegalArgumentException if {@code exprs} is empty
383 public static final @NonNull IfFeatureExpr and(final Set<IfFeatureExpr> exprs) {
384 return compose(exprs, All::new, NotAny::new, AllExprs::new);
388 * Construct a union (logical {@code OR}) expression of specified expressions.
390 * @param exprs Constituent expressions
391 * @return An expression
392 * @throws NullPointerException if {@code exprs} or any of its members is null
393 * @throws IllegalArgumentException if {@code exprs} is empty
395 public static final @NonNull IfFeatureExpr or(final Set<IfFeatureExpr> exprs) {
396 return compose(exprs, Any::new, NotAll::new, AnyExpr::new);
400 * Returns the set of all {@code feature}s referenced by this expression. Each feature is identified by its QName.
402 * @return The set of referenced features. Mutability of the returned Set and order of features is undefined.
404 public abstract @NonNull Set<QName> getReferencedFeatures();
407 public abstract @NonNull IfFeatureExpr negate();
410 public abstract boolean test(Set<QName> supportedFeatures);
413 public abstract int hashCode();
416 public abstract boolean equals(Object obj);
419 public abstract String toString();
422 * Add QNames referenced by this expression into a target set.
424 * @param set The set to fill
425 * @throws NullPointerException if {@code set} is null
427 abstract void addQNames(@NonNull Set<QName> set);
429 private static @NonNull IfFeatureExpr compose(final Set<IfFeatureExpr> exprs,
430 final Function<QName[], @NonNull Compound> allPresent,
431 final Function<QName[], @NonNull Compound> allAbsent,
432 final Function<IfFeatureExpr[], @NonNull Complex> mixed) {
433 switch (exprs.size()) {
435 throw new IllegalArgumentException("Expressions may not be empty");
437 return requireNonNull(exprs.iterator().next());
439 // Heavy lifting is needed
442 boolean negative = false;
443 boolean positive = false;
444 for (IfFeatureExpr expr : exprs) {
445 if (expr instanceof Present) {
447 } else if (expr instanceof Absent) {
450 return mixed.apply(exprs.toArray(new IfFeatureExpr[0]));
454 verify(negative || positive, "Unresolved expressions %s", exprs);
455 if (positive == negative) {
456 return mixed.apply(exprs.toArray(new IfFeatureExpr[0]));
459 final var qnames = exprs.stream()
461 verify(expr instanceof Single, "Unexpected expression %s", expr);
462 return ((Single) expr).qname;
464 .collect(ImmutableSet.toImmutableSet())
465 .toArray(new QName[0]);
466 return positive ? allPresent.apply(qnames) : allAbsent.apply(qnames);