Skip to content

Commit

Permalink
add static factory methods DescribedPredicate.and(..)/or(..)
Browse files Browse the repository at this point in the history
These methods are in particular valuable to programmatically join n predicates together. Also, some users prefer the static style over the instance method variants.

Signed-off-by: Peter Gafert <[email protected]>
  • Loading branch information
codecholeric committed Jun 18, 2022
1 parent 347dc45 commit 2065e01
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@
package com.tngtech.archunit.base;

import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.tngtech.archunit.PublicAPI;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.size;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE;
import static java.util.stream.StreamSupport.stream;

/**
* A predicate holding a description.
Expand Down Expand Up @@ -73,6 +80,7 @@ public String toString() {
return getDescription();
}

@PublicAPI(usage = ACCESS)
@SuppressWarnings("unchecked")
public static <T> DescribedPredicate<T> alwaysTrue() {
return (DescribedPredicate<T>) ALWAYS_TRUE;
Expand All @@ -85,6 +93,7 @@ public boolean test(Object input) {
}
};

@PublicAPI(usage = ACCESS)
@SuppressWarnings("unchecked")
public static <T> DescribedPredicate<T> alwaysFalse() {
return (DescribedPredicate<T>) ALWAYS_FALSE;
Expand All @@ -97,60 +106,160 @@ public boolean test(Object input) {
}
};

@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<T> equalTo(final T object) {
return new EqualToPredicate<>(object);
}

@PublicAPI(usage = ACCESS)
public static <T extends Comparable<T>> DescribedPredicate<T> lessThan(final T value) {
return new LessThanPredicate<>(value);
}

@PublicAPI(usage = ACCESS)
public static <T extends Comparable<T>> DescribedPredicate<T> greaterThan(final T value) {
return new GreaterThanPredicate<>(value);
}

@PublicAPI(usage = ACCESS)
public static <T extends Comparable<T>> DescribedPredicate<T> lessThanOrEqualTo(final T value) {
return new LessThanOrEqualToPredicate<>(value);
}

@PublicAPI(usage = ACCESS)
public static <T extends Comparable<T>> DescribedPredicate<T> greaterThanOrEqualTo(final T value) {
return new GreaterThanOrEqualToPredicate<>(value);
}

@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<T> describe(String description, Predicate<? super T> predicate) {
return new DescribePredicate<>(description, predicate).forSubtype();
}

/**
* Same as {@link #not(DescribedPredicate)} but with a different description
*/
@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<T> doesNot(final DescribedPredicate<? super T> predicate) {
return not(predicate).as("does not %s", predicate.getDescription()).forSubtype();
}

/**
* Same as {@link #not(DescribedPredicate)} but with a different description
*/
@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<T> doNot(final DescribedPredicate<? super T> predicate) {
return not(predicate).as("do not %s", predicate.getDescription()).forSubtype();
}

/**
* @param predicate Any {@link DescribedPredicate}
* @return A predicate that will return {@code true} whenever the original predicate would return {@code false} and vice versa.
* @param <T> The type of object the {@link DescribedPredicate predicate} applies to
*/
@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<T> not(final DescribedPredicate<? super T> predicate) {
return new NotPredicate<>(predicate);
}

/**
* @see #and(Iterable)
*/
@SafeVarargs
@PublicAPI(usage = ACCESS)
@SuppressWarnings("RedundantTypeArguments") // Unfortunately not really redundant with JDK 8 :-(
public static <T> DescribedPredicate<T> and(DescribedPredicate<? super T>... predicates) {
return and(ImmutableList.<DescribedPredicate<? super T>>copyOf(predicates));
}

/**
* @param predicates Any number of {@link DescribedPredicate predicates} to join together via *AND*
* @return A {@link DescribedPredicate predicate} that returns {@code true}, if all the supplied
* predicates return {@code true}. Otherwise, it returns {@code false}.
* If an empty {@link Iterable} is passed to this method the resulting predicate
* will always return {@code false}.
* @param <T> The type of object the {@link DescribedPredicate predicate} applies to
*/
@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<T> and(Iterable<? extends DescribedPredicate<? super T>> predicates) {
return joinPredicates(predicates, (a, b) -> a.and(b));
}

