Skip to content

Commit

Permalink
refactor: check for valid candidate before picking
Browse files Browse the repository at this point in the history
Since we pick a random class from the found implementations, we want to
filter out unsuitable candidates before we pick one. Otherwise, we might
pick one that can't be used while we skip other candidates which would
have worked.
This also resolves a potential NPE when creating a GenericSpecimen with
mismatching type-parameters.
  • Loading branch information
Nylle committed Nov 17, 2023
1 parent 1ebe9d2 commit 1ab8b4a
Showing 1 changed file with 9 additions and 4 deletions.
13 changes: 9 additions & 4 deletions src/main/java/com/github/nylle/javafixture/SpecimenFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
private <T> ISpecimen<T> implementationOrProxy(final SpecimenType<T> interfaceType) {
try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {
var implementingClasses = scanResult.getClassesImplementing(interfaceType.asClass()).stream()
.filter(x -> interfaceType.isParameterized() || isNotParametrized(x))
.filter(x -> isNotParametrized(x) || interfaceType.isParameterized())
.filter(x -> isNotParametrized(x) || typeParametersMatch(x, interfaceType))
.collect(Collectors.toList());

if (implementingClasses.isEmpty()) {
Expand All @@ -121,17 +122,21 @@ private static boolean isNotParametrized(ClassInfo classInfo) {
return classInfo.getTypeSignature() == null || classInfo.getTypeSignature().getTypeParameters().isEmpty();
}

private static <T> boolean typeParametersMatch(ClassInfo implementingClass, SpecimenType<T> genericType) {
return resolveTypeArguments(genericType, implementingClass).length >= implementingClass.getTypeSignature().getTypeParameters().size();
}

private static <T> Type[] resolveTypeArguments(SpecimenType<T> genericType, ClassInfo implementingClass) {
// throws NPE if implementing class has more type arguments than interface
// this was not intended, but luckily causes a fallback to proxy, because we wouldn't be able to resolve the additional type anyway
var typeParameters = IntStream.range(0, genericType.getGenericTypeArguments().length)
.boxed()
.collect(toMap(
i -> genericType.getTypeParameterName(i),
i -> SpecimenType.fromClass(genericType.getGenericTypeArgument(i))));

return implementingClass.getTypeSignature().getTypeParameters().stream()
.map(x -> typeParameters.get(x.getName()).asClass())
.map(x -> typeParameters.getOrDefault(x.getName(), null))
.filter(x -> x != null)
.map(x -> x.asClass())
.toArray(size -> new Type[size]);
}
}
Expand Down

0 comments on commit 1ab8b4a

Please sign in to comment.