Skip to content

Commit

Permalink
Merge pull request #244 from alexfedorenchik/componenttype
Browse files Browse the repository at this point in the history
Offer a way to retrieve the component type of a `JavaClass` that is an array
  • Loading branch information
codecholeric committed Nov 3, 2019
2 parents 989918f + d63d927 commit c077e0b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 3 deletions.
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");
}
}
}
}

0 comments on commit c077e0b

Please sign in to comment.