/**
* @see #or(Iterable)
*/
@SafeVarargs
@PublicAPI(usage = ACCESS)
@SuppressWarnings("RedundantTypeArguments") // Unfortunately not really redundant with JDK 8 :-(
public static <T> DescribedPredicate<T> or(DescribedPredicate<? super T>... predicates) {
return or(ImmutableList.<DescribedPredicate<? super T>>copyOf(predicates));
}

/**
* @param predicates Any number of {@link DescribedPredicate predicates} to join together via *OR*
* @return A {@link DescribedPredicate predicate} that returns {@code true}, if any of the supplied
* predicates returns {@code true}. Otherwise, it returns {@code false}.
* If an empty {@link Iterable} is passed to this method the resulting predicate
* will always return {@code false}.
* @param <T> The type of object the {@link DescribedPredicate predicate} applies to
*/
@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<T> or(Iterable<? extends DescribedPredicate<? super T>> predicates) {
return joinPredicates(predicates, (a, b) -> a.or(b));
}

// DescribedPredicate is contravariant so the cast is safe
// Calling .get() is safe because we ensure that we have at least 2 elements ahead of the reduce operation
@SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"})
private static <T> DescribedPredicate<T> joinPredicates(Iterable<? extends DescribedPredicate<? super T>> predicates, BinaryOperator<DescribedPredicate<T>> predicateJoinOperation) {
if (isEmpty(predicates)) {
return alwaysFalse();
}
if (size(predicates) == 1) {
return getOnlyElement(predicates).forSubtype();
}

return stream(predicates.spliterator(), false)
.map(it -> (DescribedPredicate<T>) it)
.reduce(predicateJoinOperation)
.get();
}

@PublicAPI(usage = ACCESS)
public static DescribedPredicate<Iterable<?>> empty() {
return EMPTY;
}

@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<Optional<T>> optionalContains(final DescribedPredicate<? super T> predicate) {
return new OptionalContainsPredicate<>(predicate);
}

@PublicAPI(usage = ACCESS)
// OPTIONAL_EMPTY is independent of the concrete type parameter T of Optional<T>
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> DescribedPredicate<Optional<T>> optionalEmpty() {
return (DescribedPredicate) OPTIONAL_EMPTY;
}

/**
* @param predicate A {@link DescribedPredicate} for the elements of the {@link Iterable}
* @return A {@link DescribedPredicate predicate} for an {@link Iterable} that returns {@code true}
* if and only if at least one element of the {@link Iterable} matches the given element predicate.
* @param <T> The type of object the {@link DescribedPredicate predicate} applies to
*/
@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<Iterable<? extends T>> anyElementThat(final DescribedPredicate<? super T> predicate) {
return new AnyElementPredicate<>(predicate);
}

/**
* @param predicate A {@link DescribedPredicate} for the elements of the {@link Iterable}
* @return A {@link DescribedPredicate predicate} for an {@link Iterable} that returns {@code true}
* if and only if all elements of the {@link Iterable} matche the given element predicate.
* @param <T> The type of object the {@link DescribedPredicate predicate} applies to
*/
@PublicAPI(usage = ACCESS)
public static <T> DescribedPredicate<Iterable<T>> allElements(final DescribedPredicate<? super T> predicate) {
return new AllElementsPredicate<>(predicate);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import static com.tngtech.archunit.testutil.Assertions.assertThat;
import static com.tngtech.java.junit.dataprovider.DataProviders.$;
import static com.tngtech.java.junit.dataprovider.DataProviders.$$;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;

@RunWith(DataProviderRunner.class)
public class DescribedPredicateTest {
Expand All @@ -43,21 +45,79 @@ public void alwaysFalse_works() {
}

@Test
public void and_works() {
public void instance_and_works() {
assertThat(alwaysFalse().and(alwaysFalse())).rejects(new Object());
assertThat(alwaysFalse().and(alwaysTrue())).rejects(new Object());
assertThat(alwaysTrue().and(alwaysFalse())).rejects(new Object());
assertThat(alwaysTrue().and(alwaysTrue())).accepts(new Object());
}

@Test
public void or_works() {
public void instance_or_works() {
assertThat(alwaysFalse().or(alwaysFalse())).rejects(new Object());
assertThat(alwaysFalse().or(alwaysTrue())).accepts(new Object());
assertThat(alwaysTrue().or(alwaysFalse())).accepts(new Object());
assertThat(alwaysTrue().or(alwaysTrue())).accepts(new Object());
}

@Test
@SuppressWarnings("unchecked")
public void static_and_works() {
assertThat(DescribedPredicate.and()).rejects(new Object());
assertThat(DescribedPredicate.and(emptyList())).rejects(new Object());
assertThat(DescribedPredicate.and(new DescribedPredicate[]{alwaysTrue()})).accepts(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysTrue()))).accepts(new Object());
assertThat(DescribedPredicate.and(new DescribedPredicate[]{alwaysFalse()})).rejects(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysFalse()))).rejects(new Object());

assertThat(DescribedPredicate.and(alwaysFalse(), alwaysFalse())).rejects(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysFalse(), alwaysFalse()))).rejects(new Object());

