diff --git a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java index 6382e2c781..01ef8a850d 100644 --- a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java +++ b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java @@ -9,6 +9,7 @@ import com.tngtech.archunit.example.layers.controller.two.UseCaseTwoController; import com.tngtech.archunit.example.layers.security.Secured; +@SuppressWarnings("unused") @MyService @ComplexServiceAnnotation( controllerAnnotation = @ComplexControllerAnnotation(simpleControllerAnnotation = @SimpleControllerAnnotation), @@ -17,9 +18,6 @@ serviceType = ServiceType.STANDARD ) public class ServiceViolatingLayerRules { - public static final String illegalAccessToController = "illegalAccessToController"; - public static final String doSomething = "doSomething"; - public static final String dependentMethod = "dependentMethod"; void illegalAccessToController() { System.out.println(UseCaseOneTwoController.someString); @@ -34,7 +32,16 @@ public SomeGuiController dependentMethod(UseCaseTwoController otherController) { return null; } + public SomeGuiController[][] dependentOnComponentTypeMethod(UseCaseTwoController[] otherController) { + return null; + } + @Secured public void properlySecured() { } + + public static final String illegalAccessToController = "illegalAccessToController"; + public static final String doSomething = "doSomething"; + public static final String dependentMethod = "dependentMethod"; + public static final String dependentOnComponentTypeMethod = "dependentOnComponentTypeMethod"; } diff --git a/archunit-example/example-plain/src/test/resources/frozen/a81a2b54-5a18-4145-b544-7a580aba0425 b/archunit-example/example-plain/src/test/resources/frozen/a81a2b54-5a18-4145-b544-7a580aba0425 index 8c6177734c..6420a7657c 100644 --- a/archunit-example/example-plain/src/test/resources/frozen/a81a2b54-5a18-4145-b544-7a580aba0425 +++ b/archunit-example/example-plain/src/test/resources/frozen/a81a2b54-5a18-4145-b544-7a580aba0425 @@ -9,7 +9,6 @@ Field has type in (SomeController.java:0) Field has type in (DaoCallingService.java:0) Field has type <[Lcom.tngtech.archunit.example.layers.service.ServiceType;> in (ServiceType.java:0) -Field depends on component type in (ServiceType.java:0) Field has type in (ServiceViolatingDaoRules.java:0) Method calls method in (SomeMediator.java:15) Method calls constructor ()> in (SomeGuiController.java:7) @@ -21,6 +20,4 @@ Method has return type in (ComplexServiceAnnotation.java:0) Method calls method <[Lcom.tngtech.archunit.example.layers.service.ServiceType;.clone()> in (ServiceType.java:3) Method has return type <[Lcom.tngtech.archunit.example.layers.service.ServiceType;> in (ServiceType.java:0) -Method depends on component type in (ServiceType.java:0) -Method depends on component type in (ServiceType.java:3) Method calls method in (ServiceViolatingDaoRules.java:27) \ No newline at end of file diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java index b72dc12c14..907d20b6bb 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java @@ -155,6 +155,7 @@ import static com.tngtech.archunit.example.layers.core.VeryCentralCore.DO_CORE_STUFF_METHOD_NAME; import static com.tngtech.archunit.example.layers.persistence.layerviolation.DaoCallingService.violateLayerRules; import static com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules.dependentMethod; +import static com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules.dependentOnComponentTypeMethod; import static com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules.illegalAccessToController; import static com.tngtech.archunit.testutils.CyclicErrorMatcher.cycle; import static com.tngtech.archunit.testutils.ExpectedAccess.callFromConstructor; @@ -251,7 +252,7 @@ private static void expectAccessToStandardStreams(ExpectedTestFailures expectFai .inLine(14)) .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .getting().field(System.class, "out") - .inLine(25)); + .inLine(23)); } private static void expectThrownGenericExceptions(ExpectedTestFailures expectFailures) { @@ -686,13 +687,13 @@ Stream LayerDependencyRulesTest() { "should access classes that reside in a package '..controller..'") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, someString) - .inLine(25)) + .inLine(23)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26)) + .inLine(24)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, doSomethingTwo) - .inLine(27)) + .inLine(25)) .ofRule("no classes that reside in a package '..persistence..' should " + "access classes that reside in a package '..service..'") @@ -719,27 +720,32 @@ Stream LayerDependencyRulesTest() { + "only access classes that reside in any package ['..service..', '..persistence..', 'java..']") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, UseCaseOneTwoController.someString) - .inLine(25)) + .inLine(23)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26)) + .inLine(24)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, UseCaseTwoController.doSomethingTwo) - .inLine(27)) + .inLine(25)) .ofRule("no classes that reside in a package '..service..' " + "should depend on classes that reside in a package '..controller..'") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, someString) - .inLine(25).asDependency()) + .inLine(23).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26).asDependency()) + .inLine(24).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, doSomethingTwo) - .inLine(27).asDependency()) + .inLine(25).asDependency()) .by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class)) .by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withReturnType(SomeGuiController[][].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(ComplexControllerAnnotation.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SimpleControllerAnnotation.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SomeEnum.class)) @@ -783,23 +789,23 @@ Stream LayerDependencyRulesTest() { + "only depend on classes that reside in any package ['..service..', '..persistence..', 'java..', 'javax..']") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, someString) - .inLine(25).asDependency()) + .inLine(23).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26).asDependency()) + .inLine(24).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, doSomethingTwo) - .inLine(27).asDependency()) - .by(method(ServiceViolatingLayerRules.class, dependentMethod) - .withParameter(UseCaseTwoController.class)) - .by(method(ServiceViolatingLayerRules.class, dependentMethod) - .withReturnType(SomeGuiController.class)) - .by(field(ServiceHelper.class, "properlySecured") - .withAnnotationType(Secured.class)) - .by(method(ServiceViolatingLayerRules.class, "properlySecured") - .withAnnotationType(Secured.class)) - .by(constructor(ServiceHelper.class) - .withAnnotationType(Secured.class)) + .inLine(25).asDependency()) + .by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withReturnType(SomeGuiController[][].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController.class)) + .by(field(ServiceHelper.class, "properlySecured").withAnnotationType(Secured.class)) + .by(method(ServiceViolatingLayerRules.class, "properlySecured").withAnnotationType(Secured.class)) + .by(constructor(ServiceHelper.class).withAnnotationType(Secured.class)) .by(annotatedClass(ServiceViolatingDaoRules.class).annotatedWith(MyService.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).annotatedWith(MyService.class)) .by(annotatedClass(ServiceImplementation.class).annotatedWith(MyService.class)) @@ -839,17 +845,17 @@ Stream LayeredArchitectureTest() { .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .toConstructor(UseCaseTwoController.class) - .inLine(26) + .inLine(24) .asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .toMethod(UseCaseTwoController.class, "doSomethingTwo") - .inLine(27) + .inLine(25) .asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .getting().field(UseCaseOneTwoController.class, "someString") - .inLine(25) + .inLine(23) .asDependency()) .by(callFromMethod(OtherJpa.class, "testConnection") @@ -858,8 +864,11 @@ Stream LayeredArchitectureTest() { .asDependency()) .by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class)) - .by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod) + .dependingOnComponentType(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod) + .dependingOnComponentType(SomeGuiController.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(ComplexControllerAnnotation.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SimpleControllerAnnotation.class)) diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java index 71a7505b85..96d42baa74 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java @@ -139,6 +139,11 @@ public ExpectedDependency withReturnType(Class returnType) { return new ExpectedDependency(owner, returnType, dependencyPattern); } + public ExpectedDependency dependingOnComponentType(Class componentType) { + String dependencyPattern = getDependencyPattern(getOriginName(), "depends on component type", componentType.getName(), 0); + return new ExpectedDependency(owner, componentType, dependencyPattern); + } + public ExpectedDependency withAnnotationType(Class annotationType) { return new ExpectedDependency(owner, annotationType, getDependencyPattern(getOriginName(), "is annotated with", annotationType.getName(), 0)); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java index 5d6e496689..950f2c55ed 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.core.domain; -import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -27,6 +26,7 @@ import com.tngtech.archunit.base.ChainableFunction; import com.tngtech.archunit.base.DescribedPredicate; 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.HasSourceCodeLocation; @@ -54,32 +54,31 @@ public class Dependency implements HasDescription, Comparable, HasSo private final int lineNumber; private final String description; private final SourceCodeLocation sourceCodeLocation; + private final int hashCode; private Dependency(JavaClass originClass, JavaClass targetClass, int lineNumber, String description) { + checkArgument(!originClass.equals(targetClass) || targetClass.isPrimitive(), + "Tried to create illegal dependency '%s' (%s -> %s), this is likely a bug!", + description, originClass.getSimpleName(), targetClass.getSimpleName()); + this.originClass = originClass; this.targetClass = targetClass; this.lineNumber = lineNumber; this.description = description; this.sourceCodeLocation = SourceCodeLocation.of(originClass, lineNumber); + hashCode = Objects.hash(originClass, targetClass, lineNumber, description); } static Set tryCreateFromAccess(JavaAccess access) { JavaClass originOwner = access.getOriginOwner(); JavaClass targetOwner = access.getTargetOwner(); - if (originOwner.equals(targetOwner) || targetOwner.isPrimitive()) { - return Collections.emptySet(); - } - ImmutableSet.Builder dependencies = ImmutableSet.builder() .addAll(createComponentTypeDependencies(originOwner, access.getOrigin().getDescription(), targetOwner, access.getSourceCodeLocation())); - dependencies.add(new Dependency(originOwner, targetOwner, access.getLineNumber(), access.getDescription())); + dependencies.addAll(tryCreateDependency(originOwner, targetOwner, access.getDescription(), access.getLineNumber()).asSet()); return dependencies.build(); } static Dependency fromInheritance(JavaClass origin, JavaClass targetSuperType) { - checkArgument(!origin.equals(targetSuperType) && !targetSuperType.isPrimitive(), - "It should never be possible to create an inheritance dependency to self or any primitive"); - String originType = origin.isInterface() ? "Interface" : "Class"; String originDescription = originType + " " + bracketFormat(origin.getName()); @@ -91,7 +90,13 @@ static Dependency fromInheritance(JavaClass origin, JavaClass targetSuperType) { String dependencyDescription = originDescription + " " + dependencyType + " " + targetType + " " + targetDescription; String description = dependencyDescription + " in " + origin.getSourceCodeLocation(); - return new Dependency(origin, targetSuperType, 0, description); + Optional result = tryCreateDependency(origin, targetSuperType, description, 0); + + if (!result.isPresent()) { + throw new IllegalStateException(String.format("Tried to create illegal inheritance dependency '%s' (%s -> %s), this is likely a bug!", + description, origin.getSimpleName(), targetSuperType.getSimpleName())); + } + return result.get(); } static Set tryCreateFromField(JavaField field) { @@ -153,33 +158,35 @@ private static Set tryCreateDependency( private static Set tryCreateDependency( JavaClass originClass, String originDescription, String dependencyType, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { - - if (originClass.equals(targetClass) || targetClass.isPrimitive()) { - return Collections.emptySet(); - } - ImmutableSet.Builder dependencies = ImmutableSet.builder() .addAll(createComponentTypeDependencies(originClass, originDescription, targetClass, sourceCodeLocation)); String targetDescription = bracketFormat(targetClass.getName()); String dependencyDescription = originDescription + " " + dependencyType + " " + targetDescription; String description = dependencyDescription + " in " + sourceCodeLocation; - dependencies.add(new Dependency(originClass, targetClass, sourceCodeLocation.getLineNumber(), description)); + dependencies.addAll(tryCreateDependency(originClass, targetClass, description, sourceCodeLocation.getLineNumber()).asSet()); return dependencies.build(); } private static Set createComponentTypeDependencies(JavaClass originClass, String originDescription, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { ImmutableSet.Builder result = ImmutableSet.builder(); - JavaClass componentType = targetClass; - while (componentType.isArray()) { - componentType = componentType.getComponentType(); - String componentTypeTargetDescription = bracketFormat(componentType.getName()); + Optional componentType = targetClass.tryGetComponentType(); + while (componentType.isPresent()) { + String componentTypeTargetDescription = bracketFormat(componentType.get().getName()); String componentTypeDependencyDescription = originDescription + " depends on component type " + componentTypeTargetDescription; String componentTypeDescription = componentTypeDependencyDescription + " in " + sourceCodeLocation; - result.add(new Dependency(originClass, componentType, sourceCodeLocation.getLineNumber(), componentTypeDescription)); + result.addAll(tryCreateDependency(originClass, componentType.get(), componentTypeDescription, sourceCodeLocation.getLineNumber()).asSet()); + componentType = componentType.get().tryGetComponentType(); } return result.build(); } + private static Optional tryCreateDependency(JavaClass originClass, JavaClass targetClass, String description, int lineNumber) { + if (originClass.equals(targetClass) || targetClass.isPrimitive()) { + return Optional.absent(); + } + return Optional.of(new Dependency(originClass, targetClass, lineNumber, description)); + } + private static String bracketFormat(String name) { return "<" + name + ">"; } @@ -217,7 +224,7 @@ public int compareTo(Dependency o) { @Override public int hashCode() { - return Objects.hash(originClass, targetClass, lineNumber, description); + return hashCode; } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 8ddf7e6680..541fd42a62 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -17,17 +17,9 @@ import java.net.URI; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.collect.SetMultimap; import com.tngtech.archunit.Internal; import com.tngtech.archunit.base.Function; import com.tngtech.archunit.base.HasDescription; @@ -175,142 +167,4 @@ public static JavaGenericArrayType createGenericArrayType(JavaType componentType public static JavaWildcardType createWildcardType(JavaWildcardTypeBuilder builder) { return new JavaWildcardType(builder); } - - static class AccessContext { - final SetMultimap fieldAccessesByTarget = HashMultimap.create(); - final SetMultimap methodCallsByTarget = HashMultimap.create(); - final SetMultimap constructorCallsByTarget = HashMultimap.create(); - - private AccessContext() { - } - - void mergeWith(AccessContext other) { - fieldAccessesByTarget.putAll(other.fieldAccessesByTarget); - methodCallsByTarget.putAll(other.methodCallsByTarget); - constructorCallsByTarget.putAll(other.constructorCallsByTarget); - } - - static class Part extends AccessContext { - Part() { - } - - Part(JavaCodeUnit codeUnit) { - for (JavaFieldAccess access : codeUnit.getFieldAccesses()) { - fieldAccessesByTarget.put(access.getTarget().getOwner(), access); - } - for (JavaMethodCall call : codeUnit.getMethodCallsFromSelf()) { - methodCallsByTarget.put(call.getTarget().getOwner(), call); - } - for (JavaConstructorCall call : codeUnit.getConstructorCallsFromSelf()) { - constructorCallsByTarget.put(call.getTarget().getFullName(), call); - } - } - } - - static class TopProcess extends AccessContext { - private final Collection classes; - - TopProcess(Collection classes) { - this.classes = classes; - } - - void finish() { - for (JavaClass clazz : classes) { - for (JavaField field : clazz.getFields()) { - field.registerAccessesToField(getFieldAccessesTo(field)); - } - for (JavaMethod method : clazz.getMethods()) { - method.registerCallsToMethod(getMethodCallsOf(method)); - } - for (final JavaConstructor constructor : clazz.getConstructors()) { - constructor.registerCallsToConstructor(constructorCallsByTarget.get(constructor.getFullName())); - } - } - } - - private Supplier> getFieldAccessesTo(final JavaField field) { - return Suppliers.memoize(new AccessSupplier<>(field.getOwner(), fieldAccessTargetResolvesTo(field))); - } - - private Function> fieldAccessTargetResolvesTo(final JavaField field) { - return new ClassToFieldAccessesToSelf(fieldAccessesByTarget, field); - } - - private Supplier> getMethodCallsOf(final JavaMethod method) { - return Suppliers.memoize(new AccessSupplier<>(method.getOwner(), methodCallTargetResolvesTo(method))); - } - - private Function> methodCallTargetResolvesTo(final JavaMethod method) { - return new ClassToMethodCallsToSelf(methodCallsByTarget, method); - } - - private static class ClassToFieldAccessesToSelf implements Function> { - private final Multimap fieldAccessesByTarget; - private final JavaField field; - - ClassToFieldAccessesToSelf(SetMultimap fieldAccessesByTarget, JavaField field) { - this.fieldAccessesByTarget = fieldAccessesByTarget; - this.field = field; - } - - @Override - public Set apply(JavaClass input) { - Set result = new HashSet<>(); - for (JavaFieldAccess access : fieldAccessesByTarget.get(input)) { - if (access.getTarget().resolveField().asSet().contains(field)) { - result.add(access); - } - } - return result; - } - } - - private static class ClassToMethodCallsToSelf implements Function> { - private final Multimap methodCallsByTarget; - private final JavaMethod method; - - ClassToMethodCallsToSelf(SetMultimap methodCallsByTarget, JavaMethod method) { - this.methodCallsByTarget = methodCallsByTarget; - this.method = method; - } - - @Override - public Set apply(JavaClass input) { - Set result = new HashSet<>(); - for (JavaMethodCall call : methodCallsByTarget.get(input)) { - if (call.getTarget().resolve().contains(method)) { - result.add(call); - } - } - return result; - } - } - - private static class AccessSupplier> implements Supplier> { - private final JavaClass owner; - private final Function> mapToAccesses; - - AccessSupplier(JavaClass owner, Function> mapToAccesses) { - this.owner = owner; - this.mapToAccesses = mapToAccesses; - } - - @Override - public Set get() { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (final JavaClass javaClass : getPossibleTargetClassesForAccess()) { - result.addAll(mapToAccesses.apply(javaClass)); - } - return result.build(); - } - - private Set getPossibleTargetClassesForAccess() { - return ImmutableSet.builder() - .add(owner) - .addAll(owner.getAllSubClasses()) - .build(); - } - } - } - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java index 307351e5da..ff179b9c62 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java @@ -44,29 +44,11 @@ public interface ImportContext { Optional createEnclosingClass(JavaClass owner); - Set getFieldAccessesFor(JavaCodeUnit codeUnit); + Set createFieldAccessesFor(JavaCodeUnit codeUnit); - Set getMethodCallsFor(JavaCodeUnit codeUnit); + Set createMethodCallsFor(JavaCodeUnit codeUnit); - Set getConstructorCallsFor(JavaCodeUnit codeUnit); - - Set getFieldsOfType(JavaClass javaClass); - - Set getMethodsWithParameterOfType(JavaClass javaClass); - - Set getMethodsWithReturnType(JavaClass javaClass); - - Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass); - - Set getConstructorsWithParameterOfType(JavaClass javaClass); - - Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass); - - Set> getAnnotationsOfType(JavaClass javaClass); - - Set> getAnnotationsWithParameterOfType(JavaClass javaClass); - - Set getInstanceofChecksOfType(JavaClass javaClass); + Set createConstructorCallsFor(JavaCodeUnit codeUnit); JavaClass resolveClass(String fullyQualifiedClassName); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 1b60d4d122..9a6fe851d2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -36,7 +36,6 @@ import com.tngtech.archunit.base.PackageMatcher; import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.ResolvesTypesViaReflection; -import com.tngtech.archunit.core.domain.DomainObjectCreationContext.AccessContext; import com.tngtech.archunit.core.domain.properties.CanBeAnnotated; import com.tngtech.archunit.core.domain.properties.HasAnnotations; import com.tngtech.archunit.core.domain.properties.HasModifiers; @@ -82,8 +81,54 @@ public class JavaClass implements JavaType, HasName.AndFullName, HasAnnotations< private Set constructors = emptySet(); private Optional staticInitializer = Optional.absent(); private Optional superClass = Optional.absent(); + private final Supplier> allSuperClasses = Suppliers.memoize(new Supplier>() { + @Override + public List get() { + ImmutableList.Builder result = ImmutableList.builder(); + JavaClass current = JavaClass.this; + while (current.getSuperClass().isPresent()) { + current = current.getSuperClass().get(); + result.add(current); + } + return result.build(); + } + }); private final Set interfaces = new HashSet<>(); + private final Supplier> allInterfaces = Suppliers.memoize(new Supplier>() { + @Override + public Set get() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaClass i : interfaces) { + result.add(i); + result.addAll(i.getAllInterfaces()); + } + if (superClass.isPresent()) { + result.addAll(superClass.get().getAllInterfaces()); + } + return result.build(); + } + }); + private final Supplier> classHierarchy = Suppliers.memoize(new Supplier>() { + @Override + public List get() { + ImmutableList.Builder result = ImmutableList.builder(); + result.add(JavaClass.this); + result.addAll(getAllSuperClasses()); + return result.build(); + } + }); private final Set subClasses = new HashSet<>(); + private final Supplier> allSubClasses = Suppliers.memoize(new Supplier>() { + @Override + public Set get() { + Set result = new HashSet<>(); + for (JavaClass subClass : subClasses) { + result.add(subClass); + result.addAll(subClass.getAllSubClasses()); + } + return result; + } + }); private Optional enclosingClass = Optional.absent(); private Optional componentType = Optional.absent(); private Map> annotations = emptyMap(); @@ -101,6 +146,7 @@ public Set get() { } }); private JavaClassDependencies javaClassDependencies; + private ReverseDependencies reverseDependencies; JavaClass(JavaClassBuilder builder) { source = checkNotNull(builder.getSource()); @@ -618,10 +664,7 @@ public Optional getSuperClass() { */ @PublicAPI(usage = ACCESS) public List getClassHierarchy() { - ImmutableList.Builder result = ImmutableList.builder(); - result.add(this); - result.addAll(getAllSuperClasses()); - return result.build(); + return classHierarchy.get(); } /** @@ -630,13 +673,7 @@ public List getClassHierarchy() { */ @PublicAPI(usage = ACCESS) public List getAllSuperClasses() { - ImmutableList.Builder result = ImmutableList.builder(); - JavaClass current = this; - while (current.getSuperClass().isPresent()) { - current = current.getSuperClass().get(); - result.add(current); - } - return result.build(); + return allSuperClasses.get(); } @PublicAPI(usage = ACCESS) @@ -651,15 +688,7 @@ public Set getInterfaces() { @PublicAPI(usage = ACCESS) public Set getAllInterfaces() { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (JavaClass i : interfaces) { - result.add(i); - result.addAll(i.getAllInterfaces()); - } - if (superClass.isPresent()) { - result.addAll(superClass.get().getAllInterfaces()); - } - return result.build(); + return allInterfaces.get(); } /** @@ -686,12 +715,7 @@ public Optional getEnclosingClass() { @PublicAPI(usage = ACCESS) public Set getAllSubClasses() { - Set result = new HashSet<>(); - for (JavaClass subClass : subClasses) { - result.add(subClass); - result.addAll(subClass.getAllSubClasses()); - } - return result; + return allSubClasses.get(); } @PublicAPI(usage = ACCESS) @@ -1014,7 +1038,7 @@ public Set getDirectDependenciesFromSelf() { */ @PublicAPI(usage = ACCESS) public Set getDirectDependenciesToSelf() { - return javaClassDependencies.getDirectDependenciesToClass(); + return reverseDependencies.getDirectDependenciesTo(this); } @PublicAPI(usage = ACCESS) @@ -1058,7 +1082,7 @@ public Set> getAccessesToSelf() { */ @PublicAPI(usage = ACCESS) public Set getFieldsWithTypeOfSelf() { - return javaClassDependencies.getFieldsWithTypeOfClass(); + return reverseDependencies.getFieldsWithTypeOf(this); } /** @@ -1066,7 +1090,7 @@ public Set getFieldsWithTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set getMethodsWithParameterTypeOfSelf() { - return javaClassDependencies.getMethodsWithParameterTypeOfClass(); + return reverseDependencies.getMethodsWithParameterTypeOf(this); } /** @@ -1074,7 +1098,7 @@ public Set getMethodsWithParameterTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set getMethodsWithReturnTypeOfSelf() { - return javaClassDependencies.getMethodsWithReturnTypeOfClass(); + return reverseDependencies.getMethodsWithReturnTypeOf(this); } /** @@ -1082,7 +1106,7 @@ public Set getMethodsWithReturnTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set> getMethodThrowsDeclarationsWithTypeOfSelf() { - return javaClassDependencies.getMethodThrowsDeclarationsWithTypeOfClass(); + return reverseDependencies.getMethodThrowsDeclarationsWithTypeOf(this); } /** @@ -1090,7 +1114,7 @@ public Set> getMethodThrowsDeclarationsWithTypeOfS */ @PublicAPI(usage = ACCESS) public Set getConstructorsWithParameterTypeOfSelf() { - return javaClassDependencies.getConstructorsWithParameterTypeOfClass(); + return reverseDependencies.getConstructorsWithParameterTypeOf(this); } /** @@ -1098,7 +1122,7 @@ public Set getConstructorsWithParameterTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set> getConstructorsWithThrowsDeclarationTypeOfSelf() { - return javaClassDependencies.getConstructorsWithThrowsDeclarationTypeOfClass(); + return reverseDependencies.getConstructorsWithThrowsDeclarationTypeOf(this); } /** @@ -1106,7 +1130,23 @@ public Set> getConstructorsWithThrowsDeclarat */ @PublicAPI(usage = ACCESS) public Set> getAnnotationsWithTypeOfSelf() { - return javaClassDependencies.getAnnotationsWithTypeOfClass(); + return reverseDependencies.getAnnotationsWithTypeOf(this); + } + + /** + * @return All imported {@link JavaAnnotation JavaAnnotations} that have a parameter with type of this class. + */ + @PublicAPI(usage = ACCESS) + public Set> getAnnotationsWithParameterTypeOfSelf() { + return reverseDependencies.getAnnotationsWithParameterTypeOf(this); + } + + /** + * @return All imported {@link InstanceofCheck InstanceofChecks} that check if another class is an instance of this class. + */ + @PublicAPI(usage = ACCESS) + public Set getInstanceofChecksWithTypeOfSelf() { + return reverseDependencies.getInstanceofChecksWithTypeOf(this); } /** @@ -1255,10 +1295,13 @@ void completeAnnotations(final ImportContext context) { } } - CompletionProcess completeFrom(ImportContext context) { + JavaClassDependencies completeFrom(ImportContext context) { completeComponentType(context); - javaClassDependencies = new JavaClassDependencies(this, context); - return new CompletionProcess(); + for (JavaCodeUnit codeUnit : codeUnits) { + codeUnit.completeFrom(context); + } + javaClassDependencies = new JavaClassDependencies(this); + return javaClassDependencies; } private void completeComponentType(ImportContext context) { @@ -1270,6 +1313,13 @@ private void completeComponentType(ImportContext context) { } } + void setReverseDependencies(ReverseDependencies reverseDependencies) { + this.reverseDependencies = reverseDependencies; + for (JavaMember member : members) { + member.setReverseDependencies(reverseDependencies); + } + } + @Override public String toString() { return "JavaClass{name='" + descriptor.getFullyQualifiedClassName() + "'}"; @@ -1790,16 +1840,6 @@ public boolean apply(JavaClass input) { } } - class CompletionProcess { - AccessContext.Part completeCodeUnitsFrom(ImportContext context) { - AccessContext.Part part = new AccessContext.Part(); - for (JavaCodeUnit codeUnit : codeUnits) { - part.mergeWith(codeUnit.completeFrom(context)); - } - return part; - } - } - @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") private class ReflectClassSupplier implements Supplier> { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java index 9819d871d7..902dd31f6a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.core.domain; -import java.util.HashSet; import java.util.Set; import com.google.common.base.Supplier; @@ -26,38 +25,17 @@ import com.tngtech.archunit.core.domain.properties.HasAnnotations; import static com.google.common.base.Suppliers.memoize; -import static com.google.common.collect.Sets.union; class JavaClassDependencies { private final JavaClass javaClass; - private final Set fieldsWithTypeOfClass; - private final Set methodsWithParameterTypeOfClass; - private final Set methodsWithReturnTypeOfClass; - private final Set> methodsWithThrowsDeclarationTypeOfClass; - private final Set constructorsWithParameterTypeOfClass; - private final Set> constructorsWithThrowsDeclarationTypeOfClass; - private final Set> annotationsWithTypeOfClass; - private final Set> annotationsWithParameterTypeOfClass; - private final Set instanceofChecksWithTypeOfClass; private final Supplier> directDependenciesFromClass; - private final Supplier> directDependenciesToClass; - JavaClassDependencies(JavaClass javaClass, ImportContext context) { + JavaClassDependencies(JavaClass javaClass) { this.javaClass = javaClass; - this.fieldsWithTypeOfClass = context.getFieldsOfType(javaClass); - this.methodsWithParameterTypeOfClass = context.getMethodsWithParameterOfType(javaClass); - this.methodsWithReturnTypeOfClass = context.getMethodsWithReturnType(javaClass); - this.methodsWithThrowsDeclarationTypeOfClass = context.getMethodThrowsDeclarationsOfType(javaClass); - this.constructorsWithParameterTypeOfClass = context.getConstructorsWithParameterOfType(javaClass); - this.constructorsWithThrowsDeclarationTypeOfClass = context.getConstructorThrowsDeclarationsOfType(javaClass); - this.annotationsWithTypeOfClass = context.getAnnotationsOfType(javaClass); - this.annotationsWithParameterTypeOfClass = context.getAnnotationsWithParameterOfType(javaClass); - this.instanceofChecksWithTypeOfClass = context.getInstanceofChecksOfType(javaClass); - this.directDependenciesFromClass = getDirectDependenciesFromClassSupplier(); - this.directDependenciesToClass = getDirectDependenciesToClassSupplier(); + this.directDependenciesFromClass = createDirectDependenciesFromClassSupplier(); } - private Supplier> getDirectDependenciesFromClassSupplier() { + private Supplier> createDirectDependenciesFromClassSupplier() { return memoize(new Supplier>() { @Override public Set get() { @@ -76,69 +54,10 @@ public Set get() { }); } - private Supplier> getDirectDependenciesToClassSupplier() { - return memoize(new Supplier>() { - @Override - public Set get() { - ImmutableSet.Builder result = ImmutableSet.builder(); - result.addAll(dependenciesFromAccesses(javaClass.getAccessesToSelf())); - result.addAll(inheritanceDependenciesToSelf()); - result.addAll(fieldDependenciesToSelf()); - result.addAll(returnTypeDependenciesToSelf()); - result.addAll(methodParameterDependenciesToSelf()); - result.addAll(throwsDeclarationDependenciesToSelf()); - result.addAll(constructorParameterDependenciesToSelf()); - result.addAll(annotationDependenciesToSelf()); - result.addAll(instanceofCheckDependenciesToSelf()); - return result.build(); - } - }); - } - Set getDirectDependenciesFromClass() { return directDependenciesFromClass.get(); } - Set getDirectDependenciesToClass() { - return directDependenciesToClass.get(); - } - - Set getFieldsWithTypeOfClass() { - return fieldsWithTypeOfClass; - } - - Set getMethodsWithParameterTypeOfClass() { - return methodsWithParameterTypeOfClass; - } - - Set getMethodsWithReturnTypeOfClass() { - return methodsWithReturnTypeOfClass; - } - - Set> getMethodThrowsDeclarationsWithTypeOfClass() { - return methodsWithThrowsDeclarationTypeOfClass; - } - - Set getConstructorsWithParameterTypeOfClass() { - return constructorsWithParameterTypeOfClass; - } - - Set> getConstructorsWithThrowsDeclarationTypeOfClass() { - return constructorsWithThrowsDeclarationTypeOfClass; - } - - private Set> getThrowsDeclarationsWithTypeOfClass() { - return union(methodsWithThrowsDeclarationTypeOfClass, constructorsWithThrowsDeclarationTypeOfClass); - } - - Set> getAnnotationsWithTypeOfClass() { - return annotationsWithTypeOfClass; - } - - Set getInstanceofChecksWithTypeOfClass() { - return instanceofChecksWithTypeOfClass; - } - private Set dependenciesFromAccesses(Set> accesses) { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaAccess access : accesses) { @@ -252,71 +171,4 @@ public void visitAnnotation(String propertyName, JavaAnnotation memberAnnotat } return result.build(); } - - private Set inheritanceDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaClass subClass : javaClass.getSubClasses()) { - result.add(Dependency.fromInheritance(subClass, javaClass)); - } - return result; - } - - private Set fieldDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaField field : javaClass.getFieldsWithTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromField(field)); - } - return result; - } - - private Set returnTypeDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaMethod method : javaClass.getMethodsWithReturnTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromReturnType(method)); - } - return result; - } - - private Set methodParameterDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaMethod method : javaClass.getMethodsWithParameterTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromParameter(method, javaClass)); - } - return result; - } - - private Set throwsDeclarationDependenciesToSelf() { - Set result = new HashSet<>(); - for (ThrowsDeclaration throwsDeclaration : getThrowsDeclarationsWithTypeOfClass()) { - result.addAll(Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration)); - } - return result; - } - - private Set constructorParameterDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaConstructor constructor : javaClass.getConstructorsWithParameterTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromParameter(constructor, javaClass)); - } - return result; - } - - private Iterable annotationDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaAnnotation annotation : annotationsWithTypeOfClass) { - result.addAll(Dependency.tryCreateFromAnnotation(annotation)); - } - for (JavaAnnotation annotation : annotationsWithParameterTypeOfClass) { - result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, javaClass)); - } - return result; - } - - private Set instanceofCheckDependenciesToSelf() { - Set result = new HashSet<>(); - for (InstanceofCheck instanceofCheck : getInstanceofChecksWithTypeOfClass()) { - result.addAll(Dependency.tryCreateFromInstanceofCheck(instanceofCheck)); - } - return result; - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java index 4d72540819..b763d8f450 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java @@ -17,10 +17,8 @@ import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Objects; -import java.util.Set; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -29,7 +27,6 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.ForwardingCollection; import com.tngtech.archunit.base.Guava; -import com.tngtech.archunit.core.domain.DomainObjectCreationContext.AccessContext; import com.tngtech.archunit.core.domain.properties.CanOverrideDescription; import static com.google.common.base.Preconditions.checkArgument; @@ -196,13 +193,14 @@ private static JavaPackage getRoot(JavaPackage javaPackage) { static JavaClasses of( Map selectedClasses, Collection allClasses, ImportContext importContext) { - CompletionProcess completionProcess = new CompletionProcess(allClasses, importContext); + ReverseDependencies.Creation reverseDependenciesCreation = new ReverseDependencies.Creation(); JavaPackage defaultPackage = JavaPackage.from(allClasses); for (JavaClass clazz : allClasses) { setPackage(clazz, defaultPackage); - completionProcess.completeClass(clazz); + JavaClassDependencies classDependencies = clazz.completeFrom(importContext); + reverseDependenciesCreation.registerDependenciesOf(clazz, classDependencies); } - completionProcess.finish(); + reverseDependenciesCreation.finish(allClasses); return new JavaClasses(defaultPackage, selectedClasses); } @@ -212,27 +210,4 @@ private static void setPackage(JavaClass clazz, JavaPackage defaultPackage) { : defaultPackage.getPackage(clazz.getPackageName()); clazz.setPackage(javaPackage); } - - private static class CompletionProcess { - private final Set classCompletionProcesses = new HashSet<>(); - private final Collection classes; - private final ImportContext context; - - CompletionProcess(Collection classes, ImportContext context) { - this.classes = classes; - this.context = context; - } - - void completeClass(JavaClass clazz) { - classCompletionProcesses.add(clazz.completeFrom(context)); - } - - void finish() { - AccessContext.TopProcess accessCompletionProcess = new AccessContext.TopProcess(classes); - for (JavaClass.CompletionProcess process : classCompletionProcesses) { - accessCompletionProcess.mergeWith(process.completeCodeUnitsFrom(context)); - } - accessCompletionProcess.finish(); - } - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java index 1479260c4c..a8082c65d4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java @@ -28,7 +28,6 @@ import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.ResolvesTypesViaReflection; -import com.tngtech.archunit.core.domain.DomainObjectCreationContext.AccessContext; import com.tngtech.archunit.core.domain.properties.HasParameterTypes; import com.tngtech.archunit.core.domain.properties.HasReturnType; import com.tngtech.archunit.core.domain.properties.HasThrowsClause; @@ -157,12 +156,10 @@ public Optional> tryGetAnnotati return (Optional>) super.tryGetAnnotationOfType(typeName); } - AccessContext.Part completeFrom(ImportContext context) { - fieldAccesses = context.getFieldAccessesFor(this); - methodCalls = context.getMethodCallsFor(this); - constructorCalls = context.getConstructorCallsFor(this); - - return new AccessContext.Part(this); + void completeFrom(ImportContext context) { + fieldAccesses = context.createFieldAccessesFor(this); + methodCalls = context.createMethodCallsFor(this); + constructorCalls = context.createConstructorCallsFor(this); } @ResolvesTypesViaReflection diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java index 773fc09aa8..a64b41d153 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java @@ -16,13 +16,10 @@ package com.tngtech.archunit.core.domain; import java.lang.reflect.Constructor; -import java.util.Collection; -import java.util.Collections; import java.util.Set; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ArchUnitException.InconsistentClassPathException; import com.tngtech.archunit.base.Optional; @@ -36,7 +33,6 @@ public final class JavaConstructor extends JavaCodeUnit { private final Supplier> constructorSupplier; private final ThrowsClause throwsClause; - private Set callsToSelf = Collections.emptySet(); @PublicAPI(usage = ACCESS) public static final String CONSTRUCTOR_NAME = ""; @@ -67,7 +63,7 @@ public Set getCallsOfSelf() { @Override @PublicAPI(usage = ACCESS) public Set getAccessesToSelf() { - return callsToSelf; + return getReverseDependencies().getCallsTo(this); } @Override @@ -102,10 +98,6 @@ public String getDescription() { return "Constructor <" + getFullName() + ">"; } - void registerCallsToConstructor(Collection calls) { - this.callsToSelf = ImmutableSet.copyOf(calls); - } - @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") private class ReflectConstructorSupplier implements Supplier> { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java index 634ea73e49..ee4e667f59 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.domain; import java.lang.reflect.Field; -import java.util.Collections; import java.util.Set; import com.google.common.base.Supplier; @@ -29,13 +28,11 @@ import com.tngtech.archunit.core.domain.properties.HasType; import com.tngtech.archunit.core.importer.DomainBuilders; -import static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; public class JavaField extends JavaMember implements HasType { private final JavaClass type; private final Supplier fieldSupplier; - private Supplier> accessesToSelf = Suppliers.ofInstance(Collections.emptySet()); JavaField(DomainBuilders.JavaFieldBuilder builder) { super(builder); @@ -71,7 +68,7 @@ public JavaClass getRawType() { @Override @PublicAPI(usage = ACCESS) public Set getAccessesToSelf() { - return accessesToSelf.get(); + return getReverseDependencies().getAccessesTo(this); } @Override @@ -106,10 +103,6 @@ public String getDescription() { return "Field <" + getFullName() + ">"; } - void registerAccessesToField(Supplier> accesses) { - this.accessesToSelf = checkNotNull(accesses); - } - @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") private class ReflectFieldSupplier implements Supplier { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java index 39337e6b0d..0b50bcd50f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java @@ -52,6 +52,7 @@ public abstract class JavaMember implements private final JavaClass owner; private final SourceCodeLocation sourceCodeLocation; private final Set modifiers; + private ReverseDependencies reverseDependencies = ReverseDependencies.EMPTY; JavaMember(JavaMemberBuilder builder) { this.name = checkNotNull(builder.getName()); @@ -183,6 +184,14 @@ void completeAnnotations(ImportContext context) { annotations = context.createAnnotations(this); } + protected ReverseDependencies getReverseDependencies() { + return reverseDependencies; + } + + void setReverseDependencies(ReverseDependencies reverseDependencies) { + this.reverseDependencies = reverseDependencies; + } + @Override public String toString() { return getClass().getSimpleName() + '{' + getFullName() + '}'; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java index 65a30e5268..a43402fce4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.domain; import java.lang.reflect.Method; -import java.util.Collections; import java.util.Set; import com.google.common.base.Supplier; @@ -29,14 +28,12 @@ import com.tngtech.archunit.core.ResolvesTypesViaReflection; import com.tngtech.archunit.core.importer.DomainBuilders; -import static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.core.domain.Formatters.formatMethod; public class JavaMethod extends JavaCodeUnit { private final Supplier methodSupplier; private final ThrowsClause throwsClause; - private Supplier> callsToSelf = Suppliers.ofInstance(Collections.emptySet()); private final Optional annotationDefaultValue; JavaMethod(DomainBuilders.JavaMethodBuilder builder, Function> createAnnotationDefaultValue) { @@ -72,7 +69,7 @@ public Set getCallsOfSelf() { @Override @PublicAPI(usage = ACCESS) public Set getAccessesToSelf() { - return callsToSelf.get(); + return getReverseDependencies().getCallsTo(this); } @Override @@ -107,10 +104,6 @@ public String getDescription() { return "Method <" + getFullName() + ">"; } - void registerCallsToMethod(Supplier> calls) { - this.callsToSelf = checkNotNull(calls); - } - @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") private class ReflectMethodSupplier implements Supplier { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java new file mode 100644 index 0000000000..3d93bb5f5c --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java @@ -0,0 +1,294 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; + +final class ReverseDependencies { + + private final LoadingCache> accessToFieldCache; + private final LoadingCache> callToMethodCache; + private final LoadingCache> callToConstructorCache; + private final SetMultimap fieldTypeDependencies; + private final SetMultimap methodParameterTypeDependencies; + private final SetMultimap methodReturnTypeDependencies; + private final SetMultimap> methodsThrowsDeclarationDependencies; + private final SetMultimap constructorParameterTypeDependencies; + private final SetMultimap> constructorThrowsDeclarationDependencies; + private final SetMultimap> annotationTypeDependencies; + private final SetMultimap> annotationParameterTypeDependencies; + private final SetMultimap instanceofCheckDependencies; + private final Supplier> directDependenciesToClass; + + private ReverseDependencies(ReverseDependencies.Creation creation) { + accessToFieldCache = CacheBuilder.newBuilder().build(new ResolvingAccessLoader<>(creation.fieldAccessDependencies.build())); + callToMethodCache = CacheBuilder.newBuilder().build(new ResolvingAccessLoader<>(creation.methodCallDependencies.build())); + callToConstructorCache = CacheBuilder.newBuilder().build(new ConstructorCallLoader(creation.constructorCallDependencies.build())); + this.fieldTypeDependencies = creation.fieldTypeDependencies.build(); + this.methodParameterTypeDependencies = creation.methodParameterTypeDependencies.build(); + this.methodReturnTypeDependencies = creation.methodReturnTypeDependencies.build(); + this.methodsThrowsDeclarationDependencies = creation.methodsThrowsDeclarationDependencies.build(); + this.constructorParameterTypeDependencies = creation.constructorParameterTypeDependencies.build(); + this.constructorThrowsDeclarationDependencies = creation.constructorThrowsDeclarationDependencies.build(); + this.annotationTypeDependencies = creation.annotationTypeDependencies.build(); + this.annotationParameterTypeDependencies = creation.annotationParameterTypeDependencies.build(); + this.instanceofCheckDependencies = creation.instanceofCheckDependencies.build(); + this.directDependenciesToClass = createDirectDependenciesToClassSupplier(creation.allDependencies); + } + + private static Supplier> createDirectDependenciesToClassSupplier(final List allDependencies) { + return Suppliers.memoize(new Supplier>() { + @Override + public SetMultimap get() { + ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); + for (JavaClassDependencies dependencies : allDependencies) { + for (Dependency dependency : dependencies.getDirectDependenciesFromClass()) { + result.put(dependency.getTargetClass(), dependency); + } + } + return result.build(); + } + }); + } + + Set getAccessesTo(JavaField field) { + return accessToFieldCache.getUnchecked(field); + } + + Set getCallsTo(JavaMethod method) { + return callToMethodCache.getUnchecked(method); + } + + Set getCallsTo(JavaConstructor constructor) { + return callToConstructorCache.getUnchecked(constructor); + } + + Set getFieldsWithTypeOf(JavaClass clazz) { + return fieldTypeDependencies.get(clazz); + } + + Set getMethodsWithParameterTypeOf(JavaClass clazz) { + return methodParameterTypeDependencies.get(clazz); + } + + Set getMethodsWithReturnTypeOf(JavaClass clazz) { + return methodReturnTypeDependencies.get(clazz); + } + + Set> getMethodThrowsDeclarationsWithTypeOf(JavaClass clazz) { + return methodsThrowsDeclarationDependencies.get(clazz); + } + + Set getConstructorsWithParameterTypeOf(JavaClass clazz) { + return constructorParameterTypeDependencies.get(clazz); + } + + Set> getConstructorsWithThrowsDeclarationTypeOf(JavaClass clazz) { + return constructorThrowsDeclarationDependencies.get(clazz); + } + + Set> getAnnotationsWithTypeOf(JavaClass clazz) { + return annotationTypeDependencies.get(clazz); + } + + Set> getAnnotationsWithParameterTypeOf(JavaClass clazz) { + return annotationParameterTypeDependencies.get(clazz); + } + + Set getInstanceofChecksWithTypeOf(JavaClass clazz) { + return instanceofCheckDependencies.get(clazz); + } + + Set getDirectDependenciesTo(JavaClass clazz) { + return directDependenciesToClass.get().get(clazz); + } + + static final ReverseDependencies EMPTY = new ReverseDependencies(new Creation()); + + static class Creation { + private final ImmutableSetMultimap.Builder fieldAccessDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder methodCallDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder constructorCallDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder fieldTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder methodParameterTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder methodReturnTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> methodsThrowsDeclarationDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder constructorParameterTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> constructorThrowsDeclarationDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> annotationTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> annotationParameterTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder instanceofCheckDependencies = ImmutableSetMultimap.builder(); + private final List allDependencies = new ArrayList<>(); + + public void registerDependenciesOf(JavaClass clazz, JavaClassDependencies classDependencies) { + registerAccesses(clazz); + registerFields(clazz); + registerMethods(clazz); + registerConstructors(clazz); + registerAnnotations(clazz); + registerStaticInitializer(clazz); + allDependencies.add(classDependencies); + } + + private void registerAccesses(JavaClass clazz) { + for (JavaFieldAccess access : clazz.getFieldAccessesFromSelf()) { + fieldAccessDependencies.put(access.getTargetOwner(), access); + } + for (JavaMethodCall call : clazz.getMethodCallsFromSelf()) { + methodCallDependencies.put(call.getTargetOwner(), call); + } + for (JavaConstructorCall call : clazz.getConstructorCallsFromSelf()) { + constructorCallDependencies.put(call.getTarget().getFullName(), call); + } + } + + private void registerFields(JavaClass clazz) { + for (JavaField field : clazz.getFields()) { + fieldTypeDependencies.put(field.getRawType(), field); + } + } + + private void registerMethods(JavaClass clazz) { + for (JavaMethod method : clazz.getMethods()) { + for (JavaClass parameter : method.getRawParameterTypes()) { + methodParameterTypeDependencies.put(parameter, method); + } + methodReturnTypeDependencies.put(method.getRawReturnType(), method); + for (ThrowsDeclaration throwsDeclaration : method.getThrowsClause()) { + methodsThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); + } + for (InstanceofCheck instanceofCheck : method.getInstanceofChecks()) { + instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); + } + } + } + + private void registerConstructors(JavaClass clazz) { + for (JavaConstructor constructor : clazz.getConstructors()) { + for (JavaClass parameter : constructor.getRawParameterTypes()) { + constructorParameterTypeDependencies.put(parameter, constructor); + } + for (ThrowsDeclaration throwsDeclaration : constructor.getThrowsClause()) { + constructorThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); + } + for (InstanceofCheck instanceofCheck : constructor.getInstanceofChecks()) { + instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); + } + } + } + + private void registerAnnotations(JavaClass clazz) { + for (final JavaAnnotation annotation : findAnnotations(clazz)) { + annotationTypeDependencies.put(annotation.getRawType(), annotation); + annotation.accept(new JavaAnnotation.DefaultParameterVisitor() { + @Override + public void visitClass(String propertyName, JavaClass javaClass) { + annotationParameterTypeDependencies.put(javaClass, annotation); + } + + @Override + public void visitEnumConstant(String propertyName, JavaEnumConstant enumConstant) { + annotationParameterTypeDependencies.put(enumConstant.getDeclaringClass(), annotation); + } + + @Override + public void visitAnnotation(String propertyName, JavaAnnotation memberAnnotation) { + annotationParameterTypeDependencies.put(memberAnnotation.getRawType(), annotation); + memberAnnotation.accept(this); + } + }); + } + } + + private Set> findAnnotations(JavaClass clazz) { + Set> result = Sets.>newHashSet(clazz.getAnnotations()); + for (JavaMember member : clazz.getMembers()) { + result.addAll(member.getAnnotations()); + } + return result; + } + + private void registerStaticInitializer(JavaClass clazz) { + if (clazz.getStaticInitializer().isPresent()) { + for (InstanceofCheck instanceofCheck : clazz.getStaticInitializer().get().getInstanceofChecks()) { + instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); + } + } + } + + void finish(Iterable classes) { + ReverseDependencies reverseDependencies = new ReverseDependencies(this); + for (JavaClass clazz : classes) { + clazz.setReverseDependencies(reverseDependencies); + } + } + } + + private static class ResolvingAccessLoader> extends CacheLoader> { + private final SetMultimap accessesToSelf; + + private ResolvingAccessLoader(SetMultimap accessesToSelf) { + this.accessesToSelf = accessesToSelf; + } + + @Override + public Set load(MEMBER member) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (final JavaClass javaClass : getPossibleTargetClassesForAccess(member.getOwner())) { + for (ACCESS access : this.accessesToSelf.get(javaClass)) { + if (access.getTarget().resolve().contains(member)) { + result.add(access); + } + } + } + return result.build(); + } + + private Set getPossibleTargetClassesForAccess(JavaClass owner) { + return ImmutableSet.builder() + .add(owner) + .addAll(owner.getAllSubClasses()) + .build(); + } + } + + private static class ConstructorCallLoader extends CacheLoader> { + private final SetMultimap accessesToSelf; + + private ConstructorCallLoader(SetMultimap accessesToSelf) { + this.accessesToSelf = accessesToSelf; + } + + @Override + public Set load(JavaConstructor member) { + ImmutableSet.Builder result = ImmutableSet.builder(); + result.addAll(accessesToSelf.get(member.getFullName())); + return result.build(); + } + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java index a87c570743..2436fcd7bd 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java @@ -17,11 +17,16 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.Internal; import com.tngtech.archunit.base.Optional; @@ -36,10 +41,8 @@ import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; +import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.properties.HasDescriptor; -import com.tngtech.archunit.core.domain.properties.HasName; -import com.tngtech.archunit.core.domain.properties.HasOwner; import com.tngtech.archunit.core.importer.DomainBuilders.ConstructorCallTargetBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.FieldAccessTargetBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder; @@ -48,6 +51,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList; +import static java.util.Collections.singletonList; interface AccessRecord { JavaCodeUnit getCaller(); @@ -140,7 +144,12 @@ private static class ConstructorTargetSupplier implements Supplier get() { - return uniqueTargetIn(tryFindMatchingTargets(targetOwner.getAllConstructors(), target)); + for (JavaConstructor constructor : targetOwner.getConstructors()) { + if (constructor.getDescriptor().equals(target.desc)) { + return Optional.of(constructor); + } + } + return Optional.absent(); } } } @@ -193,7 +202,7 @@ private static class MethodTargetSupplier implements Supplier> { @Override public Set get() { - return tryFindMatchingTargets(allMethods, target); + return tryFindMatchingTargets(allMethods, target, METHOD_SIGNATURE_PREDICATE); } } } @@ -249,7 +258,7 @@ private static class FieldTargetSupplier implements Supplier @Override public Optional get() { - return uniqueTargetIn(tryFindMatchingTargets(allFields, target)); + return uniqueTargetIn(tryFindMatchingTargets(allFields, target, FIELD_SIGNATURE_PREDICATE)); } } } @@ -273,17 +282,59 @@ private static JavaCodeUnit getCaller(CodeUnit caller, ImportedClasses classes) " that matches supposed caller " + caller); } - private static > Set - tryFindMatchingTargets(Set possibleTargets, TargetInfo targetInfo) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (T possibleTarget : possibleTargets) { - if (targetInfo.matches(possibleTarget)) { + private static Set + tryFindMatchingTargets(Set possibleTargets, TARGET target, SignaturePredicate signaturePredicate) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (MEMBER possibleTarget : possibleTargets) { + if (matches(possibleTarget, target, signaturePredicate)) { result.add(possibleTarget); } } return result.build(); } + private static boolean matches(MEMBER member, TARGET target, SignaturePredicate signaturePredicate) { + if (!target.name.equals(member.getName()) || !target.desc.equals(member.getDescriptor())) { + return false; + } + return target.owner.getFullyQualifiedClassName().equals(member.getOwner().getName()) || + containsExactlyOneMatch(new ClassHierarchyPath(target.owner, member.getOwner()), target, signaturePredicate); + } + + private static boolean containsExactlyOneMatch(Iterable classes, TARGET target, SignaturePredicate signaturePredicate) { + Set matching = new HashSet<>(); + for (JavaClass javaClass : classes) { + if (signaturePredicate.exists(javaClass, target)) { + matching.add(javaClass); + } + } + return matching.size() == 1; + } + + private interface SignaturePredicate { + boolean exists(JavaClass clazz, TARGET target); + } + + private static final SignaturePredicate FIELD_SIGNATURE_PREDICATE = new SignaturePredicate() { + @Override + public boolean exists(JavaClass clazz, TargetInfo target) { + Optional field = clazz.tryGetField(target.name); + return field.isPresent() && target.desc.equals(field.get().getDescriptor()); + } + }; + + private static final SignaturePredicate METHOD_SIGNATURE_PREDICATE = new SignaturePredicate() { + @Override + public boolean exists(JavaClass clazz, TargetInfo target) { + for (JavaMethod method : clazz.getMethods()) { + if (method.getName().equals(target.name) && method.getDescriptor().equals(target.desc)) { + return true; + } + } + return false; + } + }; + private static Optional uniqueTargetIn(Collection collection) { return collection.size() == 1 ? Optional.of(getOnlyElement(collection)) : Optional.absent(); } @@ -295,5 +346,152 @@ private static JavaClassList getArgumentTypesFrom(String descriptor, ImportedCla } return createJavaClassList(paramTypes); } + + private static class ClassHierarchyPath implements Iterable { + private final List path; + + public ClassHierarchyPath(JavaClassDescriptor childType, JavaClass parent) { + Optional child = tryFindChildInHierarchy(childType, parent); + path = child.isPresent() ? createPath(parent, child.get()) : Collections.emptyList(); + } + + private Optional tryFindChildInHierarchy(JavaClassDescriptor childType, JavaClass parent) { + for (JavaClass subclass : parent.getAllSubClasses()) { + if (subclass.getName().equals(childType.getFullyQualifiedClassName())) { + return Optional.of(subclass); + } + } + return Optional.absent(); + } + + private List createPath(JavaClass parent, JavaClass child) { + ImmutableList.Builder pathBuilder = ImmutableList.builder().add(child); + HierarchyResolutionStrategy hierarchyResolutionStrategy = hierarchyResolutionStrategyFrom(child).to(parent); + while (hierarchyResolutionStrategy.hasNext()) { + pathBuilder.add(hierarchyResolutionStrategy.next()); + } + return pathBuilder.build(); + } + + private HierarchyResolutionStrategyCreator hierarchyResolutionStrategyFrom(JavaClass child) { + return new HierarchyResolutionStrategyCreator(child); + } + + @Override + public Iterator iterator() { + return path.iterator(); + } + + private interface HierarchyResolutionStrategy { + boolean hasNext(); + + JavaClass next(); + } + + private static class HierarchyResolutionStrategyCreator { + private final JavaClass child; + + private HierarchyResolutionStrategyCreator(JavaClass child) { + this.child = child; + } + + public HierarchyResolutionStrategy to(JavaClass parent) { + return parent.isInterface() ? + new InterfaceHierarchyResolutionStrategy(child, parent) : + new ClassHierarchyResolutionStrategy(child, parent); + } + } + + private static class ClassHierarchyResolutionStrategy implements HierarchyResolutionStrategy { + private final JavaClass parent; + private JavaClass current; + + private ClassHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { + this.current = child; + this.parent = parent; + } + + @Override + public boolean hasNext() { + return !current.equals(parent) && current.getSuperClass().isPresent(); + } + + @Override + public JavaClass next() { + current = current.getSuperClass().get(); + return current; + } + } + + private static class InterfaceHierarchyResolutionStrategy implements HierarchyResolutionStrategy { + private final Iterator interfaces; + private final JavaClass parent; + private JavaClass current; + + private InterfaceHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { + interfaces = interfacesBetween(child, parent); + this.parent = parent; + current = child; + } + + private Iterator interfacesBetween(JavaClass from, JavaClass target) { + Node node = new Node(from); + List result = new ArrayList<>(); + for (Node parent : node.parents) { + result.addAll(parent.to(target)); + } + return result.iterator(); + } + + @Override + public boolean hasNext() { + return !current.equals(parent) && interfaces.hasNext(); + } + + @Override + public JavaClass next() { + current = interfaces.next(); + return current; + } + } + + private static class Node { + private final JavaClass child; + private final Set parents = new HashSet<>(); + + private Node(JavaClass child) { + this.child = child; + for (JavaClass i : child.getInterfaces()) { + parents.add(new Node(i)); + } + } + + public List to(JavaClass target) { + if (child.equals(target)) { + return singletonList(child); + } + Set result = new LinkedHashSet<>(); + for (Node parent : parents) { + if (parent.contains(target)) { + result.add(child); + result.addAll(parent.to(target)); + } + } + return new ArrayList<>(result); + } + + public boolean contains(JavaClass target) { + if (child.equals(target)) { + return true; + } + for (Node parent : parents) { + if (parent.contains(target)) { + return true; + } + } + return false; + } + } + } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java index 3c239522c5..47153708d8 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java @@ -28,8 +28,6 @@ import com.tngtech.archunit.core.importer.JavaClassProcessor.AccessHandler; import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; -import com.tngtech.archunit.core.importer.RawAccessRecord.ConstructorTargetInfo; -import com.tngtech.archunit.core.importer.RawAccessRecord.MethodTargetInfo; import com.tngtech.archunit.core.importer.RawAccessRecord.TargetInfo; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; import com.tngtech.archunit.core.importer.resolvers.ClassResolver.ClassUriImporter; @@ -158,7 +156,7 @@ public void setLineNumber(int lineNumber) { public void handleFieldInstruction(int opcode, String owner, String name, String desc) { AccessType accessType = AccessType.forOpCode(opcode); LOG.trace("Found {} access to field {}.{}:{} in line {}", accessType, owner, name, desc, lineNumber); - TargetInfo target = new RawAccessRecord.FieldTargetInfo(owner, name, desc); + TargetInfo target = new TargetInfo(owner, name, desc); importRecord.registerFieldAccess(filled(new RawAccessRecord.ForField.Builder(), target) .withAccessType(accessType) .build()); @@ -168,10 +166,10 @@ public void handleFieldInstruction(int opcode, String owner, String name, String public void handleMethodInstruction(String owner, String name, String desc) { LOG.trace("Found call of method {}.{}:{} in line {}", owner, name, desc, lineNumber); if (CONSTRUCTOR_NAME.equals(name)) { - TargetInfo target = new ConstructorTargetInfo(owner, name, desc); + TargetInfo target = new TargetInfo(owner, name, desc); importRecord.registerConstructorCall(filled(new RawAccessRecord.Builder(), target).build()); } else { - TargetInfo target = new MethodTargetInfo(owner, name, desc); + TargetInfo target = new TargetInfo(owner, name, desc); importRecord.registerMethodCall(filled(new RawAccessRecord.Builder(), target).build()); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index 53d9ec19f7..e221305542 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.core.importer; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -31,15 +30,12 @@ import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.ImportContext; -import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAnnotation; -import com.tngtech.archunit.core.domain.JavaAnnotation.DefaultParameterVisitor; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaConstructorCall; -import com.tngtech.archunit.core.domain.JavaEnumConstant; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess; import com.tngtech.archunit.core.domain.JavaMember; @@ -47,7 +43,6 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaStaticInitializer; import com.tngtech.archunit.core.domain.JavaTypeVariable; -import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.core.importer.AccessRecord.FieldAccessRecord; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorCallBuilder; @@ -75,7 +70,6 @@ class ClassGraphCreator implements ImportContext { private final SetMultimap> processedConstructorCallRecords = HashMultimap.create(); private final Function> superClassStrategy; private final Function> interfaceStrategy; - private final MemberDependenciesByTarget memberDependenciesByTarget = new MemberDependenciesByTarget(); ClassGraphCreator(ClassFileImportRecord importRecord, ClassResolver classResolver) { this.importRecord = importRecord; @@ -189,7 +183,7 @@ private , B extends RawAccessRecord> void tryProcess( } @Override - public Set getFieldAccessesFor(JavaCodeUnit codeUnit) { + public Set createFieldAccessesFor(JavaCodeUnit codeUnit) { ImmutableSet.Builder result = ImmutableSet.builder(); for (FieldAccessRecord record : processedFieldAccessRecords.get(codeUnit)) { result.add(accessBuilderFrom(new JavaFieldAccessBuilder(), record) @@ -200,7 +194,7 @@ public Set getFieldAccessesFor(JavaCodeUnit codeUnit) { } @Override - public Set getMethodCallsFor(JavaCodeUnit codeUnit) { + public Set createMethodCallsFor(JavaCodeUnit codeUnit) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedMethodCallRecords.get(codeUnit)) { result.add(accessBuilderFrom(new JavaMethodCallBuilder(), record).build()); @@ -209,7 +203,7 @@ public Set getMethodCallsFor(JavaCodeUnit codeUnit) { } @Override - public Set getConstructorCallsFor(JavaCodeUnit codeUnit) { + public Set createConstructorCallsFor(JavaCodeUnit codeUnit) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedConstructorCallRecords.get(codeUnit)) { result.add(accessBuilderFrom(new JavaConstructorCallBuilder(), record).build()); @@ -217,51 +211,6 @@ public Set getConstructorCallsFor(JavaCodeUnit codeUnit) { return result.build(); } - @Override - public Set getFieldsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getFieldsOfType(javaClass); - } - - @Override - public Set getMethodsWithParameterOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getMethodsWithParameterOfType(javaClass); - } - - @Override - public Set getMethodsWithReturnType(JavaClass javaClass) { - return memberDependenciesByTarget.getMethodsWithReturnType(javaClass); - } - - @Override - public Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getMethodThrowsDeclarationsOfType(javaClass); - } - - @Override - public Set getConstructorsWithParameterOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getConstructorsWithParameterOfType(javaClass); - } - - @Override - public Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getConstructorThrowsDeclarationsOfType(javaClass); - } - - @Override - public Set> getAnnotationsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getAnnotationsOfType(javaClass); - } - - @Override - public Set> getAnnotationsWithParameterOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getAnnotationsWithParameterOfType(javaClass); - } - - @Override - public Set getInstanceofChecksOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getInstanceofChecksOfType(javaClass); - } - private > B accessBuilderFrom(B builder, AccessRecord record) { return builder @@ -295,9 +244,7 @@ public List createTypeParameters(JavaClass owner) { @Override public Set createFields(JavaClass owner) { - Set fields = build(importRecord.getFieldBuildersFor(owner.getName()), owner, classes.byTypeName()); - memberDependenciesByTarget.registerFields(fields); - return fields; + return build(importRecord.getFieldBuildersFor(owner.getName()), owner, classes.byTypeName()); } @Override @@ -314,16 +261,12 @@ public Optional apply(JavaMethod method) { }); } } - Set methods = build(methodBuilders, owner, classes.byTypeName()); - memberDependenciesByTarget.registerMethods(methods); - return methods; + return build(methodBuilders, owner, classes.byTypeName()); } @Override public Set createConstructors(JavaClass owner) { - Set constructors = build(importRecord.getConstructorBuildersFor(owner.getName()), owner, classes.byTypeName()); - memberDependenciesByTarget.registerConstructors(constructors); - return constructors; + return build(importRecord.getConstructorBuildersFor(owner.getName()), owner, classes.byTypeName()); } @Override @@ -333,7 +276,6 @@ public Optional createStaticInitializer(JavaClass owner) return Optional.absent(); } JavaStaticInitializer staticInitializer = builder.get().build(owner, classes.byTypeName()); - memberDependenciesByTarget.registerStaticInitializer(staticInitializer); return Optional.of(staticInitializer); } @@ -348,9 +290,7 @@ public Map> createAnnotations(JavaMember owne } private Map> createAnnotations(OWNER owner, Set annotationBuilders) { - Map> annotations = buildAnnotations(owner, annotationBuilders, this); - memberDependenciesByTarget.registerAnnotations(annotations.values()); - return annotations; + return buildAnnotations(owner, annotationBuilders, this); } @Override @@ -375,116 +315,4 @@ public Optional getMethodReturnType(String declaringClassName, String } return Optional.absent(); } - - private static class MemberDependenciesByTarget { - private final SetMultimap fieldTypeDependencies = HashMultimap.create(); - private final SetMultimap methodParameterTypeDependencies = HashMultimap.create(); - private final SetMultimap methodReturnTypeDependencies = HashMultimap.create(); - private final SetMultimap> methodsThrowsDeclarationDependencies = HashMultimap.create(); - private final SetMultimap constructorParameterTypeDependencies = HashMultimap.create(); - private final SetMultimap> constructorThrowsDeclarationDependencies = HashMultimap.create(); - private final SetMultimap> annotationTypeDependencies = HashMultimap.create(); - private final SetMultimap> annotationParameterTypeDependencies = HashMultimap.create(); - private final SetMultimap instanceofCheckDependencies = HashMultimap.create(); - - void registerFields(Set fields) { - for (JavaField field : fields) { - fieldTypeDependencies.put(field.getRawType(), field); - } - } - - void registerMethods(Set methods) { - for (JavaMethod method : methods) { - for (JavaClass parameter : method.getRawParameterTypes()) { - methodParameterTypeDependencies.put(parameter, method); - } - methodReturnTypeDependencies.put(method.getRawReturnType(), method); - for (ThrowsDeclaration throwsDeclaration : method.getThrowsClause()) { - methodsThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); - } - for (InstanceofCheck instanceofCheck : method.getInstanceofChecks()) { - instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); - } - } - } - - void registerConstructors(Set constructors) { - for (JavaConstructor constructor : constructors) { - for (JavaClass parameter : constructor.getRawParameterTypes()) { - constructorParameterTypeDependencies.put(parameter, constructor); - } - for (ThrowsDeclaration throwsDeclaration : constructor.getThrowsClause()) { - constructorThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); - } - for (InstanceofCheck instanceofCheck : constructor.getInstanceofChecks()) { - instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); - } - } - } - - void registerAnnotations(Collection> annotations) { - for (final JavaAnnotation annotation : annotations) { - annotationTypeDependencies.put(annotation.getRawType(), annotation); - annotation.accept(new DefaultParameterVisitor() { - @Override - public void visitClass(String propertyName, JavaClass javaClass) { - annotationParameterTypeDependencies.put(javaClass, annotation); - } - - @Override - public void visitEnumConstant(String propertyName, JavaEnumConstant enumConstant) { - annotationParameterTypeDependencies.put(enumConstant.getDeclaringClass(), annotation); - } - - @Override - public void visitAnnotation(String propertyName, JavaAnnotation memberAnnotation) { - annotationParameterTypeDependencies.put(memberAnnotation.getRawType(), annotation); - memberAnnotation.accept(this); - } - }); - } - } - - void registerStaticInitializer(JavaStaticInitializer staticInitializer) { - for (InstanceofCheck instanceofCheck : staticInitializer.getInstanceofChecks()) { - instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); - } - } - - Set getFieldsOfType(JavaClass javaClass) { - return fieldTypeDependencies.get(javaClass); - } - - Set getMethodsWithParameterOfType(JavaClass javaClass) { - return methodParameterTypeDependencies.get(javaClass); - } - - Set getMethodsWithReturnType(JavaClass javaClass) { - return methodReturnTypeDependencies.get(javaClass); - } - - Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass) { - return methodsThrowsDeclarationDependencies.get(javaClass); - } - - Set getConstructorsWithParameterOfType(JavaClass javaClass) { - return constructorParameterTypeDependencies.get(javaClass); - } - - Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass) { - return constructorThrowsDeclarationDependencies.get(javaClass); - } - - Set> getAnnotationsOfType(JavaClass javaClass) { - return annotationTypeDependencies.get(javaClass); - } - - Set> getAnnotationsWithParameterOfType(JavaClass javaClass) { - return annotationParameterTypeDependencies.get(javaClass); - } - - Set getInstanceofChecksOfType(JavaClass javaClass) { - return instanceofCheckDependencies.get(javaClass); - } - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java index 4ec15086d4..9476e3d744 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java @@ -15,34 +15,14 @@ */ package com.tngtech.archunit.core.importer; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; -import java.util.Set; -import com.google.common.collect.Sets; -import com.tngtech.archunit.base.DescribedPredicate; -import com.tngtech.archunit.base.Optional; -import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaCodeUnit; -import com.tngtech.archunit.core.domain.JavaConstructor; -import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; -import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.properties.HasDescriptor; -import com.tngtech.archunit.core.domain.properties.HasName; -import com.tngtech.archunit.core.domain.properties.HasOwner; import static com.google.common.base.Preconditions.checkNotNull; -import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.regex.Pattern.quote; class RawAccessRecord { final CodeUnit caller; @@ -144,7 +124,7 @@ && getParameters().equals(method.getRawParameterTypes().getNames()) } } - abstract static class TargetInfo { + static class TargetInfo { final JavaClassDescriptor owner; final String name; final String desc; @@ -155,28 +135,6 @@ abstract static class TargetInfo { this.desc = desc; } - > boolean matches(T member) { - if (!name.equals(member.getName()) || !desc.equals(member.getDescriptor())) { - return false; - } - return owner.getFullyQualifiedClassName().equals(member.getOwner().getName()) || - classHierarchyFrom(member).hasExactlyOneMatchFor(this); - } - - private > ClassHierarchyPath classHierarchyFrom(T member) { - return new ClassHierarchyPath(owner, member.getOwner()); - } - - protected abstract boolean signatureExistsIn(JavaClass javaClass); - - boolean hasMatchingSignatureTo(JavaMethod method) { - return method.getName().equals(name) && method.getDescriptor().equals(desc); - } - - boolean hasMatchingSignatureTo(JavaConstructor constructor) { - return CONSTRUCTOR_NAME.equals(name) && constructor.getDescriptor().equals(desc); - } - @Override public int hashCode() { return Objects.hash(owner, name, desc); @@ -200,204 +158,6 @@ public boolean equals(Object obj) { public String toString() { return getClass().getSimpleName() + "{owner='" + owner.getFullyQualifiedClassName() + "', name='" + name + "', desc='" + desc + "'}"; } - - private static class ClassHierarchyPath { - private final List path = new ArrayList<>(); - - private ClassHierarchyPath(JavaClassDescriptor childType, JavaClass parent) { - Set classesToSearchForChild = Sets.union(singleton(parent), parent.getAllSubClasses()); - Optional child = tryFind(classesToSearchForChild, nameMatching(quote(childType.getFullyQualifiedClassName()))); - if (child.isPresent()) { - createPath(child.get(), parent); - } - } - - private static Optional tryFind(Iterable collection, DescribedPredicate predicate) { - for (T elem : collection) { - if (predicate.apply(elem)) { - return Optional.of(elem); - } - } - return Optional.absent(); - } - - private void createPath(JavaClass child, JavaClass parent) { - HierarchyResolutionStrategy hierarchyResolutionStrategy = hierarchyResolutionStrategyFrom(child).to(parent); - path.add(child); - while (hierarchyResolutionStrategy.hasNext()) { - path.add(hierarchyResolutionStrategy.next()); - } - } - - boolean hasExactlyOneMatchFor(final TargetInfo target) { - Set matching = new HashSet<>(); - for (JavaClass javaClass : path) { - if (target.signatureExistsIn(javaClass)) { - matching.add(javaClass); - } - } - return matching.size() == 1; - } - - private HierarchyResolutionStrategyCreator hierarchyResolutionStrategyFrom(JavaClass child) { - return new HierarchyResolutionStrategyCreator(child); - } - - private interface HierarchyResolutionStrategy { - boolean hasNext(); - - JavaClass next(); - } - - private static class HierarchyResolutionStrategyCreator { - private final JavaClass child; - - private HierarchyResolutionStrategyCreator(JavaClass child) { - this.child = child; - } - - public HierarchyResolutionStrategy to(JavaClass parent) { - return parent.isInterface() ? - new InterfaceHierarchyResolutionStrategy(child, parent) : - new ClassHierarchyResolutionStrategy(child, parent); - } - } - - private static class ClassHierarchyResolutionStrategy implements HierarchyResolutionStrategy { - private final JavaClass parent; - private JavaClass current; - - private ClassHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { - this.current = child; - this.parent = parent; - } - - @Override - public boolean hasNext() { - return !current.equals(parent) && current.getSuperClass().isPresent(); - } - - @Override - public JavaClass next() { - current = current.getSuperClass().get(); - return current; - } - } - - private static class InterfaceHierarchyResolutionStrategy implements HierarchyResolutionStrategy { - private final Iterator interfaces; - private final JavaClass parent; - private JavaClass current; - - private InterfaceHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { - interfaces = interfacesBetween(child, parent); - this.parent = parent; - current = child; - } - - private Iterator interfacesBetween(JavaClass from, JavaClass target) { - Node node = new Node(from); - List result = new ArrayList<>(); - for (Node parent : node.parents) { - result.addAll(parent.to(target)); - } - return result.iterator(); - } - - @Override - public boolean hasNext() { - return !current.equals(parent) && interfaces.hasNext(); - } - - @Override - public JavaClass next() { - current = interfaces.next(); - return current; - } - } - - private static class Node { - private final JavaClass child; - private final Set parents = new HashSet<>(); - - private Node(JavaClass child) { - this.child = child; - for (JavaClass i : child.getInterfaces()) { - parents.add(new Node(i)); - } - } - - public List to(JavaClass target) { - if (child.equals(target)) { - return singletonList(child); - } - Set result = new LinkedHashSet<>(); - for (Node parent : parents) { - if (parent.contains(target)) { - result.add(child); - result.addAll(parent.to(target)); - } - } - return new ArrayList<>(result); - } - - public boolean contains(JavaClass target) { - if (child.equals(target)) { - return true; - } - for (Node parent : parents) { - if (parent.contains(target)) { - return true; - } - } - return false; - } - } - } - } - - static class FieldTargetInfo extends TargetInfo { - FieldTargetInfo(String owner, String name, String desc) { - super(owner, name, desc); - } - - @Override - protected boolean signatureExistsIn(JavaClass javaClass) { - Optional field = javaClass.tryGetField(name); - return field.isPresent() && desc.equals(field.get().getDescriptor()); - } - } - - static class ConstructorTargetInfo extends TargetInfo { - ConstructorTargetInfo(String owner, String name, String desc) { - super(owner, name, desc); - } - - @Override - protected boolean signatureExistsIn(JavaClass javaClass) { - for (JavaConstructor constructor : javaClass.getConstructors()) { - if (hasMatchingSignatureTo(constructor)) { - return true; - } - } - return false; - } - } - - static class MethodTargetInfo extends TargetInfo { - MethodTargetInfo(String owner, String name, String desc) { - super(owner, name, desc); - } - - @Override - protected boolean signatureExistsIn(JavaClass javaClass) { - for (JavaMethod method : javaClass.getMethods()) { - if (hasMatchingSignatureTo(method)) { - return true; - } - } - return false; - } } static class Builder extends BaseBuilder { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java index ea7231ca3a..c23d5e9218 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java @@ -1,6 +1,5 @@ package com.tngtech.archunit.core.domain; -import java.io.File; import java.io.Serializable; import java.lang.annotation.Retention; import java.util.AbstractList; @@ -20,7 +19,9 @@ import com.tngtech.archunit.core.domain.testobjects.AExtendingSuperAImplementingInterfaceForA; import com.tngtech.archunit.core.domain.testobjects.AhavingMembersOfTypeB; import com.tngtech.archunit.core.domain.testobjects.AllPrimitiveDependencies; +import com.tngtech.archunit.core.domain.testobjects.ArrayComponentTypeDependencies; import com.tngtech.archunit.core.domain.testobjects.B; +import com.tngtech.archunit.core.domain.testobjects.ComponentTypeDependency; import com.tngtech.archunit.core.domain.testobjects.InterfaceForA; import com.tngtech.archunit.core.domain.testobjects.SuperA; import com.tngtech.archunit.core.importer.ClassFileImporter; @@ -71,11 +72,13 @@ import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatDependencies; import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.Conditions.codeUnitWithSignature; import static com.tngtech.archunit.testutil.Conditions.containing; import static com.tngtech.archunit.testutil.ReflectionTestUtils.getHierarchy; +import static com.tngtech.archunit.testutil.assertion.DependenciesAssertion.from; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.util.regex.Pattern.quote; @@ -88,77 +91,6 @@ public class JavaClassTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @Test - public void finds_array_component_types_as_dependencies_from_self() { - @SuppressWarnings("unused") - class ArrayComponentTypeDependencies { - private String[] strings; - - public ArrayComponentTypeDependencies(Integer[] ints) { - } - - private void internal() { - new File[0].clone(); - } - - public Object[] objects() { - return null; - } - - public void foo(Float[] floats) { - } - } - - JavaClass javaClass = importClassWithContext(ArrayComponentTypeDependencies.class); - - // Assert the presence of both the array type and the component type of the array for all dependencies - assertThat(javaClass.getDirectDependenciesFromSelf()) - .areAtLeastOne(callDependency() - .from(ArrayComponentTypeDependencies.class) - .to(File[].class) - .inLineNumber(101)) - .areAtLeastOne(componentTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(File.class) - .inLineNumber(101)) - - .areAtLeastOne(methodReturnTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(Object[].class) - .inLineNumber(0)) - .areAtLeastOne(componentTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(Object.class) - .inLineNumber(0)) - - .areAtLeastOne(fieldTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(String[].class) - .inLineNumber(0)) - .areAtLeastOne(componentTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(String.class) - .inLineNumber(0)) - - .areAtLeastOne(parameterTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(Integer[].class) - .inLineNumber(0)) - .areAtLeastOne(componentTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(Integer.class) - .inLineNumber(0)) - - .areAtLeastOne(parameterTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(Float[].class) - .inLineNumber(0)) - .areAtLeastOne(componentTypeDependency() - .from(ArrayComponentTypeDependencies.class) - .to(Float.class) - .inLineNumber(0)); - } - @Test public void finds_array_type() { @SuppressWarnings("unused") @@ -607,8 +539,39 @@ public void direct_dependencies_from_self_by_annotation() { .areAtLeastOne(annotationMemberOfTypeDependency() .from(ClassWithAnnotationDependencies.class) .to(B.class) - .inLineNumber(0)) - ; + .inLineNumber(0)); + } + + @Test + public void finds_array_component_types_as_dependencies_from_self() { + JavaClass javaClass = importClassWithContext(ArrayComponentTypeDependencies.class); + + assertThatDependencies(javaClass.getDirectDependenciesFromSelf()) + .contain( + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Field <%s.asField> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Constructor <%s.(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asMethodParameter(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asReturnType()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asCallTarget()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 18)); } @Test @@ -747,13 +710,47 @@ public void direct_dependencies_to_self_by_annotation() { .inLineNumber(0)); } + @Test + public void finds_array_component_types_as_dependencies_to_self() { + JavaClass javaClass = new ClassFileImporter().importClasses(ArrayComponentTypeDependencies.class, ComponentTypeDependency.class) + .get(ComponentTypeDependency.class); + + assertThatDependencies(javaClass.getDirectDependenciesToSelf()) + .contain( + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Field <%s.asField> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Constructor <%s.(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asMethodParameter(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asReturnType()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asCallTarget()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 18)); + } + @Test public void direct_dependencies_to_self_finds_correct_set_of_origin_types() { JavaClasses classes = importPackagesOf(getClass()); Set origins = getOriginsOfDependenciesTo(classes.get(WithType.class)); - assertThatTypes(origins).matchInAnyOrder(ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, OnMethodParam.class); + assertThatTypes(origins).matchInAnyOrder( + ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, WithNestedAnnotations.class, OnMethodParam.class); origins = getOriginsOfDependenciesTo(classes.get(B.class)); @@ -1134,10 +1131,6 @@ private static DependencyConditionCreation annotationMemberOfTypeDependency() { return new DependencyConditionCreation("has annotation member of type"); } - private static DependencyConditionCreation componentTypeDependency() { - return new DependencyConditionCreation("depends on component type"); - } - private static AnyDependencyConditionCreation anyDependency() { return new AnyDependencyConditionCreation(); } @@ -1666,10 +1659,11 @@ private static class ClassWithCyclicMetaAnnotation { @SuppressWarnings("ALL") private static class ClassWithSelfReferences extends Exception { static { - ClassWithSelfReferences selfReference = new ClassWithSelfReferences(null, null); + ClassWithSelfReferences selfReference = new ClassWithSelfReferences(null, (ClassWithSelfReferences) null); } ClassWithSelfReferences fieldSelfReference; + ClassWithSelfReferences[] arrayFieldSelfReference; ClassWithSelfReferences() throws ClassWithSelfReferences { } @@ -1677,25 +1671,38 @@ private static class ClassWithSelfReferences extends Exception { ClassWithSelfReferences(Object any, ClassWithSelfReferences selfReference) { } + ClassWithSelfReferences(Object any, ClassWithSelfReferences[] selfReference) { + } + ClassWithSelfReferences methodReturnTypeSelfReference() { return null; } + ClassWithSelfReferences[] methodArrayReturnTypeSelfReference() { + return null; + } + void methodParameterSelfReference(Object any, ClassWithSelfReferences selfReference) { } + void methodArrayParameterSelfReference(Object any, ClassWithSelfReferences[] selfReference) { + } + void methodCallSelfReference() { ClassWithSelfReferences self = null; self.methodParameterSelfReference(null, null); + ClassWithSelfReferences[] arraySelf = null; + arraySelf.clone(); } void constructorCallSelfReference() { - new ClassWithSelfReferences(null, null); + new ClassWithSelfReferences(null, (ClassWithSelfReferences) null); } void fieldAccessSelfReference() { ClassWithSelfReferences self = null; self.fieldSelfReference = null; + self.arrayFieldSelfReference = null; } @WithType(type = ClassWithSelfReferences.class) diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java index ca2691376c..a4dda5e075 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java @@ -181,7 +181,7 @@ private JavaMethodCall to(MethodCallTarget methodCallTarget) { for (MethodCallTarget target : targets) { calls.add(newMethodCall(method, target, lineNumber)); } - when(context.getMethodCallsFor(method)).thenReturn(ImmutableSet.copyOf(calls)); + when(context.createMethodCallsFor(method)).thenReturn(ImmutableSet.copyOf(calls)); method.completeFrom(context); return getCallToTarget(methodCallTarget); } @@ -206,7 +206,7 @@ private JavaMethodCall getCallToTarget(MethodCallTarget callTarget) { public void to(JavaField target, AccessType accessType) { ImportContext context = mock(ImportContext.class); - when(context.getFieldAccessesFor(method)) + when(context.createFieldAccessesFor(method)) .thenReturn(ImmutableSet.of( newFieldAccess(method, target, lineNumber, accessType) )); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ArrayComponentTypeDependencies.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ArrayComponentTypeDependencies.java new file mode 100644 index 0000000000..c142a1f0f0 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ArrayComponentTypeDependencies.java @@ -0,0 +1,20 @@ +package com.tngtech.archunit.core.domain.testobjects; + +@SuppressWarnings("unused") +public class ArrayComponentTypeDependencies { + private ComponentTypeDependency[] asField; + + public ArrayComponentTypeDependencies(ComponentTypeDependency[] asConstructorParameter) { + } + + public void asMethodParameter(ComponentTypeDependency[] asMethodParameter) { + } + + public ComponentTypeDependency[] asReturnType() { + return null; + } + + private void asCallTarget() { + new ComponentTypeDependency[0].clone(); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ComponentTypeDependency.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ComponentTypeDependency.java new file mode 100644 index 0000000000..a5512e2680 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ComponentTypeDependency.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.domain.testobjects; + +public class ComponentTypeDependency { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAnnotationsTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAnnotationsTest.java index 0a561dfc30..3e38e19336 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAnnotationsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAnnotationsTest.java @@ -1,26 +1,500 @@ package com.tngtech.archunit.core.importer; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaConstructor; +import com.tngtech.archunit.core.domain.JavaEnumConstant; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.properties.HasAnnotations; +import com.tngtech.archunit.core.importer.testexamples.SomeAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassAnnotationWithArrays; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithAnnotationWithEmptyArrays; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithComplexAnnotations; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithOneAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithUnimportedAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.SimpleAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.TypeAnnotationWithEnumAndArrayValue; +import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields; +import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.FieldAnnotationWithArrays; +import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods; +import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.MethodAnnotationWithArrays; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationParameter; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationToImport; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.EnumToImport; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.OTHER_VALUE; +import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.SOME_VALUE; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.enumAndArrayAnnotatedMethod; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithAnnotationFromParentPackage; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithEmptyArrays; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAndIntAnnotatedMethod; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAnnotatedMethod; +import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatAnnotation; import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.annotationProperty; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @RunWith(DataProviderRunner.class) public class ClassFileImporterAnnotationsTest { + @Test + public void imports_simple_annotation() { + JavaClass javaClass = new ClassFileImporter().importPackagesOf(AnnotationToImport.class).get(AnnotationToImport.class); + + assertThat(javaClass.getName()).as("full name").isEqualTo(AnnotationToImport.class.getName()); + assertThat(javaClass.getSimpleName()).as("simple name").isEqualTo(AnnotationToImport.class.getSimpleName()); + assertThat(javaClass.getPackageName()).as("package name").isEqualTo(AnnotationToImport.class.getPackage().getName()); + assertThat(javaClass.getModifiers()).as("modifiers").containsOnly(JavaModifier.PUBLIC, JavaModifier.ABSTRACT); + assertThat(javaClass.getSuperClass()).as("super class").isAbsent(); + assertThatTypes(javaClass.getInterfaces()).as("interfaces").matchInAnyOrder(Annotation.class); + assertThat(javaClass.isInterface()).as("is interface").isTrue(); + assertThat(javaClass.isEnum()).as("is enum").isFalse(); + assertThat(javaClass.isAnnotation()).as("is annotation").isTrue(); + + assertThat(getAnnotationDefaultValue(javaClass, "someStringMethod", String.class)).isEqualTo("DEFAULT"); + assertThatType(getAnnotationDefaultValue(javaClass, "someTypeMethod", JavaClass.class)).matches(List.class); + assertThat(getAnnotationDefaultValue(javaClass, "someEnumMethod", JavaEnumConstant.class)).isEquivalentTo(EnumToImport.SECOND); + assertThatType(getAnnotationDefaultValue(javaClass, "someAnnotationMethod", JavaAnnotation.class).getRawType()).matches(AnnotationParameter.class); + } + + @Test + public void imports_annotation_defaults() { + JavaClass annotationType = new ClassFileImporter().importPackagesOf(TypeAnnotationWithEnumAndArrayValue.class).get(TypeAnnotationWithEnumAndArrayValue.class); + + assertThat((JavaEnumConstant) annotationType.getMethod("valueWithDefault") + .getDefaultValue().get()) + .as("default of valueWithDefault()").isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotationType.getMethod("enumArrayWithDefault") + .getDefaultValue().get())) + .as("default of enumArrayWithDefault()").matches(OTHER_VALUE); + assertThat(((JavaAnnotation) annotationType.getMethod("subAnnotationWithDefault") + .getDefaultValue().get()).get("value").get()) + .as("default of subAnnotationWithDefault()").isEqualTo("default"); + assertThat(((JavaAnnotation[]) annotationType.getMethod("subAnnotationArrayWithDefault") + .getDefaultValue().get())[0].get("value").get()) + .as("default of subAnnotationArrayWithDefault()").isEqualTo("first"); + assertThatType((JavaClass) annotationType.getMethod("clazzWithDefault") + .getDefaultValue().get()) + .as("default of clazzWithDefault()").matches(String.class); + assertThat((JavaClass[]) annotationType.getMethod("classesWithDefault") + .getDefaultValue().get()) + .as("default of clazzWithDefault()").matchExactly(Serializable.class, List.class); + } + + @Test + public void imports_class_with_one_annotation_correctly() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithOneAnnotation.class) + .get(ClassWithOneAnnotation.class); + + JavaAnnotation annotation = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); + assertThatType(annotation.getRawType()).matches(SimpleAnnotation.class); + assertThatType(annotation.getOwner()).isEqualTo(clazz); + + JavaAnnotation annotationByName = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); + assertThat(annotationByName).isEqualTo(annotation); + + assertThat(annotation.get("value").get()).isEqualTo("test"); + + assertThatType(clazz).matches(ClassWithOneAnnotation.class); + } + + @Test + public void class_handles_optional_annotation_correctly() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithOneAnnotation.class) + .get(ClassWithOneAnnotation.class); + + assertThat(clazz.tryGetAnnotationOfType(SimpleAnnotation.class)).isPresent(); + assertThat(clazz.tryGetAnnotationOfType(Deprecated.class)).isAbsent(); + } + + @Test + public void imports_class_with_complex_annotations_correctly() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithComplexAnnotations.class) + .get(ClassWithComplexAnnotations.class); + + assertThat(clazz.getAnnotations()).as("annotations of " + clazz.getSimpleName()).hasSize(2); + + JavaAnnotation annotation = clazz.getAnnotationOfType(TypeAnnotationWithEnumAndArrayValue.class.getName()); + assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); + assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("sub"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(clazz); + assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) + .isEqualTo("default"); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("otherFirst"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(clazz); + assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) + .isEqualTo("first"); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Serializable.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Serializable.class, String.class); + assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); + + assertThatType(clazz).matches(ClassWithComplexAnnotations.class); + } + + @Test + public void imports_class_with_annotation_with_empty_array() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotationWithEmptyArrays.class) + .get(ClassWithAnnotationWithEmptyArrays.class); + + JavaAnnotation annotation = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class.getName()); + + assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); + assertThat(Array.getLength(annotation.get("objects").get())).isZero(); + assertThat(Array.getLength(annotation.get("enums").get())).isZero(); + assertThat(Array.getLength(annotation.get("classes").get())).isZero(); + assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); + + ClassAnnotationWithArrays reflected = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class); + Assertions.assertThat(reflected.primitives()).isEmpty(); + Assertions.assertThat(reflected.objects()).isEmpty(); + Assertions.assertThat(reflected.enums()).isEmpty(); + Assertions.assertThat(reflected.classes()).isEmpty(); + Assertions.assertThat(reflected.annotations()).isEmpty(); + } + + @Test + public void imports_class_annotated_with_unimported_annotation() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithUnimportedAnnotation.class) + .get(ClassWithUnimportedAnnotation.class); + + JavaAnnotation annotation = clazz.getAnnotationOfType(SomeAnnotation.class.getName()); + + assertThat(annotation.get("mandatory")).contains("mandatory"); + assertThat(annotation.get("optional")).contains("optional"); + assertThat((JavaEnumConstant) annotation.get("mandatoryEnum").get()).isEquivalentTo(SOME_VALUE); + assertThat((JavaEnumConstant) annotation.get("optionalEnum").get()).isEquivalentTo(OTHER_VALUE); + + SomeAnnotation reflected = clazz.getAnnotationOfType(SomeAnnotation.class); + Assertions.assertThat(reflected.mandatory()).isEqualTo("mandatory"); + Assertions.assertThat(reflected.optional()).isEqualTo("optional"); + Assertions.assertThat(reflected.mandatoryEnum()).isEqualTo(SOME_VALUE); + Assertions.assertThat(reflected.optionalEnum()).isEqualTo(OTHER_VALUE); + } + + @Test + public void imports_fields_with_one_annotation_correctly() throws Exception { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("stringAnnotatedField"); + + JavaAnnotation annotation = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class.getName()); + assertThatType(annotation.getRawType()).matches(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class); + assertThat(annotation.get("value").get()).isEqualTo("something"); + assertThat(annotation.getOwner()).as("owning field").isEqualTo(field); + + assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAnnotatedField")); + } + + @Test + public void fields_handle_optional_annotation_correctly() { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("stringAnnotatedField"); + + assertThat(field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class)).isPresent(); + assertThat(field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue.class)).isAbsent(); + + Optional> optionalAnnotation = field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class.getName()); + assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(field); + assertThat(field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue.class.getName())) + .as("optional annotation").isAbsent(); + } + + @Test + public void imports_fields_with_two_annotations_correctly() throws Exception { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("stringAndIntAnnotatedField"); + + Set> annotations = field.getAnnotations(); + assertThat(annotations).hasSize(2); + assertThat(annotations).extractingResultOf("getOwner").containsOnly(field); + assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(field); + + JavaAnnotation annotationWithString = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class.getName()); + assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); + + JavaAnnotation annotationWithInt = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithIntValue.class.getName()); + assertThat(annotationWithInt.get("intValue").get()).as("Annotation value with default").isEqualTo(0); + assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); + + assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAndIntAnnotatedField")); + } + + @Test + public void imports_fields_with_complex_annotations_correctly() throws Exception { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("enumAndArrayAnnotatedField"); + + JavaAnnotation annotation = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue.class.getName()); + assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); + assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); + assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) + .isEqualTo("default"); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); + assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) + .isEqualTo("first"); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); + assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); + + assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("enumAndArrayAnnotatedField")); + } + + @Test + public void imports_fields_with_annotation_with_empty_array() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class).get(ClassWithAnnotatedFields.class); + + JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithEmptyArrays") + .getAnnotationOfType(FieldAnnotationWithArrays.class.getName()); + + assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); + assertThat(Array.getLength(annotation.get("objects").get())).isZero(); + assertThat(Array.getLength(annotation.get("enums").get())).isZero(); + assertThat(Array.getLength(annotation.get("classes").get())).isZero(); + assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); + + FieldAnnotationWithArrays reflected = annotation.as(FieldAnnotationWithArrays.class); + assertThat(reflected.primitives()).isEmpty(); + assertThat(reflected.objects()).isEmpty(); + assertThat(reflected.enums()).isEmpty(); + assertThat(reflected.classes()).isEmpty(); + assertThat(reflected.annotations()).isEmpty(); + } + + @Test + public void imports_fields_annotated_with_unimported_annotation() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class).get(ClassWithAnnotatedFields.class); + + JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithAnnotationFromParentPackage") + .getAnnotationOfType(SomeAnnotation.class.getName()); + + assertThat(annotation.get("mandatory")).contains("mandatory"); + assertThat(annotation.get("optional")).contains("optional"); + + SomeAnnotation reflected = annotation.as(SomeAnnotation.class); + assertThat(reflected.mandatory()).isEqualTo("mandatory"); + assertThat(reflected.optional()).isEqualTo("optional"); + } + + @Test + public void imports_methods_with_one_annotation_correctly() throws Exception { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(stringAnnotatedMethod); + + JavaAnnotation annotation = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class.getName()); + assertThatType(annotation.getRawType()).matches(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class); + assertThat(annotation.getOwner()).isEqualTo(method); + assertThat(annotation.get("value").get()).isEqualTo("something"); + + assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAnnotatedMethod)); + } + + @Test + public void methods_handle_optional_annotation_correctly() { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(stringAnnotatedMethod); + + assertThat(method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class)).isPresent(); + assertThat(method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class)).isAbsent(); + + Optional> optionalAnnotation = method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class.getName()); + assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(method); + assertThat(method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class.getName())).isAbsent(); + } + + @Test + public void imports_methods_with_two_annotations_correctly() throws Exception { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(stringAndIntAnnotatedMethod); + + Set> annotations = method.getAnnotations(); + assertThat(annotations).hasSize(2); + assertThat(annotations).extractingResultOf("getOwner").containsOnly(method); + assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(method); + + JavaAnnotation annotationWithString = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class.getName()); + assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); + + JavaAnnotation annotationWithInt = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithIntValue.class.getName()); + assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); + + assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAndIntAnnotatedMethod)); + } + + @Test + public void imports_methods_with_complex_annotations_correctly() throws Exception { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(enumAndArrayAnnotatedMethod); + + JavaAnnotation annotation = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class.getName()); + assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); + assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(method); + assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) + .isEqualTo("default"); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(method); + assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) + .isEqualTo("first"); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); + assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); + + assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(enumAndArrayAnnotatedMethod)); + } + + @Test + public void imports_method_with_annotation_with_empty_array() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class).get(ClassWithAnnotatedMethods.class); + + JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithEmptyArrays) + .getAnnotationOfType(MethodAnnotationWithArrays.class.getName()); + + assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); + assertThat(Array.getLength(annotation.get("objects").get())).isZero(); + assertThat(Array.getLength(annotation.get("enums").get())).isZero(); + assertThat(Array.getLength(annotation.get("classes").get())).isZero(); + assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); + + MethodAnnotationWithArrays reflected = annotation.as(MethodAnnotationWithArrays.class); + Assertions.assertThat(reflected.primitives()).isEmpty(); + Assertions.assertThat(reflected.objects()).isEmpty(); + Assertions.assertThat(reflected.enums()).isEmpty(); + Assertions.assertThat(reflected.classes()).isEmpty(); + Assertions.assertThat(reflected.annotations()).isEmpty(); + } + + @Test + public void imports_methods_annotated_with_unimported_annotation() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class).get(ClassWithAnnotatedMethods.class); + + JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithAnnotationFromParentPackage) + .getAnnotationOfType(SomeAnnotation.class.getName()); + + assertThat(annotation.get("mandatory")).contains("mandatory"); + assertThat(annotation.get("optional")).contains("optional"); + + SomeAnnotation reflected = annotation.as(SomeAnnotation.class); + Assertions.assertThat(reflected.mandatory()).isEqualTo("mandatory"); + Assertions.assertThat(reflected.optional()).isEqualTo("optional"); + } + + @Test + public void imports_constructors_with_complex_annotations_correctly() throws Exception { + JavaConstructor constructor = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getConstructor(); + + JavaAnnotation annotation = constructor.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class.getName()); + assertThat((Object[]) annotation.get("classes").get()).extracting("name") + .containsExactly(Object.class.getName(), Serializable.class.getName()); + assertThat(annotation.getOwner()).isEqualTo(constructor); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(constructor); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(constructor); + + assertThat(constructor).isEquivalentTo(ClassWithAnnotatedMethods.class.getConstructor()); + } + + @Test + public void classes_know_which_annotations_have_their_type() { + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithOneAnnotation.class, SimpleAnnotation.class); + + Set> annotations = classes.get(SimpleAnnotation.class).getAnnotationsWithTypeOfSelf(); + + assertThat(getOnlyElement(annotations).getOwner()).isEqualTo(classes.get(ClassWithOneAnnotation.class)); + } + + @Test + public void classes_know_which_annotation_members_have_their_type() { + @SuppressWarnings("unused") + @ParameterAnnotation(value = String.class) + class Dependent { + @ParameterAnnotation(value = String.class) + String field; + + @ParameterAnnotation(value = String.class) + Dependent() { + } + + @ParameterAnnotation(value = String.class) + void method() { + } + + @ParameterAnnotation(value = List.class) + void notToFind() { + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Dependent.class, ParameterAnnotation.class, String.class); + Set> annotations = classes.get(String.class).getAnnotationsWithParameterTypeOfSelf(); + + for (JavaAnnotation annotation : annotations) { + assertThatAnnotation(annotation).hasType(ParameterAnnotation.class); + } + + Set> expected = ImmutableSet.>of( + classes.get(Dependent.class).getAnnotationOfType(ParameterAnnotation.class.getName()), + classes.get(Dependent.class).getField("field").getAnnotationOfType(ParameterAnnotation.class.getName()), + classes.get(Dependent.class).getConstructor(getClass()).getAnnotationOfType(ParameterAnnotation.class.getName()), + classes.get(Dependent.class).getMethod("method").getAnnotationOfType(ParameterAnnotation.class.getName()) + ); + assertThat(annotations).as("annotations with parameter type " + String.class.getSimpleName()).containsOnlyElementsOf(expected); + } + @Test public void meta_annotation_types_are_transitively_imported() { JavaClass javaClass = new ClassFileImporter().importClass(MetaAnnotatedClass.class); - JavaAnnotation someAnnotation = javaClass.getAnnotationOfType(SomeAnnotation.class.getName()); + JavaAnnotation someAnnotation = javaClass.getAnnotationOfType(MetaAnnotatedAnnotation.class.getName()); JavaAnnotation someMetaAnnotation = someAnnotation.getRawType() .getAnnotationOfType(SomeMetaAnnotation.class.getName()); JavaAnnotation someMetaMetaAnnotation = someMetaAnnotation.getRawType() @@ -45,7 +519,7 @@ public static Object[][] elementsAnnotatedWithSomeAnnotation() { @UseDataProvider("elementsAnnotatedWithSomeAnnotation") public void parameters_of_meta_annotations_are_transitively_imported(HasAnnotations annotatedWithSomeAnnotation) { JavaAnnotation someAnnotation = annotatedWithSomeAnnotation - .getAnnotationOfType(SomeAnnotation.class.getName()); + .getAnnotationOfType(MetaAnnotatedAnnotation.class.getName()); JavaAnnotation metaAnnotationWithParameters = someAnnotation.getRawType() .getAnnotationOfType(MetaAnnotationWithParameters.class.getName()); @@ -80,6 +554,11 @@ public void parameters_of_meta_annotations_are_transitively_imported(HasAnnotati .withAnnotationType(SomeMetaMetaMetaParameterAnnotation.class)); } + @SuppressWarnings({"unchecked", "unused"}) + private static T getAnnotationDefaultValue(JavaClass javaClass, String methodName, Class valueType) { + return (T) javaClass.getMethod(methodName).getDefaultValue().get(); + } + @SuppressWarnings("unused") private @interface MetaAnnotationWithParameters { SomeEnum someEnum(); @@ -91,6 +570,7 @@ public void parameters_of_meta_annotations_are_transitively_imported(HasAnnotati ParameterAnnotation parameterAnnotationDefault() default @ParameterAnnotation(Integer.class); } + @SuppressWarnings("unused") private @interface SomeMetaMetaMetaAnnotationWithParameters { Class classParam(); @@ -122,7 +602,7 @@ public void parameters_of_meta_annotations_are_transitively_imported(HasAnnotati parameterAnnotation = @ParameterAnnotation(SomeAnnotationParameterType.class) ) @SomeMetaAnnotation - private @interface SomeAnnotation { + private @interface MetaAnnotatedAnnotation { } private enum SomeEnum { @@ -137,26 +617,26 @@ private enum SomeEnum { private static class SomeAnnotationParameterType { } - @SomeAnnotation + @MetaAnnotatedAnnotation private static class MetaAnnotatedClass { } @SuppressWarnings("unused") private static class ClassWithMetaAnnotatedField { - @SomeAnnotation + @MetaAnnotatedAnnotation int metaAnnotatedField; } @SuppressWarnings("unused") private static class ClassWithMetaAnnotatedMethod { - @SomeAnnotation + @MetaAnnotatedAnnotation void metaAnnotatedMethod() { } } @SuppressWarnings("unused") private static class ClassWithMetaAnnotatedConstructor { - @SomeAnnotation + @MetaAnnotatedAnnotation ClassWithMetaAnnotatedConstructor() { } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java index b288552cbd..2cfd6e1a9e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java @@ -5,7 +5,6 @@ import java.io.PrintStream; import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.JarURLConnection; @@ -21,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarFile; @@ -40,8 +38,8 @@ import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAccess; -import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaClasses; @@ -65,26 +63,8 @@ import com.tngtech.archunit.core.importer.testexamples.FirstCheckedException; import com.tngtech.archunit.core.importer.testexamples.OtherClass; import com.tngtech.archunit.core.importer.testexamples.SecondCheckedException; -import com.tngtech.archunit.core.importer.testexamples.SomeAnnotation; import com.tngtech.archunit.core.importer.testexamples.SomeClass; import com.tngtech.archunit.core.importer.testexamples.SomeEnum; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassAnnotationWithArrays; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithAnnotationWithEmptyArrays; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithComplexAnnotations; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithOneAnnotation; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithUnimportedAnnotation; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.SimpleAnnotation; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.TypeAnnotationWithEnumAndArrayValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields.FieldAnnotationWithIntValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields.FieldAnnotationWithStringValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.FieldAnnotationWithArrays; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.MethodAnnotationWithIntValue; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.MethodAnnotationWithStringValue; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.MethodAnnotationWithArrays; import com.tngtech.archunit.core.importer.testexamples.arrays.ClassAccessingOneDimensionalArray; import com.tngtech.archunit.core.importer.testexamples.arrays.ClassAccessingTwoDimensionalArray; import com.tngtech.archunit.core.importer.testexamples.arrays.ClassUsedInArray; @@ -116,12 +96,9 @@ import com.tngtech.archunit.core.importer.testexamples.constructorimport.ClassWithComplexConstructor; import com.tngtech.archunit.core.importer.testexamples.constructorimport.ClassWithSimpleConstructors; import com.tngtech.archunit.core.importer.testexamples.constructorimport.ClassWithThrowingConstructor; -import com.tngtech.archunit.core.importer.testexamples.dependents.ClassDependingOnParentThroughChild; import com.tngtech.archunit.core.importer.testexamples.dependents.ClassHoldingDependencies; import com.tngtech.archunit.core.importer.testexamples.dependents.FirstClassWithDependency; -import com.tngtech.archunit.core.importer.testexamples.dependents.ParentClassHoldingDependencies; import com.tngtech.archunit.core.importer.testexamples.dependents.SecondClassWithDependency; -import com.tngtech.archunit.core.importer.testexamples.dependents.SubClassHoldingDependencies; import com.tngtech.archunit.core.importer.testexamples.diamond.ClassCallingDiamond; import com.tngtech.archunit.core.importer.testexamples.diamond.ClassImplementingD; import com.tngtech.archunit.core.importer.testexamples.diamond.InterfaceB; @@ -150,6 +127,10 @@ import com.tngtech.archunit.core.importer.testexamples.hierarchicalmethodcall.SuperClassWithCalledMethod; import com.tngtech.archunit.core.importer.testexamples.innerclassimport.CalledClass; import com.tngtech.archunit.core.importer.testexamples.innerclassimport.ClassWithInnerClass; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInConstructor; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInMethod; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInStaticInitializer; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.InstanceofChecked; import com.tngtech.archunit.core.importer.testexamples.integration.ClassA; import com.tngtech.archunit.core.importer.testexamples.integration.ClassBDependingOnClassA; import com.tngtech.archunit.core.importer.testexamples.integration.ClassCDependingOnClassB_SuperClassOfX; @@ -215,13 +196,6 @@ import static com.tngtech.archunit.core.domain.TestUtils.asClasses; import static com.tngtech.archunit.core.domain.TestUtils.md5sumOf; import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; -import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.OTHER_VALUE; -import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.SOME_VALUE; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.enumAndArrayAnnotatedMethod; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithAnnotationFromParentPackage; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithEmptyArrays; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAndIntAnnotatedMethod; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAnnotatedMethod; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; import static com.tngtech.archunit.testutil.Assertions.assertThatCall; @@ -310,26 +284,6 @@ public void imports_simple_enum() throws Exception { .containsOnly(EnumToImport.FIRST.name(), EnumToImport.SECOND.name()); } - @Test - public void imports_simple_annotation() throws Exception { - JavaClass javaClass = classesIn("testexamples/simpleimport").get(AnnotationToImport.class); - - assertThat(javaClass.getName()).as("full name").isEqualTo(AnnotationToImport.class.getName()); - assertThat(javaClass.getSimpleName()).as("simple name").isEqualTo(AnnotationToImport.class.getSimpleName()); - assertThat(javaClass.getPackageName()).as("package name").isEqualTo(AnnotationToImport.class.getPackage().getName()); - assertThat(javaClass.getModifiers()).as("modifiers").containsOnly(JavaModifier.PUBLIC, JavaModifier.ABSTRACT); - assertThat(javaClass.getSuperClass()).as("super class").isAbsent(); - assertThatTypes(javaClass.getInterfaces()).as("interfaces").matchInAnyOrder(Annotation.class); - assertThat(javaClass.isInterface()).as("is interface").isTrue(); - assertThat(javaClass.isEnum()).as("is enum").isFalse(); - assertThat(javaClass.isAnnotation()).as("is annotation").isTrue(); - - assertThat(getAnnotationDefaultValue(javaClass, "someStringMethod", String.class)).isEqualTo("DEFAULT"); - assertThatType(getAnnotationDefaultValue(javaClass, "someTypeMethod", JavaClass.class)).matches(List.class); - assertThat(getAnnotationDefaultValue(javaClass, "someEnumMethod", JavaEnumConstant.class)).isEquivalentTo(EnumToImport.SECOND); - assertThatType(getAnnotationDefaultValue(javaClass, "someAnnotationMethod", JavaAnnotation.class).getRawType()).matches(AnnotationParameter.class); - } - @DataProvider public static Object[][] nested_static_classes() { return testForEach(ClassWithInnerClass.NestedStatic.class, ClassWithInnerClass.ImplicitlyNestedStatic.class); @@ -576,145 +530,6 @@ public void imports_fields_with_correct_modifiers() throws Exception { assertThat(findAnyByName(fields, "synchronizedField").getModifiers()).containsOnly(TRANSIENT); } - @Test - public void imports_annotation_defaults() throws Exception { - ImportedClasses classes = classesIn("testexamples/annotatedclassimport"); - - JavaClass annotationType = classes.get(TypeAnnotationWithEnumAndArrayValue.class); - assertThat((JavaEnumConstant) annotationType.getMethod("valueWithDefault") - .getDefaultValue().get()) - .as("default of valueWithDefault()").isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotationType.getMethod("enumArrayWithDefault") - .getDefaultValue().get())) - .as("default of enumArrayWithDefault()").matches(OTHER_VALUE); - assertThat(((JavaAnnotation) annotationType.getMethod("subAnnotationWithDefault") - .getDefaultValue().get()).get("value").get()) - .as("default of subAnnotationWithDefault()").isEqualTo("default"); - assertThat(((JavaAnnotation[]) annotationType.getMethod("subAnnotationArrayWithDefault") - .getDefaultValue().get())[0].get("value").get()) - .as("default of subAnnotationArrayWithDefault()").isEqualTo("first"); - assertThatType((JavaClass) annotationType.getMethod("clazzWithDefault") - .getDefaultValue().get()) - .as("default of clazzWithDefault()").matches(String.class); - assertThat((JavaClass[]) annotationType.getMethod("classesWithDefault") - .getDefaultValue().get()) - .as("default of clazzWithDefault()").matchExactly(Serializable.class, List.class); - } - - @Test - public void imports_fields_with_one_annotation_correctly() throws Exception { - ImportedClasses classes = classesIn("testexamples/annotationfieldimport"); - - JavaField field = findAnyByName(classes.getFields(), "stringAnnotatedField"); - JavaAnnotation annotation = field.getAnnotationOfType(FieldAnnotationWithStringValue.class.getName()); - assertThatType(annotation.getRawType()).isEqualTo(classes.get(FieldAnnotationWithStringValue.class)); - assertThat(annotation.get("value").get()).isEqualTo("something"); - assertThat(annotation.getOwner()).as("owning field").isEqualTo(field); - - assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAnnotatedField")); - } - - @Test - public void fields_handle_optional_annotation_correctly() throws Exception { - Set fields = classesIn("testexamples/annotationfieldimport").getFields(); - - JavaField field = findAnyByName(fields, "stringAnnotatedField"); - assertThat(field.tryGetAnnotationOfType(FieldAnnotationWithStringValue.class)).isPresent(); - assertThat(field.tryGetAnnotationOfType(FieldAnnotationWithEnumClassAndArrayValue.class)).isAbsent(); - - Optional> optionalAnnotation = field.tryGetAnnotationOfType(FieldAnnotationWithStringValue.class.getName()); - assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(field); - assertThat(field.tryGetAnnotationOfType(FieldAnnotationWithEnumClassAndArrayValue.class.getName())) - .as("optional annotation").isAbsent(); - } - - @Test - public void imports_fields_with_two_annotations_correctly() throws Exception { - Set fields = classesIn("testexamples/annotationfieldimport").getFields(); - - JavaField field = findAnyByName(fields, "stringAndIntAnnotatedField"); - Set> annotations = field.getAnnotations(); - assertThat(annotations).hasSize(2); - assertThat(annotations).extractingResultOf("getOwner").containsOnly(field); - assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(field); - - JavaAnnotation annotationWithString = field.getAnnotationOfType(FieldAnnotationWithStringValue.class.getName()); - assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); - - JavaAnnotation annotationWithInt = field.getAnnotationOfType(FieldAnnotationWithIntValue.class.getName()); - assertThat(annotationWithInt.get("intValue").get()).as("Annotation value with default").isEqualTo(0); - assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); - - assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAndIntAnnotatedField")); - } - - @Test - public void imports_fields_with_complex_annotations_correctly() throws Exception { - ImportedClasses classes = classesIn("testexamples/annotationfieldimport"); - - JavaField field = findAnyByName(classes.getFields(), "enumAndArrayAnnotatedField"); - - JavaAnnotation annotation = field.getAnnotationOfType(FieldAnnotationWithEnumClassAndArrayValue.class.getName()); - assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); - assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); - assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) - .isEqualTo("default"); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); - assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) - .isEqualTo("first"); - assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); - assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); - assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); - assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); - - assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("enumAndArrayAnnotatedField")); - } - - @Test - public void imports_fields_with_annotation_with_empty_array() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationfieldimport").get(ClassWithAnnotatedFields.class); - - JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithEmptyArrays") - .getAnnotationOfType(FieldAnnotationWithArrays.class.getName()); - - assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); - assertThat(Array.getLength(annotation.get("objects").get())).isZero(); - assertThat(Array.getLength(annotation.get("enums").get())).isZero(); - assertThat(Array.getLength(annotation.get("classes").get())).isZero(); - assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); - - FieldAnnotationWithArrays reflected = annotation.as(FieldAnnotationWithArrays.class); - assertThat(reflected.primitives()).isEmpty(); - assertThat(reflected.objects()).isEmpty(); - assertThat(reflected.enums()).isEmpty(); - assertThat(reflected.classes()).isEmpty(); - assertThat(reflected.annotations()).isEmpty(); - } - - @Test - public void imports_fields_annotated_with_unimported_annotation() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationfieldimport").get(ClassWithAnnotatedFields.class); - - JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithAnnotationFromParentPackage") - .getAnnotationOfType(SomeAnnotation.class.getName()); - - assertThat(annotation.get("mandatory")).contains("mandatory"); - assertThat(annotation.get("optional")).contains("optional"); - - SomeAnnotation reflected = annotation.as(SomeAnnotation.class); - assertThat(reflected.mandatory()).isEqualTo("mandatory"); - assertThat(reflected.optional()).isEqualTo("optional"); - } - @Test public void imports_simple_methods_with_correct_parameters() throws Exception { Set methods = classesIn("testexamples/methodimport").getMethods(); @@ -796,211 +611,6 @@ public void imports_members_with_sourceCodeLocation() throws Exception { .hasToString("(" + sourceFileName + ":27)"); } - @Test - public void imports_methods_with_one_annotation_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaMethod method = findAnyByName(clazz.getMethods(), stringAnnotatedMethod); - JavaAnnotation annotation = method.getAnnotationOfType(MethodAnnotationWithStringValue.class.getName()); - assertThatType(annotation.getRawType()).matches(MethodAnnotationWithStringValue.class); - assertThat(annotation.getOwner()).isEqualTo(method); - assertThat(annotation.get("value").get()).isEqualTo("something"); - - assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAnnotatedMethod)); - } - - @Test - public void methods_handle_optional_annotation_correctly() throws Exception { - Set methods = classesIn("testexamples/annotationmethodimport").getMethods(); - - JavaMethod method = findAnyByName(methods, "stringAnnotatedMethod"); - assertThat(method.tryGetAnnotationOfType(MethodAnnotationWithStringValue.class)).isPresent(); - assertThat(method.tryGetAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class)).isAbsent(); - - Optional> optionalAnnotation = method.tryGetAnnotationOfType(MethodAnnotationWithStringValue.class.getName()); - assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(method); - assertThat(method.tryGetAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class.getName())).isAbsent(); - } - - @Test - public void imports_methods_with_two_annotations_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaMethod method = findAnyByName(clazz.getMethods(), stringAndIntAnnotatedMethod); - Set> annotations = method.getAnnotations(); - assertThat(annotations).hasSize(2); - assertThat(annotations).extractingResultOf("getOwner").containsOnly(method); - assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(method); - - JavaAnnotation annotationWithString = method.getAnnotationOfType(MethodAnnotationWithStringValue.class.getName()); - assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); - - JavaAnnotation annotationWithInt = method.getAnnotationOfType(MethodAnnotationWithIntValue.class.getName()); - assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); - - assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAndIntAnnotatedMethod)); - } - - @Test - public void imports_methods_with_complex_annotations_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaMethod method = findAnyByName(clazz.getMethods(), enumAndArrayAnnotatedMethod); - - JavaAnnotation annotation = method.getAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class.getName()); - assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); - assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(method); - assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) - .isEqualTo("default"); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(method); - assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) - .isEqualTo("first"); - assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); - assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); - assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); - assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); - - assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(enumAndArrayAnnotatedMethod)); - } - - @Test - public void imports_method_with_annotation_with_empty_array() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithEmptyArrays) - .getAnnotationOfType(MethodAnnotationWithArrays.class.getName()); - - assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); - assertThat(Array.getLength(annotation.get("objects").get())).isZero(); - assertThat(Array.getLength(annotation.get("enums").get())).isZero(); - assertThat(Array.getLength(annotation.get("classes").get())).isZero(); - assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); - - MethodAnnotationWithArrays reflected = annotation.as(MethodAnnotationWithArrays.class); - assertThat(reflected.primitives()).isEmpty(); - assertThat(reflected.objects()).isEmpty(); - assertThat(reflected.enums()).isEmpty(); - assertThat(reflected.classes()).isEmpty(); - assertThat(reflected.annotations()).isEmpty(); - } - - @Test - public void imports_methods_annotated_with_unimported_annotation() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithAnnotationFromParentPackage) - .getAnnotationOfType(SomeAnnotation.class.getName()); - - assertThat(annotation.get("mandatory")).contains("mandatory"); - assertThat(annotation.get("optional")).contains("optional"); - - SomeAnnotation reflected = annotation.as(SomeAnnotation.class); - assertThat(reflected.mandatory()).isEqualTo("mandatory"); - assertThat(reflected.optional()).isEqualTo("optional"); - } - - @Test - public void imports_class_with_one_annotation_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithOneAnnotation.class); - - JavaAnnotation annotation = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); - assertThatType(annotation.getRawType()).matches(SimpleAnnotation.class); - assertThatType(annotation.getOwner()).isEqualTo(clazz); - - JavaAnnotation annotationByName = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); - assertThat(annotationByName).isEqualTo(annotation); - - assertThat(annotation.get("value").get()).isEqualTo("test"); - - assertThatType(clazz).matches(ClassWithOneAnnotation.class); - } - - @Test - public void class_handles_optional_annotation_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithOneAnnotation.class); - - assertThat(clazz.tryGetAnnotationOfType(SimpleAnnotation.class)).isPresent(); - assertThat(clazz.tryGetAnnotationOfType(Deprecated.class)).isAbsent(); - } - - @Test - public void imports_class_with_complex_annotations_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithComplexAnnotations.class); - - assertThat(clazz.getAnnotations()).as("annotations of " + clazz.getSimpleName()).hasSize(2); - - JavaAnnotation annotation = clazz.getAnnotationOfType(TypeAnnotationWithEnumAndArrayValue.class.getName()); - assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); - assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("sub"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(clazz); - assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) - .isEqualTo("default"); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("otherFirst"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(clazz); - assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) - .isEqualTo("first"); - assertThatType((JavaClass) annotation.get("clazz").get()).matches(Serializable.class); - assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); - assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Serializable.class, String.class); - assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); - - assertThatType(clazz).matches(ClassWithComplexAnnotations.class); - } - - @Test - public void imports_class_with_annotation_with_empty_array() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithAnnotationWithEmptyArrays.class); - - JavaAnnotation annotation = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class.getName()); - - assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); - assertThat(Array.getLength(annotation.get("objects").get())).isZero(); - assertThat(Array.getLength(annotation.get("enums").get())).isZero(); - assertThat(Array.getLength(annotation.get("classes").get())).isZero(); - assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); - - ClassAnnotationWithArrays reflected = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class); - assertThat(reflected.primitives()).isEmpty(); - assertThat(reflected.objects()).isEmpty(); - assertThat(reflected.enums()).isEmpty(); - assertThat(reflected.classes()).isEmpty(); - assertThat(reflected.annotations()).isEmpty(); - } - - @Test - public void imports_class_annotated_with_unimported_annotation() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithUnimportedAnnotation.class); - - JavaAnnotation annotation = clazz.getAnnotationOfType(SomeAnnotation.class.getName()); - - assertThat(annotation.get("mandatory")).contains("mandatory"); - assertThat(annotation.get("optional")).contains("optional"); - assertThat((JavaEnumConstant) annotation.get("mandatoryEnum").get()).isEquivalentTo(SOME_VALUE); - assertThat((JavaEnumConstant) annotation.get("optionalEnum").get()).isEquivalentTo(OTHER_VALUE); - - SomeAnnotation reflected = clazz.getAnnotationOfType(SomeAnnotation.class); - assertThat(reflected.mandatory()).isEqualTo("mandatory"); - assertThat(reflected.optional()).isEqualTo("optional"); - assertThat(reflected.mandatoryEnum()).isEqualTo(SOME_VALUE); - assertThat(reflected.optionalEnum()).isEqualTo(OTHER_VALUE); - } - @Test public void imports_simple_constructors_with_correct_parameters() throws Exception { JavaClass clazz = classesIn("testexamples/constructorimport").get(ClassWithSimpleConstructors.class); @@ -1046,27 +656,6 @@ public void imports_constructor_with_correct_throws_declarations() throws Except assertThat(constructor.getExceptionTypes()).matches(FirstCheckedException.class, SecondCheckedException.class); } - @Test - public void imports_constructors_with_complex_annotations_correctly() throws Exception { - JavaConstructor constructor = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class) - .getConstructor(); - - JavaAnnotation annotation = constructor.getAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class.getName()); - assertThat((Object[]) annotation.get("classes").get()).extracting("name") - .containsExactly(Object.class.getName(), Serializable.class.getName()); - assertThat(annotation.getOwner()).isEqualTo(constructor); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(constructor); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(constructor); - - assertThat(constructor).isEquivalentTo(ClassWithAnnotatedMethods.class.getConstructor()); - } - @Test public void imports_interfaces_and_classes() throws Exception { ImportedClasses classes = classesIn("testexamples/classhierarchyimport"); @@ -1793,6 +1382,36 @@ public void classes_know_the_field_accesses_to_them() throws Exception { assertThat(accesses).as("Field Accesses to class").isEqualTo(expected); } + @Test + public void classes_know_shadowed_field_accesses_to_themselves() { + @SuppressWarnings("unused") + class Base { + String shadowed; + String nonShadowed; + } + class Child extends Base { + String shadowed; + } + @SuppressWarnings("unused") + class Accessor { + void access(Child child) { + consume(child.shadowed); + consume(child.nonShadowed); + } + + void consume(String string) { + } + } + JavaClasses classes = new ClassFileImporter().importClasses(Accessor.class, Base.class, Child.class); + + JavaFieldAccess access = getOnlyByCaller( + classes.get(Base.class).getFieldAccessesToSelf(), classes.get(Accessor.class).getMethod("access", Child.class)); + assertThatAccess(access).isFrom("access", Child.class).isTo("nonShadowed"); + access = getOnlyByCaller( + classes.get(Child.class).getFieldAccessesToSelf(), classes.get(Accessor.class).getMethod("access", Child.class)); + assertThatAccess(access).isFrom("access", Child.class).isTo("shadowed"); + } + @Test public void methods_know_callers() throws Exception { ImportedClasses classes = classesIn("testexamples/dependents"); @@ -1825,6 +1444,34 @@ public void classes_know_method_calls_to_themselves() throws Exception { assertThat(calls).as("Method calls to class").isEqualTo(expected); } + @Test + public void classes_know_overridden_method_calls_to_themselves() { + @SuppressWarnings("unused") + class Base { + void overridden() { + } + + void nonOverridden() { + } + } + class Child extends Base { + @Override + void overridden() { + } + } + @SuppressWarnings("unused") + class Caller { + void call(Child child) { + child.overridden(); + child.nonOverridden(); + } + } + JavaClasses classes = new ClassFileImporter().importClasses(Caller.class, Base.class, Child.class); + + assertThatCall(getOnlyElement(classes.get(Base.class).getMethodCallsToSelf())).isFrom("call", Child.class).isTo("nonOverridden"); + assertThatCall(getOnlyElement(classes.get(Child.class).getMethodCallsToSelf())).isFrom("call", Child.class).isTo("overridden"); + } + @Test public void constructors_know_callers() throws Exception { ImportedClasses classes = classesIn("testexamples/dependents"); @@ -1858,6 +1505,30 @@ public void classes_know_constructor_calls_to_themselves() throws Exception { assertThat(calls).as("Constructor calls to ClassWithDependents").isEqualTo(expected); } + @Test + public void classes_know_constructor_calls_to_themselves_for_subclass_default_constructors() { + // For constructors it's impossible to be accessed via a subclass, + // since the byte code always holds an explicitly declared constructor. + // Thus we do expect a call to the constructor of the subclass and one from subclass to super class + @SuppressWarnings("unused") + class Base { + Base() { + } + } + class Child extends Base { + } + @SuppressWarnings("unused") + class Caller { + void call() { + new Child(); + } + } + JavaClasses classes = new ClassFileImporter().importClasses(Caller.class, Base.class, Child.class); + + assertThatCall(getOnlyElement(classes.get(Base.class).getConstructorCallsToSelf())).isFrom(Child.class, CONSTRUCTOR_NAME, getClass()).isTo(Base.class); + assertThatCall(getOnlyElement(classes.get(Child.class).getConstructorCallsToSelf())).isFrom(Caller.class, "call").isTo(Child.class); + } + @Test public void classes_know_accesses_to_themselves() throws Exception { ImportedClasses classes = classesIn("testexamples/dependents"); @@ -1874,37 +1545,6 @@ public void classes_know_accesses_to_themselves() throws Exception { assertThat(accesses).as("Accesses to ClassWithDependents").isEqualTo(expected); } - @Test - public void inherited_field_accesses_and_method_calls_are_resolved() throws Exception { - ImportedClasses classes = classesIn("testexamples/dependents"); - JavaClass classHoldingDependencies = classes.get(ParentClassHoldingDependencies.class); - JavaClass subClassHoldingDependencies = classes.get(SubClassHoldingDependencies.class); - JavaClass dependentClass = classes.get(ClassDependingOnParentThroughChild.class); - - Set fieldAccessesToSelf = classHoldingDependencies.getFieldAccessesToSelf(); - Set expectedFieldAccesses = - getByTargetNot(dependentClass.getFieldAccessesFromSelf(), dependentClass); - assertThat(fieldAccessesToSelf).as("Field accesses to class").isEqualTo(expectedFieldAccesses); - - Set methodCalls = classHoldingDependencies.getMethodCallsToSelf(); - Set expectedMethodCalls = - getByTargetNot(dependentClass.getMethodCallsFromSelf(), dependentClass); - assertThat(methodCalls).as("Method calls to class").isEqualTo(expectedMethodCalls); - - // NOTE: For constructors it's impossible to be accessed via a subclass, - // since the byte code always holds an explicitly declared constructor - - Set constructorCalls = classHoldingDependencies.getConstructorCallsToSelf(); - Set expectedConstructorCalls = - getByTargetOwner(subClassHoldingDependencies.getConstructorCallsFromSelf(), classHoldingDependencies.getName()); - assertThat(constructorCalls).as("Constructor calls to class").isEqualTo(expectedConstructorCalls); - - constructorCalls = subClassHoldingDependencies.getConstructorCallsToSelf(); - expectedConstructorCalls = - getByTargetOwner(dependentClass.getConstructorCallsFromSelf(), subClassHoldingDependencies.getName()); - assertThat(constructorCalls).as("Constructor calls to class").isEqualTo(expectedConstructorCalls); - } - @Test public void classes_know_which_fields_have_their_type() { JavaClasses classes = new ClassFileImporter().importClasses(SomeClass.class, OtherClass.class, SomeEnum.class); @@ -1958,12 +1598,14 @@ public void classes_know_which_constructor_throws_clauses_contain_their_type() { } @Test - public void classes_know_which_annotations_have_their_type() { - JavaClasses classes = new ClassFileImporter().importClasses(ClassWithOneAnnotation.class, SimpleAnnotation.class); - - Set> annotations = classes.get(SimpleAnnotation.class).getAnnotationsWithTypeOfSelf(); + public void classes_know_which_instanceof_checks_check_their_type() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(InstanceofChecked.class).get(InstanceofChecked.class); - assertThat(getOnlyElement(annotations).getOwner()).isEqualTo(classes.get(ClassWithOneAnnotation.class)); + Set origins = new HashSet<>(); + for (InstanceofCheck instanceofCheck : clazz.getInstanceofChecksWithTypeOfSelf()) { + origins.add(instanceofCheck.getOwner().getOwner()); + } + assertThatTypes(origins).matchInAnyOrder(ChecksInstanceofInMethod.class, ChecksInstanceofInConstructor.class, ChecksInstanceofInStaticInitializer.class); } @Test @@ -2294,10 +1936,6 @@ public boolean apply(JavaAccess input) { }); } - private > Set getByTargetNot(Set accesses, JavaClass target) { - return getBy(accesses, not(targetOwnerNameEquals(target.getName()))); - } - private > Set getByTargetOwner(Set calls, Class targetOwner) { return getByTargetOwner(calls, targetOwner.getName()); } @@ -2368,11 +2006,6 @@ private T findAnyByName(Iterable thingsWithName, String n return checkNotNull(result, "No object with name '" + name + "' is present in " + thingsWithName); } - @SuppressWarnings({"unchecked", "unused"}) - private static T getAnnotationDefaultValue(JavaClass javaClass, String methodName, Class valueType) { - return (T) javaClass.getMethod(methodName).getDefaultValue().get(); - } - private ImportedClasses classesIn(String path) throws Exception { return new ImportedClasses(path); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java index 67f863548d..098d9bdbcb 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java @@ -24,7 +24,6 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.DomainObjectCreationContext; import com.tngtech.archunit.core.domain.ImportContext; -import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; @@ -40,7 +39,6 @@ import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaStaticInitializer; import com.tngtech.archunit.core.domain.JavaTypeVariable; -import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import org.objectweb.asm.Type; @@ -384,62 +382,17 @@ public Optional createEnclosingClass(JavaClass owner) { } @Override - public Set getFieldAccessesFor(JavaCodeUnit codeUnit) { + public Set createFieldAccessesFor(JavaCodeUnit codeUnit) { return Collections.emptySet(); } @Override - public Set getMethodCallsFor(JavaCodeUnit codeUnit) { + public Set createMethodCallsFor(JavaCodeUnit codeUnit) { return Collections.emptySet(); } @Override - public Set getConstructorCallsFor(JavaCodeUnit codeUnit) { - return Collections.emptySet(); - } - - @Override - public Set getFieldsOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set getMethodsWithParameterOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set getMethodsWithReturnType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set getConstructorsWithParameterOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set> getAnnotationsOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set> getAnnotationsWithParameterOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set getInstanceofChecksOfType(JavaClass javaClass) { + public Set createConstructorCallsFor(JavaCodeUnit codeUnit) { return Collections.emptySet(); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ClassDependingOnParentThroughChild.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ClassDependingOnParentThroughChild.java deleted file mode 100644 index 1a909df4a1..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ClassDependingOnParentThroughChild.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.tngtech.archunit.core.importer.testexamples.dependents; - -public class ClassDependingOnParentThroughChild { - SubClassHoldingDependencies classHoldingDependencies; - - void doSomething() { - classHoldingDependencies = new SubClassHoldingDependencies(); - classHoldingDependencies.parentField = new Object(); - classHoldingDependencies.parentMethod(); - } -} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ParentClassHoldingDependencies.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ParentClassHoldingDependencies.java deleted file mode 100644 index 0efb4828bd..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ParentClassHoldingDependencies.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.tngtech.archunit.core.importer.testexamples.dependents; - -public class ParentClassHoldingDependencies { - Object parentField; - - public ParentClassHoldingDependencies() { - } - - void parentMethod() { - } -} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/SubClassHoldingDependencies.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/SubClassHoldingDependencies.java deleted file mode 100644 index 32e5db4451..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/SubClassHoldingDependencies.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tngtech.archunit.core.importer.testexamples.dependents; - -public class SubClassHoldingDependencies extends ParentClassHoldingDependencies { -} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInConstructor.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInConstructor.java new file mode 100644 index 0000000000..9b47abb5f9 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInConstructor.java @@ -0,0 +1,9 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +@SuppressWarnings("StatementWithEmptyBody") +public class ChecksInstanceofInConstructor { + public ChecksInstanceofInConstructor(Object param) { + if (param instanceof InstanceofChecked) { + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInMethod.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInMethod.java new file mode 100644 index 0000000000..70b850e14a --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInMethod.java @@ -0,0 +1,8 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +@SuppressWarnings("unused") +public class ChecksInstanceofInMethod { + boolean method(Object param) { + return param instanceof InstanceofChecked; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInStaticInitializer.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInStaticInitializer.java new file mode 100644 index 0000000000..ea9620d30b --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInStaticInitializer.java @@ -0,0 +1,8 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +@SuppressWarnings({"unused", "ConstantConditions"}) +public class ChecksInstanceofInStaticInitializer { + static { + boolean foo = ((Object) null) instanceof InstanceofChecked; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/InstanceofChecked.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/InstanceofChecked.java new file mode 100644 index 0000000000..f8e9164a05 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/InstanceofChecked.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +public class InstanceofChecked { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index 40159c0b6e..7254ca00d4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -43,8 +43,8 @@ import com.tngtech.archunit.testutil.assertion.DependenciesAssertion; import com.tngtech.archunit.testutil.assertion.DependencyAssertion; import com.tngtech.archunit.testutil.assertion.DescribedPredicateAssertion; -import com.tngtech.archunit.testutil.assertion.JavaClassDescriptorAssertion; import com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion; +import com.tngtech.archunit.testutil.assertion.JavaClassDescriptorAssertion; import com.tngtech.archunit.testutil.assertion.JavaCodeUnitAssertion; import com.tngtech.archunit.testutil.assertion.JavaConstructorAssertion; import com.tngtech.archunit.testutil.assertion.JavaFieldAssertion; @@ -379,8 +379,13 @@ public SELF isFrom(String name, Class... parameterTypes) { return isFrom(access.getOrigin().getOwner().getCodeUnitWithParameterTypes(name, parameterTypes)); } + public SELF isFrom(Class originClass, String name, Class... parameterTypes) { + assertThatType(access.getOriginOwner()).matches(originClass); + return isFrom(name, parameterTypes); + } + public SELF isFrom(JavaCodeUnit codeUnit) { - assertThat(access.getOrigin()).as("Origin of field access").isEqualTo(codeUnit); + assertThat(access.getOrigin()).as("Origin of access").isEqualTo(codeUnit); return newAssertion(access); } @@ -413,8 +418,13 @@ protected AccessToFieldAssertion newAssertion(JavaFieldAccess access) { return new AccessToFieldAssertion(access); } - public AccessToFieldAssertion isTo(String name) { - return isTo(access.getTarget().getOwner().getField(name)); + public AccessToFieldAssertion isTo(final String name) { + return isTo(new Condition("field with name '" + name + "'") { + @Override + public boolean matches(FieldAccessTarget fieldAccessTarget) { + return fieldAccessTarget.getName().equals(name); + } + }); } public AccessToFieldAssertion isTo(JavaField field) { @@ -436,6 +446,16 @@ public MethodCallAssertion isTo(JavaMethod target) { return isTo(resolvedTargetFrom(target)); } + public MethodCallAssertion isTo(final String methodName, final Class... parameterTypes) { + return isTo(new Condition("method " + methodName + "(" + namesOf(parameterTypes) + ")") { + @Override + public boolean matches(MethodCallTarget methodCallTarget) { + return methodCallTarget.getName().equals(methodName) + && TestUtils.namesOf(methodCallTarget.getRawParameterTypes()).equals(namesOf(parameterTypes)); + } + }); + } + @Override protected MethodCallAssertion newAssertion(JavaMethodCall call) { return new MethodCallAssertion(call); @@ -451,6 +471,15 @@ public ConstructorCallAssertion isTo(JavaConstructor target) { return isTo(targetFrom(target)); } + public ConstructorCallAssertion isTo(final Class constructorOwner) { + return isTo(new Condition() { + @Override + public boolean matches(ConstructorCallTarget constructorCallTarget) { + return constructorCallTarget.getOwner().isEquivalentTo(constructorOwner); + } + }); + } + @Override protected ConstructorCallAssertion newAssertion(JavaConstructorCall call) { return new ConstructorCallAssertion(call); diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java index cd7471b174..66c6d1c694 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java @@ -31,7 +31,7 @@ public JavaAnnotationAssertion(JavaAnnotation actual) { super(actual, JavaAnnotationAssertion.class); } - private JavaAnnotationAssertion hasType(Class annotationType) { + public JavaAnnotationAssertion hasType(Class annotationType) { assertThatType(actual.getRawType()) .as("annotation type of " + descriptionText()) .matches(annotationType);