Skip to content

Commit

Permalink
Fixes generics issue in withPrefabValuesForField()
Browse files Browse the repository at this point in the history
  • Loading branch information
jqno committed Nov 6, 2024
1 parent ebe648f commit 8b2c31b
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nl.jqno.equalsverifier.internal.util;

import java.lang.reflect.Field;
import nl.jqno.equalsverifier.Func.Func1;
import nl.jqno.equalsverifier.Func.Func2;
import nl.jqno.equalsverifier.internal.reflection.instantiation.GenericPrefabValueProvider.GenericFactories;
Expand Down Expand Up @@ -42,10 +43,11 @@ public static <T> void addPrefabValuesForField(
T red,
T blue
) {
Class<T> type = (Class<T>) red.getClass();
Field field = getField(enclosingType, fieldName);
Class<T> type = (Class<T>) field.getType();

Validations.validateRedAndBluePrefabValues(type, red, blue);
Validations.validateFieldTypeMatches(enclosingType, fieldName, red.getClass());
Validations.validateFieldTypeMatches(field, red.getClass());

if (type.isArray()) {
provider.register(type, fieldName, red, blue, red);
Expand All @@ -60,6 +62,20 @@ public static <T> void addPrefabValuesForField(
}
}

private static Field getField(Class<?> type, String fieldName) {
try {
return type.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
throw new IllegalStateException(
"Precondition: class " +
type.getSimpleName() +
" does not contain field " +
fieldName +
"."
);
}
}

public static <T> void addGenericPrefabValues(
GenericFactories factories,
Class<T> otherType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,33 +88,21 @@ public static <T> void validateRedAndBluePrefabValues(Class<T> type, T red, T bl
);
}

public static <T> void validateFieldTypeMatches(
Class<T> container,
String fieldName,
Class<?> fieldType
) {
try {
Field f = container.getDeclaredField(fieldName);
boolean typeCompatible = f.getType().isAssignableFrom(fieldType);
boolean wrappingCompatible = fieldType.equals(
PrimitiveMappers.PRIMITIVE_OBJECT_MAPPER.get(f.getType())
);
validate(
!typeCompatible && !wrappingCompatible,
"Prefab values for field " +
fieldName +
" should be of type " +
f.getType().getSimpleName() +
" but are " +
fieldType.getSimpleName() +
"."
);
} catch (NoSuchFieldException e) {
validate(
false,
"Class " + container.getSimpleName() + " has no field named " + fieldName + "."
);
}
public static <T> void validateFieldTypeMatches(Field field, Class<?> realFieldType) {
boolean typeCompatible = field.getType().isAssignableFrom(realFieldType);
boolean wrappingCompatible = realFieldType.equals(
PrimitiveMappers.PRIMITIVE_OBJECT_MAPPER.get(field.getType())
);
validate(
!typeCompatible && !wrappingCompatible,
"Prefab values for field " +
field.getName() +
" should be of type " +
field.getType().getSimpleName() +
" but are " +
realFieldType.getSimpleName() +
"."
);
}

public static <T> void validateGenericPrefabValues(Class<T> type, int arity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ public void succeed_whenClassContainsSomethingThatAllowsSubclassesAndASubclassIs
.verify();
}

@Test
public void succeed_whenClassContainsAGenericInterfaceThatRefersToItself() {
DifficultGeneric one = new DifficultGeneric(new ArrayList<>());
DifficultGeneric two = new DifficultGeneric(null);

EqualsVerifier
.forClass(DifficultGeneric.class)
.withPrefabValuesForField(
"list",
Collections.singletonList(one),
Collections.singletonList(two)
)
.verify();
}

static final class SinglePrecondition {

private final FinalPoint point;
Expand Down Expand Up @@ -308,4 +323,27 @@ public int hashCode() {
return Objects.hash(list);
}
}

public final class DifficultGeneric {

private final List<DifficultGeneric> list;

public DifficultGeneric(List<DifficultGeneric> list) {
this.list = list;
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof DifficultGeneric)) {
return false;
}
DifficultGeneric other = (DifficultGeneric) obj;
return Objects.equals(list, other.list);
}

@Override
public int hashCode() {
return Objects.hash(list);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.lang.reflect.Field;
import java.util.*;
import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException;
import org.junit.jupiter.api.Test;

Expand All @@ -25,8 +23,7 @@ public void validateFieldTypeMatches_shouldFailOnWrongType() {
ExpectedException
.when(() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"listField",
getField(TestContainer.class, "listField"),
HashSet.class
)
)
Expand All @@ -37,8 +34,7 @@ public void validateFieldTypeMatches_shouldFailOnWrongType() {
ExpectedException
.when(() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"objectField",
getField(TestContainer.class, "objectField"),
int.class
)
)
Expand All @@ -49,8 +45,7 @@ public void validateFieldTypeMatches_shouldFailOnWrongType() {
ExpectedException
.when(() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"charsField",
getField(TestContainer.class, "charsField"),
Character.class
)
)
Expand All @@ -67,8 +62,7 @@ public void validateFieldTypeMatches_shouldAllowSubTypes() {
assertDoesNotThrow(
() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"listField",
getField(TestContainer.class, "listField"),
ArrayList.class
),
"Should allow ArrayList as a List"
Expand All @@ -77,8 +71,7 @@ public void validateFieldTypeMatches_shouldAllowSubTypes() {
assertDoesNotThrow(
() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"objectField",
getField(TestContainer.class, "objectField"),
Integer.class
),
"Should allow Integer as an Object"
Expand All @@ -87,8 +80,7 @@ public void validateFieldTypeMatches_shouldAllowSubTypes() {
assertDoesNotThrow(
() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"charsField",
getField(TestContainer.class, "charsField"),
String.class
),
"Should allow String as a CharSequence"
Expand All @@ -103,8 +95,7 @@ public void validateFieldTypeMatches_shouldFailOnSuperTypes() {
ExpectedException
.when(() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"listField",
getField(TestContainer.class, "listField"),
Collection.class
)
)
Expand All @@ -115,8 +106,7 @@ public void validateFieldTypeMatches_shouldFailOnSuperTypes() {
ExpectedException
.when(() ->
Validations.validateFieldTypeMatches(
TestContainer.class,
"charsField",
getField(TestContainer.class, "charsField"),
Object.class
)
)
Expand All @@ -126,6 +116,14 @@ public void validateFieldTypeMatches_shouldFailOnSuperTypes() {
);
}

private Field getField(Class<?> type, String fieldName) {
try {
return type.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
throw new AssertionError("Class " + type + " has no field " + fieldName);
}
}

@SuppressWarnings("unused")
private static final class TestContainer {

Expand Down

0 comments on commit 8b2c31b

Please sign in to comment.