From f4b0684da29ca2496d5123decaa9a57b14c9b5bb Mon Sep 17 00:00:00 2001 From: Leonard Husmann Date: Fri, 12 May 2023 15:58:29 +0200 Subject: [PATCH] add `JavaType.getAllInvolvedRawTypes()` Adds a convenience method to quickly determine all `JavaClass`es any `JavaType` depends on. For a complex type, like the parameterized type `Map>>`, this can otherwise be quite tedious and possibly make it necessary to traverse the whole type parameter hierarchy. Issue: #723 Signed-off-by: Leonard Husmann Signed-off-by: Peter Gafert --- .../archunit/core/domain/JavaClass.java | 5 ++ .../core/domain/JavaGenericArrayType.java | 7 +++ .../archunit/core/domain/JavaType.java | 28 +++++++++++ .../core/domain/JavaTypeVariable.java | 10 ++++ .../core/domain/JavaWildcardType.java | 11 +++++ .../core/importer/DomainBuilders.java | 9 ++++ .../archunit/core/domain/JavaClassTest.java | 22 +++++++++ .../core/domain/JavaTypeVariableTest.java | 25 ++++++++++ .../core/domain/JavaWildcardTypeTest.java | 47 +++++++++++++++++++ 9 files changed, 164 insertions(+) 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/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/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/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")