/* * Copyright 2017 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.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 static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; @PublicAPI(usage = INHERITANCE) public abstract class ArchCondition<T> { private String description; public ArchCondition(String description) { this.description = checkNotNull(description); } /** * Can be used to prepare this condition with respect to the collection of all objects the condition * will be tested against. * * @param allObjectsToTest All objects that {@link #check(Object, ConditionEvents)} will be called against */ public void init(Iterable<T> allObjectsToTest) { } public abstract void check(T item, ConditionEvents events); public ArchCondition<T> and(ArchCondition<? super T> condition) { return new AndCondition<>(this, condition.<T>forSubType()); } public ArchCondition<T> or(ArchCondition<? super T> condition) { return new OrCondition<>(this, condition.<T>forSubType()); } public String getDescription() { return description; } public ArchCondition<T> as(String description, Object... args) { return new ArchCondition<T>(String.format(description, args)) { @Override public void init(Iterable<T> allObjectsToTest) { ArchCondition.this.init(allObjectsToTest); } @Override public void check(T item, ConditionEvents events) { ArchCondition.this.check(item, events); } }; } @SuppressWarnings("unchecked") // Cast is safe since input parameter is contravariant public <U extends T> ArchCondition<U> forSubType() { return (ArchCondition<U>) this; } private abstract static class JoinCondition<T> extends ArchCondition<T> { private final Collection<ArchCondition<T>> conditions; private JoinCondition(String infix, Collection<ArchCondition<T>> conditions) { super(joinDescriptionsOf(infix, conditions)); this.conditions = conditions; } private static <T> String joinDescriptionsOf(String infix, Collection<ArchCondition<T>> conditions) { List<String> descriptions = new ArrayList<>(); for (ArchCondition<T> condition : conditions) { descriptions.add(condition.getDescription()); } return Joiner.on(" " + infix + " ").join(descriptions); } @Override public void init(Iterable<T> allObjectsToTest) { for (ArchCondition<T> condition : conditions) { condition.init(allObjectsToTest); } } List<ConditionWithEvents> evaluateConditions(T item) { List<ConditionWithEvents> evaluate = new ArrayList<>(); for (ArchCondition<T> condition : conditions) { evaluate.add(new ConditionWithEvents(condition, item)); } return evaluate; } @Override public String toString() { return getClass().getSimpleName() + "{" + conditions + "}"; } } private static class ConditionWithEvents { private final ArchCondition<?> condition; private final ConditionEvents events; <T> ConditionWithEvents(ArchCondition<T> condition, T item) { this(condition, check(condition, item)); } ConditionWithEvents(ArchCondition<?> condition, ConditionEvents events) { this.condition = condition; this.events = events; } private static <T> ConditionEvents check(ArchCondition<T> 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<T> implements ConditionEvent<T> { private final T correspondingObject; final List<ConditionWithEvents> evaluatedConditions; JoinConditionEvent(T correspondingObject, List<ConditionWithEvents> evaluatedConditions) { this.correspondingObject = correspondingObject; this.evaluatedConditions = evaluatedConditions; } Set<String> getUniqueLinesOfViolations() { // TODO: Sort by line number, then lexicographically final Set<String> result = new TreeSet<>(); CollectsLines lines = new CollectsLines() { @Override public void add(String line) { result.add(line); } }; for (ConditionWithEvents evaluation : evaluatedConditions) { for (ConditionEvent event : evaluation.events) { if (event.isViolation()) { event.describeTo(lines); } } } return result; } @Override public T getCorrespondingObject() { return correspondingObject; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("evaluatedConditions", evaluatedConditions) .toString(); } List<ConditionWithEvents> invert(List<ConditionWithEvents> evaluatedConditions) { List<ConditionWithEvents> inverted = new ArrayList<>(); for (ConditionWithEvents evaluation : evaluatedConditions) { inverted.add(invert(evaluation)); } return inverted; } 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<T> extends JoinCondition<T> { private AndCondition(ArchCondition<T> first, ArchCondition<T> 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<T> extends JoinCondition<T> { private OrCondition(ArchCondition<T> first, ArchCondition<T> 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<T> extends JoinConditionEvent<T> { AndConditionEvent(T item, List<ConditionWithEvents> evaluatedConditions) { super(item, evaluatedConditions); } @Override public boolean isViolation() { for (ConditionWithEvents evaluation : evaluatedConditions) { if (evaluation.events.containViolation()) { return true; } } return false; } @Override public void addInvertedTo(ConditionEvents events) { events.add(new OrConditionEvent<>(getCorrespondingObject(), invert(evaluatedConditions))); } @Override public void describeTo(CollectsLines lines) { for (String line : getUniqueLinesOfViolations()) { lines.add(line); } } } private static class OrConditionEvent<T> extends JoinConditionEvent<T> { OrConditionEvent(T item, List<ConditionWithEvents> evaluatedConditions) { super(item, evaluatedConditions); } @Override public boolean isViolation() { for (ConditionWithEvents evaluation : evaluatedConditions) { if (!evaluation.events.containViolation()) { return false; } } return true; } @Override public void addInvertedTo(ConditionEvents events) { events.add(new AndConditionEvent<>(getCorrespondingObject(), invert(evaluatedConditions))); } @Override public void describeTo(CollectsLines lines) { lines.add(Joiner.on(" and ").join(getUniqueLinesOfViolations())); } } }