Skip to content

Commit

Permalink
add code unit parameter annotations to JavaClassDependencies
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
  • Loading branch information
codecholeric committed Oct 27, 2021
1 parent 273ba74 commit 98787a0
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tngtech.archunit.example.layers.controller;

import com.tngtech.archunit.example.layers.controller.marshaller.StringUnmarshaller;

@SuppressWarnings("unused")
public class OtherController {
void receive(@UnmarshalTransport(StringUnmarshaller.class) Object param) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.tngtech.archunit.example.layers.controller;

import java.lang.annotation.Retention;

import com.tngtech.archunit.example.layers.controller.marshaller.Unmarshaller;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
public @interface UnmarshalTransport {
Class<? extends Unmarshaller<?>>[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.tngtech.archunit.example.layers.controller.marshaller;

public class ByteUnmarshaller implements Unmarshaller<Byte> {
@Override
public <T> T unmarschal(Byte from) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.tngtech.archunit.example.layers.controller.marshaller;

public class StringUnmarshaller implements Unmarshaller<String> {
@Override
public <T> T unmarschal(String from) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.tngtech.archunit.example.layers.controller.marshaller;

public interface Unmarshaller<F> {
@SuppressWarnings("unused")
<T> T unmarschal(F from);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.tngtech.archunit.example.layers.service;

import com.tngtech.archunit.example.layers.controller.UnmarshalTransport;
import com.tngtech.archunit.example.layers.controller.marshaller.ByteUnmarshaller;
import com.tngtech.archunit.example.layers.controller.marshaller.StringUnmarshaller;

public class OtherServiceViolatingLayerRules {
@SuppressWarnings("unused")
public void dependentOnParameterAnnotation(@UnmarshalTransport({StringUnmarshaller.class, ByteUnmarshaller.class}) Object param) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@
import com.tngtech.archunit.example.layers.controller.SomeController;
import com.tngtech.archunit.example.layers.controller.SomeGuiController;
import com.tngtech.archunit.example.layers.controller.SomeUtility;
import com.tngtech.archunit.example.layers.controller.UnmarshalTransport;
import com.tngtech.archunit.example.layers.controller.WronglyAnnotated;
import com.tngtech.archunit.example.layers.controller.marshaller.ByteUnmarshaller;
import com.tngtech.archunit.example.layers.controller.marshaller.StringUnmarshaller;
import com.tngtech.archunit.example.layers.controller.marshaller.Unmarshaller;
import com.tngtech.archunit.example.layers.controller.one.SomeEnum;
import com.tngtech.archunit.example.layers.controller.one.UseCaseOneThreeController;
import com.tngtech.archunit.example.layers.controller.one.UseCaseOneTwoController;
Expand All @@ -100,6 +104,7 @@
import com.tngtech.archunit.example.layers.security.Secured;
import com.tngtech.archunit.example.layers.service.Async;
import com.tngtech.archunit.example.layers.service.ComplexServiceAnnotation;
import com.tngtech.archunit.example.layers.service.OtherServiceViolatingLayerRules;
import com.tngtech.archunit.example.layers.service.ProxiedConnection;
import com.tngtech.archunit.example.layers.service.ServiceHelper;
import com.tngtech.archunit.example.layers.service.ServiceInterface;
Expand Down Expand Up @@ -174,6 +179,7 @@
import static com.tngtech.archunit.testutils.ExpectedAccess.callFromMethod;
import static com.tngtech.archunit.testutils.ExpectedAccess.callFromStaticInitializer;
import static com.tngtech.archunit.testutils.ExpectedDependency.annotatedClass;
import static com.tngtech.archunit.testutils.ExpectedDependency.annotatedParameter;
import static com.tngtech.archunit.testutils.ExpectedDependency.constructor;
import static com.tngtech.archunit.testutils.ExpectedDependency.field;
import static com.tngtech.archunit.testutils.ExpectedDependency.genericFieldType;
Expand Down Expand Up @@ -233,7 +239,7 @@ Stream<DynamicTest> CodingRulesTest() {
.inLine(9));

expectFailures.ofRule("fields that have raw type java.util.logging.Logger should be private " +
"and should be static and should be final, because we agreed on this convention")
"and should be static and should be final, because we agreed on this convention")
.by(ExpectedField.of(ClassViolatingCodingRules.class, "log").doesNotHaveModifier(JavaModifier.PRIVATE))
.by(ExpectedField.of(ClassViolatingCodingRules.class, "log").doesNotHaveModifier(JavaModifier.FINAL));

Expand All @@ -245,8 +251,8 @@ Stream<DynamicTest> CodingRulesTest() {
.by(method(ClassViolatingCodingRules.class, "jodaTimeIsBad")
.withReturnType(org.joda.time.DateTime.class));

expectFailures.ofRule("no classes should use field injection, because field injection is considered harmful; "
+ "use constructor injection or setter injection instead; see https://stackoverflow.com/q/39890849 for detailed explanations")
expectFailures.ofRule("no classes should use field injection, because field injection is considered harmful; " +
"use constructor injection or setter injection instead; see https://stackoverflow.com/q/39890849 for detailed explanations")
.by(ExpectedField.of(ClassViolatingInjectionRules.class, "badBecauseAutowiredField").beingAnnotatedWith(Autowired.class))
.by(ExpectedField.of(ClassViolatingInjectionRules.class, "badBecauseValueField").beingAnnotatedWith(Value.class))
.by(ExpectedField.of(ClassViolatingInjectionRules.class, "badBecauseJavaxInjectField").beingAnnotatedWith(javax.inject.Inject.class))
Expand Down Expand Up @@ -815,6 +821,15 @@ Stream<DynamicTest> LayerDependencyRulesTest() {
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(ComplexControllerAnnotation.class))
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SimpleControllerAnnotation.class))
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SomeEnum.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.annotatedWith(UnmarshalTransport.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.withAnnotationParameterType(StringUnmarshaller.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.withAnnotationParameterType(ByteUnmarshaller.class))
.by(method(ComplexServiceAnnotation.class, "controllerAnnotation").withReturnType(ComplexControllerAnnotation.class))
.by(method(ComplexServiceAnnotation.class, "controllerEnum").withReturnType(SomeEnum.class))

Expand Down Expand Up @@ -898,6 +913,15 @@ Stream<DynamicTest> LayerDependencyRulesTest() {
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(ComplexControllerAnnotation.class))
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SimpleControllerAnnotation.class))
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SomeEnum.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.annotatedWith(UnmarshalTransport.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.withAnnotationParameterType(StringUnmarshaller.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.withAnnotationParameterType(ByteUnmarshaller.class))
.by(method(ComplexServiceAnnotation.class, "controllerAnnotation").withReturnType(ComplexControllerAnnotation.class))
.by(method(ComplexServiceAnnotation.class, "controllerEnum").withReturnType(SomeEnum.class))

Expand Down Expand Up @@ -977,6 +1001,15 @@ Stream<DynamicTest> LayeredArchitectureTest() {
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(ComplexControllerAnnotation.class))
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SimpleControllerAnnotation.class))
.by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SomeEnum.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.annotatedWith(UnmarshalTransport.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.withAnnotationParameterType(StringUnmarshaller.class))
.by(annotatedParameter(Object.class)
.ofMethod(OtherServiceViolatingLayerRules.class, "dependentOnParameterAnnotation", Object.class)
.withAnnotationParameterType(ByteUnmarshaller.class))
.by(method(ComplexServiceAnnotation.class, "controllerAnnotation").withReturnType(ComplexControllerAnnotation.class))
.by(method(ComplexServiceAnnotation.class, "controllerEnum").withReturnType(SomeEnum.class))
.by(method(OtherJpa.class, "testConnection")
Expand Down Expand Up @@ -1117,6 +1150,10 @@ Stream<DynamicTest> NamingConventionTest() {
.by(simpleNameOf(InheritedControllerImpl.class).notEndingWith("Controller"))
.by(simpleNameOf(ComplexControllerAnnotation.class).notEndingWith("Controller"))
.by(simpleNameOf(SimpleControllerAnnotation.class).notEndingWith("Controller"))
.by(simpleNameOf(UnmarshalTransport.class).notEndingWith("Controller"))
.by(simpleNameOf(Unmarshaller.class).notEndingWith("Controller"))
.by(simpleNameOf(StringUnmarshaller.class).notEndingWith("Controller"))
.by(simpleNameOf(ByteUnmarshaller.class).notEndingWith("Controller"))
.by(simpleNameOf(SomeUtility.class).notEndingWith("Controller"))
.by(simpleNameOf(WronglyAnnotated.class).notEndingWith("Controller"))
.by(simpleNameOf(SomeEnum.class).notEndingWith("Controller"))
Expand Down Expand Up @@ -1398,14 +1435,15 @@ Stream<DynamicTest> SlicesIsolationTest() {
// controllers_should_only_use_their_own_slice
addExpectedCommonFailureFor_controllers_should_only_use_their_own_slice
.accept("controllers_should_only_use_their_own_slice", expectedTestFailures);
expectedTestFailures.by(sliceDependency()
.described("Controller one depends on Controller two")
.by(callFromMethod(UseCaseOneTwoController.class, doSomethingOne)
.toConstructor(UseCaseTwoController.class)
.inLine(10))
.by(callFromMethod(UseCaseOneTwoController.class, doSomethingOne)
.toMethod(UseCaseTwoController.class, doSomethingTwo)
.inLine(10)))
expectedTestFailures
.by(sliceDependency()
.described("Controller one depends on Controller two")
.by(callFromMethod(UseCaseOneTwoController.class, doSomethingOne)
.toConstructor(UseCaseTwoController.class)
.inLine(10))
.by(callFromMethod(UseCaseOneTwoController.class, doSomethingOne)
.toMethod(UseCaseTwoController.class, doSomethingTwo)
.inLine(10)))
.by(sliceDependency()
.described("Controller three depends on Controller one")
.by(callFromMethod(UseCaseThreeController.class, doSomethingThree)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.tngtech.archunit.core.domain.Formatters.formatMethod;
import static com.tngtech.archunit.core.domain.Formatters.formatNamesOf;
import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME;
import static java.util.regex.Pattern.quote;

Expand Down Expand Up @@ -82,6 +84,10 @@ public static AnnotationDependencyCreator annotatedClass(Class<?> clazz) {
return new AnnotationDependencyCreator(clazz);
}

public static AnnotationParameterDependencyCreator annotatedParameter(Class<?> parameterType) {
return new AnnotationParameterDependencyCreator(parameterType);
}

public static AccessCreator accessFrom(Class<?> clazz) {
return new AccessCreator(clazz);
}
Expand Down Expand Up @@ -317,21 +323,40 @@ public ExpectedDependency inLine(int lineNumber) {
}
}

public static class AnnotationParameterDependencyCreator {
private final Class<?> parameterType;

private AnnotationParameterDependencyCreator(Class<?> parameterType) {
this.parameterType = parameterType;
}

public AnnotationDependencyCreator ofMethod(Class<?> originClass, String methodName, Class<?>... paramTypes) {
return new AnnotationDependencyCreator(originClass, "Parameter <" + parameterType.getName() + "> of method <" +
formatMethod(originClass.getName(), methodName, formatNamesOf(paramTypes)) + ">");
}
}

public static class AnnotationDependencyCreator {
private final Class<?> owner;
private final Class<?> originClass;
private final String originDescription;

AnnotationDependencyCreator(Class<?> owner) {
this.owner = owner;
AnnotationDependencyCreator(Class<?> originClass) {
this(originClass, originClass.getName());
}

AnnotationDependencyCreator(Class<?> originClass, String originDescription) {
this.originClass = originClass;
this.originDescription = originDescription;
}

public ExpectedDependency annotatedWith(Class<?> annotationType) {
String dependencyPattern = getDependencyPattern(owner.getName(), "is annotated with", annotationType.getName(), 0);
return new ExpectedDependency(owner, annotationType, dependencyPattern);
String dependencyPattern = getDependencyPattern(originDescription, "is annotated with", annotationType.getName(), 0);
return new ExpectedDependency(originClass, annotationType, dependencyPattern);
}

public ExpectedDependency withAnnotationParameterType(Class<?> type) {
String dependencyPattern = getDependencyPattern(owner.getName(), "has annotation member of type", type.getName(), 0);
return new ExpectedDependency(owner, type, dependencyPattern);
String dependencyPattern = getDependencyPattern(originDescription, "has annotation member of type", type.getName(), 0);
return new ExpectedDependency(originClass, type, dependencyPattern);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ private static Origin findSuitableOrigin(Object dependencyCause, Object originCa
JavaClass clazz = (JavaClass) originCandidate;
return new Origin(clazz, clazz.getDescription());
}
if (originCandidate instanceof JavaCodeUnit.Parameter) {
JavaCodeUnit.Parameter parameter = (JavaCodeUnit.Parameter) originCandidate;
return new Origin(parameter.getOwner().getOwner(), parameter.getDescription());
}
throw new IllegalStateException("Could not find suitable dependency origin for " + dependencyCause);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.tngtech.archunit.core.domain;

import java.util.Collection;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -197,7 +198,9 @@ private Set<Dependency> annotationDependenciesFromSelf() {
.addAll(annotationDependencies(javaClass))
.addAll(annotationDependencies(javaClass.getFields()))
.addAll(annotationDependencies(javaClass.getMethods()))
.addAll(parameterAnnotationDependencies(javaClass.getMethods()))
.addAll(annotationDependencies(javaClass.getConstructors()))
.addAll(parameterAnnotationDependencies(javaClass.getConstructors()))
.build();
}

Expand Down Expand Up @@ -291,7 +294,15 @@ private static Set<JavaClass> dependenciesOfWildcardType(JavaWildcardType javaTy
return result.build();
}

private <T extends HasDescription & HasAnnotations<?>> Set<Dependency> annotationDependencies(Set<T> annotatedObjects) {
private Set<Dependency> parameterAnnotationDependencies(Set<? extends JavaCodeUnit> codeUnits) {
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
for (JavaCodeUnit codeUnit : codeUnits) {
result.addAll(annotationDependencies(codeUnit.getParameters()));
}
return result.build();
}

private <T extends HasDescription & HasAnnotations<?>> Set<Dependency> annotationDependencies(Collection<T> annotatedObjects) {
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
for (T annotated : annotatedObjects) {
result.addAll(annotationDependencies(annotated));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,27 @@ public void Dependency_from_annotation_on_member(JavaMember annotatedMember) {
.contains(annotatedMember.getDescription() + " is annotated with <" + annotationClass.getName() + ">");
}

@Test
public void Dependency_from_parameter_annotation() {
@SuppressWarnings("unused")
class SomeClass {
void method(@SomeAnnotation(String.class) Object param) {
}
}

JavaCodeUnit.Parameter parameter = getOnlyElement(
new ClassFileImporter().importClass(SomeClass.class)
.getMethod("method", Object.class).getParameters());

Dependency dependency = getOnlyElement(Dependency.tryCreateFromAnnotation(getOnlyElement(parameter.getAnnotations())));

assertThatType(dependency.getOriginClass()).matches(SomeClass.class);
assertThatType(dependency.getTargetClass()).matches(SomeAnnotation.class);
assertThat(dependency.getDescription()).as("description")
.contains("Parameter <" + Object.class.getName() + "> of method <" + SomeClass.class.getName() + "." + "method(" + Object.class.getName() + ")> "
+ "is annotated with <" + SomeAnnotation.class.getName() + ">");
}

@Test
@UseDataProvider("annotated_classes")
public void Dependency_from_class_annotation_member(JavaClass annotatedClass) {
Expand Down
Loading

0 comments on commit 98787a0

Please sign in to comment.