Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add static factory methods DescribedPredicate.and(..)/or(..) #893

Merged
merged 1 commit into from
Jun 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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