diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b8687c515..795cdd5c10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,6 +55,7 @@ jobs: uses: gradle/gradle-build-action@v2 with: arguments: test -PallTests -PtestJavaVersion=${{ matrix.test_java_version }} + cache-disabled: true integration-test: strategy: diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/AccessTargetNewerJavaVersionTest.java b/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/AccessTargetNewerJavaVersionTest.java deleted file mode 100644 index 89c7317983..0000000000 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/AccessTargetNewerJavaVersionTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.tngtech.archunit.core.domain; - -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import com.google.common.collect.ImmutableSet; -import com.tngtech.archunit.core.domain.AccessTarget.CodeUnitAccessTarget; -import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; -import com.tngtech.archunit.core.domain.AccessTarget.ConstructorReferenceTarget; -import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; -import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; -import com.tngtech.archunit.core.domain.AccessTarget.MethodReferenceTarget; -import com.tngtech.archunit.core.importer.ClassFileImporter; -import org.junit.Test; - -import static com.tngtech.archunit.testutil.Assertions.assertThat; - -public class AccessTargetNewerJavaVersionTest { - - private static class Data_function_resolve_member { - static class Target { - String field; - - void method() { - } - } - } - - @Test - public void function_resolve_member() { - @SuppressWarnings("unused") - class Origin { - String access() { - Data_function_resolve_member.Target target = new Data_function_resolve_member.Target(); - Supplier supplier = Data_function_resolve_member.Target::new; - Consumer consumer = Data_function_resolve_member.Target::method; - target.method(); - return target.field; - } - } - JavaClass targetClass = new ClassFileImporter().importClasses(Origin.class, Data_function_resolve_member.Target.class).get(Data_function_resolve_member.Target.class); - MethodCallTarget methodCallTarget = findTargetWithType(targetClass.getAccessesToSelf(), MethodCallTarget.class); - - assertThat(AccessTarget.Functions.RESOLVE_MEMBER.apply(methodCallTarget)) - .contains(methodCallTarget.resolveMember().get()); - assertThat(AccessTarget.Functions.RESOLVE.apply(methodCallTarget)) - .isEqualTo(ImmutableSet.of(methodCallTarget.resolveMember().get())); - - assertThat(CodeUnitAccessTarget.Functions.RESOLVE_MEMBER.apply(methodCallTarget)) - .contains(methodCallTarget.resolveMember().get()); - assertThat(CodeUnitAccessTarget.Functions.RESOLVE.apply(methodCallTarget)) - .isEqualTo(ImmutableSet.of(methodCallTarget.resolveMember().get())); - - assertThat(MethodCallTarget.Functions.RESOLVE_MEMBER.apply(methodCallTarget)) - .contains(methodCallTarget.resolveMember().get()); - assertThat(MethodCallTarget.Functions.RESOLVE.apply(methodCallTarget)) - .isEqualTo(ImmutableSet.of(methodCallTarget.resolveMember().get())); - - MethodReferenceTarget methodReferenceTarget = findTargetWithType(targetClass.getAccessesToSelf(), MethodReferenceTarget.class); - - assertThat(MethodReferenceTarget.Functions.RESOLVE_MEMBER.apply(methodReferenceTarget)) - .contains(methodReferenceTarget.resolveMember().get()); - - ConstructorCallTarget constructorCallTarget = findTargetWithType(targetClass.getAccessesToSelf(), ConstructorCallTarget.class); - - assertThat(ConstructorCallTarget.Functions.RESOLVE_MEMBER.apply(constructorCallTarget)) - .contains(constructorCallTarget.resolveMember().get()); - assertThat(ConstructorCallTarget.Functions.RESOLVE.apply(constructorCallTarget)) - .isEqualTo(ImmutableSet.of(constructorCallTarget.resolveMember().get())); - - ConstructorReferenceTarget constructorReferenceTarget = findTargetWithType(targetClass.getAccessesToSelf(), ConstructorReferenceTarget.class); - - assertThat(ConstructorReferenceTarget.Functions.RESOLVE_MEMBER.apply(constructorReferenceTarget)) - .contains(constructorReferenceTarget.resolveMember().get()); - - FieldAccessTarget fieldAccessTarget = findTargetWithType(targetClass.getAccessesToSelf(), FieldAccessTarget.class); - - assertThat(FieldAccessTarget.Functions.RESOLVE_MEMBER.apply(fieldAccessTarget)) - .contains(fieldAccessTarget.resolveMember().get()); - assertThat(FieldAccessTarget.Functions.RESOLVE.apply(fieldAccessTarget)) - .isEqualTo(ImmutableSet.of(fieldAccessTarget.resolveMember().get())); - - } - - @SuppressWarnings("unchecked") - private T findTargetWithType(Set> set, Class type) { - for (JavaAccess access : set) { - if (type.isInstance(access.getTarget())) { - return (T) access.getTarget(); - } - } - throw new AssertionError(String.format("Set %s does not contain element of type %s", set, type.getName())); - } -} diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/DependencyReferencesTest.java b/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/DependencyReferencesTest.java deleted file mode 100644 index ab4f9a40a7..0000000000 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/DependencyReferencesTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.tngtech.archunit.core.domain; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -import com.tngtech.archunit.core.importer.ClassFileImporter; -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.tngtech.archunit.testutil.Assertions.assertThatType; -import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(DataProviderRunner.class) -public class DependencyReferencesTest { - - static class Data_dependency_from_access { - static class Target { - String field; - - void callMe() { - } - } - } - - @DataProvider - public static Object[][] data_dependency_from_access() { - @SuppressWarnings("unused") - class Origin { - String fieldAccess(Data_dependency_from_access.Target target) { - return target.field; - } - - void constructorCall() { - new Data_dependency_from_access.Target(); - } - - void methodCall(Data_dependency_from_access.Target target) { - target.callMe(); - } - - Supplier constructorReference() { - return Data_dependency_from_access.Target::new; - } - - Consumer methodReference() { - return Data_dependency_from_access.Target::callMe; - } - } - JavaClass origin = new ClassFileImporter().importClasses(Origin.class, Data_dependency_from_access.Target.class).get(Origin.class); - return testForEach( - getOnlyElement(origin.getMethod("fieldAccess", Data_dependency_from_access.Target.class).getFieldAccesses()), - getOnlyElement(origin.getMethod("constructorCall").getConstructorCallsFromSelf()), - getOnlyElement(origin.getMethod("methodCall", Data_dependency_from_access.Target.class).getMethodCallsFromSelf()), - getOnlyElement(origin.getMethod("constructorReference").getConstructorReferencesFromSelf()), - getOnlyElement(origin.getMethod("methodReference").getMethodReferencesFromSelf()) - ); - } - - @Test - @UseDataProvider - public void test_dependency_from_access(JavaAccess access) { - Dependency dependency = getOnlyElement(Dependency.tryCreateFromAccess(access)); - assertThatType(dependency.getTargetClass()).as("target class").isEqualTo(access.getTargetOwner()); - assertThat(dependency.getDescription()).as("description").isEqualTo(access.getDescription()); - } -} diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/JavaClassReferencesTest.java b/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/JavaClassReferencesTest.java deleted file mode 100644 index 36e0ccd407..0000000000 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/JavaClassReferencesTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.tngtech.archunit.core.domain; - -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import com.tngtech.archunit.core.domain.JavaClassTest.DependencyConditionCreation; -import com.tngtech.archunit.core.domain.testexamples.AReferencingB; -import com.tngtech.archunit.core.domain.testexamples.BReferencedByA; -import com.tngtech.archunit.core.importer.ClassFileImporter; -import org.junit.Test; - -import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; -import static com.tngtech.archunit.core.domain.TestUtils.importClasses; -import static org.assertj.core.api.Assertions.assertThat; - -public class JavaClassReferencesTest { - - @Test - public void direct_dependencies_from_self_by_references() { - JavaClass javaClass = importClasses(AReferencingB.class, BReferencedByA.class).get(AReferencingB.class); - - assertReferencesFromAToB(javaClass.getDirectDependenciesFromSelf()); - } - - @Test - public void direct_dependencies_to_self_by_references() { - JavaClass javaClass = importClasses(AReferencingB.class, BReferencedByA.class).get(BReferencedByA.class); - - assertReferencesFromAToB(javaClass.getDirectDependenciesToSelf()); - } - - @Test - public void function_get_code_unit_references_from_self() { - class Target { - void target() { - } - } - @SuppressWarnings("unused") - class Origin { - Consumer origin() { - Supplier supplier = Target::new; - return Target::target; - } - } - - JavaClass javaClass = new ClassFileImporter().importClasses(Origin.class, Target.class).get(Origin.class); - - assertThat(JavaClass.Functions.GET_CODE_UNIT_REFERENCES_FROM_SELF.apply(javaClass)).isEqualTo(javaClass.getCodeUnitReferencesFromSelf()); - assertThat(JavaClass.Functions.GET_METHOD_REFERENCES_FROM_SELF.apply(javaClass)).isEqualTo(javaClass.getMethodReferencesFromSelf()); - assertThat(JavaClass.Functions.GET_CONSTRUCTOR_REFERENCES_FROM_SELF.apply(javaClass)).isEqualTo(javaClass.getConstructorReferencesFromSelf()); - } - - @Test - public void function_get_code_unit_references_to_self() { - class Target { - void target() { - } - } - @SuppressWarnings("unused") - class Origin { - Consumer origin() { - Supplier supplier = Target::new; - return Target::target; - } - } - - JavaClass javaClass = new ClassFileImporter().importClasses(Origin.class, Target.class).get(Target.class); - - assertThat(JavaClass.Functions.GET_CODE_UNIT_REFERENCES_TO_SELF.apply(javaClass)).isEqualTo(javaClass.getCodeUnitReferencesToSelf()); - assertThat(JavaClass.Functions.GET_METHOD_REFERENCES_TO_SELF.apply(javaClass)).isEqualTo(javaClass.getMethodReferencesToSelf()); - assertThat(JavaClass.Functions.GET_CONSTRUCTOR_REFERENCES_TO_SELF.apply(javaClass)).isEqualTo(javaClass.getConstructorReferencesToSelf()); - } - - private void assertReferencesFromAToB(Set dependencies) { - assertThat(dependencies) - .areAtLeastOne(referenceDependency() - .from(AReferencingB.class) - .to(BReferencedByA.class, CONSTRUCTOR_NAME) - .inLineNumber(9)) - .areAtLeastOne(referenceDependency() - .from(AReferencingB.class) - .to(BReferencedByA.class, CONSTRUCTOR_NAME) - .inLineNumber(10)) - .areAtLeastOne(referenceDependency() - .from(AReferencingB.class) - .to(BReferencedByA.class, "getSomeField") - .inLineNumber(14)) - .areAtLeastOne(referenceDependency() - .from(AReferencingB.class) - .to(BReferencedByA.class, "getSomeField") - .inLineNumber(15)) - .areAtLeastOne(referenceDependency() - .from(AReferencingB.class) - .to(BReferencedByA.class, "getNothing") - .inLineNumber(16)); - } - - private DependencyConditionCreation referenceDependency() { - return new DependencyConditionCreation("references"); - } -} diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesNewerJavaVersionTest.java b/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesNewerJavaVersionTest.java new file mode 100644 index 0000000000..d1c2262787 --- /dev/null +++ b/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesNewerJavaVersionTest.java @@ -0,0 +1,40 @@ +package com.tngtech.archunit.core.importer; + +import java.util.function.Supplier; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(DataProviderRunner.class) +public class ClassFileImporterCodeUnitReferencesNewerJavaVersionTest { + /** + * A local class constructor obtains extra parameters from the outer scope that the compiler transparently adds + * to the byte code. A reference to this local constructor will then always be translated to a lambda call. + * Thus, in this case we do not expect a constructor reference. + * + * Note that this actually does not compile with JDK 8 + */ + @Test + public void does_not_import_local_constructor_references() { + @SuppressWarnings("unused") + class ReferencedTarget { + ReferencedTarget() { + } + } + @SuppressWarnings("unused") + class Origin { + void referencesConstructor() { + Supplier a = ReferencedTarget::new; + } + } + + JavaClasses javaClasses = new ClassFileImporter().importClasses(Origin.class, ReferencedTarget.class); + + assertThat(javaClasses.get(Origin.class).getMethod("referencesConstructor").getConstructorReferencesFromSelf()).isEmpty(); + assertThat(javaClasses.get(ReferencedTarget.class).getConstructor(ClassFileImporterCodeUnitReferencesNewerJavaVersionTest.class).getReferencesToSelf()).isEmpty(); + } +} diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaAccessesNewerJavaVersionTest.java b/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaAccessesNewerJavaVersionTest.java new file mode 100644 index 0000000000..b882fce4e4 --- /dev/null +++ b/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaAccessesNewerJavaVersionTest.java @@ -0,0 +1,51 @@ +package com.tngtech.archunit.core.importer; + +import java.util.Set; +import java.util.function.Supplier; + +import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaConstructorCall; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; +import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; +import static java.util.stream.Collectors.toSet; + +@RunWith(DataProviderRunner.class) +public class ClassFileImporterLambdaAccessesNewerJavaVersionTest { + /** + * This is a special case: For local constructors the Java compiler actually adds a lambda calling the constructor + * for a constructor reference. Since we do not distinguish between calls from within a lambda and outside it, + * this will lead to such a constructor reference being reported as a constructor call. + * + * Note that this actually does not compile with JDK 8 + */ + @Test + public void imports_constructor_reference_to_local_class_from_lambda_without_parameter_as_direct_call() { + class Target { + } + + @SuppressWarnings("unused") + class Caller { + Supplier> call() { + return () -> Target::new; + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + JavaConstructorCall call = getOnlyElement( + filterOriginByName(classes.get(Caller.class).getConstructorCallsFromSelf(), "call")); + + assertThatAccess(call).isFrom("call").isTo(Target.class, CONSTRUCTOR_NAME, getClass()); + } + + private > Set filterOriginByName(Set calls, String methodName) { + return calls.stream() + .filter(call -> call.getOrigin().getName().equals(methodName)) + .collect(toSet()); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java index 6bc7c0e3c2..0870415f88 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.core.domain; -import java.util.Objects; import java.util.Set; import com.tngtech.archunit.PublicAPI; @@ -26,7 +25,7 @@ import com.tngtech.archunit.core.domain.properties.HasOwner; import com.tngtech.archunit.core.domain.properties.HasOwner.Functions.Get; import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; -import com.tngtech.archunit.core.importer.DomainBuilders; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaAccessBuilder; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -38,15 +37,15 @@ public abstract class JavaAccess private final JavaCodeUnit origin; private final TARGET target; private final int lineNumber; - private final int hashCode; private final SourceCodeLocation sourceCodeLocation; + private final boolean declaredInLambda; - JavaAccess(DomainBuilders.JavaAccessBuilder builder) { + JavaAccess(JavaAccessBuilder builder) { this.origin = checkNotNull(builder.getOrigin()); this.target = checkNotNull(builder.getTarget()); this.lineNumber = builder.getLineNumber(); - this.hashCode = Objects.hash(origin.getFullName(), target.getFullName(), lineNumber); this.sourceCodeLocation = SourceCodeLocation.of(getOriginOwner(), lineNumber); + this.declaredInLambda = builder.isDeclaredInLambda(); } @Override @@ -92,23 +91,9 @@ public SourceCodeLocation getSourceCodeLocation() { return sourceCodeLocation; } - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final JavaAccess other = (JavaAccess) obj; - return Objects.equals(this.origin.getFullName(), other.origin.getFullName()) && - Objects.equals(this.target.getFullName(), other.target.getFullName()) && - Objects.equals(this.lineNumber, other.lineNumber); + @PublicAPI(usage = ACCESS) + public boolean isDeclaredInLambda() { + return declaredInLambda; } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaFieldAccess.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaFieldAccess.java index 67c946f7f9..f7e391c277 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaFieldAccess.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaFieldAccess.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.domain; import java.util.Map; -import java.util.Objects; import com.google.common.collect.ImmutableMap; import com.tngtech.archunit.PublicAPI; @@ -48,23 +47,6 @@ public AccessType getAccessType() { return accessType; } - @Override - public int hashCode() { - return 31 * super.hashCode() + accessType.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final JavaFieldAccess other = (JavaFieldAccess) obj; - return super.equals(other) && Objects.equals(this.accessType, other.accessType); - } - @Override protected String additionalToStringFields() { return ", accessType=" + accessType; 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 385c815eb3..7441194918 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 @@ -54,6 +54,8 @@ interface AccessRecord { int getLineNumber(); + boolean isDeclaredInLambda(); + RawAccessRecord getRaw(); @Internal @@ -263,6 +265,11 @@ public int getLineNumber() { return record.lineNumber; } + @Override + public boolean isDeclaredInLambda() { + return record.declaredInLambda; + } + @Override public RawAccessRecord getRaw() { return record; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index 385ed40076..bc99145552 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -15,19 +15,18 @@ */ package com.tngtech.archunit.core.importer; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.stream.Stream; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.core.domain.JavaClass; @@ -36,23 +35,27 @@ import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaClassTypeParametersBuilder; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaCodeUnitBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaFieldBuilder; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaMemberBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; +import com.tngtech.archunit.core.importer.RawAccessRecord.MemberSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import static com.tngtech.archunit.core.importer.JavaClassDescriptorImporter.isLambdaMethodName; +import static java.util.Collections.emptyList; class ClassFileImportRecord { + private static final Logger log = LoggerFactory.getLogger(ClassFileImportRecord.class); + private static final JavaClassTypeParametersBuilder NO_TYPE_PARAMETERS = - new JavaClassTypeParametersBuilder(Collections.>emptyList()); + new JavaClassTypeParametersBuilder(emptyList()); private final Map classes = new HashMap<>(); @@ -75,6 +78,7 @@ class ClassFileImportRecord { private final Set rawConstructorCallRecords = new HashSet<>(); private final Set rawMethodReferenceRecords = new HashSet<>(); private final Set rawConstructorReferenceRecords = new HashSet<>(); + private final Map rawSyntheticLambdaMethodInvocationRecordsByTarget = new HashMap<>(); void setSuperclass(String ownerName, String superclassName) { checkState(!superclassNamesByOwner.containsKey(ownerName), @@ -181,54 +185,10 @@ Optional getStaticInitializerBuilderFor(String own return Optional.ofNullable(staticInitializerBuildersByOwner.get(ownerName)); } - Set getAnnotationTypeNamesFor(JavaClass owner) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (JavaAnnotationBuilder annotationBuilder : annotationsByOwner.get(owner.getName())) { - result.add(annotationBuilder.getFullyQualifiedClassName()); - } - return result.build(); - } - Set getAnnotationsFor(JavaClass owner) { return annotationsByOwner.get(owner.getName()); } - Set getMemberAnnotationTypeNamesFor(JavaClass owner) { - Iterable> memberBuilders = Iterables.concat( - fieldBuildersByOwner.get(owner.getName()), - methodBuildersByOwner.get(owner.getName()), - constructorBuildersByOwner.get(owner.getName()), - nullToEmpty(staticInitializerBuildersByOwner.get(owner.getName()))); - - ImmutableSet.Builder result = ImmutableSet.builder(); - for (JavaMemberBuilder memberBuilder : memberBuilders) { - for (JavaAnnotationBuilder annotationBuilder : annotationsByOwner.get(getMemberKey(owner.getName(), memberBuilder.getName(), memberBuilder.getDescriptor()))) { - result.add(annotationBuilder.getFullyQualifiedClassName()); - } - } - return result.build(); - } - - Set getParameterAnnotationTypeNamesFor(JavaClass owner) { - Iterable> codeUnitBuilders = Iterables.>concat( - methodBuildersByOwner.get(owner.getName()), - constructorBuildersByOwner.get(owner.getName())); - - ImmutableSet.Builder result = ImmutableSet.builder(); - for (JavaCodeUnitBuilder codeUnitBuilder : codeUnitBuilders) { - for (JavaAnnotationBuilder annotationBuilder : codeUnitBuilder.getParameterAnnotationBuilders()) { - result.add(annotationBuilder.getFullyQualifiedClassName()); - } - } - return result.build(); - } - - private Iterable> nullToEmpty(JavaStaticInitializerBuilder staticInitializerBuilder) { - return staticInitializerBuilder != null - ? Collections.>singleton(staticInitializerBuilder) - : Collections.>emptySet(); - } - Set getAnnotationsFor(JavaMember owner) { return annotationsByOwner.get(getMemberKey(owner)); } @@ -269,24 +229,61 @@ void registerConstructorReference(RawAccessRecord record) { rawConstructorReferenceRecords.add(record); } + void registerLambdaInvocation(RawAccessRecord record) { + rawSyntheticLambdaMethodInvocationRecordsByTarget.put(getMemberKey(record.target), record); + } + void forEachRawFieldAccessRecord(Consumer doWithRecord) { - rawFieldAccessRecords.forEach(doWithRecord); + fixLambdaOrigins(rawFieldAccessRecords, createLambdaFieldAccessWithNewOrigin).forEach(doWithRecord); } void forEachRawMethodCallRecord(Consumer doWithRecord) { - rawMethodCallRecords.forEach(doWithRecord); + fixLambdaOrigins(rawMethodCallRecords, createLambdaAccessWithNewOrigin).forEach(doWithRecord); } void forEachRawConstructorCallRecord(Consumer doWithRecord) { - rawConstructorCallRecords.forEach(doWithRecord); + fixLambdaOrigins(rawConstructorCallRecords, createLambdaAccessWithNewOrigin).forEach(doWithRecord); } void forEachRawMethodReferenceRecord(Consumer doWithRecord) { - rawMethodReferenceRecords.forEach(doWithRecord); + fixLambdaOrigins(rawMethodReferenceRecords, createLambdaAccessWithNewOrigin).forEach(doWithRecord); } void forEachRawConstructorReferenceRecord(Consumer doWithRecord) { - rawConstructorReferenceRecords.forEach(doWithRecord); + fixLambdaOrigins(rawConstructorReferenceRecords, createLambdaAccessWithNewOrigin).forEach(doWithRecord); + } + + private Stream fixLambdaOrigins( + Set rawAccessRecordsIncludingSyntheticLambda, + BiFunction createAccessWithNewOrigin + ) { + return rawAccessRecordsIncludingSyntheticLambda.stream() + .flatMap(access -> isLambdaMethodName(access.caller.getName()) + ? replaceOriginByLambdaOrigin(access, createAccessWithNewOrigin) + : Stream.of(access)); + } + + private Stream replaceOriginByLambdaOrigin(ACCESS accessFromSyntheticLambda, BiFunction createAccessWithNewOrigin) { + RawAccessRecord lambdaInvocationRecord = findNonSyntheticOriginOf(accessFromSyntheticLambda); + + if (lambdaInvocationRecord != null) { + return Stream.of(createAccessWithNewOrigin.apply(accessFromSyntheticLambda, lambdaInvocationRecord.caller)); + } else { + log.warn("Could not find matching dynamic invocation for synthetic lambda method {}.{}|{}", + accessFromSyntheticLambda.target.getDeclaringClassName(), + accessFromSyntheticLambda.target.name, + accessFromSyntheticLambda.target.getDescriptor()); + return Stream.empty(); + } + } + + private RawAccessRecord findNonSyntheticOriginOf(ACCESS accessFromSyntheticLambda) { + RawAccessRecord result = accessFromSyntheticLambda; + do { + result = rawSyntheticLambdaMethodInvocationRecordsByTarget.get(getMemberKey(result.caller)); + } while (result != null && isLambdaMethodName(result.caller.getName())); + + return result; } void add(JavaClass javaClass) { @@ -297,12 +294,8 @@ Map getClasses() { return classes; } - Set getAllSuperclassNames() { - return ImmutableSet.copyOf(superclassNamesByOwner.values()); - } - - Set getAllSuperinterfaceNames() { - return ImmutableSet.copyOf(interfaceNamesByOwner.values()); + private static String getMemberKey(MemberSignature member) { + return getMemberKey(member.getDeclaringClassName(), member.getName(), member.getDescriptor()); } private static String getMemberKey(JavaMember member) { @@ -313,6 +306,23 @@ private static String getMemberKey(String declaringClassName, String methodName, return declaringClassName + "|" + methodName + "|" + descriptor; } + private static final BiFunction createLambdaAccessWithNewOrigin = + (access, newOrigin) -> fillWithNewOriginDeclaredInLambda(new RawAccessRecord.Builder(), access, newOrigin) + .build(); + + private static final BiFunction createLambdaFieldAccessWithNewOrigin = + (access, newOrigin) -> fillWithNewOriginDeclaredInLambda(new RawAccessRecord.ForField.Builder(), access, newOrigin) + .withAccessType(access.accessType) + .build(); + + private static > T fillWithNewOriginDeclaredInLambda(T builder, RawAccessRecord originalAccess, CodeUnit newOrigin) { + return builder + .withCaller(newOrigin) + .withTarget(originalAccess.target) + .withLineNumber(originalAccess.lineNumber) + .withDeclaredInLambda(); + } + private static class EnclosingDeclarationsByInnerClasses { private final Map innerClassNameToEnclosingClassName = new HashMap<>(); private final Map innerClassNameToEnclosingCodeUnit = new HashMap<>(); 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 0c1a9d3d5b..cd1f536779 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 @@ -271,6 +271,12 @@ public void handleMethodReferenceInstruction(String owner, String name, String d dependencyResolutionProcess.registerAccessToType(target.owner.getFullyQualifiedClassName()); } + @Override + public void handleLambdaInstruction(String owner, String name, String desc) { + TargetInfo target = new TargetInfo(owner, name, desc); + importRecord.registerLambdaInvocation(filled(new RawAccessRecord.Builder(), target).build()); + } + @Override public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { LOG.trace("Found try/catch block between {} and {} for throwable {}", start, end, throwableType); 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 47f8e97db0..b2738b4807 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 @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; @@ -55,6 +56,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorReferenceBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaFieldAccessBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodReferenceBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; @@ -72,6 +74,7 @@ import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClasses; import static com.tngtech.archunit.core.importer.DomainBuilders.BuilderWithBuildParameter.BuildFinisher.build; import static com.tngtech.archunit.core.importer.DomainBuilders.buildAnnotations; +import static com.tngtech.archunit.core.importer.JavaClassDescriptorImporter.isLambdaMethodName; class ClassGraphCreator implements ImportContext { private final ImportedClasses classes; @@ -198,23 +201,21 @@ B accessBuilderFrom(B builder, AccessRecord record) { return builder .withOrigin(record.getOrigin()) .withTarget(record.getTarget()) - .withLineNumber(record.getLineNumber()); + .withLineNumber(record.getLineNumber()) + .withDeclaredInLambda(record.isDeclaredInLambda()); } @Override public Optional createSuperclass(JavaClass owner) { Optional superclassName = importRecord.getSuperclassFor(owner.getName()); - return superclassName.isPresent() ? - Optional.of(classes.getOrResolve(superclassName.get())) : - Optional.empty(); + return superclassName.map(classes::getOrResolve); } @Override public Optional createGenericSuperclass(JavaClass owner) { Optional> genericSuperclassBuilder = importRecord.getGenericSuperclassFor(owner); - return genericSuperclassBuilder.isPresent() - ? Optional.of(genericSuperclassBuilder.get().build(owner, getTypeParametersInContextOf(owner), classes)) - : Optional.empty(); + return genericSuperclassBuilder.map(javaClassJavaParameterizedTypeBuilder -> + javaClassJavaParameterizedTypeBuilder.build(owner, getTypeParametersInContextOf(owner), classes)); } @Override @@ -228,11 +229,11 @@ public Optional> createGenericInterfaces(JavaClass owner) { for (JavaParameterizedTypeBuilder builder : genericInterfaceBuilders.get()) { result.add(builder.build(owner, getTypeParametersInContextOf(owner), classes)); } - return Optional.>of(result.build()); + return Optional.of(result.build()); } private static Iterable> getTypeParametersInContextOf(JavaClass javaClass) { - Set> result = Sets.>newHashSet(javaClass.getTypeParameters()); + Set> result = Sets.newHashSet(javaClass.getTypeParameters()); while (javaClass.getEnclosingClass().isPresent()) { javaClass = javaClass.getEnclosingClass().get(); result.addAll(javaClass.getTypeParameters()); @@ -262,19 +263,22 @@ public Set createFields(JavaClass owner) { @Override public Set createMethods(JavaClass owner) { - Set methodBuilders = importRecord.getMethodBuildersFor(owner.getName()); + Stream methodBuilders = getNonSyntheticLambdaMethodBuildersFor(owner); if (owner.isAnnotation()) { - for (DomainBuilders.JavaMethodBuilder methodBuilder : methodBuilders) { - methodBuilder.withAnnotationDefaultValue(method -> - importRecord.getAnnotationDefaultValueBuilderFor(method) - .map(builder -> builder.build(method, classes)) - .orElseGet(() -> Optional.empty()) - ); - } + methodBuilders = methodBuilders.map(methodBuilder -> methodBuilder + .withAnnotationDefaultValue(method -> + importRecord.getAnnotationDefaultValueBuilderFor(method) + .flatMap(builder -> builder.build(method, classes)) + )); } return build(methodBuilders, owner, classes); } + private Stream getNonSyntheticLambdaMethodBuildersFor(JavaClass owner) { + return importRecord.getMethodBuildersFor(owner.getName()).stream() + .filter(methodBuilder -> !isLambdaMethodName(methodBuilder.getName())); + } + @Override public Set createConstructors(JavaClass owner) { return build(importRecord.getConstructorBuildersFor(owner.getName()), owner, classes); @@ -307,9 +311,7 @@ private Map> create @Override public Optional createEnclosingClass(JavaClass owner) { Optional enclosingClassName = importRecord.getEnclosingClassFor(owner.getName()); - return enclosingClassName.isPresent() ? - Optional.of(classes.getOrResolve(enclosingClassName.get())) : - Optional.empty(); + return enclosingClassName.map(classes::getOrResolve); } @Override @@ -335,7 +337,7 @@ public JavaClass resolveClass(String fullyQualifiedClassName) { } private Optional getMethodReturnType(String declaringClassName, String methodName) { - for (DomainBuilders.JavaMethodBuilder methodBuilder : importRecord.getMethodBuildersFor(declaringClassName)) { + for (JavaMethodBuilder methodBuilder : importRecord.getMethodBuildersFor(declaringClassName)) { if (methodBuilder.getName().equals(methodName) && methodBuilder.hasNoParameters()) { return Optional.of(classes.getOrResolve(methodBuilder.getReturnTypeName())); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index 184b7b0bb9..5b55658889 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Stream; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -90,6 +91,7 @@ import static com.tngtech.archunit.core.domain.Formatters.ensureCanonicalArrayTypeName; import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; +import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; @Internal @@ -223,10 +225,6 @@ JavaFieldBuilder withType(Optional> genericTy return self(); } - String getTypeName() { - return rawType.getFullyQualifiedClassName(); - } - public JavaType getType(JavaField field) { return genericType.isPresent() ? genericType.get().finish(field, allTypeParametersInContextOf(field.getOwner()), importedClasses) @@ -295,14 +293,6 @@ SELF addInstanceOfCheck(RawInstanceofCheck rawInstanceOfChecks) { return self(); } - List getParameterTypeNames() { - ImmutableList.Builder result = ImmutableList.builder(); - for (JavaClassDescriptor parameter : rawParameterTypes) { - result.add(parameter.getFullyQualifiedClassName()); - } - return result.build(); - } - String getReturnTypeName() { return rawReturnType.getFullyQualifiedClassName(); } @@ -341,10 +331,6 @@ private List build(List> generic return result.build(); } - public Set getParameterAnnotations(int index) { - return parameterAnnotationsByIndex.get(index); - } - public List> getTypeParameters(JavaCodeUnit owner) { return typeParametersBuilder.build(owner, importedClasses); } @@ -381,10 +367,6 @@ public ParameterAnnotationsBuilder getParameterAnnotationsBuilder(int index) { return new ParameterAnnotationsBuilder(parameterAnnotationsByIndex.get(index), importedClasses); } - public Iterable getParameterAnnotationBuilders() { - return parameterAnnotationsByIndex.values(); - } - @Internal public static class ParameterAnnotationsBuilder { private final Iterable annotationBuilders; @@ -413,8 +395,9 @@ public static final class JavaMethodBuilder extends JavaCodeUnitBuilder> createAnnotationDefaultValue) { + JavaMethodBuilder withAnnotationDefaultValue(Function> createAnnotationDefaultValue) { this.createAnnotationDefaultValue = createAnnotationDefaultValue; + return this; } @Override @@ -516,9 +499,7 @@ JavaClass build() { } public Optional getSource() { - return sourceDescriptor.isPresent() - ? Optional.of(createSource(sourceDescriptor.get().getUri(), sourceFileName, sourceDescriptor.get().isMd5InClassSourcesEnabled())) - : Optional.empty(); + return sourceDescriptor.map(value -> createSource(value.getUri(), sourceFileName, value.isMd5InClassSourcesEnabled())); } public JavaClassDescriptor getDescriptor() { @@ -592,10 +573,7 @@ public JavaClass getType() { public Map getValues(T owner) { ImmutableMap.Builder result = ImmutableMap.builder(); for (Map.Entry entry : values.entrySet()) { - Optional value = entry.getValue().build(owner, importedClasses); - if (value.isPresent()) { - result.put(entry.getKey(), value.get()); - } + entry.getValue().build(owner, importedClasses).ifPresent(value -> result.put(entry.getKey(), value)); } return result.build(); } @@ -621,7 +599,7 @@ public static ValueBuilder fromEnumProperty(final JavaClassDescriptor enumType, return new ValueBuilder() { @Override Optional build(T owner, ImportedClasses importedClasses) { - return Optional.of( + return Optional.of( new DomainBuilders.JavaEnumConstantBuilder() .withDeclaringClass(importedClasses.getOrResolve(enumType.getFullyQualifiedClassName())) .withName(value) @@ -634,7 +612,7 @@ static ValueBuilder fromClassProperty(final JavaClassDescriptor value) { return new ValueBuilder() { @Override Optional build(T owner, ImportedClasses importedClasses) { - return Optional.of(importedClasses.getOrResolve(value.getFullyQualifiedClassName())); + return Optional.of(importedClasses.getOrResolve(value.getFullyQualifiedClassName())); } }; } @@ -643,7 +621,7 @@ static ValueBuilder fromAnnotationProperty(final JavaAnnotationBuilder builder) return new ValueBuilder() { @Override Optional build(T owner, ImportedClasses importedClasses) { - return Optional.of(builder.build(owner, importedClasses)); + return Optional.of(builder.build(owner, importedClasses)); } }; } @@ -653,12 +631,12 @@ Optional build(T owner, ImportedClasses impor @Internal public static final class JavaStaticInitializerBuilder extends JavaCodeUnitBuilder { JavaStaticInitializerBuilder() { - withReturnType(Optional.>empty(), JavaClassDescriptor.From.name(void.class.getName())); - withParameterTypes(Collections.>emptyList(), Collections.emptyList()); + withReturnType(Optional.empty(), JavaClassDescriptor.From.name(void.class.getName())); + withParameterTypes(emptyList(), emptyList()); withName(JavaStaticInitializer.STATIC_INITIALIZER_NAME); withDescriptor("()V"); - withModifiers(Collections.emptySet()); - withThrowsClause(Collections.emptyList()); + withModifiers(Collections.emptySet()); + withThrowsClause(emptyList()); } @Override @@ -764,7 +742,7 @@ private static abstract class AbstractTypeParametersBuilder> build(OWNER owner, ImportedClasses ImportedClasses) { if (typeParameterBuilders.isEmpty()) { - return Collections.emptyList(); + return emptyList(); } Map, JavaTypeParameterBuilder> typeArgumentsToBuilders = new LinkedHashMap<>(); @@ -931,14 +909,17 @@ static Set build( Set> builders, PARAMETER parameter, ImportedClasses importedClasses) { + return build(builders.stream(), parameter, importedClasses); + } + + static Set build( + Stream> builders, + PARAMETER parameter, + ImportedClasses importedClasses) { checkNotNull(builders); checkNotNull(parameter); - ImmutableSet.Builder result = ImmutableSet.builder(); - for (BuilderWithBuildParameter builder : builders) { - result.add(builder.build(parameter, importedClasses)); - } - return result.build(); + return builders.map(builder -> builder.build(parameter, importedClasses)).collect(toImmutableSet()); } } } @@ -1006,6 +987,7 @@ public abstract static class JavaAccessBuilder createJavaClass() { - return javaClassBuilder != null ? Optional.of(javaClassBuilder.build()) : Optional.empty(); + return javaClassBuilder != null ? Optional.of(javaClassBuilder.build()) : Optional.empty(); } @Override @@ -140,7 +140,7 @@ private boolean alreadyImported(JavaClassDescriptor descriptor) { private Optional getSuperclassName(String superName, boolean isInterface) { return superName != null && !isInterface ? Optional.of(createTypeName(superName)) : - Optional.empty(); + Optional.empty(); } private boolean importAborted() { @@ -199,7 +199,7 @@ public void visitInnerClass(String name, String outerName, String innerName, int // visitInnerClass is called for named inner classes, even if we are currently importing // this class itself (i.e. visit(..) and visitInnerClass(..) are called with the same class name. // visitInnerClass offers some more properties like correct modifiers. - // Modifier handling is somewhat counter intuitive for nested named classes, even though we 'visit' the nested class + // Modifier handling is somewhat counterintuitive for nested named classes, even though we 'visit' the nested class // like any outer class in visit(..) before, the modifiers like 'PUBLIC' or 'PRIVATE' // are found in the access flags of visitInnerClass(..) private boolean visitingCurrentClass(String innerTypeName) { @@ -432,7 +432,9 @@ public void visitInvokeDynamicInsn( } private void processLambdaMetafactoryMethodHandleArgument(Handle methodHandle) { - if (!isLambdaMethod(methodHandle)) { + if (isLambdaMethod(methodHandle)) { + accessHandler.handleLambdaInstruction(methodHandle.getOwner(), methodHandle.getName(), methodHandle.getDesc()); + } else { accessHandler.handleMethodReferenceInstruction(methodHandle.getOwner(), methodHandle.getName(), methodHandle.getDesc()); } } @@ -536,6 +538,8 @@ interface AccessHandler { void handleMethodReferenceInstruction(String owner, String name, String desc); + void handleLambdaInstruction(String owner, String name, String desc); + void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType); void handleTryFinallyBlock(Label start, Label end, Label handler); @@ -568,6 +572,10 @@ public void handleMethodInstruction(String owner, String name, String desc) { public void handleMethodReferenceInstruction(String owner, String name, String desc) { } + @Override + public void handleLambdaInstruction(String owner, String name, String desc) { + } + @Override public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { } @@ -732,12 +740,8 @@ public void visitEnd() { private class ArrayValueBuilder extends ValueBuilder { @Override public Optional build(T owner, ImportedClasses importContext) { - Optional> componentType = determineComponentType(importContext); - if (!componentType.isPresent()) { - return Optional.empty(); - } + return determineComponentType(importContext).map(aClass -> toArray(aClass, buildValues(owner, importContext))); - return Optional.of(toArray(componentType.get(), buildValues(owner, importContext))); } @SuppressWarnings({"unchecked", "rawtypes"}) // NOTE: We assume the component type matches the list @@ -770,7 +774,7 @@ private List buildValues(T owner, ImportedCla private Optional> determineComponentType(ImportedClasses importContext) { if (derivedComponentType != null) { - return Optional.>of(derivedComponentType); + return Optional.of(derivedComponentType); } Optional returnType = importContext.getMethodReturnType( @@ -779,12 +783,12 @@ private Optional> determineComponentType(ImportedClasses importContext) return returnType.isPresent() ? determineComponentTypeFromReturnValue(returnType.get()) : - Optional.>empty(); + Optional.empty(); } private Optional> determineComponentTypeFromReturnValue(JavaClass returnType) { if (returnType.isEquivalentTo(Class[].class)) { - return Optional.>of(JavaClass.class); + return Optional.of(JavaClass.class); } return resolveComponentTypeFrom(returnType.getName()); @@ -796,10 +800,10 @@ private Optional> resolveComponentTypeFrom(String arrayTypeName) { JavaClassDescriptor componentType = getComponentType(descriptor); if (componentType.isPrimitive()) { - return Optional.>of(componentType.resolveClass()); + return Optional.of(componentType.resolveClass()); } if (String.class.getName().equals(componentType.getFullyQualifiedClassName())) { - return Optional.>of(String.class); + return Optional.of(String.class); } // if we couldn't determine the type up to now, it must be an empty enum or annotation array, @@ -807,7 +811,7 @@ private Optional> resolveComponentTypeFrom(String arrayTypeName) { // if this will ever make a problem, since annotation proxy would to the conversion backwards // and if one has to handle get(property): Object, this to be an empty Object[] // instead of a JavaEnumConstant[] or JavaAnnotation[] should hardly cause any real problems - return Optional.>of(Object.class); + return Optional.of(Object.class); } private JavaClassDescriptor getComponentType(JavaClassDescriptor descriptor) { 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 eb6ee6c7c0..e86764edeb 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 @@ -29,30 +29,13 @@ class RawAccessRecord { final CodeUnit caller; final TargetInfo target; final int lineNumber; + public boolean declaredInLambda; - RawAccessRecord(CodeUnit caller, TargetInfo target, int lineNumber) { + RawAccessRecord(CodeUnit caller, TargetInfo target, int lineNumber, boolean declaredInLambda) { this.caller = checkNotNull(caller); this.target = checkNotNull(target); this.lineNumber = lineNumber; - } - - @Override - public int hashCode() { - return Objects.hash(caller, target, lineNumber); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final RawAccessRecord other = (RawAccessRecord) obj; - return Objects.equals(this.caller, other.caller) && - Objects.equals(this.target, other.target) && - Objects.equals(this.lineNumber, other.lineNumber); + this.declaredInLambda = declaredInLambda; } @Override @@ -64,11 +47,18 @@ private String fieldsAsString() { return "caller=" + caller + ", target=" + target + ", lineNumber=" + lineNumber; } - static class CodeUnit { + interface MemberSignature { + String getName(); + + String getDescriptor(); + + String getDeclaringClassName(); + } + + static class CodeUnit implements MemberSignature { private final String name; private final String descriptor; private final List rawParameterTypes; - private final JavaClassDescriptor returnType; private final List rawParameterTypeNames; private final String declaringClassName; private final int hashCode; @@ -77,7 +67,6 @@ static class CodeUnit { this.name = name; this.descriptor = descriptor; this.rawParameterTypes = JavaClassDescriptorImporter.importAsmMethodArgumentTypes(descriptor); - this.returnType = JavaClassDescriptorImporter.importAsmMethodReturnType(descriptor); this.rawParameterTypeNames = namesOf(rawParameterTypes); this.declaringClassName = declaringClassName; this.hashCode = Objects.hash(name, descriptor, declaringClassName); @@ -91,10 +80,16 @@ private static List namesOf(Iterable descriptors) { return result.build(); } - String getName() { + @Override + public String getName() { return name; } + @Override + public String getDescriptor() { + return descriptor; + } + List getRawParameterTypes() { return rawParameterTypes; } @@ -103,14 +98,11 @@ List getRawParameterTypeNames() { return rawParameterTypeNames; } - String getDeclaringClassName() { + @Override + public String getDeclaringClassName() { return declaringClassName; } - String getDescriptor() { - return descriptor; - } - boolean is(JavaCodeUnit method) { return getName().equals(method.getName()) && descriptor.equals(method.getDescriptor()) @@ -147,20 +139,38 @@ public String toString() { } } - static final class TargetInfo { + static final class TargetInfo implements MemberSignature { final JavaClassDescriptor owner; final String name; final String desc; + private final int hashCode; + TargetInfo(String owner, String name, String desc) { this.owner = JavaClassDescriptorImporter.createFromAsmObjectTypeName(owner); this.name = name; this.desc = desc; + hashCode = Objects.hash(owner, name, desc); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescriptor() { + return desc; + } + + @Override + public String getDeclaringClassName() { + return owner.getFullyQualifiedClassName(); } @Override public int hashCode() { - return Objects.hash(owner, name, desc); + return hashCode; } @Override @@ -190,6 +200,7 @@ static class BaseBuilder> { CodeUnit caller; TargetInfo target; int lineNumber = -1; + boolean declaredInLambda = false; SELF withCaller(CodeUnit caller) { this.caller = caller; @@ -206,44 +217,29 @@ SELF withLineNumber(int lineNumber) { return self(); } + public SELF withDeclaredInLambda() { + declaredInLambda = true; + return self(); + } + @SuppressWarnings("unchecked") SELF self() { return (SELF) this; } RawAccessRecord build() { - return new RawAccessRecord(caller, target, lineNumber); + return new RawAccessRecord(caller, target, lineNumber, declaredInLambda); } } static class ForField extends RawAccessRecord { final AccessType accessType; - private ForField(CodeUnit caller, TargetInfo target, int lineNumber, AccessType accessType) { - super(caller, target, lineNumber); + private ForField(CodeUnit caller, TargetInfo target, int lineNumber, AccessType accessType, boolean declaredInLambda) { + super(caller, target, lineNumber, declaredInLambda); this.accessType = accessType; } - @Override - public int hashCode() { - return 31 * super.hashCode() + Objects.hash(accessType); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final ForField other = (ForField) obj; - return Objects.equals(this.accessType, other.accessType); - } - static class Builder extends BaseBuilder { private AccessType accessType; @@ -254,7 +250,7 @@ Builder withAccessType(AccessType accessType) { @Override ForField build() { - return new ForField(super.caller, super.target, super.lineNumber, accessType); + return new ForField(super.caller, super.target, super.lineNumber, accessType, declaredInLambda); } } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java index 3e9d26a35b..76751b6aab 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java @@ -4,9 +4,18 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; +import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.AccessTarget.CodeUnitCallTarget; +import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; +import com.tngtech.archunit.core.domain.AccessTarget.ConstructorReferenceTarget; +import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; +import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; +import com.tngtech.archunit.core.domain.AccessTarget.MethodReferenceTarget; +import com.tngtech.archunit.core.importer.ClassFileImporter; import org.junit.Test; import static com.tngtech.archunit.core.domain.AccessTarget.Predicates.constructor; @@ -49,10 +58,10 @@ public void isAnnotatedWith_typeName_on_resolved_target() { public void isAnnotatedWith_predicate_on_resolved_target() { JavaCall call = simulateCall().from(Origin.class, "call").to(Target.class, "called"); - assertThat(call.getTarget().isAnnotatedWith(DescribedPredicate.>alwaysTrue())) + assertThat(call.getTarget().isAnnotatedWith(DescribedPredicate.alwaysTrue())) .as("target is annotated with anything") .isTrue(); - assertThat(call.getTarget().isAnnotatedWith(DescribedPredicate.>alwaysFalse())) + assertThat(call.getTarget().isAnnotatedWith(DescribedPredicate.alwaysFalse())) .as("target is annotated with nothing") .isFalse(); } @@ -67,7 +76,7 @@ public void annotated_on_unresolved_target() { assertThat(call.getTarget().isAnnotatedWith(QueriedAnnotation.class.getName())) .as("target is annotated with @" + QueriedAnnotation.class.getSimpleName()) .isFalse(); - assertThat(call.getTarget().isAnnotatedWith(DescribedPredicate.>alwaysTrue())) + assertThat(call.getTarget().isAnnotatedWith(DescribedPredicate.alwaysTrue())) .as("target is annotated with anything") .isFalse(); } @@ -109,10 +118,10 @@ public void isMetaAnnotatedWith_predicate_on_resolved_target() { JavaClasses classes = importClassesWithContext(Origin.class, Target.class, QueriedAnnotation.class); JavaCall call = simulateCall().from(classes.get(Origin.class), "call").to(classes.get(Target.class).getMethod("called")); - assertThat(call.getTarget().isMetaAnnotatedWith(DescribedPredicate.>alwaysTrue())) + assertThat(call.getTarget().isMetaAnnotatedWith(DescribedPredicate.alwaysTrue())) .as("target is meta-annotated with anything") .isTrue(); - assertThat(call.getTarget().isMetaAnnotatedWith(DescribedPredicate.>alwaysFalse())) + assertThat(call.getTarget().isMetaAnnotatedWith(DescribedPredicate.alwaysFalse())) .as("target is meta-annotated with nothing") .isFalse(); } @@ -128,7 +137,7 @@ public void meta_annotated_on_unresolved_target() { assertThat(call.getTarget().isMetaAnnotatedWith(Retention.class.getName())) .as("target is meta-annotated with @" + Retention.class.getSimpleName()) .isFalse(); - assertThat(call.getTarget().isMetaAnnotatedWith(DescribedPredicate.>alwaysTrue())) + assertThat(call.getTarget().isMetaAnnotatedWith(DescribedPredicate.alwaysTrue())) .as("target is meta-annotated with anything") .isFalse(); } @@ -204,6 +213,80 @@ public void predicate_constructor() { .hasDescription("constructor"); } + private static class Data_function_resolve_member { + static class Target { + String field; + + void method() { + } + } + } + + @Test + public void function_resolve_member() { + @SuppressWarnings("unused") + class Origin { + String access() { + Data_function_resolve_member.Target target = new Data_function_resolve_member.Target(); + Supplier supplier = Data_function_resolve_member.Target::new; + Consumer consumer = Data_function_resolve_member.Target::method; + target.method(); + return target.field; + } + } + JavaClass targetClass = new ClassFileImporter().importClasses(Origin.class, Data_function_resolve_member.Target.class).get(Data_function_resolve_member.Target.class); + MethodCallTarget methodCallTarget = findTargetWithType(targetClass.getAccessesToSelf(), MethodCallTarget.class); + + assertThat(AccessTarget.Functions.RESOLVE_MEMBER.apply(methodCallTarget)) + .contains(methodCallTarget.resolveMember().get()); + assertThat(AccessTarget.Functions.RESOLVE.apply(methodCallTarget)) + .isEqualTo(ImmutableSet.of(methodCallTarget.resolveMember().get())); + + assertThat(AccessTarget.CodeUnitAccessTarget.Functions.RESOLVE_MEMBER.apply(methodCallTarget)) + .contains(methodCallTarget.resolveMember().get()); + assertThat(AccessTarget.CodeUnitAccessTarget.Functions.RESOLVE.apply(methodCallTarget)) + .isEqualTo(ImmutableSet.of(methodCallTarget.resolveMember().get())); + + assertThat(MethodCallTarget.Functions.RESOLVE_MEMBER.apply(methodCallTarget)) + .contains(methodCallTarget.resolveMember().get()); + assertThat(MethodCallTarget.Functions.RESOLVE.apply(methodCallTarget)) + .isEqualTo(ImmutableSet.of(methodCallTarget.resolveMember().get())); + + MethodReferenceTarget methodReferenceTarget = findTargetWithType(targetClass.getAccessesToSelf(), MethodReferenceTarget.class); + + assertThat(MethodReferenceTarget.Functions.RESOLVE_MEMBER.apply(methodReferenceTarget)) + .contains(methodReferenceTarget.resolveMember().get()); + + ConstructorCallTarget constructorCallTarget = findTargetWithType(targetClass.getAccessesToSelf(), ConstructorCallTarget.class); + + assertThat(ConstructorCallTarget.Functions.RESOLVE_MEMBER.apply(constructorCallTarget)) + .contains(constructorCallTarget.resolveMember().get()); + assertThat(ConstructorCallTarget.Functions.RESOLVE.apply(constructorCallTarget)) + .isEqualTo(ImmutableSet.of(constructorCallTarget.resolveMember().get())); + + ConstructorReferenceTarget constructorReferenceTarget = findTargetWithType(targetClass.getAccessesToSelf(), ConstructorReferenceTarget.class); + + assertThat(ConstructorReferenceTarget.Functions.RESOLVE_MEMBER.apply(constructorReferenceTarget)) + .contains(constructorReferenceTarget.resolveMember().get()); + + FieldAccessTarget fieldAccessTarget = findTargetWithType(targetClass.getAccessesToSelf(), FieldAccessTarget.class); + + assertThat(FieldAccessTarget.Functions.RESOLVE_MEMBER.apply(fieldAccessTarget)) + .contains(fieldAccessTarget.resolveMember().get()); + assertThat(FieldAccessTarget.Functions.RESOLVE.apply(fieldAccessTarget)) + .isEqualTo(ImmutableSet.of(fieldAccessTarget.resolveMember().get())); + } + + @SuppressWarnings("unchecked") + private T findTargetWithType(Set> set, Class type) { + for (JavaAccess access : set) { + if (type.isInstance(access.getTarget())) { + return (T) access.getTarget(); + } + } + throw new AssertionError(String.format("Set %s does not contain element of type %s", set, type.getName())); + } + private void assertDeclarations(CodeUnitCallTarget target, Class... exceptionTypes) { Method reflectedMethod = publicMethod(target.getOwner().reflect(), target.getName()); assertThat(reflectedMethod.getExceptionTypes()).containsOnly(exceptionTypes); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java index 808e9e0011..e12df15b2a 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java @@ -7,6 +7,8 @@ import java.lang.reflect.Method; import java.nio.file.FileSystem; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; import com.google.common.base.MoreObjects; import com.tngtech.archunit.base.DescribedPredicate; @@ -79,6 +81,57 @@ public void Dependencies_from_field_with_component_type(Field reflectionArrayFie assertThatDependencies(dependencies).containOnly(expectedDependencies); } + static class Data_dependency_from_access { + static class Target { + String field; + + void callMe() { + } + } + } + + @DataProvider + public static Object[][] data_dependency_from_access() { + @SuppressWarnings("unused") + class Origin { + String fieldAccess(Data_dependency_from_access.Target target) { + return target.field; + } + + void constructorCall() { + new Data_dependency_from_access.Target(); + } + + void methodCall(Data_dependency_from_access.Target target) { + target.callMe(); + } + + Supplier constructorReference() { + return Data_dependency_from_access.Target::new; + } + + Consumer methodReference() { + return Data_dependency_from_access.Target::callMe; + } + } + JavaClass origin = new ClassFileImporter().importClasses(Origin.class, Data_dependency_from_access.Target.class).get(Origin.class); + return testForEach( + getOnlyElement(origin.getMethod("fieldAccess", Data_dependency_from_access.Target.class).getFieldAccesses()), + getOnlyElement(origin.getMethod("constructorCall").getConstructorCallsFromSelf()), + getOnlyElement(origin.getMethod("methodCall", Data_dependency_from_access.Target.class).getMethodCallsFromSelf()), + getOnlyElement(origin.getMethod("constructorReference").getConstructorReferencesFromSelf()), + getOnlyElement(origin.getMethod("methodReference").getMethodReferencesFromSelf()) + ); + } + + @Test + @UseDataProvider + public void test_dependency_from_access(JavaAccess access) { + Dependency dependency = getOnlyElement(Dependency.tryCreateFromAccess(access)); + assertThatType(dependency.getTargetClass()).as("target class").isEqualTo(access.getTargetOwner()); + assertThat(dependency.getDescription()).as("description").isEqualTo(access.getDescription()); + } + @DataProvider public static Object[][] method_calls_to_array_types() { return $$( 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 5f696e4f9c..4d0ebe326b 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 @@ -17,6 +17,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.base.ArchUnitException.InvalidSyntaxUsageException; @@ -24,10 +27,12 @@ import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.core.domain.testobjects.AAccessingB; import com.tngtech.archunit.core.domain.testobjects.AExtendingSuperAImplementingInterfaceForA; +import com.tngtech.archunit.core.domain.testobjects.AReferencingB; 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.BReferencedByA; import com.tngtech.archunit.core.domain.testobjects.ComponentTypeDependency; import com.tngtech.archunit.core.domain.testobjects.DependenciesOnClassObjects; import com.tngtech.archunit.core.domain.testobjects.InterfaceForA; @@ -41,7 +46,6 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import org.assertj.core.api.AbstractBooleanAssert; import org.assertj.core.api.Condition; -import org.assertj.core.api.iterable.Extractor; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -316,10 +320,10 @@ public void isAnnotatedWith_typeName() { @Test public void isAnnotatedWith_predicate() { assertThat(importClassWithContext(Parent.class) - .isAnnotatedWith(DescribedPredicate.>alwaysTrue())) + .isAnnotatedWith(DescribedPredicate.alwaysTrue())) .as("predicate matches").isTrue(); assertThat(importClassWithContext(Parent.class) - .isAnnotatedWith(DescribedPredicate.>alwaysFalse())) + .isAnnotatedWith(DescribedPredicate.alwaysFalse())) .as("predicate matches").isFalse(); } @@ -352,10 +356,10 @@ public void isMetaAnnotatedWith_predicate() { JavaClass clazz = importClassesWithContext(Parent.class, SomeAnnotation.class).get(Parent.class); assertThat(clazz - .isMetaAnnotatedWith(DescribedPredicate.>alwaysTrue())) + .isMetaAnnotatedWith(DescribedPredicate.alwaysTrue())) .as("predicate matches").isTrue(); assertThat(clazz - .isMetaAnnotatedWith(DescribedPredicate.>alwaysFalse())) + .isMetaAnnotatedWith(DescribedPredicate.alwaysFalse())) .as("predicate matches").isFalse(); } @@ -400,12 +404,12 @@ public void getMembers_and_getAllMembers() { assertThat(clazz.getMembers()) .extracting(memberIdentifier()) - .containsOnlyElementsOf(ChildWithFieldAndMethod.Members.MEMBERS); + .hasSameElementsAs(ChildWithFieldAndMethod.Members.MEMBERS); assertThat(clazz.getAllMembers()) .filteredOn(isNotObject()) .extracting(memberIdentifier()) - .containsOnlyElementsOf(ImmutableSet.builder() + .hasSameElementsAs(ImmutableSet.builder() .addAll(ChildWithFieldAndMethod.Members.MEMBERS) .addAll(ParentWithFieldAndMethod.Members.MEMBERS) .addAll(InterfaceWithFieldAndMethod.Members.MEMBERS) @@ -434,19 +438,19 @@ public void getCodeUnitWithParameterTypes() { public void tryGetCodeUnitWithParameterTypes() { final JavaClass clazz = importClasses(ChildWithFieldAndMethod.class).get(ChildWithFieldAndMethod.class); - assertThatCodeUnit(clazz.tryGetCodeUnitWithParameterTypes("childMethod", Collections.>singletonList(String.class)).get()) + assertThatCodeUnit(clazz.tryGetCodeUnitWithParameterTypes("childMethod", Collections.singletonList(String.class)).get()) .matchesMethod(ChildWithFieldAndMethod.class, "childMethod", String.class); assertThatCodeUnit(clazz.tryGetCodeUnitWithParameterTypeNames("childMethod", singletonList(String.class.getName())).get()) .matchesMethod(ChildWithFieldAndMethod.class, "childMethod", String.class); - assertThatCodeUnit(clazz.tryGetCodeUnitWithParameterTypes(CONSTRUCTOR_NAME, Collections.>singletonList(Object.class)).get()) + assertThatCodeUnit(clazz.tryGetCodeUnitWithParameterTypes(CONSTRUCTOR_NAME, Collections.singletonList(Object.class)).get()) .matchesConstructor(ChildWithFieldAndMethod.class, Object.class); assertThatCodeUnit(clazz.tryGetCodeUnitWithParameterTypeNames(CONSTRUCTOR_NAME, singletonList(Object.class.getName())).get()) .matchesConstructor(ChildWithFieldAndMethod.class, Object.class); - assertThat(clazz.tryGetCodeUnitWithParameterTypes("childMethod", Collections.>emptyList())).isEmpty(); - assertThat(clazz.tryGetCodeUnitWithParameterTypeNames("childMethod", Collections.emptyList())).isEmpty(); - assertThat(clazz.tryGetCodeUnitWithParameterTypes(CONSTRUCTOR_NAME, Collections.>emptyList())).isEmpty(); - assertThat(clazz.tryGetCodeUnitWithParameterTypeNames(CONSTRUCTOR_NAME, Collections.emptyList())).isEmpty(); + assertThat(clazz.tryGetCodeUnitWithParameterTypes("childMethod", Collections.emptyList())).isEmpty(); + assertThat(clazz.tryGetCodeUnitWithParameterTypeNames("childMethod", Collections.emptyList())).isEmpty(); + assertThat(clazz.tryGetCodeUnitWithParameterTypes(CONSTRUCTOR_NAME, Collections.emptyList())).isEmpty(); + assertThat(clazz.tryGetCodeUnitWithParameterTypeNames(CONSTRUCTOR_NAME, Collections.emptyList())).isEmpty(); } @Test @@ -1230,6 +1234,44 @@ public void direct_dependencies_to_self_by_member_declarations() { .inLineNumber(0)); } + @Test + public void direct_dependencies_from_self_by_references() { + JavaClass javaClass = importClasses(AReferencingB.class, BReferencedByA.class).get(AReferencingB.class); + + assertReferencesFromAToB(javaClass.getDirectDependenciesFromSelf()); + } + + @Test + public void direct_dependencies_to_self_by_references() { + JavaClass javaClass = importClasses(AReferencingB.class, BReferencedByA.class).get(BReferencedByA.class); + + assertReferencesFromAToB(javaClass.getDirectDependenciesToSelf()); + } + + private void assertReferencesFromAToB(Set dependencies) { + assertThat(dependencies) + .areAtLeastOne(referenceDependency() + .from(AReferencingB.class) + .to(BReferencedByA.class, CONSTRUCTOR_NAME) + .inLineNumber(9)) + .areAtLeastOne(referenceDependency() + .from(AReferencingB.class) + .to(BReferencedByA.class, CONSTRUCTOR_NAME) + .inLineNumber(10)) + .areAtLeastOne(referenceDependency() + .from(AReferencingB.class) + .to(BReferencedByA.class, "getSomeField") + .inLineNumber(14)) + .areAtLeastOne(referenceDependency() + .from(AReferencingB.class) + .to(BReferencedByA.class, "getSomeField") + .inLineNumber(15)) + .areAtLeastOne(referenceDependency() + .from(AReferencingB.class) + .to(BReferencedByA.class, "getNothing") + .inLineNumber(16)); + } + @Test public void direct_dependencies_to_self_by_annotation() { JavaClasses javaClasses = importPackagesOf(getClass()); @@ -1481,6 +1523,49 @@ public void functions_get_members() { assertThat(GET_STATIC_INITIALIZER.apply(javaClass)).contains(javaClass.getStaticInitializer().get()); } + static class Data_function_get_code_unit_references { + static class Target { + void target() { + } + } + } + + @Test + public void function_get_code_unit_references_from_self() { + @SuppressWarnings("unused") + class Origin { + Consumer origin() { + Supplier supplier = + Data_function_get_code_unit_references.Target::new; + return Data_function_get_code_unit_references.Target::target; + } + } + + JavaClass javaClass = new ClassFileImporter().importClasses(Origin.class, Data_function_get_code_unit_references.Target.class).get(Origin.class); + + assertThat(JavaClass.Functions.GET_CODE_UNIT_REFERENCES_FROM_SELF.apply(javaClass)).isEqualTo(javaClass.getCodeUnitReferencesFromSelf()); + assertThat(JavaClass.Functions.GET_METHOD_REFERENCES_FROM_SELF.apply(javaClass)).isEqualTo(javaClass.getMethodReferencesFromSelf()); + assertThat(JavaClass.Functions.GET_CONSTRUCTOR_REFERENCES_FROM_SELF.apply(javaClass)).isEqualTo(javaClass.getConstructorReferencesFromSelf()); + } + + @Test + public void function_get_code_unit_references_to_self() { + @SuppressWarnings("unused") + class Origin { + Consumer origin() { + Supplier supplier = Data_function_get_code_unit_references.Target::new; + return Data_function_get_code_unit_references.Target::target; + } + } + + JavaClass javaClass = new ClassFileImporter().importClasses(Origin.class, Data_function_get_code_unit_references.Target.class) + .get(Data_function_get_code_unit_references.Target.class); + + assertThat(JavaClass.Functions.GET_CODE_UNIT_REFERENCES_TO_SELF.apply(javaClass)).isEqualTo(javaClass.getCodeUnitReferencesToSelf()); + assertThat(JavaClass.Functions.GET_METHOD_REFERENCES_TO_SELF.apply(javaClass)).isEqualTo(javaClass.getMethodReferencesToSelf()); + assertThat(JavaClass.Functions.GET_CONSTRUCTOR_REFERENCES_TO_SELF.apply(javaClass)).isEqualTo(javaClass.getConstructorReferencesToSelf()); + } + @Test public void predicate_withType() { assertThat(type(Parent.class)) @@ -1913,6 +1998,10 @@ private static DependencyConditionCreation methodReturnTypeDependency() { return new DependencyConditionCreation("has return type"); } + private static DependencyConditionCreation referenceDependency() { + return new DependencyConditionCreation("references"); + } + private static DependencyConditionCreation methodThrowsDeclarationDependency() { return new DependencyConditionCreation("throws type"); } @@ -2031,7 +2120,7 @@ private void assertIllegalArgumentException(String expectedMessagePart, Runnable } } - private Extractor memberIdentifier() { + private Function memberIdentifier() { return input -> input.getOwner().getSimpleName() + "#" + input.getName(); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaFieldAccessTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaFieldAccessTest.java index 23514deb05..e28b27cad7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaFieldAccessTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaFieldAccessTest.java @@ -26,45 +26,22 @@ @RunWith(DataProviderRunner.class) public class JavaFieldAccessTest { @Test - public void equals_should_work() throws Exception { + public void equals_of_JavaAccess_should_be_identity_equals() throws Exception { JavaClass clazz = importClassWithContext(SomeClass.class); JavaFieldAccess access = stringFieldAccessRecordBuilder(clazz) .withOrigin(accessFieldMethod(clazz)) .build(); - JavaFieldAccess equalAccess = stringFieldAccessRecordBuilder(clazz) + JavaFieldAccess samePropertiesAccess = stringFieldAccessRecordBuilder(clazz) .withOrigin(accessFieldMethod(clazz)) .build(); assertThat(access).isEqualTo(access); assertThat(access.hashCode()).isEqualTo(access.hashCode()); - assertThat(access).isEqualTo(equalAccess); - assertThat(access.hashCode()).isEqualTo(equalAccess.hashCode()); - - JavaFieldAccess otherAccessType = stringFieldAccessRecordBuilder(clazz) - .withAccessType(SET) - .withOrigin(accessFieldMethod(clazz)) - .build(); - JavaFieldAccess otherLineNumber = stringFieldAccessRecordBuilder(clazz) - .withOrigin(accessFieldMethod(clazz)) - .withLineNumber(999) - .build(); - JavaFieldAccess otherTarget = stringFieldAccessRecordBuilder(clazz) - .withOrigin(accessFieldMethod(clazz)) - .withTarget(targetFrom(clazz.getField("intField"))) - .build(); - JavaFieldAccess otherCaller = stringFieldAccessRecordBuilder(clazz) - .withOrigin(clazz.getMethod("accessInt")) - .build(); - - assertThat(access).isNotEqualTo(otherAccessType); - assertThat(access).isNotEqualTo(otherLineNumber); - assertThat(access).isNotEqualTo(otherTarget); - assertThat(access).isNotEqualTo(otherCaller); + assertThat(access).isNotEqualTo(samePropertiesAccess); } @Test public void fieldAccess_should_have_same_name_as_target() throws Exception { - JavaClass clazz = importClassWithContext(SomeClass.class); JavaFieldAccess access = stringFieldAccessRecordBuilder(clazz) diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/JavaParameterizedTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaParameterizedTypeTest.java similarity index 100% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/JavaParameterizedTypeTest.java rename to archunit/src/test/java/com/tngtech/archunit/core/domain/JavaParameterizedTypeTest.java diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/testexamples/AReferencingB.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AReferencingB.java similarity index 91% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/testexamples/AReferencingB.java rename to archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AReferencingB.java index 389984e4f4..3d3fc6da6b 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/testexamples/AReferencingB.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AReferencingB.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.domain.testexamples; +package com.tngtech.archunit.core.domain.testobjects; import java.util.function.Function; import java.util.function.Supplier; diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/testexamples/BReferencedByA.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BReferencedByA.java similarity index 83% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/testexamples/BReferencedByA.java rename to archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BReferencedByA.java index 32d22276b5..fd29a2ecdb 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/domain/testexamples/BReferencedByA.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BReferencedByA.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.domain.testexamples; +package com.tngtech.archunit.core.domain.testobjects; public class BReferencedByA { String someField; diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java index 7da8e8fb88..b2b2e99778 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java @@ -1176,6 +1176,28 @@ public void identifies_call_origin_if_signature_and_descriptor_deviate() { assertThat(javaClass.getMethod("keyOf", Object.class).getMethodCallsFromSelf()).isNotEmpty(); } + @Test + public void imports_multiple_identical_accesses_in_same_line() { + class Target { + Target callMe() { + return this; + } + } + @SuppressWarnings("unused") + class Origin { + void call(Target target) { + target.callMe().callMe(); + } + } + + JavaClass origin = new ClassFileImporter().importClasses(Origin.class, Target.class).get(Origin.class); + + assertThat(origin.getMethodCallsFromSelf()).hasSize(2); + for (JavaMethodCall call : origin.getMethodCallsFromSelf()) { + assertThatCall(call).isTo("callMe"); + } + } + private Set withoutJavaLangTargets(Set dependencies) { return dependencies.stream() .filter(dependency -> !dependency.getTargetClass().getPackageName().startsWith("java.lang")) diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java similarity index 99% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java index 85c00bf291..c394871fb6 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java @@ -40,10 +40,10 @@ import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields; 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.annotations.AnotherAnnotationWithAnnotationParameter; -import com.tngtech.archunit.core.importer.testexamples.annotations.SomeAnnotationWithAnnotationParameter; -import com.tngtech.archunit.core.importer.testexamples.annotations.SomeAnnotationWithClassParameter; -import com.tngtech.archunit.core.importer.testexamples.classhierarchy.Child; +import com.tngtech.archunit.core.importer.testexamples.annotationresolution.AnotherAnnotationWithAnnotationParameter; +import com.tngtech.archunit.core.importer.testexamples.annotationresolution.SomeAnnotationWithAnnotationParameter; +import com.tngtech.archunit.core.importer.testexamples.annotationresolution.SomeAnnotationWithClassParameter; +import com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution.Child; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesTest.java similarity index 93% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesTest.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesTest.java index c09ec25b26..2f00bede78 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterCodeUnitReferencesTest.java @@ -221,31 +221,6 @@ public void imports_constructor_references() { .isTo(targetClass.getConstructor()); } - /** - * A local class constructor obtains extra parameters from the outer scope that the compiler transparently adds - * to the byte code. A reference to this local constructor will then always be translated to a lambda call. - * Thus, in this case we do not expect a constructor reference. - */ - @Test - public void does_not_import_local_constructor_references() { - @SuppressWarnings("unused") - class ReferencedTarget { - ReferencedTarget() { - } - } - @SuppressWarnings("unused") - class Origin { - void referencesConstructor() { - Supplier a = ReferencedTarget::new; - } - } - - JavaClasses javaClasses = new ClassFileImporter().importClasses(Origin.class, ReferencedTarget.class); - - assertThat(javaClasses.get(Origin.class).getMethod("referencesConstructor").getConstructorReferencesFromSelf()).isEmpty(); - assertThat(javaClasses.get(ReferencedTarget.class).getConstructor(ClassFileImporterCodeUnitReferencesTest.class).getReferencesToSelf()).isEmpty(); - } - @Test public void does_not_import_lambdas_as_method_or_constructor_references() { @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaAccessesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaAccessesTest.java new file mode 100644 index 0000000000..a223c747ca --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaAccessesTest.java @@ -0,0 +1,535 @@ +package com.tngtech.archunit.core.importer; + +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; +import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaConstructorCall; +import com.tngtech.archunit.core.domain.JavaConstructorReference; +import com.tngtech.archunit.core.domain.JavaFieldAccess; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaMethodCall; +import com.tngtech.archunit.core.domain.JavaMethodReference; +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.Condition; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.core.domain.Formatters.formatNamesOf; +import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; +import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; +import static com.tngtech.archunit.core.importer.JavaClassDescriptorImporterTestUtils.isLambdaMethodName; +import static com.tngtech.archunit.testutil.assertion.AccessesAssertion.access; +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; +import static com.tngtech.archunit.testutil.Assertions.assertThatAccesses; +import static com.tngtech.archunit.testutil.Assertions.assertThatCall; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; +import static java.util.stream.Collectors.toSet; + +@RunWith(DataProviderRunner.class) +public class ClassFileImporterLambdaAccessesTest { + @Test + public void imports_method_call_from_lambda_without_parameter() { + class Target { + void target() { + } + } + + @SuppressWarnings("unused") + class Caller { + private Target target; + + Runnable call() { + return () -> target.target(); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + JavaMethodCall call = getOnlyElement(classes.get(Caller.class).getMethodCallsFromSelf()); + + assertThatCall(call).isFrom("call").isTo(Target.class, "target"); + } + + @Test + public void imports_constructor_call_from_lambda_without_parameter() { + class Target { + } + + @SuppressWarnings({"unused", "Convert2MethodRef"}) + class Caller { + Runnable call() { + return () -> new Target(); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + JavaConstructorCall call = getOnlyElement( + filterOriginByName(classes.get(Caller.class).getConstructorCallsFromSelf(), "call")); + + assertThatCall(call).isFrom("call").isTo(Target.class, CONSTRUCTOR_NAME, getClass()); + } + + @Test + public void imports_field_access_from_lambda_without_parameter() { + class Target { + } + + @SuppressWarnings("unused") + class Caller { + Target target; + + Consumer call() { + return (target) -> this.target = target; + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + JavaFieldAccess access = getOnlyElement(filterOriginByName(classes.get(Caller.class).getFieldAccessesFromSelf(), "call")); + + assertThatAccess(access).isFrom("call").isTo(Caller.class, "target"); + } + + @Test + public void imports_method_reference_from_lambda_without_parameter() { + class Target { + void target() { + } + } + + @SuppressWarnings("unused") + class Caller { + Supplier> call() { + return () -> Target::target; + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + JavaMethodReference reference = getOnlyElement(classes.get(Caller.class).getMethodReferencesFromSelf()); + + assertThatAccess(reference).isFrom("call").isTo(Target.class, "target"); + } + + private static class Data_of_imports_constructor_reference_from_lambda_without_parameter { + static class Target { + } + } + + @Test + public void imports_constructor_reference_from_lambda_without_parameter() { + @SuppressWarnings("unused") + class Caller { + Supplier> call() { + return () -> Data_of_imports_constructor_reference_from_lambda_without_parameter.Target::new; + } + } + + JavaClasses classes = new ClassFileImporter() + .importClasses(Data_of_imports_constructor_reference_from_lambda_without_parameter.Target.class, Caller.class); + JavaConstructorReference reference = getOnlyElement(classes.get(Caller.class).getConstructorReferencesFromSelf()); + + assertThatAccess(reference).isFrom("call") + .isTo(Data_of_imports_constructor_reference_from_lambda_without_parameter.Target.class, CONSTRUCTOR_NAME); + } + + @Test + public void imports_method_call_from_lambda_with_parameter() { + @SuppressWarnings("unused") + class Target { + void target(String param) { + } + } + + @SuppressWarnings("unused") + class Caller { + private Target target; + + Consumer call() { + return s -> target.target(s); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + JavaMethodCall call = getOnlyElement(classes.get(Caller.class).getMethodCallsFromSelf()); + + assertThatCall(call).isFrom("call").isTo(Target.class, "target", String.class); + } + + @Test + public void imports_method_call_from_lambda_with_return_types() { + @SuppressWarnings("unused") + class Target { + String target() { + return ""; + } + } + + @SuppressWarnings("unused") + class Caller { + private Target target; + + Supplier call() { + return () -> target.target(); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + JavaMethodCall call = getOnlyElement(classes.get(Caller.class).getMethodCallsFromSelf()); + + assertThatCall(call).isFrom("call").isTo(Target.class, "target"); + } + + @Test + public void imports_method_call_from_lambda_with_overloads() { + @SuppressWarnings("unused") + class Target { + void target() { + } + + void target(String param) { + } + } + + @SuppressWarnings("unused") + class Caller { + private Target target; + + Consumer call() { + return s -> target.target(); + } + + Consumer callParam() { + return s -> target.target(s); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + Set calls = classes.get(Caller.class).getMethodCallsFromSelf(); + + assertThatCall(singleByName(calls, "call")).isFrom("call").isTo(Target.class, "target"); + assertThatCall(singleByName(calls, "callParam")).isFrom("callParam").isTo(Target.class, "target", String.class); + } + + @Test + public void imports_multiple_method_calls_from_single_lambda() { + class Target { + void target() { + } + } + + @SuppressWarnings("unused") + class Caller { + private Target target; + + Runnable call() { + return () -> { + target.target(); + target.target(); + }; + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + Set calls = classes.get(Caller.class).getMethodCallsFromSelf(); + + assertThat(calls).hasSize(2); + calls.forEach(call -> assertThatCall(call).isFrom("call").isTo(Target.class, "target")); + } + + @Test + public void imports_multiple_method_calls_from_multiple_lambda() { + @SuppressWarnings("unused") + class Target { + Target target() { + return this; + } + + Target target(String s) { + return this; + } + } + + @SuppressWarnings("unused") + class Caller { + private Target target; + + Runnable call1() { + return () -> target.target().target().target(); + } + + Function call2() { + return s -> target.target(s).target(); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + Set calls = classes.get(Caller.class).getMethodCallsFromSelf(); + assertThat(calls).hasSize(5); + + assertThat(filterOriginByName(calls, "call1")) + .haveAtLeast(3, access().fromOrigin(Caller.class, "call1").toTarget(Target.class, "target")) + .hasSize(3); + + assertThat(filterOriginByName(calls, "call2")) + .extracting(JavaMethodCall::getTarget) + .areExactly(1, targetCodeUnit("target")) + .areExactly(1, targetCodeUnit("target", String.class)) + .hasSize(2); + } + + @Test + public void imports_multiple_method_calls_from_multiple_lambda_in_one_method() { + @SuppressWarnings("unused") + class Target { + Target target(String s) { + return this; + } + + Target target(int i) { + return this; + } + } + + @SuppressWarnings("unused") + class Caller { + private Target target; + + Function call(int i) { + Function function = t -> t.target(i); + return s -> target.target(s); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller.class); + Set calls = classes.get(Caller.class).getMethodCallsFromSelf(); + + assertThat(filterOriginByName(calls, "call")) + .extracting(JavaMethodCall::getTarget) + .areExactly(1, targetCodeUnit("target", String.class)) + .areExactly(1, targetCodeUnit("target", int.class)) + .hasSize(2); + } + + @Test + public void imports_method_call_from_lambdas_from_multiple_callers() { + @SuppressWarnings("unused") + class Target { + String target() { + return ""; + } + } + + @SuppressWarnings("unused") + class Caller1 { + private Target target; + + Supplier call() { + return () -> target.target(); + } + } + + @SuppressWarnings("unused") + class Caller2 { + private Target target; + + Supplier call() { + return () -> target.target(); + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Target.class, Caller1.class, Caller2.class); + + JavaMethodCall call1 = getOnlyElement(classes.get(Caller1.class).getMethodCallsFromSelf()); + assertThatCall(call1).isFrom("call").isTo(Target.class, "target"); + + JavaMethodCall call2 = getOnlyElement(classes.get(Caller2.class).getMethodCallsFromSelf()); + assertThatCall(call2).isFrom("call").isTo(Target.class, "target"); + } + + @Test + public void imports_complex_combination_of_lambda_accesses_to_nested_class() { + @SuppressWarnings("unused") + class Caller { + private Target target; + + void call() { + Supplier>> quiteNestedConstructorCallSupplier = () -> () -> Target::new; + Function>>> quiteNestedMethodCallSupplier = s -> () -> () -> () -> target.inner.method(s); + Supplier>> quiteNestedFieldSupplier = () -> () -> () -> target.inner.field; + } + + class Target { + final Inner inner = new Inner(); + + class Inner { + String field; + + Target method(String s) { + return Target.this; + } + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Caller.class, Caller.Target.class); + Set> accesses = classes.get(Caller.class).getAccessesFromSelf(); + assertThatAccesses(accesses) + .contain(access() + .fromOrigin(Caller.class, "call") + .toTarget(Caller.Target.class, CONSTRUCTOR_NAME) + ) + .contain(access() + .fromOrigin(Caller.class, "call") + .toTarget(Caller.Target.Inner.class, "method") + ) + .contain(access() + .fromOrigin(Caller.class, "call") + .toTarget(Caller.Target.Inner.class, "field") + ); + } + + @Test + public void does_not_add_synthetic_lambda_methods_to_classes() { + class Target { + void target() { + } + } + + @SuppressWarnings("unused") + class Caller { + @SuppressWarnings("Convert2MethodRef") + Runnable call(Target target) { + return () -> target.target(); + } + } + + JavaClass caller = new ClassFileImporter().importClasses(Target.class, Caller.class).get(Caller.class); + + assertThat(caller.getMethods()) + .doNotHave(syntheticLambdaMethods()) + .hasSize(1); + } + + private static class Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses { + static class Target { + Object field; + + void method() { + } + } + } + + @DataProvider + public static Object[][] data_adds_information_about_being_declared_inside_a_lambda_to_accesses() { + @SuppressWarnings("unused") + class FieldAccessCase { + Object fieldAccessDirect(Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target target) { + return target.field; + } + + Supplier fieldAccessFromLambda(Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target target) { + return () -> target.field; + } + } + @SuppressWarnings("unused") + class MethodCallCase { + void methodCallDirect(Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target target) { + target.method(); + } + + @SuppressWarnings("Convert2MethodRef") + Runnable methodCallFromLambda(Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target target) { + return () -> target.method(); + } + } + @SuppressWarnings("unused") + class ConstructorCallCase { + Object constructorCallDirect() { + return new Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target(); + } + + @SuppressWarnings("Convert2MethodRef") + Supplier constructorCallFromLambda() { + return () -> new Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target(); + } + } + @SuppressWarnings("unused") + class MethodReferenceCase { + Runnable methodReferenceDirect(Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target target) { + return target::method; + } + + Supplier methodReferenceFromLambda(Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target target) { + return () -> target::method; + } + } + @SuppressWarnings("unused") + class ConstructorReferenceCase { + Supplier constructorReferenceDirect() { + return Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target::new; + } + + Supplier> constructorReferenceFromLambda() { + return () -> Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target::new; + } + } + + return $$( + $(FieldAccessCase.class, Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target.class), + $(MethodCallCase.class, Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target.class), + $(ConstructorCallCase.class, Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target.class), + $(MethodReferenceCase.class, Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target.class), + $(ConstructorReferenceCase.class, Data_of_adds_information_about_being_declared_inside_a_lambda_to_accesses.Target.class) + ); + } + + @Test + @UseDataProvider + public void test_adds_information_about_being_declared_inside_a_lambda_to_accesses(Class caller, Class target) { + Set> accesses = new ClassFileImporter() + .importClasses(caller, target) + .get(caller).getAccessesFromSelf().stream() + .filter(a -> a.getTargetOwner().isEquivalentTo(target)) + .collect(toSet()); + + assertThat(accesses.stream().filter(a -> a.getOrigin().getName().contains("FromLambda"))) + .isNotEmpty() + .allMatch(JavaAccess::isDeclaredInLambda); + assertThat(accesses.stream().filter(a -> a.getOrigin().getName().contains("Direct"))) + .isNotEmpty() + .allMatch(javaAccess -> !javaAccess.isDeclaredInLambda()); + } + + private Condition syntheticLambdaMethods() { + return new Condition<>(method -> isLambdaMethodName(method.getName()), "synthetic lambda methods"); + } + + @SuppressWarnings("SameParameterValue") + private Condition targetCodeUnit(String codeUnitName, Class... parameterTypes) { + Predicate targetMatches = target -> + target.getName().equals(codeUnitName) + && namesOf(target.getRawParameterTypes()).equals(formatNamesOf(parameterTypes)); + + return new Condition<>(targetMatches, String.format("%s(%s)", codeUnitName, String.join(", ", formatNamesOf(parameterTypes)))); + } + + private JavaMethodCall singleByName(Set calls, String methodName) { + return getOnlyElement(filterOriginByName(calls, methodName)); + } + + private > Set filterOriginByName(Set calls, String methodName) { + return calls.stream() + .filter(call -> call.getOrigin().getName().equals(methodName)) + .collect(toSet()); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTestUtils.java new file mode 100644 index 0000000000..e7ff585cee --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTestUtils.java @@ -0,0 +1,7 @@ +package com.tngtech.archunit.core.importer; + +public class JavaClassDescriptorImporterTestUtils { + public static boolean isLambdaMethodName(String methodName) { + return JavaClassDescriptorImporter.isLambdaMethodName(methodName); + } +} diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/AnotherAnnotationWithAnnotationParameter.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/AnotherAnnotationWithAnnotationParameter.java similarity index 92% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/AnotherAnnotationWithAnnotationParameter.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/AnotherAnnotationWithAnnotationParameter.java index 692339fb4b..92ba114d91 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/AnotherAnnotationWithAnnotationParameter.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/AnotherAnnotationWithAnnotationParameter.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.annotations; +package com.tngtech.archunit.core.importer.testexamples.annotationresolution; public @interface AnotherAnnotationWithAnnotationParameter { SomeAnnotationWithAnnotationParameter value(); diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/SomeAnnotationWithAnnotationParameter.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/SomeAnnotationWithAnnotationParameter.java similarity index 92% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/SomeAnnotationWithAnnotationParameter.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/SomeAnnotationWithAnnotationParameter.java index 01358b071f..539fa1155f 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/SomeAnnotationWithAnnotationParameter.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/SomeAnnotationWithAnnotationParameter.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.annotations; +package com.tngtech.archunit.core.importer.testexamples.annotationresolution; public @interface SomeAnnotationWithAnnotationParameter { SomeAnnotationWithClassParameter value(); diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/SomeAnnotationWithClassParameter.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/SomeAnnotationWithClassParameter.java similarity index 91% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/SomeAnnotationWithClassParameter.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/SomeAnnotationWithClassParameter.java index 08856d09bd..2f26ab3530 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/annotations/SomeAnnotationWithClassParameter.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/annotationresolution/SomeAnnotationWithClassParameter.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.annotations; +package com.tngtech.archunit.core.importer.testexamples.annotationresolution; public @interface SomeAnnotationWithClassParameter { Class value(); diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/Child.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/Child.java similarity index 88% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/Child.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/Child.java index b34e5956be..72168ef42c 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/Child.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/Child.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.classhierarchy; +package com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution; public class Child extends Parent implements ParentInterfaceDirect { } diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParent.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParent.java similarity index 87% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParent.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParent.java index d3a89462ee..416ca84b41 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParent.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParent.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.classhierarchy; +package com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution; public class GrandParent implements ParentInterfaceIndirect { } diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParentInterfaceDirect.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParentInterfaceDirect.java similarity index 86% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParentInterfaceDirect.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParentInterfaceDirect.java index 29989956f1..a9d2ee59ee 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParentInterfaceDirect.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParentInterfaceDirect.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.classhierarchy; +package com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution; public interface GrandParentInterfaceDirect { } diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParentInterfaceIndirect.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParentInterfaceIndirect.java similarity index 86% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParentInterfaceIndirect.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParentInterfaceIndirect.java index d668927cc9..6a864ceffb 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/GrandParentInterfaceIndirect.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/GrandParentInterfaceIndirect.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.classhierarchy; +package com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution; public interface GrandParentInterfaceIndirect { } diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/Parent.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/Parent.java similarity index 85% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/Parent.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/Parent.java index ceebf26d70..52c18c7e71 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/Parent.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/Parent.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.classhierarchy; +package com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution; public class Parent extends GrandParent { } diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/ParentInterfaceDirect.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/ParentInterfaceDirect.java similarity index 88% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/ParentInterfaceDirect.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/ParentInterfaceDirect.java index 35c3d953c8..b563ab35d9 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/ParentInterfaceDirect.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/ParentInterfaceDirect.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.classhierarchy; +package com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution; public interface ParentInterfaceDirect extends GrandParentInterfaceDirect { } diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/ParentInterfaceIndirect.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/ParentInterfaceIndirect.java similarity index 89% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/ParentInterfaceIndirect.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/ParentInterfaceIndirect.java index 5cc0380c44..85009559e1 100644 --- a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchy/ParentInterfaceIndirect.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/classhierarchyresolution/ParentInterfaceIndirect.java @@ -1,4 +1,4 @@ -package com.tngtech.archunit.core.importer.testexamples.classhierarchy; +package com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution; public interface ParentInterfaceIndirect extends GrandParentInterfaceIndirect { } diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Origin.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Origin.java similarity index 100% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Origin.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Origin.java diff --git a/archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Target.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Target.java similarity index 100% rename from archunit/src/jdk9test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Target.java rename to archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/codeunitreferences/Target.java diff --git a/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java b/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java index a9033c9996..fb3f5ad8b0 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/freeze/FreezingArchRuleTest.java @@ -414,16 +414,14 @@ public void default_ViolationLineMatcher_ignores_line_numbers_and_auto_generated .withViolations( "first violation one in (SomeClass.java:12) and first violation two in (SomeClass.java:13)", "second violation in (SomeClass.java:77)", - "third violation in (OtherClass.java:123)", - "Method has a violation in (MyClass.java:123)" + "third violation in (OtherClass.java:123)" ).create()); String onlyLineNumberChanged = "first violation one in (SomeClass.java:98) and first violation two in (SomeClass.java:99)"; String locationClassDoesNotMatch = "second violation in (OtherClass.java:77)"; String descriptionDoesNotMatch = "unknown violation in (SomeClass.java:77)"; - String lambdaWithDifferentNumber = "Method has a violation in (MyClass.java:123)"; FreezingArchRule updatedViolations = freeze(rule("some description") - .withViolations(onlyLineNumberChanged, locationClassDoesNotMatch, descriptionDoesNotMatch, lambdaWithDifferentNumber).create()) + .withViolations(onlyLineNumberChanged, locationClassDoesNotMatch, descriptionDoesNotMatch).create()) .persistIn(violationStore); assertThatRule(updatedViolations) @@ -509,7 +507,7 @@ private static class RuleCreator { private final Function textModifier; private RuleCreator(String description) { - this(description, new ArrayList(), Functions.identity()); + this(description, new ArrayList<>(), Functions.identity()); } private RuleCreator(String description, List events, Function textModifier) { @@ -519,7 +517,7 @@ private RuleCreator(String description, List events, Function(), textModifier); + return new RuleCreator(description, new ArrayList<>(), textModifier); } RuleCreator withViolations(final String... messages) { diff --git a/archunit/src/test/java/com/tngtech/archunit/library/freeze/ViolationLineMatcherFactoryTest.java b/archunit/src/test/java/com/tngtech/archunit/library/freeze/ViolationLineMatcherFactoryTest.java index 66582f32ae..9ffbe57b2e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/freeze/ViolationLineMatcherFactoryTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/freeze/ViolationLineMatcherFactoryTest.java @@ -39,11 +39,11 @@ public class ViolationLineMatcherFactoryTest { "A:1|" + "A$2|" + false, "" + - "Method has a violation in (MyClass.java:123)|" + - "Method has a violation in (MyClass.java:0)|" + true, + "Method has a violation in (MyClass.java:123)|" + + "Method has a violation in (MyClass.java:0)|" + true, "" + - "Method is bad in (C.java:123)|" + - "Method is bad in (C.java:123), too|" + false, + "Method is bad in (C.java:123)|" + + "Method is bad in (C.java:123), too|" + false, "" + "A:1) B$2 C|" + // limitation of the current implementation: "A: B$ C|" + true, // false positive diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java index 1912003180..c41dff2727 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java @@ -1,6 +1,6 @@ package com.tngtech.archunit.testutil.assertion; -import com.tngtech.archunit.core.domain.AccessTarget; +import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess; import org.assertj.core.api.Condition; @@ -8,7 +8,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; import static org.assertj.core.api.Assertions.assertThat; -public class AccessToFieldAssertion extends BaseAccessAssertion { +public class AccessToFieldAssertion extends BaseAccessAssertion { public AccessToFieldAssertion(JavaFieldAccess access) { super(access); } @@ -19,14 +19,23 @@ protected AccessToFieldAssertion newAssertion(JavaFieldAccess access) { } public AccessToFieldAssertion isTo(final String name) { - return isTo(new Condition("field with name '" + name + "'") { + return isTo(new Condition("field with name '" + name + "'") { @Override - public boolean matches(AccessTarget.FieldAccessTarget fieldAccessTarget) { + public boolean matches(FieldAccessTarget fieldAccessTarget) { return fieldAccessTarget.getName().equals(name); } }); } + public AccessToFieldAssertion isTo(final Class owner, final String name) { + return isTo(new Condition("field " + owner.getName() + "." + name) { + @Override + public boolean matches(FieldAccessTarget fieldAccessTarget) { + return fieldAccessTarget.getOwner().isEquivalentTo(owner) && fieldAccessTarget.getName().equals(name); + } + }); + } + public AccessToFieldAssertion isTo(JavaField field) { return isTo(targetFrom(field)); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java index 08f02ff4d9..89ec0bf66c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java @@ -5,6 +5,7 @@ import java.util.Iterator; import java.util.Set; +import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaAccess; import org.assertj.core.api.Condition; @@ -34,5 +35,38 @@ public final AccessesAssertion containOnly(Condition>... c } assertThat(actualRemaining).as("Unexpected " + JavaAccess.class.getSimpleName()).isEmpty(); return this; + }public static AccessCondition access() { + return new AccessCondition(); + } + + public static class AccessCondition extends Condition> { + private final DescribedPredicate> predicate; + + public AccessCondition() { + this(DescribedPredicate.>alwaysTrue().as("access")); + } + + public AccessCondition(DescribedPredicate> predicate) { + super(predicate, predicate.getDescription()); + this.predicate = predicate; + } + + public AccessCondition fromOrigin(Class owner, String name) { + DescribedPredicate> fromCodeUnit = DescribedPredicate.describe("", access -> + access.getOrigin().getOwner().isEquivalentTo(owner) + && access.getOrigin().getName().equals(name)); + return new AccessCondition( + predicate.and(fromCodeUnit) + .as("%s from origin %s.%s()", predicate.getDescription(), owner.getName(), name)); + } + + public AccessCondition toTarget(Class owner, String name) { + DescribedPredicate> toCodeUnit = DescribedPredicate.describe("", access -> + access.getTarget().getOwner().isEquivalentTo(owner) + && access.getTarget().getName().equals(name)); + return new AccessCondition( + predicate.and(toCodeUnit) + .as("%s to target %s.%s", predicate.getDescription(), owner.getName(), name)); + } } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java index 9c322c7eed..f361422570 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java @@ -47,6 +47,10 @@ public boolean matches(AccessTarget.CodeUnitAccessTarget target) { }); } + public CodeUnitAccessAssertion isTo(Class targetClass, String codeUnitName, final Class... parameterTypes) { + return isTo(targetClass).isTo(codeUnitName, parameterTypes); + } + public CodeUnitAccessAssertion isWrappedWithTryCatchFor(Class throwableType) { assertThat(access.getContainingTryBlocks()) .as("containing try blocks") diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java index 45f4707f57..0a4fddd7e6 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java @@ -3,6 +3,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Member; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -25,11 +26,13 @@ import static com.tngtech.archunit.core.domain.JavaModifier.SYNCHRONIZED; import static com.tngtech.archunit.core.domain.JavaModifier.TRANSIENT; import static com.tngtech.archunit.core.domain.JavaModifier.VOLATILE; +import static com.tngtech.archunit.core.importer.JavaClassDescriptorImporterTestUtils.isLambdaMethodName; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; import static java.util.stream.StreamSupport.stream; public class JavaMembersAssertion extends AbstractObjectAssert> { @@ -62,7 +65,7 @@ public void matchInAnyOrderMembersOf(Class... classes) { Set members = new HashSet<>(); for (Class clazz : classes) { members.addAll(ImmutableSet.copyOf(clazz.getDeclaredFields())); - members.addAll(ImmutableSet.copyOf(clazz.getDeclaredMethods())); + members.addAll(Arrays.stream(clazz.getDeclaredMethods()).filter(m -> !isLambdaMethodName(m.getName())).collect(toSet())); members.addAll(ImmutableSet.copyOf(clazz.getDeclaredConstructors())); } @@ -71,7 +74,7 @@ public void matchInAnyOrderMembersOf(Class... classes) { // We know this is compatible since every Member also implements AnnotatedElement // Unfortunately the Java type system does not make it easy here - @SuppressWarnings("rawtypes") + @SuppressWarnings({"rawtypes", "unchecked"}) private void matchCasted(Set members) { matchInAnyOrder((Set) members); }