Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offer a way to retrieve the component type of a JavaClass that is an array #244

Merged
merged 4 commits into from
Nov 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,6 @@ public interface ImportContext {
Set<JavaConstructor> getConstructorsWithParameterOfType(JavaClass javaClass);

Set<ThrowsDeclaration<JavaConstructor>> getConstructorThrowsDeclarationsOfType(JavaClass javaClass);

JavaClass resolveClass(String fullyQualifiedClassName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public class JavaClass implements HasName.AndFullName, HasAnnotations, HasModifi
private final Set<JavaClass> interfaces = new HashSet<>();
private final Set<JavaClass> subClasses = new HashSet<>();
private Optional<JavaClass> enclosingClass = Optional.absent();
private Optional<JavaClass> componentType = Optional.absent();
private Supplier<Map<String, JavaAnnotation>> annotations =
Suppliers.ofInstance(Collections.<String, JavaAnnotation>emptyMap());
private Supplier<Set<JavaMethod>> allMethods;
Expand Down Expand Up @@ -186,6 +187,30 @@ public boolean isArray() {
return javaType.isArray();
}

/**
* This is a convenience method for {@link #tryGetComponentType()} in cases where
* clients know that this type is certainly an array type and thus the component type present.
* @throws IllegalArgumentException if this class is no array
* @return The result of {@link #tryGetComponentType()}
*/
@PublicAPI(usage = ACCESS)
public JavaClass getComponentType() {
return tryGetComponentType().getOrThrow(new IllegalArgumentException(
String.format("Type %s is no array", getSimpleName())));
}

/**
* Returns the component type of this class, if this class is an array, otherwise
* {@link Optional#absent()}. The component type is the type of the elements of an array type.
* Consider {@code String[]}, then the component type would be {@code String}.
* Likewise for {@code String[][]} the component type would be {@code String[]}.
* @return The component type, if this type is an array, otherwise {@link Optional#absent()}
*/
@PublicAPI(usage = ACCESS)
Optional<JavaClass> tryGetComponentType() {
return componentType;
}

/**
* @return Returns true if this class is declared within another class.
* Returns false for top-level classes.
Expand Down Expand Up @@ -841,6 +866,7 @@ public Map<String, JavaAnnotation> get() {
}

CompletionProcess completeFrom(ImportContext context) {
completeComponentType(context);
enclosingClass = context.createEnclosingClass(this);
memberDependenciesOnClass = new MemberDependenciesOnClass(
context.getFieldsOfType(this),
Expand All @@ -852,6 +878,15 @@ CompletionProcess completeFrom(ImportContext context) {
return new CompletionProcess();
}

private void completeComponentType(ImportContext context) {
JavaClass current = this;
while (current.isArray() && !current.componentType.isPresent()) {
JavaClass componentType = context.resolveClass(current.javaType.tryGetComponentType().get().getName());
current.componentType = Optional.of(componentType);
current = componentType;
}
}

@Override
public String toString() {
return "JavaClass{name='" + javaType.getName() + "\'}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ public Optional<JavaClass> createEnclosingClass(JavaClass owner) {
Optional.<JavaClass>absent();
}

@Override
public JavaClass resolveClass(String fullyQualifiedClassName) {
return classes.getOrResolve(fullyQualifiedClassName);
}

private static class MemberDependenciesByTarget {
private final SetMultimap<JavaClass, JavaField> fieldTypeDependencies = HashMultimap.create();
private final SetMultimap<JavaClass, JavaMethod> methodParameterTypeDependencies = HashMultimap.create();
Expand Down Expand Up @@ -336,4 +341,4 @@ Set<ThrowsDeclaration<JavaConstructor>> getConstructorThrowsDeclarationsOfType(J
return constructorThrowsDeclarationDependencies.get(javaClass);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import com.tngtech.archunit.core.domain.testobjects.IsArrayTestClass;
import com.tngtech.archunit.core.domain.testobjects.SuperA;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.testexamples.arrays.ClassAccessingOneDimensionalArray;
import com.tngtech.archunit.core.importer.testexamples.arrays.ClassAccessingTwoDimensionalArray;
import com.tngtech.archunit.core.importer.testexamples.arrays.ClassUsedInArray;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
Expand Down Expand Up @@ -80,13 +83,49 @@ public void finds_array_type() {
JavaMethod method = importClassWithContext(IsArrayTestClass.class).getMethod("anArray");

assertThat(method.getRawReturnType().isArray()).isTrue();
assertThat(method.getRawReturnType().tryGetComponentType().get()).matches(Object.class);
}

@Test
public void finds_non_array_type() {
JavaMethod method = importClassWithContext(IsArrayTestClass.class).getMethod("notAnArray");

assertThat(method.getRawReturnType().isArray()).isFalse();
assertThat(method.getRawReturnType().tryGetComponentType()).isAbsent();
}

@Test
public void finds_multidimensional_array_type() {
JavaClasses classes = importClassesWithContext(ClassUsedInArray.class, ClassAccessingOneDimensionalArray.class, ClassAccessingTwoDimensionalArray.class);
JavaClass type = classes.get(ClassUsedInArray.class);
JavaClass oneDimArray = classes.get(ClassAccessingOneDimensionalArray.class).getField("array").getRawType();
JavaClass twoDimArray = classes.get(ClassAccessingTwoDimensionalArray.class).getField("array").getRawType();

assertThat(oneDimArray.isArray()).isTrue();
assertThat(oneDimArray.tryGetComponentType().get()).isEqualTo(type);
assertThat(twoDimArray.isArray()).isTrue();
assertThat(twoDimArray.tryGetComponentType().get()).isEqualTo(oneDimArray);
assertThat(twoDimArray.tryGetComponentType().get().tryGetComponentType().get()).isEqualTo(type);
}

@Test
public void finds_component_type_chain_of_otherwise_unreferenced_component_type() {
class OnlyReferencingMultiDimArray {
OnlyReferencingMultiDimArray[][][] field;
}

JavaClass javaClass = importClassesWithContext(OnlyReferencingMultiDimArray.class)
.get(OnlyReferencingMultiDimArray.class);

JavaClass arrayType = javaClass.getField("field").getRawType();
JavaClass twoDim = arrayType.getComponentType();
assertThat(twoDim.getName()).isEqualTo(OnlyReferencingMultiDimArray[][].class.getName());

JavaClass oneDim = twoDim.getComponentType();
assertThat(oneDim.getName()).isEqualTo(OnlyReferencingMultiDimArray[].class.getName());

JavaClass original = oneDim.getComponentType();
assertThat(original).isEqualTo(javaClass);
}

@Test
Expand Down Expand Up @@ -1134,4 +1173,4 @@ private class NestedNamedInnerClass {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -428,5 +428,10 @@ public Set<JavaConstructor> getConstructorsWithParameterOfType(JavaClass javaCla
public Set<ThrowsDeclaration<JavaConstructor>> getConstructorThrowsDeclarationsOfType(JavaClass javaClass) {
return Collections.emptySet();
}

@Override
public JavaClass resolveClass(String fullyQualifiedClassName) {
throw new UnsupportedOperationException("Override me where necessary");
}
}
}
}