Skip to content

Commit

Permalink
Add referenced class objects to JavaClass #518
Browse files Browse the repository at this point in the history
So far ArchUnit has not been able to detect the pure usage of class objects as dependencies. E.g. the following example would not have detected a dependency on `Evil`, since besides the reference to the class object no further dependency on `Evil` (like a field access or method call) has occurred.

```
class Example {
  final Map<Class<?>, Object> association = Map.of(Evil.class, anything);
}
```

With this PR `JavaClass` now knows its `referencedClassObjects`, including the respective line number. Furthermore class objects are now parts of the `dependencies{From/To}Self` of a `JavaClass`.

Resolves: #309 
Issue: #446 
Resolves: #474 
Resolves: #515
  • Loading branch information
codecholeric committed Jan 31, 2021
2 parents 11006ed + 1263ae5 commit 8777583
Show file tree
Hide file tree
Showing 24 changed files with 589 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,9 @@ Stream<DynamicTest> OnionArchitectureTest() {
.by(field(ShoppingService.class, "productRepository").ofType(ProductRepository.class))
.by(field(ShoppingService.class, "shoppingCartRepository").ofType(ShoppingCartRepository.class))

.by(method(AdministrationCLI.class, "handle")
.referencingClassObject(ProductRepository.class)
.inLine(16))
.by(callFromMethod(AdministrationCLI.class, "handle", String[].class, AdministrationPort.class)
.toMethod(ProductRepository.class, "getTotalCount")
.inLine(17).asDependency())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ public ExpectedDependency withAnnotationType(Class<?> annotationType) {
}

public AddsLineNumber checkingInstanceOf(Class<?> target) {
return new AddsLineNumber(owner, getOriginName(), target);
return new AddsLineNumber(owner, getOriginName(), "checks instanceof", target);
}

public AddsLineNumber referencingClassObject(Class<?> target) {
return new AddsLineNumber(owner, getOriginName(), "references class object", target);
}

private String getOriginName() {
Expand All @@ -201,15 +205,17 @@ public static class AddsLineNumber {
private final Class<?> owner;
private final String origin;
private final Class<?> target;
private final String dependencyType;

private AddsLineNumber(Class<?> owner, String origin, Class<?> target) {
private AddsLineNumber(Class<?> owner, String origin, String dependencyType, Class<?> target) {
this.owner = owner;
this.origin = origin;
this.target = target;
this.dependencyType = dependencyType;
}

public ExpectedDependency inLine(int lineNumber) {
String dependencyPattern = getDependencyPattern(origin, "checks instanceof", target.getName(), lineNumber);
String dependencyPattern = getDependencyPattern(origin, dependencyType, target.getName(), lineNumber);
return new ExpectedDependency(owner, target, dependencyPattern);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.tngtech.archunit.base.HasDescription;
import com.tngtech.archunit.base.Optional;
import com.tngtech.archunit.core.domain.properties.HasName;
import com.tngtech.archunit.core.domain.properties.HasOwner;
import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation;

import static com.google.common.base.Preconditions.checkArgument;
Expand Down Expand Up @@ -100,44 +101,52 @@ static Dependency fromInheritance(JavaClass origin, JavaClass targetSupertype) {
}

static Set<Dependency> tryCreateFromField(JavaField field) {
return tryCreateDependencyFromJavaMember(field, "has type", field.getRawType());
return tryCreateDependency(field, "has type", field.getRawType());
}

static Set<Dependency> tryCreateFromReturnType(JavaMethod method) {
return tryCreateDependencyFromJavaMember(method, "has return type", method.getRawReturnType());
return tryCreateDependency(method, "has return type", method.getRawReturnType());
}

static Set<Dependency> tryCreateFromParameter(JavaCodeUnit codeUnit, JavaClass parameter) {
return tryCreateDependencyFromJavaMember(codeUnit, "has parameter of type", parameter);
return tryCreateDependency(codeUnit, "has parameter of type", parameter);
}

static Set<Dependency> tryCreateFromThrowsDeclaration(ThrowsDeclaration<? extends JavaCodeUnit> declaration) {
return tryCreateDependencyFromJavaMember(declaration.getLocation(), "throws type", declaration.getRawType());
return tryCreateDependency(declaration.getLocation(), "throws type", declaration.getRawType());
}

static Set<Dependency> tryCreateFromInstanceofCheck(InstanceofCheck instanceofCheck) {
return tryCreateDependencyFromJavaMemberWithLocation(instanceofCheck.getOwner(), "checks instanceof", instanceofCheck.getRawType(), instanceofCheck.getLineNumber());
return tryCreateDependency(
instanceofCheck.getOwner(), "checks instanceof",
instanceofCheck.getRawType(), instanceofCheck.getSourceCodeLocation());
}

static Set<Dependency> tryCreateFromReferencedClassObject(ReferencedClassObject referencedClassObject) {
return tryCreateDependency(
referencedClassObject.getOwner(), "references class object",
referencedClassObject.getRawType(), referencedClassObject.getSourceCodeLocation());
}

static Set<Dependency> tryCreateFromAnnotation(JavaAnnotation<?> target) {
Origin origin = findSuitableOrigin(target, target.getAnnotatedElement());
return tryCreateDependency(origin.originClass, origin.originDescription, "is annotated with", target.getRawType());
return tryCreateDependency(origin, "is annotated with", target.getRawType());
}

static Set<Dependency> tryCreateFromAnnotationMember(JavaAnnotation<?> annotation, JavaClass memberType) {
Origin origin = findSuitableOrigin(annotation, annotation.getAnnotatedElement());
return tryCreateDependency(origin.originClass, origin.originDescription, "has annotation member of type", memberType);
return tryCreateDependency(origin, "has annotation member of type", memberType);
}

static Set<Dependency> tryCreateFromTypeParameter(JavaTypeVariable<?> typeParameter, JavaClass typeParameterDependency) {
String dependencyType = "has type parameter '" + typeParameter.getName() + "' depending on";
Origin origin = findSuitableOrigin(typeParameter, typeParameter.getOwner());
return tryCreateDependency(origin.originClass, origin.originDescription, dependencyType, typeParameterDependency);
return tryCreateDependency(origin, dependencyType, typeParameterDependency);
}

static Set<Dependency> tryCreateFromGenericSuperclassTypeArguments(JavaClass originClass, JavaType superclass, JavaClass typeArgumentDependency) {
String dependencyType = "has generic superclass " + bracketFormat(superclass.getName()) + " with type argument depending on";
return tryCreateDependency(originClass, originClass.getDescription(), dependencyType, typeArgumentDependency);
return tryCreateDependency(originClass, originClass.getDescription(), dependencyType, typeArgumentDependency, originClass.getSourceCodeLocation());
}

private static Origin findSuitableOrigin(Object dependencyCause, Object originCandidate) {
Expand All @@ -152,22 +161,20 @@ private static Origin findSuitableOrigin(Object dependencyCause, Object originCa
throw new IllegalStateException("Could not find suitable dependency origin for " + dependencyCause);
}

private static Set<Dependency> tryCreateDependencyFromJavaMember(JavaMember origin, String dependencyType, JavaClass target) {
return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, target);
}

private static Set<Dependency> tryCreateDependencyFromJavaMemberWithLocation(JavaMember origin, String dependencyType, JavaClass target, int lineNumber) {
return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, target, SourceCodeLocation.of(origin.getOwner(), lineNumber));
private static <T extends HasOwner<JavaClass> & HasDescription> Set<Dependency> tryCreateDependency(
T origin, String dependencyType, JavaClass targetClass) {
return tryCreateDependency(origin, dependencyType, targetClass, origin.getOwner().getSourceCodeLocation());
}

private static Set<Dependency> tryCreateDependency(
JavaClass originClass, String originDescription, String dependencyType, JavaClass targetClass) {
private static <T extends HasOwner<JavaClass> & HasDescription> Set<Dependency> tryCreateDependency(
T origin, String dependencyType, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) {

return tryCreateDependency(originClass, originDescription, dependencyType, targetClass, originClass.getSourceCodeLocation());
return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, targetClass, sourceCodeLocation);
}

private static Set<Dependency> tryCreateDependency(
JavaClass originClass, String originDescription, String dependencyType, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) {

ImmutableSet.Builder<Dependency> dependencies = ImmutableSet.<Dependency>builder()
.addAll(createComponentTypeDependencies(originClass, originDescription, targetClass, sourceCodeLocation));
String targetDescription = bracketFormat(targetClass.getName());
Expand All @@ -177,7 +184,9 @@ private static Set<Dependency> tryCreateDependency(
return dependencies.build();
}

private static Set<Dependency> createComponentTypeDependencies(JavaClass originClass, String originDescription, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) {
private static Set<Dependency> createComponentTypeDependencies(
JavaClass originClass, String originDescription, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) {

ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
Optional<JavaClass> componentType = targetClass.tryGetComponentType();
while (componentType.isPresent()) {
Expand Down Expand Up @@ -271,14 +280,24 @@ public static JavaClasses toTargetClasses(Iterable<Dependency> dependencies) {
return JavaClasses.of(classes);
}

private static class Origin {
private static class Origin implements HasOwner<JavaClass>, HasDescription {
private final JavaClass originClass;
private final String originDescription;

private Origin(JavaClass originClass, String originDescription) {
this.originClass = originClass;
this.originDescription = originDescription;
}

@Override
public JavaClass getOwner() {
return originClass;
}

@Override
public String getDescription() {
return originDescription;
}
}

public static final class Predicates {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ public static Source createSource(URI uri, Optional<String> sourceFileName, bool
return new Source(uri, sourceFileName, md5InClassSourcesEnabled);
}

public static ReferencedClassObject createReferencedClassObject(JavaCodeUnit codeUnit, JavaClass javaClass, int lineNumber) {
return ReferencedClassObject.from(codeUnit, javaClass, lineNumber);
}

public static <CODE_UNIT extends JavaCodeUnit> ThrowsClause<CODE_UNIT> createThrowsClause(CODE_UNIT codeUnit, List<JavaClass> types) {
return ThrowsClause.from(codeUnit, types);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,25 @@

import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.core.domain.properties.HasOwner;
import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation;
import com.tngtech.archunit.core.domain.properties.HasType;

import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;

public final class InstanceofCheck implements HasType, HasOwner<JavaCodeUnit> {
public final class InstanceofCheck implements HasType, HasOwner<JavaCodeUnit>, HasSourceCodeLocation {

private final JavaCodeUnit owner;
private final JavaClass target;
private final int lineNumber;
private final SourceCodeLocation sourceCodeLocation;

private InstanceofCheck(JavaCodeUnit owner, JavaClass target, int lineNumber) {
this.owner = checkNotNull(owner);
this.target = checkNotNull(target);
this.lineNumber = lineNumber;
sourceCodeLocation = SourceCodeLocation.of(owner.getOwner(), lineNumber);
}

@Override
Expand All @@ -57,6 +61,20 @@ public int getLineNumber() {
return lineNumber;
}

@Override
public SourceCodeLocation getSourceCodeLocation() {
return sourceCodeLocation;
}

@Override
public String toString() {
return toStringHelper(this)
.add("owner", owner)
.add("target", target)
.add("lineNumber", lineNumber)
.toString();
}

static InstanceofCheck from(JavaCodeUnit owner, JavaClass target, int lineNumber) {
return new InstanceofCheck(owner, target, lineNumber);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,15 @@ public List<JavaTypeVariable<JavaClass>> getTypeParameters() {
return typeParameters;
}

@PublicAPI(usage = ACCESS)
public Set<ReferencedClassObject> getReferencedClassObjects() {
ImmutableSet.Builder<ReferencedClassObject> result = ImmutableSet.builder();
for (JavaCodeUnit codeUnit : codeUnits) {
result.addAll(codeUnit.getReferencedClassObjects());
}
return result.build();
}

@Override
@PublicAPI(usage = ACCESS)
public JavaClass toErasure() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public Set<Dependency> get() {
result.addAll(constructorParameterDependenciesFromSelf());
result.addAll(annotationDependenciesFromSelf());
result.addAll(instanceofCheckDependenciesFromSelf());
result.addAll(referencedClassObjectDependenciesFromSelf());
result.addAll(typeParameterDependenciesFromSelf());
return result.build();
}
Expand Down Expand Up @@ -158,6 +159,14 @@ private Set<Dependency> instanceofCheckDependenciesFromSelf() {
return result.build();
}

private Set<Dependency> referencedClassObjectDependenciesFromSelf() {
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
for (ReferencedClassObject referencedClassObject : javaClass.getReferencedClassObjects()) {
result.addAll(Dependency.tryCreateFromReferencedClassObject(referencedClassObject));
}
return result.build();
}

private Set<Dependency> typeParameterDependenciesFromSelf() {
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
for (JavaTypeVariable<?> typeVariable : javaClass.getTypeParameters()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp
private final JavaClass returnType;
private final JavaClassList parameters;
private final String fullName;
private final Set<ReferencedClassObject> referencedClassObjects;
private final Set<InstanceofCheck> instanceofChecks;

private Set<JavaFieldAccess> fieldAccesses = Collections.emptySet();
Expand All @@ -61,7 +62,8 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp
this.returnType = builder.getReturnType();
this.parameters = builder.getParameters();
fullName = formatMethod(getOwner().getName(), getName(), getRawParameterTypes());
instanceofChecks = builder.getInstanceofChecks(this);
referencedClassObjects = ImmutableSet.copyOf(builder.getReferencedClassObjects(this));
instanceofChecks = ImmutableSet.copyOf(builder.getInstanceofChecks(this));
}

/**
Expand Down Expand Up @@ -112,6 +114,11 @@ public Set<JavaConstructorCall> getConstructorCallsFromSelf() {
return constructorCalls;
}

@PublicAPI(usage = ACCESS)
public Set<ReferencedClassObject> getReferencedClassObjects() {
return referencedClassObjects;
}

@PublicAPI(usage = ACCESS)
public Set<InstanceofCheck> getInstanceofChecks() {
return instanceofChecks;
Expand Down
Loading

0 comments on commit 8777583

Please sign in to comment.