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.base.VerifyException;
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 public abstract sealed class IfFeatureExpr implements Immutable, Predicate<FeatureSet> {
35 private abstract static sealed class Single extends IfFeatureExpr {
36 final @NonNull QName qname;
38 Single(final QName qname) {
39 this.qname = requireNonNull(qname);
43 public final ImmutableSet<QName> getReferencedFeatures() {
44 return ImmutableSet.of(qname);
48 public final int hashCode() {
49 return qname.hashCode();
53 final void addQNames(final Set<QName> set) {
58 public final boolean equals(final Object obj) {
59 return this == obj || getClass().isInstance(obj) && qname.equals(((Single) obj).qname);
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
66 private abstract static sealed class AbstractArray<T> extends IfFeatureExpr {
69 AbstractArray(final T[] array) {
70 this.array = requireNonNull(array);
71 verify(array.length > 1);
75 public final int hashCode() {
76 return Arrays.hashCode(array);
80 public final boolean equals(final Object obj) {
81 return this == obj || getClass().isInstance(obj)
82 && Arrays.deepEquals(array, ((AbstractArray<?>) obj).array);
85 abstract String infix();
88 private abstract static sealed class Complex extends AbstractArray<IfFeatureExpr> {
89 Complex(final IfFeatureExpr[] array) {
94 public final Set<QName> getReferencedFeatures() {
95 final var ret = new HashSet<QName>();
101 final void addQNames(final Set<QName> set) {
102 for (var expr : array) {
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());
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]);
122 return sb.append(')').toString();
126 private abstract static sealed class Compound extends AbstractArray<QName> {
127 Compound(final QName[] qnames) {
132 public final ImmutableSet<QName> getReferencedFeatures() {
133 return ImmutableSet.copyOf(array);
137 final void addQNames(final Set<QName> set) {
138 set.addAll(Arrays.asList(array));
142 public final String toString() {
143 final var sb = new StringBuilder();
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('"');
153 return sb.append(')').toString();
156 abstract boolean negated();
159 private static final class Absent extends Single {
160 Absent(final QName qname) {
165 public IfFeatureExpr negate() {
166 return isPresent(qname);
170 public boolean test(final FeatureSet supportedFeatures) {
171 return !supportedFeatures.contains(qname);
175 public String toString() {
176 return "not \"" + qname + '"';
180 private static final class Present extends Single {
181 Present(final QName qname) {
186 public IfFeatureExpr negate() {
187 return new Absent(qname);
191 public boolean test(final FeatureSet supportedFeatures) {
192 return supportedFeatures.contains(qname);
196 public String toString() {
197 return "\"" + qname + '"';
201 private static final class AllExprs extends Complex {
202 AllExprs(final IfFeatureExpr[] exprs) {
207 public IfFeatureExpr negate() {
208 return new AnyExpr(negateExprs());
212 public boolean test(final FeatureSet supportedFeatures) {
213 for (var expr : array) {
214 if (!expr.test(supportedFeatures)) {
227 private static final class AnyExpr extends Complex {
228 AnyExpr(final IfFeatureExpr[] exprs) {
233 public IfFeatureExpr negate() {
234 return new AllExprs(negateExprs());
238 public boolean test(final FeatureSet supportedFeatures) {
239 for (var expr : array) {
240 if (expr.test(supportedFeatures)) {
253 private abstract static sealed class AbstractAll extends Compound {
254 AbstractAll(final QName[] qnames) {
259 public final boolean test(final FeatureSet supportedFeatures) {
260 final boolean neg = negated();
261 for (var qname : array) {
262 if (supportedFeatures.contains(qname) == neg) {
270 final String infix() {
275 private static final class All extends AbstractAll {
276 All(final QName[] qnames) {
281 public IfFeatureExpr negate() {
282 return new NotAll(array);
291 private static final class NotAll extends AbstractAll {
292 NotAll(final QName[] qnames) {
297 public IfFeatureExpr negate() {
298 return new All(array);
307 private abstract static sealed class AbstractAny extends Compound {
308 AbstractAny(final QName[] qnames) {
313 public final boolean test(final FeatureSet supportedFeatures) {
314 for (var qname : array) {
315 if (supportedFeatures.contains(qname)) {
323 final String infix() {
328 private static final class Any extends AbstractAny {
329 Any(final QName[] array) {
334 public IfFeatureExpr negate() {
335 return new NotAny(array);
344 private static final class NotAny extends AbstractAny {
345 NotAny(final QName[] qnames) {
350 public IfFeatureExpr negate() {
351 return new Any(array);
361 * Construct an assertion that a feature is present in the set passed to {@link #test(FeatureSet)}.
363 * @param qname Feature QName
364 * @return An expression
365 * @throws NullPointerException if {@code qname} is {@code null}
367 public static final @NonNull IfFeatureExpr isPresent(final QName qname) {
368 return new Present(qname);
372 * Construct a intersection (logical {@code AND}) expression of specified expressions.
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
379 public static final @NonNull IfFeatureExpr and(final Set<IfFeatureExpr> exprs) {
380 return compose(exprs, All::new, NotAny::new, AllExprs::new);
384 * Construct a union (logical {@code OR}) expression of specified expressions.
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
391 public static final @NonNull IfFeatureExpr or(final Set<IfFeatureExpr> exprs) {
392 return compose(exprs, Any::new, NotAll::new, AnyExpr::new);
396 * Returns the set of all {@code feature}s referenced by this expression. Each feature is identified by its QName.
398 * @return The set of referenced features. Mutability of the returned Set and order of features is undefined.
400 public abstract @NonNull Set<QName> getReferencedFeatures();
403 public abstract @NonNull IfFeatureExpr negate();
406 public abstract boolean test(FeatureSet supportedFeatures);
409 public abstract int hashCode();
412 public abstract boolean equals(Object obj);
415 public abstract String toString();
418 * Add QNames referenced by this expression into a target set.
420 * @param set The set to fill
421 * @throws NullPointerException if {@code set} is {@code null}
423 abstract void addQNames(@NonNull Set<QName> set);
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());
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) {
438 case Present present -> {
441 case Absent absent -> {
444 case AbstractArray<?> array -> {
445 negative = positive = true;
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]));
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);
462 .collect(ImmutableSet.toImmutableSet())
463 .toArray(new QName[0]);
464 yield positive ? allPresent.apply(qnames) : allAbsent.apply(qnames);