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.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static com.google.common.base.Verify.verifyNotNull;
13 import static java.util.Objects.requireNonNull;
15 import com.google.common.annotations.Beta;
16 import com.google.common.collect.ImmutableSet;
17 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
18 import java.util.Arrays;
19 import java.util.HashSet;
21 import java.util.function.Predicate;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.yangtools.concepts.Immutable;
25 import org.opendaylight.yangtools.yang.common.QName;
28 * A resolved {@code if-feature} expression, implementing a {@link Predicate}. Internal representation is that of
29 * a tree of expressions, optimized for memory usage. {@link #negate()} performs an efficient logical negation without
30 * relying on default predicate methods. Other Predicate methods, like {@link #and(Predicate)} are not optimized in
31 * this implementation.
34 * The set of features referenced in this expression is available through {@link #getReferencedFeatures()}.
36 * @author Robert Varga
39 public abstract class IfFeatureExpr implements Immutable, Predicate<Set<QName>> {
40 private abstract static class Single extends IfFeatureExpr {
43 Single(final QName qname) {
44 this.qname = requireNonNull(qname);
48 public final ImmutableSet<QName> getReferencedFeatures() {
49 return ImmutableSet.of(qname);
53 public final int hashCode() {
54 return qname.hashCode();
58 final void addQNames(final Set<QName> set) {
63 public final boolean equals(final Object obj) {
64 return this == obj || getClass().isInstance(obj) && qname.equals(((Single) obj).qname);
68 // We are using arrays to hold our components to save a wee bit of space. The arrays originate from Sets retaining
69 // insertion order of Lists, each component is guaranteed to be unique, in definition order, not appearing multiple
71 private abstract static class AbstractArray<T> extends IfFeatureExpr {
74 AbstractArray(final T[] array) {
75 this.array = requireNonNull(array);
76 verify(array.length > 1);
80 public final int hashCode() {
81 return Arrays.hashCode(array);
85 public final boolean equals(final Object obj) {
86 return this == obj || getClass().isInstance(obj)
87 && Arrays.deepEquals(array, ((AbstractArray<?>) obj).array);
90 abstract String infix();
93 private abstract static class Complex extends AbstractArray<IfFeatureExpr> {
94 Complex(final IfFeatureExpr[] array) {
99 public final Set<QName> getReferencedFeatures() {
100 final Set<QName> ret = new HashSet<>();
106 final void addQNames(final Set<QName> set) {
107 for (IfFeatureExpr expr : array) {
112 final IfFeatureExpr[] negateExprs() {
113 final IfFeatureExpr[] ret = new IfFeatureExpr[array.length];
114 for (int i = 0; i < array.length; i++) {
115 ret[i] = verifyNotNull(array[i].negate());
121 public final String toString() {
122 final StringBuilder sb = new StringBuilder("(");
124 final String sep = infix();
125 for (int i = 1; i < array.length; ++i) {
126 sb.append(sep).append(array[i]);
128 return sb.append(')').toString();
132 private abstract static class Compound extends AbstractArray<QName> {
133 Compound(final QName[] qnames) {
138 public final ImmutableSet<QName> getReferencedFeatures() {
139 return ImmutableSet.copyOf(array);
143 final void addQNames(final Set<QName> set) {
144 set.addAll(Arrays.asList(array));
148 public final String toString() {
149 final StringBuilder sb = new StringBuilder();
154 sb.append("(\"").append(array[0]).append('"');
155 final String sep = infix();
156 for (int i = 1; i < array.length; ++i) {
157 sb.append(sep).append('"').append(array[i]).append('"');
159 return sb.append(')').toString();
162 abstract boolean negated();
165 private static final class Absent extends Single {
166 Absent(final QName qname) {
171 public IfFeatureExpr negate() {
172 return isPresent(qname);
176 public boolean test(final Set<QName> supportedFeatures) {
177 return !supportedFeatures.contains(qname);
181 public String toString() {
182 return "not \"" + qname + '"';
186 private static final class Present extends Single {
187 Present(final QName qname) {
192 public IfFeatureExpr negate() {
193 return new Absent(qname);
197 public boolean test(final Set<QName> supportedFeatures) {
198 return supportedFeatures.contains(qname);
202 public String toString() {
203 return "\"" + qname + '"';
207 private static final class AllExprs extends Complex {
208 AllExprs(final IfFeatureExpr[] exprs) {
213 public IfFeatureExpr negate() {
214 return new AnyExpr(negateExprs());
218 public boolean test(final Set<QName> supportedFeatures) {
219 for (IfFeatureExpr expr : array) {
220 if (!expr.test(supportedFeatures)) {
233 private static final class AnyExpr extends Complex {
234 AnyExpr(final IfFeatureExpr[] exprs) {
239 public IfFeatureExpr negate() {
240 return new AllExprs(negateExprs());
244 public boolean test(final Set<QName> supportedFeatures) {
245 for (IfFeatureExpr expr : array) {
246 if (expr.test(supportedFeatures)) {
259 private abstract static class AbstractAll extends Compound {
260 AbstractAll(final QName[] qnames) {
265 public final boolean test(final Set<QName> supportedFeatures) {
266 final boolean neg = negated();
267 for (QName qname : array) {
268 if (supportedFeatures.contains(qname) == neg) {
276 final String infix() {
281 private static final class All extends AbstractAll {
282 All(final QName[] qnames) {
287 public IfFeatureExpr negate() {
288 return new NotAll(array);
297 private static final class NotAll extends AbstractAll {
298 NotAll(final QName[] qnames) {
303 public IfFeatureExpr negate() {
304 return new All(array);
313 private abstract static class AbstractAny extends Compound {
314 AbstractAny(final QName[] qnames) {
319 public final boolean test(final Set<QName> supportedFeatures) {
320 for (QName qname : array) {
321 if (supportedFeatures.contains(qname)) {
329 final String infix() {
334 private static final class Any extends AbstractAny {
335 Any(final QName[] array) {
340 public IfFeatureExpr negate() {
341 return new NotAny(array);
350 private static final class NotAny extends AbstractAny {
351 NotAny(final QName[] qnames) {
356 public IfFeatureExpr negate() {
357 return new Any(array);
367 * Construct an assertion that a feature is present in the set passed to {@link #test(Set)}.
369 * @param qname Feature QName
370 * @return An expression
371 * @throws NullPointerException if {@code qname} is null
373 public static final @NonNull IfFeatureExpr isPresent(final QName qname) {
374 return new Present(qname);
378 * Construct a intersection (logical {@code AND}) expression of specified expressions.
380 * @param exprs Constituent expressions
381 * @return An expression
382 * @throws NullPointerException if {@code exprs} or any of its members is null
383 * @throws IllegalArgumentException if {@code exprs} is empty
385 public static final @NonNull IfFeatureExpr and(final Set<IfFeatureExpr> exprs) {
386 checkArgument(!exprs.isEmpty(), "Expressions may not be empty");
387 if (exprs.size() == 1) {
388 return exprs.iterator().next();
390 final Boolean composition = composition(exprs);
391 if (composition == null) {
392 return new AllExprs(exprs.toArray(new IfFeatureExpr[0]));
395 final QName[] qnames = extractQNames(exprs);
396 return composition ? new All(qnames) : new NotAny(qnames);
400 * Construct a union (logical {@code OR}) expression of specified expressions.
402 * @param exprs Constituent expressions
403 * @return An expression
404 * @throws NullPointerException if {@code exprs} or any of its members is null
405 * @throws IllegalArgumentException if {@code exprs} is empty
407 public static final @NonNull IfFeatureExpr or(final Set<IfFeatureExpr> exprs) {
408 checkArgument(!exprs.isEmpty(), "Expressions may not be empty");
409 if (exprs.size() == 1) {
410 return exprs.iterator().next();
412 final Boolean composition = composition(exprs);
413 if (composition == null) {
414 return new AnyExpr(exprs.toArray(new IfFeatureExpr[0]));
417 final QName[] qnames = extractQNames(exprs);
418 return composition ? new Any(qnames) : new NotAll(qnames);
422 * Returns the set of all {@code feature}s referenced by this expression. Each feature is identified by its QName.
424 * @return The set of referenced features. Mutability of the returned Set and order of features is undefined.
426 public abstract @NonNull Set<QName> getReferencedFeatures();
429 public abstract @NonNull IfFeatureExpr negate();
432 public abstract boolean test(Set<QName> supportedFeatures);
435 public abstract int hashCode();
438 public abstract boolean equals(Object obj);
441 public abstract String toString();
444 * Add QNames referenced by this expression into a target set.
446 * @param set The set to fill
447 * @throws NullPointerException if {@code set} is null
449 abstract void addQNames(@NonNull Set<QName> set);
451 @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL",
452 justification = "We need a tri-state value, this is the simplest")
453 private static @Nullable Boolean composition(final Set<IfFeatureExpr> exprs) {
454 boolean negative = false;
455 boolean positive = false;
456 for (IfFeatureExpr expr : exprs) {
457 if (expr instanceof Present) {
459 } else if (expr instanceof Absent) {
466 verify(negative || positive, "Unresolved expressions %s", exprs);
467 return positive == negative ? null : positive;
470 private static QName[] extractQNames(final Set<IfFeatureExpr> exprs) {
471 return exprs.stream().map(expr -> {
472 verify(expr instanceof Single, "Unexpected expression %s", expr);
473 return ((Single) expr).qname;
474 }).collect(ImmutableSet.toImmutableSet()).toArray(new QName[0]);