From 8b2c31b2a6c34f7c27918cec12b51f450f720749 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Wed, 6 Nov 2024 17:54:38 +0100 Subject: [PATCH] Fixes generics issue in withPrefabValuesForField() --- .../internal/util/PrefabValuesApi.java | 20 ++++++++- .../internal/util/Validations.java | 42 +++++++------------ .../WithPrefabValuesForFieldTest.java | 38 +++++++++++++++++ .../internal/util/ValidationsTest.java | 38 ++++++++--------- 4 files changed, 89 insertions(+), 49 deletions(-) diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/PrefabValuesApi.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/PrefabValuesApi.java index 429d660aa..ff99f6333 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/PrefabValuesApi.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/PrefabValuesApi.java @@ -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; @@ -42,10 +43,11 @@ public static void addPrefabValuesForField( T red, T blue ) { - Class type = (Class) red.getClass(); + Field field = getField(enclosingType, fieldName); + Class type = (Class) 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); @@ -60,6 +62,20 @@ public static 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 void addGenericPrefabValues( GenericFactories factories, Class otherType, diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Validations.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Validations.java index f8689b308..4d7666227 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Validations.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Validations.java @@ -88,33 +88,21 @@ public static void validateRedAndBluePrefabValues(Class type, T red, T bl ); } - public static void validateFieldTypeMatches( - Class 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 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 void validateGenericPrefabValues(Class type, int arity) { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithPrefabValuesForFieldTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithPrefabValuesForFieldTest.java index 1f8161e8b..a3330ca3c 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithPrefabValuesForFieldTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithPrefabValuesForFieldTest.java @@ -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; @@ -308,4 +323,27 @@ public int hashCode() { return Objects.hash(list); } } + + public final class DifficultGeneric { + + private final List list; + + public DifficultGeneric(List 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); + } + } } diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/ValidationsTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/ValidationsTest.java index 3906f1183..a4fd8efd6 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/ValidationsTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/ValidationsTest.java @@ -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; @@ -25,8 +23,7 @@ public void validateFieldTypeMatches_shouldFailOnWrongType() { ExpectedException .when(() -> Validations.validateFieldTypeMatches( - TestContainer.class, - "listField", + getField(TestContainer.class, "listField"), HashSet.class ) ) @@ -37,8 +34,7 @@ public void validateFieldTypeMatches_shouldFailOnWrongType() { ExpectedException .when(() -> Validations.validateFieldTypeMatches( - TestContainer.class, - "objectField", + getField(TestContainer.class, "objectField"), int.class ) ) @@ -49,8 +45,7 @@ public void validateFieldTypeMatches_shouldFailOnWrongType() { ExpectedException .when(() -> Validations.validateFieldTypeMatches( - TestContainer.class, - "charsField", + getField(TestContainer.class, "charsField"), Character.class ) ) @@ -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" @@ -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" @@ -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" @@ -103,8 +95,7 @@ public void validateFieldTypeMatches_shouldFailOnSuperTypes() { ExpectedException .when(() -> Validations.validateFieldTypeMatches( - TestContainer.class, - "listField", + getField(TestContainer.class, "listField"), Collection.class ) ) @@ -115,8 +106,7 @@ public void validateFieldTypeMatches_shouldFailOnSuperTypes() { ExpectedException .when(() -> Validations.validateFieldTypeMatches( - TestContainer.class, - "charsField", + getField(TestContainer.class, "charsField"), Object.class ) ) @@ -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 {