Skip to content

Commit

Permalink
add JavaType.getAllInvolvedRawTypes()
Browse files Browse the repository at this point in the history
Adds a convenience method to quickly determine all `JavaClass`es any `JavaType` depends on. For a complex type, like the parameterized type `Map<? extends Serializable, List<? super Set<String>>>`, 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 <leonard.husmann@tum.de>
Signed-off-by: Peter Gafert <peter.gafert@archunit.org>
  • Loading branch information
leonardhusmann authored and codecholeric committed Nov 5, 2023
1 parent 6f9c954 commit f4b0684
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,11 @@ public JavaClass toErasure() {
return this;
}

@Override
public Set<JavaClass> getAllInvolvedRawTypes() {
return ImmutableSet.of(getBaseComponentType());
}

@PublicAPI(usage = ACCESS)
public Optional<JavaClass> getRawSuperclass() {
return superclass.getRaw();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -67,6 +69,11 @@ public JavaClass toErasure() {
return erasure;
}

@Override
public Set<JavaClass> getAllInvolvedRawTypes() {
return this.componentType.getAllInvolvedRawTypes();
}

@Override
public String toString() {
return getClass().getSimpleName() + '{' + getName() + '}';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <br><br>
* Examples:<br>
* For the parameterized type
* <pre><code>
* List&lt;String&gt;</code></pre>
* the result would be the {@link JavaClass classes} <code>[List, String]</code>.<br>
* For the parameterized type
* <pre><code>
* Map&lt;? extends Serializable, List&lt;? super Integer[]&gt;&gt;</code></pre>
* the result would be <code>[Map, Serializable, List, Integer]</code>.<br>
* And for the type variable
* <pre><code>
* T extends List&lt;? super Integer&gt;</code></pre>
* the result would be <code>[List, Integer]</code>.<br>
* 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<JavaClass> getAllInvolvedRawTypes();

/**
* Predefined {@link ChainableFunction functions} to transform {@link JavaType}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.<br>
Expand Down Expand Up @@ -119,6 +121,14 @@ public JavaClass toErasure() {
return erasure;
}

@Override
public Set<JavaClass> getAllInvolvedRawTypes() {
return this.upperBounds.stream()
.map(JavaType::getAllInvolvedRawTypes)
.flatMap(Set::stream)
.collect(toSet());
}

@Override
public String toString() {
String bounds = printExtendsClause() ? " extends " + joinTypeNames(upperBounds) : "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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).
Expand Down Expand Up @@ -95,6 +98,14 @@ public JavaClass toErasure() {
return erasure;
}

@Override
public Set<JavaClass> getAllInvolvedRawTypes() {
return Stream.concat(upperBounds.stream(), lowerBounds.stream())
.map(JavaType::getAllInvolvedRawTypes)
.flatMap(Set::stream)
.collect(toSet());
}

@Override
public String toString() {
return getClass().getSimpleName() + '{' + getName() + '}';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -1216,6 +1217,14 @@ public JavaClass toErasure() {
return type.toErasure();
}

@Override
public Set<JavaClass> getAllInvolvedRawTypes() {
return Stream.concat(
type.getAllInvolvedRawTypes().stream(),
typeArguments.stream().map(JavaType::getAllInvolvedRawTypes).flatMap(Set::stream)
).collect(toSet());
}

@Override
public List<JavaType> getActualTypeArguments() {
return typeArguments;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -119,6 +120,30 @@ class ClassWithBoundTypeParameterWithGenericArrayBounds<A, B extends String, C e
assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(5)).toErasure()).matches(List[][][].class);
}

@Test
public void all_involved_raw_types() {
class SampleClass<T extends String & List<Serializable[]>> {
@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<T extends String & List<Serializable>> {
@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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -154,6 +157,50 @@ class ClassWithBoundTypeParameterWithGenericArrayBounds<A, B extends String, C e
assertThatType(wildcard.toErasure()).matches(List[][][].class);
}

@Test
public void allInvolvedRawTypes_finds_no_involved_raw_types_of_unbound_wildcard() {
@SuppressWarnings("unused")
class SampleClass<T extends List<?>> {
}

JavaWildcardType type = importWildcardTypeOf(SampleClass.class);

assertThat(type.getAllInvolvedRawTypes()).isEmpty();
}

@Test
public void allInvolvedRawTypes_finds_involved_raw_types_of_lower_bounds() {
@SuppressWarnings("unused")
class SampleClass<T extends List<? super String>> {
}

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<T extends List<? extends String>> {
}

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<T extends List<? extends Map<? extends List<String[][]>, Set<? super File[]>>>> {
}

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")
Expand Down

0 comments on commit f4b0684

Please sign in to comment.