forked from TNG/ArchUnit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add arch condition to check for any transitive dependency
Issue: TNG#780 Signed-off-by: e.solutions <info@esolutions.de> on-behalf-of: @e-esolutions-GmbH <info@esolutions.de>
- Loading branch information
Showing
5 changed files
with
294 additions
and
21 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
.../src/main/java/com/tngtech/archunit/lang/conditions/AnyTransitiveDependencyCondition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package com.tngtech.archunit.lang.conditions; | ||
|
||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.HashSet; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.StringJoiner; | ||
|
||
import com.google.common.collect.Lists; | ||
import com.tngtech.archunit.base.DescribedPredicate; | ||
import com.tngtech.archunit.core.domain.Dependency; | ||
import com.tngtech.archunit.core.domain.JavaClass; | ||
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 static com.google.common.base.Preconditions.checkNotNull; | ||
|
||
public class AnyTransitiveDependencyCondition extends ArchCondition<JavaClass> { | ||
|
||
private final DescribedPredicate<? super JavaClass> conditionPredicate; | ||
private final JavaClassTransitiveDependencyPath transitiveDependencyPath; | ||
private Collection<JavaClass> allClasses; | ||
|
||
public AnyTransitiveDependencyCondition(DescribedPredicate<? super JavaClass> conditionPredicate) { | ||
super("transitively depend on any classes that " + conditionPredicate.getDescription()); | ||
|
||
this.conditionPredicate = checkNotNull(conditionPredicate); | ||
this.transitiveDependencyPath = new JavaClassTransitiveDependencyPath(); | ||
} | ||
|
||
@Override | ||
public void init(Collection<JavaClass> allObjectsToTest) { | ||
this.allClasses = allObjectsToTest; | ||
} | ||
|
||
@Override | ||
public void check(JavaClass item, ConditionEvents events) { | ||
for (JavaClass dependency : getDirectDependencies(item)) { | ||
if (!allClasses.contains(dependency)) { | ||
List<JavaClass> dependencyPath = transitiveDependencyPath.findFirstPathToTransitiveDependency(dependency); | ||
if (!dependencyPath.isEmpty()) { | ||
events.add(new TransitivePathFoundEvent(item, dependencyPath)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private class JavaClassTransitiveDependencyPath { | ||
/** | ||
* @return the first dependency path to a matching class or empty if there is none | ||
*/ | ||
List<JavaClass> findFirstPathToTransitiveDependency(JavaClass clazz) { | ||
LinkedList<JavaClass> transitivePath = new LinkedList<>(); | ||
addDependenciesToPathFrom(clazz, transitivePath, new HashSet<>()); | ||
return Collections.unmodifiableList(transitivePath); | ||
} | ||
|
||
private boolean addDependenciesToPathFrom( | ||
JavaClass clazz, | ||
LinkedList<JavaClass> dependencyPath, | ||
Set<JavaClass> analyzedClasses | ||
) { | ||
if (conditionPredicate.test(clazz)) { | ||
dependencyPath.addFirst(clazz); | ||
return true; | ||
} | ||
|
||
analyzedClasses.add(clazz); | ||
|
||
for (JavaClass directDependency : getDirectDependencies(clazz)) { | ||
if (!allClasses.contains(directDependency) | ||
&& !analyzedClasses.contains(directDependency) | ||
&& addDependenciesToPathFrom(directDependency, dependencyPath, analyzedClasses)) { | ||
dependencyPath.addFirst(clazz); | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
|
||
private static Set<JavaClass> getDirectDependencies(JavaClass item) { | ||
Set<JavaClass> directDependencies = new HashSet<>(); | ||
for (Dependency dependency : item.getDirectDependenciesFromSelf()) { | ||
directDependencies.add(dependency.getTargetClass().getBaseComponentType()); | ||
} | ||
return directDependencies; | ||
} | ||
|
||
private static class TransitivePathFoundEvent implements ConditionEvent { | ||
private final JavaClass selected; | ||
private final String message; | ||
|
||
public TransitivePathFoundEvent(JavaClass selected, List<JavaClass> transitivePath) { | ||
this.selected = selected; | ||
|
||
String messageStr = | ||
String.format("Class <%s> accesses <%s>", selected.getFullName(), transitivePath.get(0).getFullName()); | ||
|
||
if (transitivePath.size() > 1) { | ||
messageStr += " which transitively accesses e.g. "; | ||
StringJoiner joiner = new StringJoiner("> <- <", "<", ">"); | ||
for (JavaClass dependencyTarget : Lists.reverse(transitivePath)) { | ||
joiner.add(dependencyTarget.getFullName()); | ||
} | ||
messageStr += joiner.toString(); | ||
} | ||
message = messageStr; | ||
} | ||
|
||
@Override | ||
public boolean isViolation() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public void addInvertedTo(ConditionEvents events) { | ||
events.add(SimpleConditionEvent.violated(selected, message)); | ||
} | ||
|
||
@Override | ||
public List<String> getDescriptionLines() { | ||
return Collections.singletonList(message); | ||
} | ||
|
||
@Override | ||
public void handleWith(Handler handler) { | ||
handler.handle(Collections.singletonList(selected), message); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.