diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 013e452635..ac035dc82b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -656,6 +656,11 @@ public JavaClass toErasure() { return this; } + @Override + public Set getAllInvolvedRawTypes() { + return ImmutableSet.of(getBaseComponentType()); + } + @PublicAPI(usage = ACCESS) public Optional getRawSuperclass() { return superclass.getRaw(); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java index efe43febbb..f0473a1990 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -43,6 +44,7 @@ import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.core.domain.Formatters.formatMethod; import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; +import static java.util.stream.Collectors.toSet; /** * Represents a unit of code containing accesses to other units of code. A unit of code can be @@ -169,6 +171,22 @@ public JavaClass getRawReturnType() { return returnType.getRaw(); } + /** + * @return All raw types involved in this code unit's signature, + * which is the union of all raw types involved in the {@link #getReturnType() return type}, + * the {@link #getParameterTypes() parameter types} and the {@link #getTypeParameters() type parameters} of this code unit. + * For a definition of "all raw types involved" consult {@link JavaType#getAllInvolvedRawTypes()}. + */ + @Override + @PublicAPI(usage = ACCESS) + public Set getAllInvolvedRawTypes() { + return Stream.of( + Stream.of(this.returnType.get()), + this.parameters.getParameterTypes().stream(), + this.typeParameters.stream() + ).flatMap(s -> s).map(JavaType::getAllInvolvedRawTypes).flatMap(Set::stream).collect(toSet()); + } + @PublicAPI(usage = ACCESS) public Set getFieldAccesses() { return fieldAccesses; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java index 335f0e93b5..2022429a21 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java @@ -62,6 +62,15 @@ public JavaClass getRawType() { return type.toErasure(); } + /** + * @return All raw types involved in this field's signature, which is equivalent to {@link #getType()}.{@link JavaType#getAllInvolvedRawTypes() getAllInvolvedRawTypes()}. + */ + @Override + @PublicAPI(usage = ACCESS) + public Set getAllInvolvedRawTypes() { + return getType().getAllInvolvedRawTypes(); + } + @Override @PublicAPI(usage = ACCESS) public Set getAccessesToSelf() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java index 1a126f24ae..0b8f1a3df9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java @@ -15,6 +15,8 @@ */ package com.tngtech.archunit.core.domain; +import java.util.Set; + import com.tngtech.archunit.PublicAPI; import static com.google.common.base.Preconditions.checkNotNull; @@ -67,6 +69,11 @@ public JavaClass toErasure() { return erasure; } + @Override + public Set getAllInvolvedRawTypes() { + return this.componentType.getAllInvolvedRawTypes(); + } + @Override public String toString() { return getClass().getSimpleName() + '{' + getName() + '}'; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java index ae0213b2f6..4b7f0e57b1 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java @@ -63,6 +63,15 @@ public abstract class JavaMember implements this.modifiers = checkNotNull(builder.getModifiers()); } + /** + * Similar to {@link JavaType#getAllInvolvedRawTypes()}, this method returns all raw types involved in this {@link JavaMember member's} signature. + * For more concrete details refer to {@link JavaField#getAllInvolvedRawTypes()} and {@link JavaCodeUnit#getAllInvolvedRawTypes()}. + * + * @return All raw types involved in the signature of this member + */ + @PublicAPI(usage = ACCESS) + public abstract Set getAllInvolvedRawTypes(); + @Override @PublicAPI(usage = ACCESS) public Set> getAnnotations() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java index 9bd0c68dc9..119140ed65 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java @@ -16,6 +16,7 @@ package com.tngtech.archunit.core.domain; import java.lang.reflect.Type; +import java.util.Set; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ChainableFunction; @@ -56,6 +57,33 @@ public interface JavaType extends HasName { @PublicAPI(usage = ACCESS) JavaClass toErasure(); + /** + * Returns the set of all raw types that are involved in this type. + * If this type is a {@link JavaClass}, then this method trivially returns only the class itself. + * If this type is a {@link JavaParameterizedType}, {@link JavaTypeVariable}, {@link JavaWildcardType}, etc., + * then this method returns all raw types involved in type arguments and upper and lower bounds recursively. + * If this type is an array type, then this method returns all raw types involved in the component type of the array type. + *

+ * Examples:
+ * For the parameterized type + *

+     * List<String>
+ * the result would be the {@link JavaClass classes} [List, String].
+ * For the parameterized type + *

+     * Map<? extends Serializable, List<? super Integer[]>>
+ * the result would be [Map, Serializable, List, Integer].
+ * And for the type variable + *

+     * T extends List<? super Integer>
+ * the result would be [List, Integer].
+ * Thus, this method offers a quick way to determine all types a (possibly complex) type depends on. + * + * @return All raw types involved in this {@link JavaType} + */ + @PublicAPI(usage = ACCESS) + Set getAllInvolvedRawTypes(); + /** * Predefined {@link ChainableFunction functions} to transform {@link JavaType}. */ diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java index 764806a10d..74b4d8d65e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java @@ -17,6 +17,7 @@ import java.lang.reflect.TypeVariable; import java.util.List; +import java.util.Set; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.HasDescription; @@ -28,6 +29,7 @@ import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toSet; /** * Represents a type variable used by generic types and members.
@@ -119,6 +121,14 @@ public JavaClass toErasure() { return erasure; } + @Override + public Set getAllInvolvedRawTypes() { + return this.upperBounds.stream() + .map(JavaType::getAllInvolvedRawTypes) + .flatMap(Set::stream) + .collect(toSet()); + } + @Override public String toString() { String bounds = printExtendsClause() ? " extends " + joinTypeNames(upperBounds) : ""; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java index 3ecb8d3c58..12192d3165 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java @@ -17,6 +17,8 @@ import java.lang.reflect.WildcardType; import java.util.List; +import java.util.Set; +import java.util.stream.Stream; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.core.domain.properties.HasUpperBounds; @@ -25,6 +27,7 @@ import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.core.domain.Formatters.ensureCanonicalArrayTypeName; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toSet; /** * Represents a wildcard type in a type signature (compare the JLS). @@ -95,6 +98,14 @@ public JavaClass toErasure() { return erasure; } + @Override + public Set getAllInvolvedRawTypes() { + return Stream.concat(upperBounds.stream(), lowerBounds.stream()) + .map(JavaType::getAllInvolvedRawTypes) + .flatMap(Set::stream) + .collect(toSet()); + } + @Override public String toString() { return getClass().getSimpleName() + '{' + getName() + '}'; 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 86f7a32a2e..c037184981 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 @@ -88,6 +88,7 @@ import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toSet; @Internal @SuppressWarnings("UnusedReturnValue") @@ -1216,6 +1217,14 @@ public JavaClass toErasure() { return type.toErasure(); } + @Override + public Set getAllInvolvedRawTypes() { + return Stream.concat( + type.getAllInvolvedRawTypes().stream(), + typeArguments.stream().map(JavaType::getAllInvolvedRawTypes).flatMap(Set::stream) + ).collect(toSet()); + } + @Override public List getActualTypeArguments() { return typeArguments; 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 6654d5e962..e5596fed13 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 @@ -174,6 +174,28 @@ class SimpleClass { assertThat(type.toErasure()).isEqualTo(type); } + @Test + public void get_all_involved_raw_types_returns_only_self_for_non_array_type() { + class SimpleClass { + } + + JavaClass clazz = new ClassFileImporter().importClass(SimpleClass.class); + + assertThatTypes(clazz.getAllInvolvedRawTypes()).matchExactly(SimpleClass.class); + } + + @Test + public void get_all_involved_raw_types_returns_component_type_for_array_type() { + class SimpleClass { + @SuppressWarnings("unused") + SimpleClass[][] field; + } + + JavaType arrayType = new ClassFileImporter().importClass(SimpleClass.class).getField("field").getType(); + + assertThatTypes(arrayType.getAllInvolvedRawTypes()).matchExactly(SimpleClass.class); + } + @Test public void finds_component_type_chain_of_otherwise_unreferenced_component_type() { @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaCodeUnitTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaCodeUnitTest.java index e2b61efd45..726c56230b 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaCodeUnitTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaCodeUnitTest.java @@ -1,8 +1,11 @@ package com.tngtech.archunit.core.domain; import java.io.File; +import java.io.Serializable; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import com.google.common.collect.ImmutableList; import com.tngtech.archunit.core.importer.ClassFileImporter; @@ -20,12 +23,28 @@ import static com.tngtech.archunit.core.domain.properties.HasType.Functions.GET_RAW_TYPE; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatAnnotation; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static org.assertj.core.api.Assertions.assertThatThrownBy; @RunWith(DataProviderRunner.class) public class JavaCodeUnitTest { + @Test + public void offers_all_involved_raw_types() { + class SampleClass & Serializable> { + @SuppressWarnings("unused") + T method(List>> input) { + return null; + } + } + + JavaMethod method = new ClassFileImporter().importClass(SampleClass.class).getMethod("method", List.class); + + assertThatTypes(method.getAllInvolvedRawTypes()) + .matchInAnyOrder(Collection.class, File.class, Serializable.class, List.class, Map.class, Number.class, Set.class, String.class); + } + @Test public void offers_all_calls_from_Self() { JavaMethod method = importClassWithContext(ClassAccessingOtherClass.class).getMethod("access", ClassBeingAccessed.class); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaFieldTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaFieldTest.java new file mode 100644 index 0000000000..2d93d7e879 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaFieldTest.java @@ -0,0 +1,25 @@ +package com.tngtech.archunit.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.Test; + +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; + +public class JavaFieldTest { + @Test + public void offers_all_involved_raw_types() { + class SomeClass { + @SuppressWarnings("unused") + List>> field; + } + + JavaField field = new ClassFileImporter().importClass(SomeClass.class).getField("field"); + + assertThatTypes(field.getAllInvolvedRawTypes()).matchInAnyOrder(List.class, Map.class, Serializable.class, Set.class, Number.class); + } +} \ No newline at end of file diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java index 88e8bd8c19..b5aa0aea0c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -10,6 +10,7 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.Assertions.assertThatTypeErasuresOf; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; public class JavaTypeVariableTest { @@ -119,6 +120,30 @@ class ClassWithBoundTypeParameterWithGenericArrayBounds> { + @SuppressWarnings("unused") + private T field; + } + + JavaTypeVariable typeVariable = (JavaTypeVariable) new ClassFileImporter().importClass(SampleClass.class).getField("field").getType(); + + assertThatTypes(typeVariable.getAllInvolvedRawTypes()).matchInAnyOrder(String.class, List.class, Serializable.class); + } + + @Test + public void all_involved_raw_types_of_generic_array() { + class SampleClass> { + @SuppressWarnings("unused") + private T[][] field; + } + + JavaGenericArrayType typeVariable = (JavaGenericArrayType) new ClassFileImporter().importClass(SampleClass.class).getField("field").getType(); + + assertThatTypes(typeVariable.getAllInvolvedRawTypes()).matchInAnyOrder(String.class, List.class, Serializable.class); + } + @Test public void toString_unbounded() { @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java index 4a7c5c6dc7..8bf27c37bf 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java @@ -1,7 +1,10 @@ package com.tngtech.archunit.core.domain; +import java.io.File; import java.io.Serializable; import java.util.List; +import java.util.Map; +import java.util.Set; import com.tngtech.archunit.core.importer.ClassFileImporter; import org.junit.Test; @@ -154,6 +157,50 @@ class ClassWithBoundTypeParameterWithGenericArrayBounds> { + } + + JavaWildcardType type = importWildcardTypeOf(SampleClass.class); + + assertThat(type.getAllInvolvedRawTypes()).isEmpty(); + } + + @Test + public void allInvolvedRawTypes_finds_involved_raw_types_of_lower_bounds() { + @SuppressWarnings("unused") + class SampleClass> { + } + + JavaWildcardType type = importWildcardTypeOf(SampleClass.class); + + assertThatTypes(type.getAllInvolvedRawTypes()).matchInAnyOrder(String.class); + } + + @Test + public void allInvolvedRawTypes_finds_involved_raw_types_of_upper_bounds() { + @SuppressWarnings("unused") + class SampleClass> { + } + + JavaWildcardType type = importWildcardTypeOf(SampleClass.class); + + assertThatTypes(type.getAllInvolvedRawTypes()).matchInAnyOrder(String.class); + } + + @Test + public void allInvolvedRawTypes_finds_involved_raw_types_for_complex_upper_and_lower_bounds() { + @SuppressWarnings("unused") + class SampleClass, Set>>> { + } + + JavaWildcardType type = importWildcardTypeOf(SampleClass.class); + + assertThatTypes(type.getAllInvolvedRawTypes()).matchInAnyOrder(Map.class, List.class, String.class, Set.class, File.class); + } + @Test public void toString_unbounded() { @SuppressWarnings("unused") diff --git a/develop/ArchUnit-codestyle-intellij.xml b/develop/ArchUnit-codestyle-intellij.xml index 67526d8b48..ed5f56a780 100644 --- a/develop/ArchUnit-codestyle-intellij.xml +++ b/develop/ArchUnit-codestyle-intellij.xml @@ -22,6 +22,7 @@