assertThat(DescribedPredicate.and(alwaysFalse(), alwaysTrue())).rejects(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysFalse(), alwaysTrue()))).rejects(new Object());

assertThat(DescribedPredicate.and(alwaysTrue(), alwaysFalse())).rejects(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysTrue(), alwaysFalse()))).rejects(new Object());

assertThat(DescribedPredicate.and(alwaysTrue(), alwaysTrue())).accepts(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysTrue(), alwaysTrue()))).accepts(new Object());

assertThat(DescribedPredicate.and(alwaysTrue(), alwaysTrue(), alwaysTrue())).accepts(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysTrue(), alwaysTrue(), alwaysTrue()))).accepts(new Object());

assertThat(DescribedPredicate.and(alwaysTrue(), alwaysTrue(), alwaysFalse())).rejects(new Object());
assertThat(DescribedPredicate.and(ImmutableList.of(alwaysTrue(), alwaysTrue(), alwaysFalse()))).rejects(new Object());
}

@Test
@SuppressWarnings("unchecked")
public void static_or_works() {
assertThat(DescribedPredicate.or()).rejects(new Object());
assertThat(DescribedPredicate.or(emptyList())).rejects(new Object());
assertThat(DescribedPredicate.or(new DescribedPredicate[]{alwaysTrue()})).accepts(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysTrue()))).accepts(new Object());
assertThat(DescribedPredicate.or(new DescribedPredicate[]{alwaysFalse()})).rejects(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysFalse()))).rejects(new Object());

assertThat(DescribedPredicate.or(alwaysFalse(), alwaysFalse())).rejects(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysFalse(), alwaysFalse()))).rejects(new Object());

assertThat(DescribedPredicate.or(alwaysFalse(), alwaysTrue())).accepts(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysFalse(), alwaysTrue()))).accepts(new Object());

assertThat(DescribedPredicate.or(alwaysTrue(), alwaysFalse())).accepts(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysTrue(), alwaysFalse()))).accepts(new Object());

assertThat(DescribedPredicate.or(alwaysTrue(), alwaysTrue())).accepts(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysTrue(), alwaysTrue()))).accepts(new Object());

assertThat(DescribedPredicate.or(alwaysFalse(), alwaysFalse(), alwaysFalse())).rejects(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysFalse(), alwaysFalse(), alwaysFalse()))).rejects(new Object());

assertThat(DescribedPredicate.or(alwaysFalse(), alwaysFalse(), alwaysTrue())).accepts(new Object());
assertThat(DescribedPredicate.or(ImmutableList.of(alwaysFalse(), alwaysFalse(), alwaysTrue()))).accepts(new Object());
}

@Test
public void equalTo_works() {
assertThat(equalTo(5))
Expand Down Expand Up @@ -185,8 +245,8 @@ public void onResultOf_works() {
public void empty_works() {
assertThat(empty())
.hasDescription("empty")
.accepts(Collections.emptyList())
.accepts(Collections.emptySet())
.accepts(emptyList())
.accepts(emptySet())
.rejects(ImmutableList.of(1))
.rejects(Collections.singleton(""));
}
Expand Down

0 comments on commit 2065e01

Please sign in to comment.