diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/HandlingAssertion.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/HandlingAssertion.java index 9699e10fcf..2341ce840b 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/HandlingAssertion.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/HandlingAssertion.java @@ -15,7 +15,6 @@ import com.tngtech.archunit.core.domain.JavaFieldAccess; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.lang.EvaluationResult; -import com.tngtech.archunit.lang.ViolationHandler; import com.tngtech.archunit.testutils.ExpectedAccess.ExpectedCall; import com.tngtech.archunit.testutils.ExpectedAccess.ExpectedFieldAccess; @@ -26,8 +25,6 @@ import static java.util.Collections.singleton; import static java.util.stream.Collectors.toSet; -@SuppressWarnings("Convert2Lambda") - // We need the reified type parameter here class HandlingAssertion { private final Set expectedFieldAccesses; private final Set expectedMethodCalls; @@ -81,53 +78,35 @@ Result evaluate(EvaluationResult evaluationResult) { return result; } - // Too bad Java erases types, otherwise a lot of boilerplate could be avoided here :-( - // This way we must write explicitly ViolationHandler or the bound wont work correctly private Set evaluateFieldAccesses(EvaluationResult result) { Set errorMessages = new HashSet<>(); final Set left = new HashSet<>(this.expectedFieldAccesses); - result.handleViolations(new ViolationHandler() { - @Override - public void handle(Collection violatingObjects, String message) { - errorMessages.addAll(removeExpectedAccesses(violatingObjects, left)); - } - }); + result.handleViolations((Collection violatingObjects, String message) -> + errorMessages.addAll(removeExpectedAccesses(violatingObjects, left))); return union(errorMessages, errorMessagesFrom(left)); } private Set evaluateMethodCalls(EvaluationResult result) { Set errorMessages = new HashSet<>(); final Set left = new HashSet<>(expectedMethodCalls); - result.handleViolations(new ViolationHandler() { - @Override - public void handle(Collection violatingObjects, String message) { - errorMessages.addAll(removeExpectedAccesses(violatingObjects, left)); - } - }); + result.handleViolations((Collection violatingObjects, String message) -> + errorMessages.addAll(removeExpectedAccesses(violatingObjects, left))); return union(errorMessages, errorMessagesFrom(left)); } private Set evaluateConstructorCalls(EvaluationResult result) { Set errorMessages = new HashSet<>(); final Set left = new HashSet<>(expectedConstructorCalls); - result.handleViolations(new ViolationHandler() { - @Override - public void handle(Collection violatingObjects, String message) { - errorMessages.addAll(removeExpectedAccesses(violatingObjects, left)); - } - }); + result.handleViolations((Collection violatingObjects, String message) -> + errorMessages.addAll(removeExpectedAccesses(violatingObjects, left))); return union(errorMessages, errorMessagesFrom(left)); } private Set evaluateCalls(EvaluationResult result) { Set errorMessages = new HashSet<>(); final Set left = new HashSet<>(Sets.union(expectedConstructorCalls, expectedMethodCalls)); - result.handleViolations(new ViolationHandler>() { - @Override - public void handle(Collection> violatingObjects, String message) { - errorMessages.addAll(removeExpectedAccesses(violatingObjects, left)); - } - }); + result.handleViolations((Collection> violatingObjects, String message) -> + errorMessages.addAll(removeExpectedAccesses(violatingObjects, left))); return union(errorMessages, errorMessagesFrom(left)); } @@ -140,24 +119,16 @@ private Set evaluateAccesses(EvaluationResult result) { addAll(expectedFieldAccesses); } }; - result.handleViolations(new ViolationHandler>() { - @Override - public void handle(Collection> violatingObjects, String message) { - errorMessages.addAll(removeExpectedAccesses(violatingObjects, left)); - } - }); + result.handleViolations((Collection> violatingObjects, String message) -> + errorMessages.addAll(removeExpectedAccesses(violatingObjects, left))); return union(errorMessages, errorMessagesFrom(left)); } private Set evaluateDependencies(EvaluationResult result) { Set errorMessages = new HashSet<>(); final Set left = new HashSet<>(expectedDependencies); - result.handleViolations(new ViolationHandler() { - @Override - public void handle(Collection violatingObjects, String message) { - errorMessages.addAll(removeExpectedAccesses(violatingObjects, left)); - } - }); + result.handleViolations((Collection violatingObjects, String message) -> + errorMessages.addAll(removeExpectedAccesses(violatingObjects, left))); return union(errorMessages, errorMessagesFrom(left)); } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java index 56af5bdb62..07cfa69e23 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java @@ -16,19 +16,11 @@ package com.tngtech.archunit.lang; import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import com.google.common.base.Joiner; -import com.google.common.base.MoreObjects; -import com.google.common.collect.ImmutableList; import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.lang.conditions.ArchConditions; import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; @PublicAPI(usage = INHERITANCE) public abstract class ArchCondition { @@ -60,11 +52,11 @@ public void finish(ConditionEvents events) { } public ArchCondition and(ArchCondition condition) { - return new AndCondition<>(this, condition.forSubtype()); + return ArchConditions.and(this, condition.forSubtype()); } public ArchCondition or(ArchCondition condition) { - return new OrCondition<>(this, condition.forSubtype()); + return ArchConditions.or(this, condition.forSubtype()); } public String getDescription() { @@ -99,189 +91,4 @@ public String toString() { public ArchCondition forSubtype() { return (ArchCondition) this; } - - private abstract static class JoinCondition extends ArchCondition { - private final Collection> conditions; - - private JoinCondition(String infix, Collection> conditions) { - super(joinDescriptionsOf(infix, conditions)); - this.conditions = conditions; - } - - private static String joinDescriptionsOf(String infix, Collection> conditions) { - return conditions.stream().map(ArchCondition::getDescription).collect(joining(" " + infix + " ")); - } - - @Override - public void init(Collection allObjectsToTest) { - for (ArchCondition condition : conditions) { - condition.init(allObjectsToTest); - } - } - - @Override - public void finish(ConditionEvents events) { - for (ArchCondition condition : conditions) { - condition.finish(events); - } - } - - List> evaluateConditions(T item) { - return conditions.stream().map(condition -> new ConditionWithEvents<>(condition, item)).collect(toList()); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "{" + conditions + "}"; - } - } - - private static class ConditionWithEvents { - private final ArchCondition condition; - private final ConditionEvents events; - - ConditionWithEvents(ArchCondition condition, T item) { - this(condition, check(condition, item)); - } - - ConditionWithEvents(ArchCondition condition, ConditionEvents events) { - this.condition = condition; - this.events = events; - } - - private static ConditionEvents check(ArchCondition condition, T item) { - ConditionEvents events = new ConditionEvents(); - condition.check(item, events); - return events; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("condition", condition) - .add("events", events) - .toString(); - } - } - - private abstract static class JoinConditionEvent implements ConditionEvent { - final T correspondingObject; - final List> evaluatedConditions; - - JoinConditionEvent(T correspondingObject, List> evaluatedConditions) { - this.correspondingObject = correspondingObject; - this.evaluatedConditions = evaluatedConditions; - } - - List getUniqueLinesOfViolations() { // TODO: Sort by line number, then lexicographically - final Set result = new TreeSet<>(); - for (ConditionWithEvents evaluation : evaluatedConditions) { - for (ConditionEvent event : evaluation.events) { - if (event.isViolation()) { - result.addAll(event.getDescriptionLines()); - } - } - } - return ImmutableList.copyOf(result); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("evaluatedConditions", evaluatedConditions) - .toString(); - } - - List> invert(List> evaluatedConditions) { - return evaluatedConditions.stream().map(this::invert).collect(toList()); - } - - private ConditionWithEvents invert(ConditionWithEvents evaluation) { - ConditionEvents invertedEvents = new ConditionEvents(); - for (ConditionEvent event : evaluation.events) { - event.addInvertedTo(invertedEvents); - } - return new ConditionWithEvents<>(evaluation.condition, invertedEvents); - } - } - - private static class AndCondition extends JoinCondition { - private AndCondition(ArchCondition first, ArchCondition second) { - super("and", ImmutableList.of(first, second)); - } - - @Override - public void check(T item, ConditionEvents events) { - events.add(new AndConditionEvent<>(item, evaluateConditions(item))); - } - } - - private static class OrCondition extends JoinCondition { - private OrCondition(ArchCondition first, ArchCondition second) { - super("or", ImmutableList.of(first, second)); - } - - @Override - public void check(T item, ConditionEvents events) { - events.add(new OrConditionEvent<>(item, evaluateConditions(item))); - } - } - - private static class AndConditionEvent extends JoinConditionEvent { - AndConditionEvent(T item, List> evaluatedConditions) { - super(item, evaluatedConditions); - } - - @Override - public boolean isViolation() { - return evaluatedConditions.stream().anyMatch(evaluation -> evaluation.events.containViolation()); - } - - @Override - public void addInvertedTo(ConditionEvents events) { - events.add(new OrConditionEvent<>(correspondingObject, invert(evaluatedConditions))); - } - - @Override - public List getDescriptionLines() { - return getUniqueLinesOfViolations(); - } - - @Override - public void handleWith(final Handler handler) { - for (ConditionWithEvents condition : evaluatedConditions) { - condition.events.handleViolations(handler::handle); - } - } - } - - private static class OrConditionEvent extends JoinConditionEvent { - OrConditionEvent(T item, List> evaluatedConditions) { - super(item, evaluatedConditions); - } - - @Override - public boolean isViolation() { - return evaluatedConditions.stream().allMatch(evaluation -> evaluation.events.containViolation()); - } - - @Override - public void addInvertedTo(ConditionEvents events) { - events.add(new AndConditionEvent<>(correspondingObject, invert(evaluatedConditions))); - } - - @Override - public List getDescriptionLines() { - return ImmutableList.of(createMessage()); - } - - private String createMessage() { - return Joiner.on(" and ").join(getUniqueLinesOfViolations()); - } - - @Override - public void handleWith(final Handler handler) { - handler.handle(Collections.singleton(correspondingObject), createMessage()); - } - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ArchRule.java b/archunit/src/main/java/com/tngtech/archunit/lang/ArchRule.java index a81862b00c..304f218c0f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ArchRule.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ArchRule.java @@ -227,7 +227,7 @@ public EvaluationResult evaluate(JavaClasses classes) { verifyNoEmptyShouldIfEnabled(allObjects); condition.init(allObjects); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); for (T object : allObjects) { condition.check(object, events); } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java index b8ca9963a3..fa58bdbb9a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java @@ -25,6 +25,11 @@ import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; +/** + * An event that occurred while checking an {@link ArchCondition}. This can either be a {@link #isViolation() violation} + * or be allowed. An event that is allowed will turn into a violation if it is {@link #invert() inverted} + * (e.g. for negation of the rule). + */ @PublicAPI(usage = INHERITANCE) public interface ConditionEvent { /** @@ -33,15 +38,13 @@ public interface ConditionEvent { boolean isViolation(); /** - * Adds the 'opposite' of the event.
- * E.g. The event is a violation, if some conditions A and B are both true? - *
{@literal ->} The 'inverted' event is a violation if either A or B (or both) are not true
- * In the most simple case, this is just an equivalent event evaluating {@link #isViolation()} - * inverted. - * - * @param events The events to add the 'inverted self' to + * @return the 'opposite' of the event.
+ * Assume e.g. The event is a violation, if some conditions A and B are both true + *
{@literal =>} The 'inverted' event is a violation if either A or B (or both) are not true
+ * In the most simple case, this is just an equivalent event evaluating {@link #isViolation()} + * inverted. */ - void addInvertedTo(ConditionEvents events); + ConditionEvent invert(); /** * @return A textual description of this event as a list of lines diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java index 8ef2b77614..6110ecd2fe 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java @@ -16,34 +16,26 @@ package com.tngtech.archunit.lang; import java.util.Collection; -import java.util.Iterator; import java.util.Optional; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.reflect.TypeToken; import com.tngtech.archunit.PublicAPI; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.Ordering.natural; -import static com.tngtech.archunit.PublicAPI.State.EXPERIMENTAL; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; -public final class ConditionEvents implements Iterable { - - @PublicAPI(usage = ACCESS) - public ConditionEvents() { - } +/** + * Collects {@link ConditionEvent events} that occur when checking {@link ArchCondition ArchConditions}. + */ +@PublicAPI(usage = INHERITANCE) +public interface ConditionEvents { - private final Multimap eventsByViolation = ArrayListMultimap.create(); - private Optional informationAboutNumberOfViolations = Optional.empty(); + /** + * Adds a {@link ConditionEvent} to these events. + * @param event A {@link ConditionEvent} caused by an {@link ArchCondition} when checking some element + */ + void add(ConditionEvent event); - @PublicAPI(usage = ACCESS) - public void add(ConditionEvent event) { - eventsByViolation.get(Type.from(event.isViolation())).add(event); - } + Optional getInformationAboutNumberOfViolations(); /** * Can be used to override the information about the number of violations. If absent the violated rule @@ -53,100 +45,26 @@ public void add(ConditionEvent event) { * can be supplied here to inform users that there actually were more violations than reported. * @param informationAboutNumberOfViolations The text to be shown for the number of times a rule has been violated */ - @PublicAPI(usage = ACCESS) - public void setInformationAboutNumberOfViolations(String informationAboutNumberOfViolations) { - this.informationAboutNumberOfViolations = Optional.of(informationAboutNumberOfViolations); - } - - @PublicAPI(usage = ACCESS) - public Collection getViolating() { - return eventsByViolation.get(Type.VIOLATION); - } - - @PublicAPI(usage = ACCESS) - public Collection getAllowed() { - return eventsByViolation.get(Type.ALLOWED); - } - - @PublicAPI(usage = ACCESS) - public boolean containViolation() { - return !getViolating().isEmpty(); - } - - @PublicAPI(usage = ACCESS) - public boolean isEmpty() { - return getAllowed().isEmpty() && getViolating().isEmpty(); - } + void setInformationAboutNumberOfViolations(String informationAboutNumberOfViolations); /** - * @return Sorted failure messages describing the contained failures of these events. - * Also offers information about the number of violations contained in these events. + * @return All {@link ConditionEvent events} that correspond to violations. */ - @PublicAPI(usage = ACCESS) - public FailureMessages getFailureMessages() { - ImmutableList result = getViolating().stream() - .flatMap(event -> event.getDescriptionLines().stream()) - .sorted(natural()) - .collect(toImmutableList()); - return new FailureMessages(result, informationAboutNumberOfViolations); - } + Collection getViolating(); /** - * Passes violations to the supplied {@link ViolationHandler}. The passed violations will automatically - * be filtered by the reified type of the given {@link ViolationHandler}. That is, if a - * ViolationHandler<SomeClass> is passed, only violations by objects assignable to - * SomeClass will be reported. The term 'reified' means that the type parameter - * was not erased, i.e. ArchUnit can still determine the actual type parameter of the passed violation handler, - * otherwise the upper bound, in extreme cases {@link Object}, will be used (i.e. all violations will be passed). - * - * @param violationHandler The violation handler that is supposed to handle all violations matching the - * respective type parameter + * @return {@code true}, if these events contain any {@link #getViolating() violating} event, otherwise {@code false} */ - @PublicAPI(usage = ACCESS, state = EXPERIMENTAL) - public void handleViolations(ViolationHandler violationHandler) { - ConditionEvent.Handler eventHandler = convertToEventHandler(violationHandler); - for (final ConditionEvent event : eventsByViolation.get(Type.VIOLATION)) { - event.handleWith(eventHandler); - } - } - - private ConditionEvent.Handler convertToEventHandler(final ViolationHandler handler) { - final Class supportedElementType = TypeToken.of(handler.getClass()) - .resolveType(ViolationHandler.class.getTypeParameters()[0]).getRawType(); + boolean containViolation(); - return (correspondingObjects, message) -> { - if (allElementTypesMatch(correspondingObjects, supportedElementType)) { - // If all elements are assignable to T (= supportedElementType), covariance of Collection allows this cast - @SuppressWarnings("unchecked") - Collection collection = (Collection) correspondingObjects; - handler.handle(collection, message); - } - }; - } - - private boolean allElementTypesMatch(Collection violatingObjects, Class supportedElementType) { - return violatingObjects.stream().allMatch(supportedElementType::isInstance); - } - - @Override @PublicAPI(usage = ACCESS) - public Iterator iterator() { - return ImmutableSet.copyOf(eventsByViolation.values()).iterator(); - } - - @Override - public String toString() { - return "ConditionEvents{" + - "Allowed Events: " + getAllowed() + - "; Violating Events: " + getViolating() + - '}'; - } - - private enum Type { - ALLOWED, VIOLATION; + final class Factory { + private Factory() { + } - private static Type from(boolean violation) { - return violation ? VIOLATION : ALLOWED; + @PublicAPI(usage = ACCESS) + public static ConditionEvents create() { + return new SimpleConditionEvents(); } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/EvaluationResult.java b/archunit/src/main/java/com/tngtech/archunit/lang/EvaluationResult.java index f591f597f5..f9a0242031 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/EvaluationResult.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/EvaluationResult.java @@ -15,71 +15,135 @@ */ package com.tngtech.archunit.lang; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.function.Predicate; +import com.google.common.collect.ImmutableList; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaMethod; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Ordering.natural; import static com.tngtech.archunit.PublicAPI.State.EXPERIMENTAL; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; /** * Represents the result of evaluating an {@link ArchRule} against some {@link JavaClasses}. - * To react to failures during evaluation of the rule, one can use {@link #handleViolations(ViolationHandler)}: + * To react to failures during evaluation of the rule, one can use {@link #handleViolations(ViolationHandler, Object[])}: *

*

- * result.handleViolations(new ViolationHandler<JavaAccess<?>>() {
- *     {@literal @}Override
- *     public void handle(Collection<JavaAccess<?>> violatingObjects, String message) {
- *         // do some reporting or react in any way to violation
- *     }
+ * result.handleViolations((Collection<JavaAccess<?>> violatingObjects, String message) -> {
+ *     // do some reporting or react in any way to violation
  * });
  * 
*/ public final class EvaluationResult { private final HasDescription rule; - private final ConditionEvents events; + private final List violations; + private final Optional informationAboutNumberOfViolations; private final Priority priority; @PublicAPI(usage = ACCESS) public EvaluationResult(HasDescription rule, Priority priority) { - this(rule, new ConditionEvents(), priority); + this(rule, emptyList(), Optional.empty(), priority); } @PublicAPI(usage = ACCESS) public EvaluationResult(HasDescription rule, ConditionEvents events, Priority priority) { + this( + rule, + events.getViolating(), + events.getInformationAboutNumberOfViolations(), + priority + ); + } + + EvaluationResult(HasDescription rule, Collection violations, Optional informationAboutNumberOfViolations, Priority priority) { this.rule = rule; - this.events = events; + this.violations = new ArrayList<>(violations); + this.informationAboutNumberOfViolations = informationAboutNumberOfViolations; this.priority = priority; } @PublicAPI(usage = ACCESS) public FailureReport getFailureReport() { - return new FailureReport(rule, priority, events.getFailureMessages()); + ImmutableList result = violations.stream() + .flatMap(event -> event.getDescriptionLines().stream()) + .sorted(natural()) + .collect(toImmutableList()); + FailureMessages failureMessages = new FailureMessages(result, informationAboutNumberOfViolations); + return new FailureReport(rule, priority, failureMessages); } @PublicAPI(usage = ACCESS) public void add(EvaluationResult part) { - for (ConditionEvent event : part.events) { - events.add(event); - } + violations.addAll(part.violations); } /** - * @see ConditionEvents#handleViolations(ViolationHandler) + * Passes violations to the supplied {@link ViolationHandler}. The passed violations will automatically + * be filtered by the type of the given {@link ViolationHandler}. That is, when a + * ViolationHandler<SomeClass> is passed, only violations by objects assignable to + * SomeClass will be reported. Note that this will be unsafe for generics, i.e. ArchUnit + * cannot filter to match the full generic type signature. E.g. + *

+     * handleViolations((Collection<Optional<String>> objects, String message) ->
+     *     assertType(objects.iterator().next().get(), String.class)
+     * )
+     * 
+ * might throw an exception if there are also {@code Optional} violations. + * So, in general it is safer to use the wildcard {@code ?} for generic types, unless it is absolutely + * certain from the context what the type parameter will be + * (for example when only analyzing methods it might be clear that the type parameter will be {@link JavaMethod}). + * + * @param Type of the relevant objects causing violations. E.g. {@code JavaAccess} + * @param violationHandler The violation handler that is supposed to handle all violations matching the + * respective type parameter + * @param __ignored_parameter_to_reify_type__ This parameter will be ignored; its only use is to make the + * generic type reified, so we can retrieve it at runtime. + * Without this parameter, the generic type would be erased. */ + @SafeVarargs + @SuppressWarnings("unused") @PublicAPI(usage = ACCESS, state = EXPERIMENTAL) - public void handleViolations(ViolationHandler violationHandler) { - events.handleViolations(violationHandler); + public final void handleViolations(ViolationHandler violationHandler, T... __ignored_parameter_to_reify_type__) { + Class correspondingObjectType = componentTypeOf(__ignored_parameter_to_reify_type__); + ConditionEvent.Handler eventHandler = convertToEventHandler(correspondingObjectType, violationHandler); + for (final ConditionEvent event : violations) { + event.handleWith(eventHandler); + } + } + + @SuppressWarnings("unchecked") // The cast is safe, since the component type of T[] will be type T + private Class componentTypeOf(T[] array) { + return (Class) array.getClass().getComponentType(); + } + + private ConditionEvent.Handler convertToEventHandler(Class correspondingObjectType, ViolationHandler violationHandler) { + return (correspondingObjects, message) -> { + if (allElementTypesMatch(correspondingObjects, correspondingObjectType)) { + // If all elements are assignable to ITEM, covariance of ImmutableList allows this cast + @SuppressWarnings("unchecked") + Collection collection = ImmutableList.copyOf((Collection) correspondingObjects); + violationHandler.handle(collection, message); + } + }; + } + + private boolean allElementTypesMatch(Collection violatingObjects, Class supportedElementType) { + return violatingObjects.stream().allMatch(supportedElementType::isInstance); } @PublicAPI(usage = ACCESS) public boolean hasViolation() { - return events.containViolation(); + return !violations.isEmpty(); } @PublicAPI(usage = ACCESS) @@ -96,20 +160,22 @@ public Priority getPriority() { */ @PublicAPI(usage = ACCESS) public EvaluationResult filterDescriptionsMatching(Predicate linePredicate) { - ConditionEvents filtered = new ConditionEvents(); - for (ConditionEvent event : events) { - filtered.add(new FilteredEvent(event, linePredicate)); - } - return new EvaluationResult(rule, filtered, priority); + List filtered = violations.stream() + .map(e -> new FilteredEvent(e, linePredicate)) + .filter(FilteredEvent::isViolation) + .collect(toList()); + return new EvaluationResult(rule, filtered, Optional.empty(), priority); } private static class FilteredEvent implements ConditionEvent { private final ConditionEvent delegate; private final Predicate linePredicate; + private final List filteredDescriptionLines; private FilteredEvent(ConditionEvent delegate, Predicate linePredicate) { this.delegate = delegate; this.linePredicate = linePredicate; + filteredDescriptionLines = delegate.getDescriptionLines().stream().filter(linePredicate).collect(toList()); } @Override @@ -118,13 +184,13 @@ public boolean isViolation() { } @Override - public void addInvertedTo(ConditionEvents events) { - delegate.addInvertedTo(events); + public ConditionEvent invert() { + return new FilteredEvent(delegate.invert(), linePredicate); } @Override public List getDescriptionLines() { - return delegate.getDescriptionLines().stream().filter(linePredicate).collect(toList()); + return filteredDescriptionLines; } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/SimpleConditionEvent.java b/archunit/src/main/java/com/tngtech/archunit/lang/SimpleConditionEvent.java index e144d99b5b..934d5e5b63 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/SimpleConditionEvent.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/SimpleConditionEvent.java @@ -44,8 +44,8 @@ public boolean isViolation() { } @Override - public void addInvertedTo(ConditionEvents events) { - events.add(new SimpleConditionEvent(correspondingObject, !conditionSatisfied, message)); + public ConditionEvent invert() { + return new SimpleConditionEvent(correspondingObject, !conditionSatisfied, message); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/SimpleConditionEvents.java b/archunit/src/main/java/com/tngtech/archunit/lang/SimpleConditionEvents.java new file mode 100644 index 0000000000..eb655b91d9 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/SimpleConditionEvents.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import com.google.common.collect.ImmutableList; + +final class SimpleConditionEvents implements ConditionEvents { + private final List violations = new ArrayList<>(); + private Optional informationAboutNumberOfViolations = Optional.empty(); + + SimpleConditionEvents() { + } + + @Override + public void add(ConditionEvent event) { + if (event.isViolation()) { + violations.add(event); + } + } + + @Override + public Optional getInformationAboutNumberOfViolations() { + return informationAboutNumberOfViolations; + } + + @Override + public void setInformationAboutNumberOfViolations(String informationAboutNumberOfViolations) { + this.informationAboutNumberOfViolations = Optional.of(informationAboutNumberOfViolations); + } + + @Override + public Collection getViolating() { + return ImmutableList.copyOf(violations); + } + + @Override + public boolean containViolation() { + return !violations.isEmpty(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + violations + '}'; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/AndCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/AndCondition.java new file mode 100644 index 0000000000..df75f9d400 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/AndCondition.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.conditions; + +import java.util.List; + +import com.google.common.collect.ImmutableList; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ConditionEvent; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.conditions.OrCondition.OrConditionEvent; + +class AndCondition extends JoinCondition { + AndCondition(ArchCondition first, ArchCondition second) { + super("and", ImmutableList.of(first, second)); + } + + @Override + public void check(T item, ConditionEvents events) { + events.add(new AndConditionEvent<>(item, evaluateConditions(item))); + } + + static class AndConditionEvent extends JoinConditionEvent { + AndConditionEvent(T item, List> evaluatedConditions) { + super(item, evaluatedConditions); + } + + @Override + public boolean isViolation() { + return evaluatedConditions.stream().anyMatch(evaluation -> evaluation.getEvents().containViolation()); + } + + @Override + public ConditionEvent invert() { + return new OrConditionEvent<>(correspondingObject, invert(evaluatedConditions)); + } + + @Override + public List getDescriptionLines() { + return getUniqueLinesOfViolations(); + } + + @Override + public void handleWith(final ConditionEvent.Handler handler) { + for (ConditionWithEvents condition : evaluatedConditions) { + condition.getEvents().getViolating().forEach(event -> event.handleWith(handler)); + } + } + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java index 1bebc7e9d0..2ac14dd2e3 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java @@ -416,6 +416,16 @@ public static AllDependenciesCondition onlyHaveDependenciesWhere(DescribedPredic return new AllDependenciesCondition(description, predicate, GET_DIRECT_DEPENDENCIES_FROM_SELF); } + @PublicAPI(usage = ACCESS) + public static ArchCondition and(ArchCondition first, ArchCondition second) { + return new AndCondition<>(first, second); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition or(ArchCondition first, ArchCondition second) { + return new OrCondition<>(first, second); + } + @PublicAPI(usage = ACCESS) public static ArchCondition never(ArchCondition condition) { return new NeverCondition<>(condition); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainAnyCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainAnyCondition.java index 49ac4a9f4d..ea049c2f44 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainAnyCondition.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainAnyCondition.java @@ -35,11 +35,11 @@ class ContainAnyCondition extends ArchCondition> { @Override public void check(Collection collection, ConditionEvents events) { - ConditionEvents subEvents = new ConditionEvents(); + ViolatedAndSatisfiedConditionEvents subEvents = new ViolatedAndSatisfiedConditionEvents(); for (T element : collection) { condition.check(element, subEvents); } - if (!subEvents.isEmpty()) { + if (!subEvents.getAllowed().isEmpty() || !subEvents.getViolating().isEmpty()) { events.add(new AnyConditionEvent(collection, subEvents)); } } @@ -54,7 +54,7 @@ static class AnyConditionEvent implements ConditionEvent { private final Collection allowed; private final Collection violating; - private AnyConditionEvent(Collection correspondingObjects, ConditionEvents events) { + private AnyConditionEvent(Collection correspondingObjects, ViolatedAndSatisfiedConditionEvents events) { this(correspondingObjects, events.getAllowed(), events.getViolating()); } @@ -72,8 +72,8 @@ public boolean isViolation() { } @Override - public void addInvertedTo(ConditionEvents events) { - events.add(new OnlyConditionEvent(correspondingObjects, violating, allowed)); + public ConditionEvent invert() { + return new OnlyConditionEvent(correspondingObjects, violating, allowed); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainsOnlyCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainsOnlyCondition.java index 88cf2b1c78..a79dbdcaf6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainsOnlyCondition.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ContainsOnlyCondition.java @@ -34,11 +34,11 @@ class ContainsOnlyCondition extends ArchCondition> { @Override public void check(Collection collection, ConditionEvents events) { - ConditionEvents subEvents = new ConditionEvents(); + ViolatedAndSatisfiedConditionEvents subEvents = new ViolatedAndSatisfiedConditionEvents(); for (T item : collection) { condition.check(item, subEvents); } - if (!subEvents.isEmpty()) { + if (!subEvents.getAllowed().isEmpty() || !subEvents.getViolating().isEmpty()) { events.add(new OnlyConditionEvent(collection, subEvents)); } } @@ -53,7 +53,7 @@ static class OnlyConditionEvent implements ConditionEvent { private final Collection allowed; private final Collection violating; - private OnlyConditionEvent(Collection correspondingObjects, ConditionEvents events) { + private OnlyConditionEvent(Collection correspondingObjects, ViolatedAndSatisfiedConditionEvents events) { this(correspondingObjects, events.getAllowed(), events.getViolating()); } @@ -71,8 +71,8 @@ public boolean isViolation() { } @Override - public void addInvertedTo(ConditionEvents events) { - events.add(new AnyConditionEvent(correspondingObjects, violating, allowed)); + public ConditionEvent invert() { + return new AnyConditionEvent(correspondingObjects, violating, allowed); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/DelegatingConditionEvents.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/DelegatingConditionEvents.java new file mode 100644 index 0000000000..170d7999c6 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/DelegatingConditionEvents.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.conditions; + +import java.util.Collection; +import java.util.Optional; + +import com.tngtech.archunit.lang.ConditionEvent; +import com.tngtech.archunit.lang.ConditionEvents; + +abstract class DelegatingConditionEvents implements ConditionEvents { + final ConditionEvents delegate; + + DelegatingConditionEvents(ConditionEvents delegate) { + this.delegate = delegate; + } + + @Override + public Optional getInformationAboutNumberOfViolations() { + return delegate.getInformationAboutNumberOfViolations(); + } + + @Override + public void setInformationAboutNumberOfViolations(String informationAboutNumberOfViolations) { + delegate.setInformationAboutNumberOfViolations(informationAboutNumberOfViolations); + } + + @Override + public Collection getViolating() { + return delegate.getViolating(); + } + + @Override + public boolean containViolation() { + return delegate.containViolation(); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/JoinCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/JoinCondition.java new file mode 100644 index 0000000000..fa22c0afe1 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/JoinCondition.java @@ -0,0 +1,139 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.conditions; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Stream; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ConditionEvent; +import com.tngtech.archunit.lang.ConditionEvents; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +abstract class JoinCondition extends ArchCondition { + private final Collection> conditions; + + JoinCondition(String infix, Collection> conditions) { + super(joinDescriptionsOf(infix, conditions)); + this.conditions = conditions; + } + + private static String joinDescriptionsOf(String infix, Collection> conditions) { + return conditions.stream().map(ArchCondition::getDescription).collect(joining(" " + infix + " ")); + } + + @Override + public void init(Collection allObjectsToTest) { + for (ArchCondition condition : conditions) { + condition.init(allObjectsToTest); + } + } + + @Override + public void finish(ConditionEvents events) { + for (ArchCondition condition : conditions) { + condition.finish(events); + } + } + + List> evaluateConditions(T item) { + return conditions.stream().map(condition -> new ConditionWithEvents<>(condition, item)).collect(toList()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + conditions + "}"; + } + + static class ConditionWithEvents { + private final ArchCondition condition; + private final ViolatedAndSatisfiedConditionEvents events; + + ConditionWithEvents(ArchCondition condition, T item) { + this(condition, check(condition, item)); + } + + ConditionWithEvents(ArchCondition condition, ViolatedAndSatisfiedConditionEvents events) { + this.condition = condition; + this.events = events; + } + + public ConditionEvents getEvents() { + return events; + } + + private static ViolatedAndSatisfiedConditionEvents check(ArchCondition condition, T item) { + ViolatedAndSatisfiedConditionEvents events = new ViolatedAndSatisfiedConditionEvents(); + condition.check(item, events); + return events; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("condition", condition) + .add("events", events) + .toString(); + } + } + + abstract static class JoinConditionEvent implements ConditionEvent { + final T correspondingObject; + final List> evaluatedConditions; + + JoinConditionEvent(T correspondingObject, List> evaluatedConditions) { + this.correspondingObject = correspondingObject; + this.evaluatedConditions = evaluatedConditions; + } + + List getUniqueLinesOfViolations() { + final Set result = new TreeSet<>(); + for (ConditionWithEvents evaluation : evaluatedConditions) { + for (ConditionEvent event : evaluation.events.getViolating()) { + result.addAll(event.getDescriptionLines()); + } + } + return ImmutableList.copyOf(result); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("evaluatedConditions", evaluatedConditions) + .toString(); + } + + List> invert(List> evaluatedConditions) { + return evaluatedConditions.stream().map(this::invert).collect(toList()); + } + + private ConditionWithEvents invert(ConditionWithEvents evaluation) { + ViolatedAndSatisfiedConditionEvents invertedEvents = new ViolatedAndSatisfiedConditionEvents(); + Stream.concat( + evaluation.events.getAllowed().stream(), + evaluation.events.getViolating().stream() + ).forEach(event -> invertedEvents.add(event.invert())); + return new ConditionWithEvents<>(evaluation.condition, invertedEvents); + } + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/NeverCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/NeverCondition.java index 9fc432745d..8ee33505a9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/NeverCondition.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/NeverCondition.java @@ -36,20 +36,12 @@ public void init(Collection allObjectsToTest) { @Override public void finish(ConditionEvents events) { - ConditionEvents subEvents = new ConditionEvents(); - condition.finish(subEvents); - for (ConditionEvent event : subEvents) { - event.addInvertedTo(events); - } + condition.finish(new InvertingConditionEvents(events)); } @Override public void check(T item, ConditionEvents events) { - ConditionEvents subEvents = new ConditionEvents(); - condition.check(item, subEvents); - for (ConditionEvent event : subEvents) { - event.addInvertedTo(events); - } + condition.check(item, new InvertingConditionEvents(events)); } @Override @@ -57,4 +49,14 @@ public String toString() { return getClass().getSimpleName() + "{condition=" + condition + "}"; } + private static class InvertingConditionEvents extends DelegatingConditionEvents { + InvertingConditionEvents(ConditionEvents originalEvents) { + super(originalEvents); + } + + @Override + public void add(ConditionEvent event) { + delegate.add(event.invert()); + } + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/OrCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/OrCondition.java new file mode 100644 index 0000000000..12b47e041c --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/OrCondition.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.conditions; + +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ConditionEvent; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.conditions.AndCondition.AndConditionEvent; + +import static java.util.Collections.singleton; + +class OrCondition extends JoinCondition { + OrCondition(ArchCondition first, ArchCondition second) { + super("or", ImmutableList.of(first, second)); + } + + @Override + public void check(T item, ConditionEvents events) { + events.add(new OrConditionEvent<>(item, evaluateConditions(item))); + } + + static class OrConditionEvent extends JoinConditionEvent { + OrConditionEvent(T item, List> evaluatedConditions) { + super(item, evaluatedConditions); + } + + @Override + public boolean isViolation() { + return evaluatedConditions.stream().allMatch(evaluation -> evaluation.getEvents().containViolation()); + } + + @Override + public ConditionEvent invert() { + return new AndConditionEvent<>(correspondingObject, invert(evaluatedConditions)); + } + + @Override + public List getDescriptionLines() { + return ImmutableList.of(createMessage()); + } + + private String createMessage() { + return Joiner.on(" and ").join(getUniqueLinesOfViolations()); + } + + @Override + public void handleWith(final Handler handler) { + handler.handle(singleton(correspondingObject), createMessage()); + } + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ViolatedAndSatisfiedConditionEvents.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ViolatedAndSatisfiedConditionEvents.java new file mode 100644 index 0000000000..edb1a54684 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ViolatedAndSatisfiedConditionEvents.java @@ -0,0 +1,78 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.conditions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ConditionEvent; +import com.tngtech.archunit.lang.ConditionEvents; + +/** + * A version of {@link ConditionEvents} that tracks both violated and satisfied {@link ConditionEvent events}, + * so specific {@link ArchCondition ArchConditions} can use them to create composite events.
+ * E.g. {@link ContainAnyCondition} needs to track satisfied events to be able to invert the event + * if it is used in the context of {@link NeverCondition}. + */ +final class ViolatedAndSatisfiedConditionEvents implements ConditionEvents { + private final List allowedEvents = new ArrayList<>(); + private final List violatingEvents = new ArrayList<>(); + private Optional informationAboutNumberOfViolations = Optional.empty(); + + @Override + public void add(ConditionEvent event) { + if (event.isViolation()) { + violatingEvents.add(event); + } else { + allowedEvents.add(event); + } + } + + @Override + public Optional getInformationAboutNumberOfViolations() { + return informationAboutNumberOfViolations; + } + + @Override + public void setInformationAboutNumberOfViolations(String informationAboutNumberOfViolations) { + this.informationAboutNumberOfViolations = Optional.of(informationAboutNumberOfViolations); + } + + @Override + public Collection getViolating() { + return violatingEvents; + } + + Collection getAllowed() { + return allowedEvents; + } + + @Override + public boolean containViolation() { + return !getViolating().isEmpty(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "Allowed Events: " + getAllowed() + + "; Violating Events: " + getViolating() + + '}'; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java index 1e8370e634..06e100553f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java @@ -469,13 +469,13 @@ public interface MembersThat { * E.g. someField in * *

-     * class Example {
+     * class Example implements Serializable {
      *     Object someField;
      * }
* * will be matched by *

-     * {@link ArchRuleDefinition#members() members()}.{@link GivenMembers#that() that()}.{@link MembersThat#areDeclaredInClassesThat(DescribedPredicate) areDeclaredInClassesThat(areSubtypeOf(Object.class))}
+     * {@link ArchRuleDefinition#members() members()}.{@link GivenMembers#that() that()}.{@link MembersThat#areDeclaredInClassesThat(DescribedPredicate) areDeclaredInClassesThat(are(assignableTo(Serializable.class)))}
      * 
* * @param predicate A predicate which matches classes where members have to be declared in diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java index 898337a4a4..2a005128a7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import static com.tngtech.archunit.lang.Priority.MEDIUM; import static com.tngtech.archunit.lang.conditions.ArchConditions.never; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.java.junit.dataprovider.DataProviders.$; @@ -35,7 +36,7 @@ public void as_inits_delegate() { public void as_finishes_delegate() { ConditionWithInitAndFinish original = someCondition("any"); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); original.as("changed").finish(events); assertThat(original.eventsFromFinish).isEqualTo(events); @@ -45,18 +46,18 @@ public void as_finishes_delegate() { public void and_checks_all_conditions() { ArchCondition greaterThan10_14And20 = greaterThan(10).and(greaterThan(14, 20)); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); greaterThan10_14And20.check(15, events); assertThat(events).containViolations("15 is not greater than 20"); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); greaterThan10_14And20.check(5, events); assertThat(events).containViolations( "5 is not greater than 10", "5 is not greater than 14", "5 is not greater than 20"); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); greaterThan10_14And20.check(21, events); assertThat(events).containNoViolation(); } @@ -65,10 +66,11 @@ public void and_checks_all_conditions() { public void and_handles_each_violating_object_separately() { ArchCondition condition = greaterThan(1).and(greaterThan(2)).and(greaterThan(3)); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(2, events); final List handledViolations = new ArrayList<>(); - events.handleViolations((ViolationHandler) (violatingObjects, message) -> handledViolations.add(new HandledViolation(violatingObjects, message))); + evaluationResultOf(events).handleViolations((Collection violatingObjects, String message) -> + handledViolations.add(new HandledViolation(violatingObjects, message))); assertThat(handledViolations).containsOnly( new HandledViolation(2, "2 is not greater than 2"), @@ -80,16 +82,16 @@ public void or_checks_all_conditions() { ArchCondition greaterThan15OrGreater14And20 = greaterThan(15).or(greaterThan(14, 20)); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); greaterThan15OrGreater14And20.check(15, events); assertThat(events).containViolations("15 is not greater than 15 and 15 is not greater than 20"); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); greaterThan15OrGreater14And20.check(5, events); assertThat(events).containViolations( "5 is not greater than 14 and 5 is not greater than 15 and 5 is not greater than 20"); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); greaterThan15OrGreater14And20.check(16, events); assertThat(events).containNoViolation(); } @@ -99,7 +101,7 @@ public void or_events_join_descriptions() { ArchCondition isGreaterThan15OrEndsWith1 = greaterThan(15).or(endsWith(1)); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); isGreaterThan15OrEndsWith1.check(12, events); assertThat(events).containViolations("12 does not end with 1 and 12 is not greater than 15"); } @@ -108,10 +110,11 @@ public void or_events_join_descriptions() { public void or_handles_all_violated_conditions_as_unit() { ArchCondition condition = greaterThan(1).or(greaterThan(2)).or(greaterThan(3)); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(1, events); final List handledViolations = new ArrayList<>(); - events.handleViolations((ViolationHandler) (violatingObjects, message) -> handledViolations.add(new HandledViolation(violatingObjects, message))); + evaluationResultOf(events).handleViolations((Collection violatingObjects, String message) -> + handledViolations.add(new HandledViolation(violatingObjects, message))); assertThat(handledViolations).containsOnly(new HandledViolation( 1, "1 is not greater than 1 and 1 is not greater than 2 and 1 is not greater than 3")); @@ -153,7 +156,7 @@ public void join_finishes_all_conditions(ConditionCombination combination) { ConditionWithInitAndFinish one = someCondition("one"); ConditionWithInitAndFinish two = someCondition("two"); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); combination.combine(one, two).finish(events); assertThat(one.eventsFromFinish).isEqualTo(events); @@ -175,11 +178,11 @@ public void and_joins_descriptions(ConditionCombination combination) { public void never_and() { ArchCondition condition = never(greaterThan(3, 9).and(greaterThan(5, 7))); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(4, events); assertThat(events.containViolation()).as("Events contain violation").isFalse(); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); condition.check(6, events); assertThat(events.containViolation()).as("Events contain violation").isTrue(); } @@ -188,11 +191,11 @@ public void never_and() { public void double_never_and() { ArchCondition condition = never(never(greaterThan(3, 9).and(greaterThan(5, 7)))); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(9, events); assertThat(events.containViolation()).as("Events contain violation").isTrue(); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); condition.check(10, events); assertThat(events.containViolation()).as("Events contain violation").isFalse(); } @@ -201,11 +204,11 @@ public void double_never_and() { public void never_or() { ArchCondition condition = never(greaterThan(3, 9).or(greaterThan(5, 7))); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(3, events); assertThat(events.containViolation()).as("Events contain violation").isFalse(); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); condition.check(4, events); assertThat(events.containViolation()).as("Events contain violation").isTrue(); } @@ -214,11 +217,11 @@ public void never_or() { public void double_never_or() { ArchCondition condition = never(never(greaterThan(3, 9).or(greaterThan(5, 7)))); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(7, events); assertThat(events.containViolation()).as("Events contain violation").isTrue(); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); condition.check(8, events); assertThat(events.containViolation()).as("Events contain violation").isFalse(); } @@ -234,6 +237,10 @@ public void check(final Integer item, ConditionEvents events) { }; } + private EvaluationResult evaluationResultOf(ConditionEvents events) { + return new EvaluationResult(() -> "irrelevant", events, MEDIUM); + } + private ArchCondition endsWith(final int number) { return new ArchCondition("ends with " + number) { @Override diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/ArchRuleTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/ArchRuleTest.java index ed72eba7f3..15eb59be03 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/ArchRuleTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/ArchRuleTest.java @@ -163,7 +163,6 @@ public void finish(ConditionEvents events) { all(strings()).should(condition).evaluate(importClasses(getClass())); assertThat(condition.allObjectsToTest).containsOnly(getClass().getName()); - assertThat(condition.eventsFromFinish.getAllowed()).isEmpty(); assertThat(condition.eventsFromFinish.getViolating()).hasSize(1); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/ConditionEventsTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/ConditionEventsTest.java deleted file mode 100644 index 73c93aadc4..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/lang/ConditionEventsTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.tngtech.archunit.lang; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static com.tngtech.java.junit.dataprovider.DataProviders.$; -import static com.tngtech.java.junit.dataprovider.DataProviders.$$; -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(DataProviderRunner.class) -public class ConditionEventsTest { - @DataProvider - public static Object[][] eventsWithEmpty() { - return $$( - $(events(SimpleConditionEvent.satisfied("irrelevant", "irrelevant")), false), - $(events(SimpleConditionEvent.violated("irrelevant", "irrelevant")), false), - $(new ConditionEvents(), true)); - } - - @Test - @UseDataProvider("eventsWithEmpty") - public void isEmpty(ConditionEvents events, boolean expectedEmpty) { - assertThat(events.isEmpty()).as("events are empty").isEqualTo(expectedEmpty); - } - - @Test - @SuppressWarnings("Convert2Lambda") // to retrieve the type information ViolationHandler may not be converted to a Lambda - public void handleViolations_reports_only_violations_referring_to_the_correct_type() { - ConditionEvents events = events( - SimpleConditionEvent.satisfied(new CorrectType("do not handle"), "I'm not violated"), - SimpleConditionEvent.violated(new WrongType(), "I'm violated, but wrong type"), - SimpleConditionEvent.violated(new WrongSupertype(), "I'm violated, but wrong type"), - SimpleConditionEvent.violated(new CorrectType("handle type"), "I'm violated and correct type"), - SimpleConditionEvent.violated(new CorrectSubtype("handle sub type"), "I'm violated and correct sub type")); - - final Set handledFailures = new HashSet<>(); - events.handleViolations(new ObjectToStringAndMessageJoiningTestHandler(handledFailures)); - - assertThat(handledFailures).containsOnly( - "handle type: I'm violated and correct type", - "handle sub type: I'm violated and correct sub type"); - - handledFailures.clear(); - events.handleViolations(new ViolationHandler() { - @Override - public void handle(Collection violatingObject, String message) { - new ObjectToStringAndMessageJoiningTestHandler(handledFailures).handle(violatingObject, message); - } - }); - - assertThat(handledFailures).containsOnly( - "handle type: I'm violated and correct type", - "handle sub type: I'm violated and correct sub type"); - } - - @Test - public void handles_erased_generics_as_upper_bound() { - ConditionEvents events = events( - SimpleConditionEvent.violated(new CorrectType("ignore"), "correct"), - SimpleConditionEvent.violated(new WrongType(), "wrong")); - - Set handledFailureMessages = new HashSet<>(); - events.handleViolations(genericBoundByCorrectType(handledFailureMessages)); - - assertThat(handledFailureMessages).containsOnly("correct"); - - handledFailureMessages = new HashSet<>(); - events.handleViolations(unboundGeneric(handledFailureMessages)); - - assertThat(handledFailureMessages).containsOnly("correct", "wrong"); - } - - @SuppressWarnings("Convert2Lambda") // to retrieve the type information ViolationHandler may not be converted to a Lambda - private ViolationHandler genericBoundByCorrectType(final Set handledFailureMessages) { - return new ViolationHandler() { - @Override - public void handle(Collection violatingObjects, String message) { - handledFailureMessages.add(message); - } - }; - } - - @SuppressWarnings("Convert2Lambda") // to retrieve the type information ViolationHandler may not be converted to a Lambda - private ViolationHandler unboundGeneric(final Set handledFailureMessages) { - return new ViolationHandler() { - @Override - public void handle(Collection violatingObjects, String message) { - handledFailureMessages.add(message); - } - }; - } - - @Test - public void can_handle_with_generic_superclasses() { - ConditionEvents events = events( - SimpleConditionEvent.violated(new Object(), "ignore"), - SimpleConditionEvent.violated("correct", "ignore")); - - StringHandler handler = new StringHandler(); - events.handleViolations(handler); - - assertThat(handler.getRecorded()).containsOnly("correct"); - } - - @Test - public void reports_description_lines_of_events() { - List expectedDescriptionLines = ImmutableList.of("another event message", "some event message"); - ConditionEvents events = events( - SimpleConditionEvent.violated(new Object(), expectedDescriptionLines.get(1)), - SimpleConditionEvent.violated(new Object(), expectedDescriptionLines.get(0))); - - assertThat(events.getFailureMessages()).containsExactlyElementsOf(expectedDescriptionLines); - } - - private static class BaseHandler implements ViolationHandler { - private final List recorded = new ArrayList<>(); - - @Override - public void handle(Collection violatingObjects, String message) { - recorded.add(Iterables.getOnlyElement(violatingObjects)); - } - - List getRecorded() { - return recorded; - } - } - - private static class StringHandler extends BaseHandler { - } - - private static ConditionEvents events(ConditionEvent... events) { - ConditionEvents result = new ConditionEvents(); - for (ConditionEvent event : events) { - result.add(event); - } - return result; - } - - private static class CorrectSubtype extends CorrectType { - CorrectSubtype(String message) { - super(message); - } - } - - static class CorrectType extends WrongSupertype { - String message; - - CorrectType(String message) { - this.message = message; - } - - @Override - public String toString() { - return message; - } - } - - private static class WrongType { - } - - private static class WrongSupertype { - } - -} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/EvaluationResultTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/EvaluationResultTest.java index 426eee1868..0b7fb451d6 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/EvaluationResultTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/EvaluationResultTest.java @@ -5,16 +5,31 @@ import java.util.List; import java.util.Set; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.base.HasDescription; import org.junit.Test; import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.lang.Priority.MEDIUM; import static java.util.Arrays.stream; import static org.assertj.core.api.Assertions.assertThat; public class EvaluationResultTest { + + @Test + public void reports_description_lines_of_events() { + List expectedLinesAlphabetically = ImmutableList.of("another event message", "some event message"); + + EvaluationResult result = new EvaluationResult( + hasDescription("irrelevant"), + events(expectedLinesAlphabetically.get(1), expectedLinesAlphabetically.get(0)), + MEDIUM); + + assertThat(result.getFailureReport().getDetails()).containsExactlyElementsOf(expectedLinesAlphabetically); + } + @Test public void properties_are_passed_to_FailureReport() { EvaluationResult result = new EvaluationResult( @@ -31,7 +46,6 @@ public void properties_are_passed_to_FailureReport() { } @Test - @SuppressWarnings("Convert2Lambda") // to retrieve the type information ViolationHandler may not be converted to a Lambda public void allows_clients_to_handle_violations() { EvaluationResult result = evaluationResultWith( new SimpleConditionEvent(ImmutableSet.of("message"), false, "expected"), @@ -40,12 +54,8 @@ public void allows_clients_to_handle_violations() { new SimpleConditionEvent(ImmutableSet.of("second message"), false, "also expected")); final Set actual = new HashSet<>(); - result.handleViolations(new ViolationHandler>() { - @Override - public void handle(Collection> violatingObject, String message) { - actual.add(getOnlyElement(getOnlyElement(violatingObject)) + ": " + message); - } - }); + result.handleViolations((Collection> violatingObject, String message) -> + actual.add(getOnlyElement(getOnlyElement(violatingObject)) + ": " + message)); assertThat(actual).containsOnly("message: expected", "second message: also expected"); } @@ -64,8 +74,40 @@ public void can_filter_lines() { assertThat(filtered.getFailureReport().getDetails()).containsOnly("keep first line1", "keep second line1", "keep second line2"); } + @Test + public void filtering_lines_resets_information_about_number_of_violations() { + ConditionEvents events = events(new TestEvent(true, "drop first line1", "keep second line1")); + String informationAboutNumberOfViolations = "test number"; + events.setInformationAboutNumberOfViolations(informationAboutNumberOfViolations); + EvaluationResult result = new EvaluationResult(hasDescription("unimportant"), events, MEDIUM); + + EvaluationResult filtered = result.filterDescriptionsMatching(input -> input.contains("keep")); + + assertThat(filtered.getFailureReport().toString()).contains("(1 times)"); + assertThat(filtered.getFailureReport().toString()).doesNotContain(informationAboutNumberOfViolations); + assertThat(filtered.getFailureReport().getDetails()).containsOnly("keep second line1"); + } + + @Test + public void handleViolations_reports_only_violations_referring_to_the_correct_type() { + EvaluationResult result = evaluationResultWith( + SimpleConditionEvent.satisfied(new CorrectType("do not handle"), "I'm not violated"), + SimpleConditionEvent.violated(new WrongType(), "I'm violated, but wrong type"), + SimpleConditionEvent.violated(new WrongSupertype(), "I'm violated, but wrong type"), + SimpleConditionEvent.violated(new CorrectType("handle type"), "I'm violated and correct type"), + SimpleConditionEvent.violated(new CorrectSubtype("handle sub type"), "I'm violated and correct sub type")); + + final Set handledFailures = new HashSet<>(); + result.handleViolations((Collection violatingObjects, String message) -> + handledFailures.add(Joiner.on(", ").join(violatingObjects) + ": " + message)); + + assertThat(handledFailures).containsOnly( + "handle type: I'm violated and correct type", + "handle sub type: I'm violated and correct sub type"); + } + private EvaluationResult evaluationResultWith(ConditionEvent... events) { - return new EvaluationResult(hasDescription("unimportant"), events(events), Priority.MEDIUM); + return new EvaluationResult(hasDescription("unimportant"), events(events), MEDIUM); } private ConditionEvents events(String... messages) { @@ -76,7 +118,7 @@ private ConditionEvents events(String... messages) { } private ConditionEvents events(ConditionEvent... events) { - ConditionEvents result = new ConditionEvents(); + ConditionEvents result = ConditionEvents.Factory.create(); for (ConditionEvent event : events) { result.add(event); } @@ -87,9 +129,34 @@ private HasDescription hasDescription(final String description) { return () -> description; } + private static class CorrectSubtype extends CorrectType { + CorrectSubtype(String message) { + super(message); + } + } + + static class CorrectType extends WrongSupertype { + String message; + + CorrectType(String message) { + this.message = message; + } + + @Override + public String toString() { + return message; + } + } + + private static class WrongType { + } + + private static class WrongSupertype { + } + private static class TestEvent implements ConditionEvent { - private boolean violation; - private List descriptionLines; + private final boolean violation; + private final List descriptionLines; private TestEvent(boolean violation, String... descriptionLines) { this.violation = violation; @@ -102,7 +169,7 @@ public boolean isViolation() { } @Override - public void addInvertedTo(ConditionEvents events) { + public ConditionEvent invert() { throw new UnsupportedOperationException("Implement me"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/ObjectToStringAndMessageJoiningTestHandler.java b/archunit/src/test/java/com/tngtech/archunit/lang/ObjectToStringAndMessageJoiningTestHandler.java deleted file mode 100644 index 2b4cc6cc79..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/lang/ObjectToStringAndMessageJoiningTestHandler.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.tngtech.archunit.lang; - -import java.util.Collection; -import java.util.Set; - -import com.google.common.base.Joiner; -import com.tngtech.archunit.lang.ConditionEventsTest.CorrectType; - -class ObjectToStringAndMessageJoiningTestHandler implements ViolationHandler { - private final Set messages; - - ObjectToStringAndMessageJoiningTestHandler(Set messages) { - this.messages = messages; - } - - @Override - public void handle(Collection violatingObjects, String message) { - messages.add(Joiner.on(", ").join(violatingObjects) + ": " + message); - } -} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassAccessesFieldConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassAccessesFieldConditionTest.java index 99985c317b..720a1c44bf 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassAccessesFieldConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassAccessesFieldConditionTest.java @@ -58,7 +58,7 @@ public class ClassAccessesFieldConditionTest { @Theory public void condition_works(PositiveTestCase testCase) { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); testCase.condition.check(CALLER_CLASS, events); @@ -67,7 +67,7 @@ public void condition_works(PositiveTestCase testCase) { @Theory public void condition_works(NegativeTestCase testCase) { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); testCase.condition.check(CALLER_CLASS, events); diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassCallsCodeUnitConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassCallsCodeUnitConditionTest.java index 87a054580a..f6a79e3011 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassCallsCodeUnitConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ClassCallsCodeUnitConditionTest.java @@ -56,7 +56,7 @@ public void call_with_wrong_method_name_doesnt_match() { } private ConditionEvents checkCondition(ArchCondition condition) { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(CALLER_CLASS, events); return events; } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainAnyConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainAnyConditionTest.java index a4e31dc9c3..3e250157d2 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainAnyConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainAnyConditionTest.java @@ -1,16 +1,18 @@ package com.tngtech.archunit.lang.conditions; +import java.util.Collection; import java.util.List; +import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ConditionEvents; import org.junit.Test; import static com.tngtech.archunit.lang.conditions.ArchConditions.containAnyElementThat; import static com.tngtech.archunit.lang.conditions.ArchConditions.containOnlyElementsThat; +import static com.tngtech.archunit.lang.conditions.ArchConditions.never; import static com.tngtech.archunit.lang.conditions.ContainsOnlyConditionTest.IS_SERIALIZABLE; import static com.tngtech.archunit.lang.conditions.ContainsOnlyConditionTest.ONE_SERIALIZABLE_AND_ONE_NON_SERIALIZABLE_OBJECT; import static com.tngtech.archunit.lang.conditions.ContainsOnlyConditionTest.SerializableObject; -import static com.tngtech.archunit.lang.conditions.ContainsOnlyConditionTest.getInverted; import static com.tngtech.archunit.lang.conditions.ContainsOnlyConditionTest.isSerializableMessageFor; import static com.tngtech.archunit.lang.conditions.ContainsOnlyConditionTest.messageForTwoTimes; import static com.tngtech.archunit.testutil.Assertions.assertThat; @@ -22,30 +24,33 @@ public class ContainAnyConditionTest { @Test public void satisfied_works_and_description_contains_mismatches() { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); containAnyElementThat(IS_SERIALIZABLE).check(TWO_NONSERIALIZABLE_OBJECTS, events); assertThat(events).containViolations(messageForTwoTimes(isSerializableMessageFor(Object.class))); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); containAnyElementThat(IS_SERIALIZABLE).check(ONE_SERIALIZABLE_AND_ONE_NON_SERIALIZABLE_OBJECT, events); assertThat(events).containNoViolation(); } @Test - public void inverting_works() throws Exception { - ConditionEvents events = new ConditionEvents(); - containAnyElementThat(IS_SERIALIZABLE).check(ONE_SERIALIZABLE_AND_ONE_NON_SERIALIZABLE_OBJECT, events); + public void inverting_works() { + ConditionEvents events = ConditionEvents.Factory.create(); + ArchCondition> condition = containAnyElementThat(IS_SERIALIZABLE); + condition.check(ONE_SERIALIZABLE_AND_ONE_NON_SERIALIZABLE_OBJECT, events); assertThat(events).containNoViolation(); - assertThat(events.getAllowed()).as("Exactly one allowed event occurred").hasSize(1); - assertThat(getInverted(events)).containViolations(isSerializableMessageFor(SerializableObject.class)); + events = ConditionEvents.Factory.create(); + never(containAnyElementThat(IS_SERIALIZABLE)).check(ONE_SERIALIZABLE_AND_ONE_NON_SERIALIZABLE_OBJECT, events); + assertThat(events).containViolations(isSerializableMessageFor(SerializableObject.class)); } @Test public void if_there_are_no_input_events_no_ContainsAnyEvent_is_added() { - ConditionEvents events = new ConditionEvents(); + ViolatedAndSatisfiedConditionEvents events = new ViolatedAndSatisfiedConditionEvents(); containOnlyElementsThat(IS_SERIALIZABLE).check(emptyList(), events); - assertThat(events.isEmpty()).as("events are empty").isTrue(); + assertThat(events.getAllowed()).as("allowed events").isEmpty(); + assertThat(events.getViolating()).as("violated events").isEmpty(); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainsOnlyConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainsOnlyConditionTest.java index 691b40e787..5a1840a7ea 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainsOnlyConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ContainsOnlyConditionTest.java @@ -1,15 +1,16 @@ package com.tngtech.archunit.lang.conditions; import java.io.Serializable; +import java.util.Collection; import java.util.List; import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ConditionEvent; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; import org.junit.Test; import static com.tngtech.archunit.lang.conditions.ArchConditions.containOnlyElementsThat; +import static com.tngtech.archunit.lang.conditions.ArchConditions.never; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -32,41 +33,36 @@ static String isSerializableMessageFor(Class clazz) { @Test public void satisfied_works_and_description_contains_mismatches() { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); containOnlyElementsThat(IS_SERIALIZABLE).check(ONE_SERIALIZABLE_AND_ONE_NON_SERIALIZABLE_OBJECT, events); assertThat(events).containViolations(isSerializableMessageFor(Object.class)); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); containOnlyElementsThat(IS_SERIALIZABLE).check(TWO_SERIALIZABLE_OBJECTS, events); assertThat(events).containNoViolation(); } @Test - public void inverting_works() throws Exception { - ConditionEvents events = new ConditionEvents(); - containOnlyElementsThat(IS_SERIALIZABLE).check(TWO_SERIALIZABLE_OBJECTS, events); + public void inverting_works() { + ConditionEvents events = ConditionEvents.Factory.create(); + ArchCondition> condition = containOnlyElementsThat(IS_SERIALIZABLE); + condition.check(TWO_SERIALIZABLE_OBJECTS, events); assertThat(events).containNoViolation(); - assertThat(events.getAllowed()).as("Exactly one allowed event occurred").hasSize(1); - assertThat(getInverted(events)).containViolations(messageForTwoTimes(isSerializableMessageFor(SerializableObject.class))); + events = ConditionEvents.Factory.create(); + never(condition).check(TWO_SERIALIZABLE_OBJECTS, events); + assertThat(events).containViolations(messageForTwoTimes(isSerializableMessageFor(SerializableObject.class))); } @Test public void if_there_are_no_input_events_no_ContainsOnlyEvent_is_added() { - ConditionEvents events = new ConditionEvents(); + ViolatedAndSatisfiedConditionEvents events = new ViolatedAndSatisfiedConditionEvents(); containOnlyElementsThat(IS_SERIALIZABLE).check(emptyList(), events); - assertThat(events.isEmpty()).as("events are empty").isTrue(); - } - - static ConditionEvents getInverted(ConditionEvents events) { - ConditionEvents inverted = new ConditionEvents(); - for (ConditionEvent event : events) { - event.addInvertedTo(inverted); - } - return inverted; + assertThat(events.getAllowed()).as("allowed events").isEmpty(); + assertThat(events.getViolating()).as("violated events").isEmpty(); } static String messageForTwoTimes(String message) { @@ -75,4 +71,4 @@ static String messageForTwoTimes(String message) { static class SerializableObject implements Serializable { } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/FieldAccessConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/FieldAccessConditionTest.java index fbd3018f4e..c189b5ee17 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/FieldAccessConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/FieldAccessConditionTest.java @@ -25,7 +25,7 @@ public void FieldGetAccessCondition_only_satisfied_on_get_field() { JavaFieldAccess getAccess = accessFromCallerToTargetWithType(GET); FieldGetAccessCondition getFieldCondition = new FieldGetAccessCondition( target(name(getAccess.getTarget().getName()))); - assertSatisfiedWithMessage(getFieldCondition, getAccess, "gets"); + assertSatisfied(getFieldCondition, getAccess); JavaFieldAccess setAccess = accessFromCallerToTargetWithType(SET); getFieldCondition = new FieldGetAccessCondition( @@ -38,7 +38,7 @@ public void FieldSetAccessCondition_only_satisfied_on_set_field() { JavaFieldAccess setAccess = accessFromCallerToTargetWithType(SET); FieldSetAccessCondition setFieldCondition = new FieldSetAccessCondition( target(name(setAccess.getTarget().getName()))); - assertSatisfiedWithMessage(setFieldCondition, setAccess, "sets"); + assertSatisfied(setFieldCondition, setAccess); JavaFieldAccess getAccess = accessFromCallerToTargetWithType(GET); setFieldCondition = new FieldSetAccessCondition( @@ -51,36 +51,33 @@ public void FieldAccessCondition_satisfied_on_both_get_and_set_field() { JavaFieldAccess setAccess = accessFromCallerToTargetWithType(SET); FieldAccessCondition setFieldCondition = new FieldAccessCondition( target(name(setAccess.getTarget().getName()))); - assertSatisfiedWithMessage(setFieldCondition, setAccess, "sets"); + assertSatisfied(setFieldCondition, setAccess); JavaFieldAccess getAccess = accessFromCallerToTargetWithType(GET); setFieldCondition = new FieldAccessCondition( target(name(getAccess.getTarget().getName()))); - assertSatisfiedWithMessage(setFieldCondition, getAccess, "gets"); + assertSatisfied(setFieldCondition, getAccess); } - private void assertSatisfiedWithMessage(FieldAccessCondition getFieldCondition, JavaFieldAccess access, - String accessDescription) { + private void assertSatisfied(FieldAccessCondition getFieldCondition, JavaFieldAccess access) { ConditionEvents events = checkCondition(getFieldCondition, access); - boolean satisfied = !events.containViolation(); - - assertThat(satisfied).as("Events are satisfied").isTrue(); + assertThat(events.containViolation()).as("Events contain violation").isFalse(); assertThat(events.getViolating()).isEmpty(); - assertDescription(access, accessDescription, messageOf(events.getAllowed())); } - private void assertViolatedWithMessage(FieldAccessCondition getFieldCondition, JavaFieldAccess access, - String accessDescription) { + private void assertViolatedWithMessage( + FieldAccessCondition getFieldCondition, + JavaFieldAccess access, + String accessDescription + ) { ConditionEvents events = checkCondition(getFieldCondition, access); - boolean satisfied = !events.containViolation(); - assertThat(satisfied).as("Events are satisfied").isFalse(); - assertThat(events.getAllowed()).isEmpty(); + assertThat(events.containViolation()).as("Events contain violation").isTrue(); assertDescription(access, accessDescription, messageOf(events.getViolating())); } private ConditionEvents checkCondition(FieldAccessCondition getFieldCondition, JavaFieldAccess access) { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); getFieldCondition.check(access, events); return events; } @@ -105,4 +102,4 @@ private static JavaFieldAccess accessFromCallerToTargetWithType(AccessType type) } throw new RuntimeException(CALLER_CLASS + " has no field access with type " + type); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/JavaAccessConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/JavaAccessConditionTest.java index 0b4c70e069..0fa52ceb85 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/JavaAccessConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/JavaAccessConditionTest.java @@ -60,7 +60,7 @@ public void description_is_correct() { private ConditionEventsAssertion assertThatOnlyAccessToSomeClassFor(JavaClass clazz, JavaAccessCondition> condition) { Set> accesses = filterByTarget(clazz.getAccessesFromSelf(), SomeClass.class); - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); for (JavaAccess access : accesses) { condition.check(access, events); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/NeverConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/NeverConditionTest.java index 43a3f6de78..776f9d66ac 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/NeverConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/NeverConditionTest.java @@ -55,18 +55,16 @@ public static Object[][] conditions() { @Test @UseDataProvider("conditions") public void inverts_condition(ArchCondition condition) { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); condition.check(new Object(), events); condition.finish(events); - assertThat(events).containAllowed(ORIGINALLY_NO_MISMATCH); assertThat(events).containViolations(ORIGINALLY_MISMATCH); - events = new ConditionEvents(); + events = ConditionEvents.Factory.create(); never(condition).check(new Object(), events); never(condition).finish(events); - assertThat(events).containAllowed(ORIGINALLY_MISMATCH); assertThat(events).containViolations(ORIGINALLY_NO_MISMATCH); } @@ -83,4 +81,4 @@ public void inits_inverted_condition() { assertThat(original.allObjectsToTest).containsExactly("something"); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/handling/ViolationHandlingTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/handling/ViolationHandlingTest.java index 6376d29ac2..434c6ab00b 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/handling/ViolationHandlingTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/handling/ViolationHandlingTest.java @@ -121,6 +121,7 @@ private Condition> accessToTargetField(String fieldOne) { .to(Target.class, fieldOne); } + @SuppressWarnings("unused") private static class Origin { Target target; @@ -141,6 +142,7 @@ void callConstructor() { } } + @SuppressWarnings("unused") private static class Target { String fieldOne; Object fieldTwo; diff --git a/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java b/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java index fb3f5ad8b0..e3653162a3 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java @@ -629,7 +629,7 @@ public boolean isViolation() { } @Override - public void addInvertedTo(ConditionEvents events) { + public ConditionEvent invert() { throw new UnsupportedOperationException("Implement me"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchConditionAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchConditionAssertion.java index a0a1316187..19423321c4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchConditionAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchConditionAssertion.java @@ -18,7 +18,7 @@ public ArchConditionAssertion hasDescription(String description) { } public ConditionEventsAssertion checking(T item) { - ConditionEvents events = new ConditionEvents(); + ConditionEvents events = ConditionEvents.Factory.create(); actual.check(item, events); return assertThat(events); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ConditionEventsAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ConditionEventsAssertion.java index 9162fa12c2..8e485c9f93 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ConditionEventsAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ConditionEventsAssertion.java @@ -11,16 +11,13 @@ import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.lang.ConditionEvent; import com.tngtech.archunit.lang.ConditionEvents; -import org.assertj.core.api.AbstractIterableAssert; +import org.assertj.core.api.AbstractObjectAssert; import org.assertj.core.api.Assertions; -import org.assertj.core.api.ObjectAssert; -import org.assertj.core.api.ObjectAssertFactory; import static com.google.common.collect.Iterables.getOnlyElement; import static java.util.stream.Collectors.toList; -public class ConditionEventsAssertion - extends AbstractIterableAssert> { +public class ConditionEventsAssertion extends AbstractObjectAssert { public ConditionEventsAssertion(ConditionEvents actual) { super(actual, ConditionEventsAssertion.class); @@ -35,15 +32,6 @@ public void containViolations(String violation, String... additional) { } } - public void containAllowed(String message, String... additional) { - Assertions.assertThat(actual.getAllowed()).as("Allowed events").isNotEmpty(); - - List expected = concat(message, additional); - if (!sorted(messagesOf(actual.getAllowed())).equals(sorted(expected))) { - failWithMessage("Expected %s to contain only allowed events %s", actual, expected); - } - } - private List messagesOf(Collection events) { return events.stream().flatMap(event -> event.getDescriptionLines().stream()).collect(toList()); } @@ -88,16 +76,4 @@ public ConditionEventsAssertion haveAtLeastOneViolationMessageMatching(String re } throw new AssertionError(String.format("No message matches pattern '%s'", regex)); } - - @Override - protected ObjectAssert toAssert(ConditionEvent value, String description) { - return new ObjectAssertFactory().createAssert(value).as(description); - } - - @Override - protected ConditionEventsAssertion newAbstractIterableAssert(Iterable iterable) { - ConditionEvents actual = new ConditionEvents(); - iterable.forEach(actual::add); - return new ConditionEventsAssertion(actual); - } }