diff --git a/CHANGELOG.md b/CHANGELOG.md index bfdec83db..ff7d0cc53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- #withPrefabValuesForField method, where you can assign prefab values to a field instead of to a class. The values will then be used for that field only. ([Issue 747](https://github.com/jqno/equalsverifier/issues/747)) + +### Changed + +- The internal instantiation logic has been heavily refactored. + +### Deprecated + +- `Warning.ZERO_FIELDS`: the use case for this Warning is better handled by the new `#withPrefabValuesForField` method. + ## [3.16.2] - 2024-08-23 ### Changed diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/PrefabValuesForFieldInRecordTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/PrefabValuesForFieldInRecordTest.java new file mode 100644 index 000000000..c0f29a826 --- /dev/null +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/PrefabValuesForFieldInRecordTest.java @@ -0,0 +1,73 @@ +package nl.jqno.equalsverifier.integration.extended_contract; + +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; +import org.junit.jupiter.api.Test; + +public class PrefabValuesForFieldInRecordTest { + + @Test + public void fail_whenRecordHasSinglePrecondition() { + ExpectedException + .when(() -> EqualsVerifier.forClass(SinglePrecondition.class).verify()) + .assertFailure() + .assertMessageContains("Record:", "failed to run constructor", "[1]"); + } + + @Test + public void succeed_whenRecordHasSinglePrecondition_givenPrefabValuesForField() { + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("i", 111, 142) + .verify(); + } + + @Test + public void fail_whenRecordHasDualPrecondition() { + ExpectedException + .when(() -> EqualsVerifier.forClass(DualPrecondition.class).verify()) + .assertFailure() + .assertMessageContains("Record:", "failed to run constructor", "[1, 1]"); + } + + @Test + public void fail_whenRecordHasDualPrecondition_givenPrefabValuesForOnlyOneField() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(DualPrecondition.class) + .withPrefabValuesForField("x", 111, 142) + .verify() + ) + .assertFailure() + .assertMessageContains("Record:", "failed to run constructor", "[111, 1]"); + } + + @Test + public void succeed_whenRecordHasDualPrecondition_givenPrefabValueForBothFields() { + EqualsVerifier + .forClass(DualPrecondition.class) + .withPrefabValuesForField("x", 111, 142) + .withPrefabValuesForField("y", 505, 555) + .verify(); + } + + record SinglePrecondition(int i) { + public SinglePrecondition { + if (i < 100 || i > 200) { + throw new IllegalArgumentException("i must be between 100 and 200! But was " + i); + } + } + } + + record DualPrecondition(int x, int y) { + public DualPrecondition { + if (x < 100 || x > 200) { + throw new IllegalArgumentException("x must be between 100 and 200! But was " + x); + } + if (y < 500 || y > 600) { + throw new IllegalArgumentException("y must be between 500 and 600! But was " + y); + } + } + } +} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/RecordsTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/RecordsTest.java index 36d6fb4c6..076a44c41 100644 --- a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/RecordsTest.java +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/RecordsTest.java @@ -51,15 +51,6 @@ public void fail_whenConstructorChecksValue() { .assertMessageContains("Record:", "failed to run constructor", "prefab values"); } - @Test - public void succeed_whenConstructorChecksValue_givenPrefabValues() { - EqualsVerifier - .forClass(ValueCheckingRecord.class) - .withPrefabValues(int.class, 10, 11) - .suppress(Warning.ZERO_FIELDS) - .verify(); - } - @Test public void fail_whenRecordInvariantIsViolated_givenIntFieldIsModifiedInConstructor() { ExpectedException diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorRecordTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorRecordTest.java deleted file mode 100644 index 6d4f532bf..000000000 --- a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorRecordTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class ClassAccessorRecordTest { - - private PrefabValues prefabValues; - - @BeforeEach - public void setup() { - FactoryCache factoryCache = JavaApiPrefabValues.build(); - prefabValues = new PrefabValues(factoryCache); - } - - @Test - public void isRecord() { - var accessor = ClassAccessor.of(SimpleRecord.class, prefabValues); - assertTrue(accessor.isRecord()); - } - - record SimpleRecord(int i) {} -} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassProbeRecordTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassProbeRecordTest.java new file mode 100644 index 000000000..327141776 --- /dev/null +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassProbeRecordTest.java @@ -0,0 +1,16 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class ClassProbeRecordTest { + + @Test + public void isRecord() { + var probe = new ClassProbe<>(SimpleRecord.class); + assertTrue(probe.isRecord()); + } + + record SimpleRecord(int i) {} +} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordInstantiatorTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordInstantiatorTest.java index 63b3f010a..be0c04dcf 100644 --- a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordInstantiatorTest.java +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordInstantiatorTest.java @@ -3,12 +3,13 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; public class RecordInstantiatorTest { @Test public void instantiateRecord() { - Instantiator instantiator = Instantiator.of(SimpleRecord.class); + Instantiator instantiator = Instantiator.of(SimpleRecord.class, new ObjenesisStd()); Object simpleRecord = instantiator.instantiate(); assertEquals(SimpleRecord.class, simpleRecord.getClass()); } diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorCopyingTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorCopyingTest.java deleted file mode 100644 index 0c6ed35b8..000000000 --- a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorCopyingTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static org.junit.jupiter.api.Assertions.*; - -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.exceptions.EqualsVerifierInternalBugException; -import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; -import org.junit.jupiter.api.Test; - -public class RecordObjectAccessorCopyingTest { - - @Test - public void copyHappyPath() { - Object original = instantiate(SimpleRecord.class); - Object copy = copyOf(original); - - assertNotSame(original, copy); - assertEquals(original, copy); - } - - @Test - public void shallowCopy() { - Object original = instantiate(RecordContainer.class); - Object copy = copyOf(original); - ObjectAccessor originalAccessor = create(original); - ObjectAccessor copyAccessor = create(copy); - - assertNotSame(original, copy); - for (Field f : FieldIterable.of(original.getClass())) { - Object a = originalAccessor.getField(f); - Object b = copyAccessor.getField(f); - assertSame(a, b); - } - } - - @Test - public void copyIntoSubclass() { - Object object = instantiate(SimpleRecord.class); - ExpectedException - .when(() -> create(object).copyIntoSubclass(null)) - .assertThrows(EqualsVerifierInternalBugException.class) - .assertMessageContains("Can't copy a record into a subclass of itself."); - } - - @Test - public void copyIntoAnonymousSubclass() { - Object object = instantiate(SimpleRecord.class); - ExpectedException - .when(() -> create(object).copyIntoAnonymousSubclass()) - .assertThrows(EqualsVerifierInternalBugException.class) - .assertMessageContains("Can't copy a record into an anonymous subclass of itself."); - } - - @SuppressWarnings("unchecked") - private RecordObjectAccessor create(T object) { - return new RecordObjectAccessor(object, (Class) object.getClass()); - } - - @SuppressWarnings("unchecked") - private T instantiate(Class type) { - return Instantiator.of(type).instantiate(); - } - - private T copyOf(T from) { - return create(from).copy(); - } - - record SimpleRecord(int i, String s) {} - - record RecordContainer(SimpleRecord r) {} -} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorModificationTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorModificationTest.java deleted file mode 100644 index decfa4e0a..000000000 --- a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorModificationTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class RecordObjectAccessorModificationTest { - - private static final int INITIAL_INT = 42; - private static final boolean INITIAL_BOOLEAN = true; - private static final String INITIAL_STRING = "hello"; - private static final Object INITIAL_OBJECT = new Object(); - - private PrefabValues prefabValues; - private Class modifiable; - private Object m; - private ObjectAccessor accessor; - private Field stringField; - private ObjectAccessor modified; - - @BeforeEach - public void setUp() throws Exception { - prefabValues = new PrefabValues(JavaApiPrefabValues.build()); - - modifiable = ModifiableRecord.class; - Constructor c = modifiable.getDeclaredConstructor( - int.class, - boolean.class, - String.class, - Object.class - ); - m = c.newInstance(INITIAL_INT, INITIAL_BOOLEAN, INITIAL_STRING, INITIAL_OBJECT); - accessor = create(m); - stringField = modifiable.getDeclaredField("s"); - } - - @Test - public void clearClears() throws Exception { - modified = accessor.clear(f -> true, prefabValues, TypeTag.NULL); - assertEquals(0, fieldValue(modified, "i")); - assertEquals(false, fieldValue(modified, "b")); - assertNull(fieldValue(modified, "s")); - assertNull(fieldValue(modified, "o")); - } - - @Test - public void clearClearsExcept() throws Exception { - modified = accessor.clear(f -> !f.getName().equals("s"), prefabValues, TypeTag.NULL); - assertEquals(prefabValues.giveRed(new TypeTag(String.class)), fieldValue(modified, "s")); - assertEquals(0, fieldValue(modified, "i")); - assertEquals(false, fieldValue(modified, "b")); - assertNull(fieldValue(modified, "o")); - } - - @Test - public void clearLeavesOriginalUnaffected() { - modified = accessor.clear(f -> true, prefabValues, TypeTag.NULL); - assertNotSame(m, modified.get()); - } - - @Test - public void withDefaultedField() throws Exception { - modified = accessor.withDefaultedField(stringField); - assertNull(fieldValue(modified, "s")); - assertEquals(INITIAL_INT, fieldValue(modified, "i")); - assertEquals(INITIAL_BOOLEAN, fieldValue(modified, "b")); - assertEquals(INITIAL_OBJECT, fieldValue(modified, "o")); - } - - @Test - public void withDefaultedFieldLeavesOriginalUnaffected() { - modified = accessor.withDefaultedField(stringField); - assertNotSame(m, modified.get()); - } - - @Test - public void withChangedField() throws Exception { - modified = accessor.withChangedField(stringField, prefabValues, TypeTag.NULL); - assertEquals(prefabValues.giveRed(new TypeTag(String.class)), fieldValue(modified, "s")); - assertEquals(INITIAL_INT, fieldValue(modified, "i")); - assertEquals(INITIAL_BOOLEAN, fieldValue(modified, "b")); - assertEquals(INITIAL_OBJECT, fieldValue(modified, "o")); - } - - @Test - public void withChangedFieldLeavesOriginalUnaffected() { - modified = accessor.withChangedField(stringField, prefabValues, TypeTag.NULL); - assertNotSame(m, modified.get()); - } - - @Test - public void withFieldSetTo() throws Exception { - modified = accessor.withFieldSetTo(stringField, "something else"); - assertEquals("something else", fieldValue(modified, "s")); - assertEquals(INITIAL_INT, fieldValue(modified, "i")); - assertEquals(INITIAL_BOOLEAN, fieldValue(modified, "b")); - assertEquals(INITIAL_OBJECT, fieldValue(modified, "o")); - } - - @Test - public void withFieldSetToLeavesOriginalUnaffected() { - modified = accessor.withFieldSetTo(stringField, "something else"); - assertNotSame(m, modified.get()); - } - - @SuppressWarnings("unchecked") - private RecordObjectAccessor create(T object) { - return new RecordObjectAccessor(object, (Class) object.getClass()); - } - - private Object fieldValue(ObjectAccessor objectAccessor, String fieldName) - throws NoSuchFieldException { - Field field = objectAccessor.get().getClass().getDeclaredField(fieldName); - return objectAccessor.getField(field); - } - - record ModifiableRecord(int i, boolean b, String s, Object o) {} -} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorScramblingTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorScramblingTest.java deleted file mode 100644 index ce1af09c6..000000000 --- a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorScramblingTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; -import static org.junit.jupiter.api.Assertions.*; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import nl.jqno.equalsverifier.internal.exceptions.EqualsVerifierInternalBugException; -import nl.jqno.equalsverifier.internal.prefabvalues.*; -import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class RecordObjectAccessorScramblingTest { - - private static final LinkedHashSet EMPTY_TYPE_STACK = new LinkedHashSet<>(); - private FactoryCache factoryCache; - private PrefabValues prefabValues; - - @BeforeEach - public void setup() throws Exception { - factoryCache = JavaApiPrefabValues.build(); - prefabValues = new PrefabValues(factoryCache); - } - - @Test - public void scrambleLeavesOriginalUnaffected() throws Exception { - Constructor c = Point.class.getDeclaredConstructor(int.class, int.class); - Object original = c.newInstance(2, 3); - Object copy = doScramble(original).get(); - assertNotSame(original, copy); - } - - @Test - public void scramble() throws Exception { - Constructor constructor = Point.class.getDeclaredConstructor(int.class, int.class); - factoryCache.put( - Point.class, - values( - constructor.newInstance(1, 2), - constructor.newInstance(2, 3), - constructor.newInstance(1, 2) - ) - ); - Object original = constructor.newInstance(1, 2); - - Object scrambled = doScramble(original); - assertFalse(original.equals(scrambled)); - } - - @Test - public void scrambleAllFields() throws Exception { - Constructor constructor = - TypeContainerRecord.class.getDeclaredConstructor( - int.class, - boolean.class, - String.class, - Object.class - ); - Object someObject = new Object(); - Object original = constructor.newInstance(42, true, "hello", someObject); - - ObjectAccessor scrambled = doScramble(original); - - assertNotEquals(42, fieldValue(scrambled, "i")); - assertNotEquals(true, fieldValue(scrambled, "b")); - assertNotEquals("hello", fieldValue(scrambled, "s")); - assertNotEquals(someObject, fieldValue(scrambled, "o")); - } - - @Test - public void shallowScramble() throws Exception { - Constructor constructor = Point.class.getDeclaredConstructor(int.class, int.class); - Object original = constructor.newInstance(1, 2); - - ExpectedException - .when(() -> create(original).shallowScramble(prefabValues, TypeTag.NULL)) - .assertThrows(EqualsVerifierInternalBugException.class) - .assertMessageContains("Record:", "can't shallow-scramble a record."); - } - - @Test - public void dontScrambleStaticFinal() throws NoSuchFieldException { - Object instance = Instantiator.of(StaticFieldContainer.class).instantiate(); - - ObjectAccessor scrambled = doScramble(instance); - - Object scrambledStaticFinal = fieldValue(scrambled, "STATIC_FINAL"); - Object scrambledStaticNonfinal = fieldValue(scrambled, "staticNonfinal"); - assertEquals(ORIGINAL_VALUE, scrambledStaticFinal); - assertEquals(ORIGINAL_VALUE, scrambledStaticNonfinal); - } - - @Test - public void scrambleNestedGenerics() throws Exception { - Constructor constructor = - GenericContainerContainer.class.getDeclaredConstructor( - GenericContainer.class, - GenericContainer.class - ); - Object instance = constructor.newInstance( - new GenericContainer(new ArrayList()), - new GenericContainer(new ArrayList()) - ); - ObjectAccessor accessor = create(instance); - - assertTrue(GenericContainer.cast(fieldValue(accessor, "strings")).ts.isEmpty()); - assertTrue(GenericContainer.cast(fieldValue(accessor, "points")).ts.isEmpty()); - - ObjectAccessor scrambled = doScramble(instance); - - List strings = GenericContainer.cast(fieldValue(scrambled, "strings")).ts; - assertFalse(strings.isEmpty()); - assertEquals(String.class, strings.get(0).getClass()); - List points = GenericContainer.cast(fieldValue(scrambled, "points")).ts; - assertFalse(points.isEmpty()); - assertEquals(Point.class, points.get(0).getClass()); - } - - @SuppressWarnings("unchecked") - private RecordObjectAccessor create(T object) { - return new RecordObjectAccessor(object, (Class) object.getClass()); - } - - private Object fieldValue(ObjectAccessor accessor, String fieldName) - throws NoSuchFieldException { - Field field = accessor.get().getClass().getDeclaredField(fieldName); - return accessor.getField(field); - } - - private ObjectAccessor doScramble(Object object) { - return create(object).scramble(prefabValues, TypeTag.NULL, EMPTY_TYPE_STACK); - } - - record Point(int x, int y) {} - - record TypeContainerRecord(int i, boolean b, String s, Object o) {} - - private static final String ORIGINAL_VALUE = "original"; - - record StaticFieldContainer(int nonstatic) { - public static final String STATIC_FINAL = ORIGINAL_VALUE; - public static String staticNonfinal = ORIGINAL_VALUE; - } - - public static final class GenericContainer { - - private List ts; - - public GenericContainer(List ts) { - this.ts = ts; - } - - @SuppressWarnings("unchecked") - public static GenericContainer cast(Object object) { - return (GenericContainer) object; - } - } - - record GenericContainerContainer( - GenericContainer strings, - GenericContainer points - ) {} -} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/RecordInstanceCreatorTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/RecordInstanceCreatorTest.java new file mode 100644 index 000000000..8bced9fd4 --- /dev/null +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/RecordInstanceCreatorTest.java @@ -0,0 +1,33 @@ +package nl.jqno.equalsverifier.internal.reflection.instantiation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import nl.jqno.equalsverifier.internal.reflection.ClassProbe; +import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; + +public class RecordInstanceCreatorTest { + + @Test + public void instanceCreator() throws NoSuchFieldException { + ClassProbe probe = new ClassProbe<>(SomeRecord.class); + InstanceCreator sut = new InstanceCreator<>(probe, new ObjenesisStd()); + + Field x = SomeRecord.class.getDeclaredField("x"); + Field z = SomeRecord.class.getDeclaredField("z"); + Map values = new HashMap<>(); + values.put(x, 42); + values.put(z, "42"); + + SomeRecord actual = sut.instantiate(values); + + assertEquals(42, actual.x); + assertEquals(0, actual.y); + assertEquals("42", actual.z); + } + + record SomeRecord(int x, int y, String z) {} +} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorCopyingTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorCopyingTest.java new file mode 100644 index 000000000..25c904414 --- /dev/null +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorCopyingTest.java @@ -0,0 +1,58 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.lang.reflect.Field; +import nl.jqno.equalsverifier.internal.reflection.FieldIterable; +import nl.jqno.equalsverifier.internal.reflection.Instantiator; +import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; + +public class RecordObjectAccessorCopyingTest { + + private Objenesis objenesis = new ObjenesisStd(); + + @Test + public void copyHappyPath() { + Object original = instantiate(SimpleRecord.class); + Object copy = copyOf(original); + + assertNotSame(original, copy); + assertEquals(original, copy); + } + + @Test + public void shallowCopy() { + Object original = instantiate(RecordContainer.class); + Object copy = copyOf(original); + RecordObjectAccessor originalAccessor = create(original); + RecordObjectAccessor copyAccessor = create(copy); + + assertNotSame(original, copy); + for (Field f : FieldIterable.of(original.getClass())) { + Object a = originalAccessor.getField(f); + Object b = copyAccessor.getField(f); + assertSame(a, b); + } + } + + @SuppressWarnings("unchecked") + private RecordObjectAccessor create(T object) { + return new RecordObjectAccessor(object, (Class) object.getClass()); + } + + private T instantiate(Class type) { + return Instantiator.of(type, objenesis).instantiate(); + } + + private T copyOf(T from) { + return create(from).copy(objenesis); + } + + record SimpleRecord(int i, String s) {} + + record RecordContainer(SimpleRecord r) {} +} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorScramblingTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorScramblingTest.java new file mode 100644 index 000000000..830186672 --- /dev/null +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorScramblingTest.java @@ -0,0 +1,73 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import java.lang.reflect.Constructor; +import java.util.LinkedHashSet; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.JavaApiPrefabValues; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; + +public class RecordObjectAccessorScramblingTest { + + private static final LinkedHashSet EMPTY_TYPE_STACK = new LinkedHashSet<>(); + private FactoryCache factoryCache; + private VintageValueProvider valueProvider; + + @BeforeEach + public void setup() throws Exception { + factoryCache = JavaApiPrefabValues.build(); + valueProvider = new VintageValueProvider(factoryCache, new ObjenesisStd()); + } + + @Test + public void scrambleLeavesOriginalUnaffected() throws Exception { + Constructor c = Point.class.getDeclaredConstructor(int.class, int.class); + Object original = c.newInstance(2, 3); + Object copy = doScramble(original).get(); + assertNotSame(original, copy); + } + + @Test + public void scramble() throws Exception { + Constructor constructor = Point.class.getDeclaredConstructor(int.class, int.class); + factoryCache.put( + Point.class, + values( + constructor.newInstance(1, 2), + constructor.newInstance(2, 3), + constructor.newInstance(1, 2) + ) + ); + Object original = constructor.newInstance(1, 2); + + Object scrambled = doScramble(original); + assertFalse(original.equals(scrambled)); + } + + @SuppressWarnings("unchecked") + private RecordObjectAccessor create(T object) { + return new RecordObjectAccessor(object, (Class) object.getClass()); + } + + private ObjectAccessor doScramble(Object object) { + return create(object).scramble(valueProvider, TypeTag.NULL, EMPTY_TYPE_STACK); + } + + record Point(int x, int y) {} + + record TypeContainerRecord(int i, boolean b, String s, Object o) {} + + private static final String ORIGINAL_VALUE = "original"; + + record StaticFieldContainer(int nonstatic) { + public static final String STATIC_FINAL = ORIGINAL_VALUE; + public static String staticNonfinal = ORIGINAL_VALUE; + } +} diff --git a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorTest.java b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorTest.java similarity index 70% rename from equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorTest.java rename to equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorTest.java index beb9eb68e..dbcbb904f 100644 --- a/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessorTest.java +++ b/equalsverifier-16/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessorTest.java @@ -1,24 +1,26 @@ -package nl.jqno.equalsverifier.internal.reflection; +package nl.jqno.equalsverifier.internal.reflection.vintage; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.util.LinkedHashSet; import java.util.Objects; import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Instantiator; +import nl.jqno.equalsverifier.internal.reflection.JavaApiPrefabValues; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; public class RecordObjectAccessorTest { private static final LinkedHashSet EMPTY_TYPE_STACK = new LinkedHashSet<>(); + private Objenesis objenesis; private Object recordInstance; @BeforeEach @@ -26,6 +28,7 @@ public void setUp() throws Exception { Constructor constructor = SimpleRecord.class.getDeclaredConstructor(int.class, String.class); constructor.setAccessible(true); + objenesis = new ObjenesisStd(); recordInstance = constructor.newInstance(42, "hello"); } @@ -41,40 +44,39 @@ public void get() { assertSame(recordInstance, accessor.get()); } - @Test - public void getField() throws Exception { - Field f = SimpleRecord.class.getDeclaredField("i"); - RecordObjectAccessor accessor = accessorFor(recordInstance); - assertEquals(42, accessor.getField(f)); - } - @Test public void fail_whenConstructorThrowsNpe() { - Object instance = Instantiator.of(NpeThrowingConstructorRecord.class).instantiate(); + Object instance = Instantiator + .of(NpeThrowingConstructorRecord.class, objenesis) + .instantiate(); ExpectedException - .when(() -> accessorFor(instance).copy()) + .when(() -> accessorFor(instance).copy(objenesis)) .assertThrows(ReflectionException.class) .assertMessageContains("Record:", "failed to run constructor", "Warning.NULL_FIELDS"); } @Test public void fail_whenConstructorThrowsOnZero() { - Object instance = Instantiator.of(ZeroThrowingConstructorRecord.class).instantiate(); + Object instance = Instantiator + .of(ZeroThrowingConstructorRecord.class, objenesis) + .instantiate(); ExpectedException - .when(() -> accessorFor(instance).copy()) + .when(() -> accessorFor(instance).copy(objenesis)) .assertThrows(ReflectionException.class) .assertMessageContains("Record:", "failed to run constructor", "Warning.ZERO_FIELDS"); } @Test public void fail_whenConstructorThrowsOnSomethingElse() { - Object instance = Instantiator.of(OtherThrowingConstructorRecord.class).instantiate(); + Object instance = Instantiator + .of(OtherThrowingConstructorRecord.class, objenesis) + .instantiate(); - PrefabValues pv = new PrefabValues(JavaApiPrefabValues.build()); + VintageValueProvider vp = new VintageValueProvider(JavaApiPrefabValues.build(), objenesis); ExpectedException - .when(() -> accessorFor(instance).scramble(pv, TypeTag.NULL, EMPTY_TYPE_STACK)) + .when(() -> accessorFor(instance).scramble(vp, TypeTag.NULL, EMPTY_TYPE_STACK)) .assertThrows(ReflectionException.class) .assertMessageContains("Record:", "failed to run constructor", "prefab values"); } diff --git a/equalsverifier-17/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorSealedTest.java b/equalsverifier-17/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorSealedTest.java index c779c01d9..2b7ad0633 100644 --- a/equalsverifier-17/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorSealedTest.java +++ b/equalsverifier-17/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorSealedTest.java @@ -3,32 +3,20 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class ClassAccessorSealedTest { - private PrefabValues prefabValues; - - @BeforeEach - public void setup() { - FactoryCache factoryCache = JavaApiPrefabValues.build(); - prefabValues = new PrefabValues(factoryCache); - } - @Test public void isNotSealed() { - var accessor = ClassAccessor.of(SealedChild.class, prefabValues); - assertFalse(accessor.isSealed()); + var probe = new ClassProbe<>(SealedChild.class); + assertFalse(probe.isSealed()); } @Test public void isSealed() { - var accessor = ClassAccessor.of(SealedParent.class, prefabValues); - assertTrue(accessor.isSealed()); + var probe = new ClassProbe<>(SealedParent.class); + assertTrue(probe.isSealed()); } public abstract static sealed class SealedParent {} diff --git a/equalsverifier-core/pom.xml b/equalsverifier-core/pom.xml index 5e4c670bc..32ab9bf1b 100644 --- a/equalsverifier-core/pom.xml +++ b/equalsverifier-core/pom.xml @@ -93,18 +93,6 @@ ${version.spotbugs} provided - - joda-time - joda-time - ${version.joda-time} - provided - - - com.google.guava - guava - ${version.guava} - provided - diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java index c9fba2ab4..5574cfb43 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java @@ -9,11 +9,13 @@ import nl.jqno.equalsverifier.api.EqualsVerifierApi; import nl.jqno.equalsverifier.api.MultipleTypeEqualsVerifierApi; import nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; import nl.jqno.equalsverifier.internal.reflection.PackageScanner; import nl.jqno.equalsverifier.internal.util.ListBuilders; import nl.jqno.equalsverifier.internal.util.PrefabValuesApi; import nl.jqno.equalsverifier.internal.util.Validations; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; public final class ConfiguredEqualsVerifier implements EqualsVerifierApi { @@ -21,6 +23,7 @@ public final class ConfiguredEqualsVerifier implements EqualsVerifierApi { private final FactoryCache factoryCache; private boolean usingGetClass; private Function fieldnameToGetter; + private final Objenesis objenesis = new ObjenesisStd(); /** Constructor. */ public ConfiguredEqualsVerifier() { @@ -64,7 +67,7 @@ public ConfiguredEqualsVerifier suppress(Warning... warnings) { /** {@inheritDoc} */ @Override public ConfiguredEqualsVerifier withPrefabValues(Class otherType, S red, S blue) { - PrefabValuesApi.addPrefabValues(factoryCache, otherType, red, blue); + PrefabValuesApi.addPrefabValues(factoryCache, objenesis, otherType, red, blue); return this; } @@ -125,6 +128,7 @@ public SingleTypeEqualsVerifierApi forClass(Class type) { type, EnumSet.copyOf(warningsToSuppress), factoryCache, + objenesis, usingGetClass, fieldnameToGetter ); diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/Warning.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/Warning.java index ab67cf27a..7d151f2a9 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/Warning.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/Warning.java @@ -1,5 +1,7 @@ package nl.jqno.equalsverifier; +import nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi; + /** * Enum of warnings that can be suppressed in {@link nl.jqno.equalsverifier.EqualsVerifier}. * @@ -206,12 +208,10 @@ public enum Warning { BIGDECIMAL_EQUALITY, /** - * Disables checks with fields with value 0 within {@code equals}, {@code hashCode} and - * {@code toString} methods. + * No longer does anything. * - *

Sometimes the constructor of a class makes sure no field can be 0. If this is the case, - * and if the fields cannot be made 0 later in the lifecycle of the class by setters or other - * methods, suppress this warning to disable the checks with fields that have value 0. + * @deprecated Use {@link SingleTypeEqualsVerifierApi#withPrefabValuesForField(String, Object, Object)} instead. */ + @Deprecated ZERO_FIELDS } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java index 4bde026d1..c09b94d68 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java @@ -1,38 +1,20 @@ package nl.jqno.equalsverifier.api; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Function; import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.EqualsVerifierReport; import nl.jqno.equalsverifier.Func.Func1; import nl.jqno.equalsverifier.Func.Func2; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.checkers.AbstractDelegationChecker; -import nl.jqno.equalsverifier.internal.checkers.CachedHashCodeChecker; -import nl.jqno.equalsverifier.internal.checkers.Checker; -import nl.jqno.equalsverifier.internal.checkers.ExamplesChecker; -import nl.jqno.equalsverifier.internal.checkers.FieldsChecker; -import nl.jqno.equalsverifier.internal.checkers.HierarchyChecker; -import nl.jqno.equalsverifier.internal.checkers.MapEntryHashCodeRequirementChecker; -import nl.jqno.equalsverifier.internal.checkers.NullChecker; -import nl.jqno.equalsverifier.internal.checkers.RecordChecker; -import nl.jqno.equalsverifier.internal.checkers.SignatureChecker; +import nl.jqno.equalsverifier.internal.checkers.*; import nl.jqno.equalsverifier.internal.exceptions.MessagingException; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; -import nl.jqno.equalsverifier.internal.util.Configuration; -import nl.jqno.equalsverifier.internal.util.ErrorMessage; -import nl.jqno.equalsverifier.internal.util.FieldNameExtractor; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FieldCache; +import nl.jqno.equalsverifier.internal.util.*; import nl.jqno.equalsverifier.internal.util.Formatter; -import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper; -import nl.jqno.equalsverifier.internal.util.PrefabValuesApi; -import nl.jqno.equalsverifier.internal.util.Validations; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; /** * Helps to construct an {@link EqualsVerifier} test with a fluent API. @@ -49,6 +31,7 @@ public class SingleTypeEqualsVerifierApi implements EqualsVerifierApi { private boolean hasRedefinedSuperclass = false; private Class redefinedSubclass = null; private FactoryCache factoryCache = new FactoryCache(); + private FieldCache fieldCache = new FieldCache(); private CachedHashCodeInitializer cachedHashCodeInitializer = CachedHashCodeInitializer.passthrough(); private Function fieldnameToGetter = null; @@ -58,6 +41,7 @@ public class SingleTypeEqualsVerifierApi implements EqualsVerifierApi { private Set ignoredAnnotationClassNames = new HashSet<>(); private List equalExamples = new ArrayList<>(); private List unequalExamples = new ArrayList<>(); + private final Objenesis objenesis; /** * Constructor. @@ -65,8 +49,19 @@ public class SingleTypeEqualsVerifierApi implements EqualsVerifierApi { * @param type The class for which the {@code equals} method should be tested. */ public SingleTypeEqualsVerifierApi(Class type) { + this(type, new ObjenesisStd()); + } + + /** + * Constructor. + * + * @param type The class for which the {@code equals} method should be tested. + * @param objenesis To instantiate non-record classes. + */ + public SingleTypeEqualsVerifierApi(Class type, Objenesis objenesis) { this.type = type; - actualFields = FieldNameExtractor.extractFieldNames(type); + this.actualFields = FieldNameExtractor.extractFieldNames(type); + this.objenesis = objenesis; } /** @@ -75,6 +70,7 @@ public SingleTypeEqualsVerifierApi(Class type) { * @param type The class for which the {@code equals} method should be tested. * @param warningsToSuppress A list of warnings to suppress in {@code EqualsVerifier}. * @param factoryCache Factories that can be used to create values. + * @param objenesis To instantiate non-record classes. * @param usingGetClass Whether {@code getClass} is used in the implementation of the {@code * equals} method, instead of an {@code instanceof} check. * @param converter A function that converts from field name to getter name. @@ -83,10 +79,11 @@ public SingleTypeEqualsVerifierApi( Class type, EnumSet warningsToSuppress, FactoryCache factoryCache, + Objenesis objenesis, boolean usingGetClass, Function converter ) { - this(type); + this(type, objenesis); this.warningsToSuppress = EnumSet.copyOf(warningsToSuppress); this.factoryCache = this.factoryCache.merge(factoryCache); this.usingGetClass = usingGetClass; @@ -102,7 +99,7 @@ public SingleTypeEqualsVerifierApi( List equalExamples, List unequalExamples ) { - this(type); + this(type, new ObjenesisStd()); this.equalExamples = equalExamples; this.unequalExamples = unequalExamples; } @@ -124,7 +121,30 @@ public SingleTypeEqualsVerifierApi suppress(Warning... warnings) { /** {@inheritDoc} */ @Override public SingleTypeEqualsVerifierApi withPrefabValues(Class otherType, S red, S blue) { - PrefabValuesApi.addPrefabValues(factoryCache, otherType, red, blue); + PrefabValuesApi.addPrefabValues(factoryCache, objenesis, otherType, red, blue); + return this; + } + + /** + * Adds prefabricated values for instance fields with a given name (and only the fields with + * the given name) that EqualsVerifier cannot instantiate by itself. + * + * @param The class of the prefabricated values. + * @param fieldName The name of the field that the prefabricated values are linked to. + * @param red An instance of {@code S}. + * @param blue Another instance of {@code S}, not equal to {@code red}. + * @return {@code this}, for easy method chaining. + * @throws NullPointerException If {@code red} or {@code blue} is null, or if the named field + * does not exist in the class. + * @throws IllegalArgumentException If {@code red} equals {@code blue}. + */ + public SingleTypeEqualsVerifierApi withPrefabValuesForField( + String fieldName, + S red, + S blue + ) { + PrefabValuesApi.addPrefabValuesForField(fieldCache, objenesis, type, fieldName, red, blue); + withNonnullFields(fieldName); return this; } @@ -405,13 +425,13 @@ private String buildErrorMessage(String description, boolean showUrl) { } private void performVerification() { - ObjenesisWrapper.reset(); if (type.isEnum() || type.isInterface()) { return; } Validations.validateClassCanBeVerified(type); Configuration config = buildConfig(); + Context context = new Context<>(config, factoryCache, fieldCache, objenesis); Validations.validateProcessedAnnotations( type, config.getAnnotationCache(), @@ -420,8 +440,8 @@ private void performVerification() { allExcludedFields ); - verifyWithoutExamples(config); - verifyWithExamples(config); + verifyWithoutExamples(context); + verifyWithExamples(context); } private Configuration buildConfig() { @@ -430,13 +450,13 @@ private Configuration buildConfig() { allExcludedFields, allIncludedFields, nonnullFields, + fieldCache.getFieldNames(), cachedHashCodeInitializer, hasRedefinedSuperclass, redefinedSubclass, usingGetClass, warningsToSuppress, fieldnameToGetter, - factoryCache, ignoredAnnotationClassNames, actualFields, equalExamples, @@ -444,12 +464,13 @@ private Configuration buildConfig() { ); } - private void verifyWithoutExamples(Configuration config) { + private void verifyWithoutExamples(Context context) { + Configuration config = context.getConfiguration(); Checker[] checkers = { - new SignatureChecker<>(config), - new AbstractDelegationChecker<>(config), - new NullChecker<>(config), - new RecordChecker<>(config), + new SignatureChecker<>(context), + new AbstractDelegationChecker<>(context), + new NullChecker<>(context), + new RecordChecker<>(context), new CachedHashCodeChecker<>(config) }; @@ -458,12 +479,12 @@ private void verifyWithoutExamples(Configuration config) { } } - private void verifyWithExamples(Configuration config) { + private void verifyWithExamples(Context context) { Checker[] checkers = { - new ExamplesChecker<>(config), - new HierarchyChecker<>(config), - new FieldsChecker<>(config), - new MapEntryHashCodeRequirementChecker<>(config) + new ExamplesChecker<>(context), + new HierarchyChecker<>(context), + new FieldsChecker<>(context), + new MapEntryHashCodeRequirementChecker<>(context) }; for (Checker checker : checkers) { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/AbstractDelegationChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/AbstractDelegationChecker.java index 57534112a..ca64fabb1 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/AbstractDelegationChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/AbstractDelegationChecker.java @@ -4,28 +4,30 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; +import nl.jqno.equalsverifier.internal.reflection.ClassProbe; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; -import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; -import nl.jqno.equalsverifier.internal.util.Configuration; -import nl.jqno.equalsverifier.internal.util.Formatter; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.reflection.instantiation.ValueProvider; +import nl.jqno.equalsverifier.internal.util.*; public class AbstractDelegationChecker implements Checker { private final Class type; private final TypeTag typeTag; - private final PrefabValues prefabValues; - private final ClassAccessor classAccessor; + private final SubjectCreator subjectCreator; + private final ValueProvider valueProvider; + private final ClassProbe classProbe; private final CachedHashCodeInitializer cachedHashCodeInitializer; - public AbstractDelegationChecker(Configuration config) { - this.type = config.getType(); + public AbstractDelegationChecker(Context context) { + Configuration config = context.getConfiguration(); + this.type = context.getType(); this.typeTag = config.getTypeTag(); - this.prefabValues = config.getPrefabValues(); - this.classAccessor = config.getClassAccessor(); + this.subjectCreator = context.getSubjectCreator(); + this.valueProvider = context.getValueProvider(); + this.classProbe = context.getClassProbe(); this.cachedHashCodeInitializer = config.getCachedHashCodeInitializer(); } @@ -35,14 +37,14 @@ public void check() { checkAbstractDelegationInFields(); - T instance = prefabValues.giveRed(typeTag); - T copy = prefabValues.giveBlue(typeTag); + T instance = subjectCreator.plain(); + T copy = subjectCreator.plain(); checkAbstractDelegation(instance, copy); } private void checkAbstractEqualsAndHashCode() { - boolean equalsIsAbstract = classAccessor.isEqualsAbstract(); - boolean hashCodeIsAbstract = classAccessor.isHashCodeAbstract(); + boolean equalsIsAbstract = classProbe.isEqualsAbstract(); + boolean hashCodeIsAbstract = classProbe.isHashCodeAbstract(); if (equalsIsAbstract && hashCodeIsAbstract) { fail( @@ -72,7 +74,7 @@ private void checkAbstractDelegationInFields() { private Tuple safelyGetTuple(TypeTag tag) { try { - return prefabValues.giveTuple(tag); + return valueProvider.provide(tag); } catch (Exception ignored) { // If it fails for some reason, any reason, just return null so we can skip the test. return null; diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/ExamplesChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/ExamplesChecker.java index 7057fbb4e..fd53331b1 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/ExamplesChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/ExamplesChecker.java @@ -5,26 +5,28 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import nl.jqno.equalsverifier.internal.exceptions.AssertionException; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; -import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; -import nl.jqno.equalsverifier.internal.util.Configuration; -import nl.jqno.equalsverifier.internal.util.Formatter; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.util.*; public class ExamplesChecker implements Checker { private final Class type; private final List equalExamples; private final List unequalExamples; + private final SubjectCreator subjectCreator; private final CachedHashCodeInitializer cachedHashCodeInitializer; - public ExamplesChecker(Configuration config) { + public ExamplesChecker(Context context) { + Configuration config = context.getConfiguration(); this.type = config.getType(); this.equalExamples = config.getEqualExamples(); this.unequalExamples = config.getUnequalExamples(); + this.subjectCreator = context.getSubjectCreator(); this.cachedHashCodeInitializer = config.getCachedHashCodeInitializer(); } @@ -43,11 +45,23 @@ public void check() { } } - for (T reference : unequalExamples) { + List unequals = ensureEnoughExamples(unequalExamples); + for (T reference : unequals) { checkSingle(reference); } } + private List ensureEnoughExamples(List examples) { + if (examples.size() > 0) { + return examples; + } + + List result = new ArrayList<>(); + result.add(subjectCreator.plain()); + result.add(subjectCreator.withAllFieldsChanged()); + return result; + } + private void checkPreconditions() { for (T example : equalExamples) { assertTrue( @@ -81,7 +95,7 @@ private void checkEqualButNotIdentical(T reference, T other) { } private void checkSingle(T reference) { - final T copy = ObjectAccessor.of(reference, type).copy(); + final T copy = subjectCreator.copy(reference); checkReflexivity(reference); checkNonNullity(reference); diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldInspector.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldInspector.java index f3886e32e..9905082c6 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldInspector.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldInspector.java @@ -1,60 +1,22 @@ package nl.jqno.equalsverifier.internal.checkers; import java.lang.reflect.Field; -import java.util.Set; import nl.jqno.equalsverifier.internal.checkers.fieldchecks.FieldCheck; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; -import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; public class FieldInspector { - private final ClassAccessor classAccessor; - private final TypeTag typeTag; + private final Class type; - public FieldInspector(ClassAccessor classAccessor, TypeTag typeTag) { - this.classAccessor = classAccessor; - this.typeTag = typeTag; + public FieldInspector(Class type) { + this.type = type; } public void check(FieldCheck check) { - for (Field field : FieldIterable.of(classAccessor.getType())) { - ObjectAccessor reference = classAccessor.getRedAccessor(typeTag); - ObjectAccessor copy = classAccessor.getRedAccessor(typeTag); - FieldAccessor fieldAccessor = FieldAccessor.of(field); - - check.execute(reference, copy, fieldAccessor); - } - } - - public void checkWithNull( - boolean isNullWarningSuppressed, - boolean isZeroWarningSuppressed, - Set nonnullFields, - AnnotationCache annotationCache, - FieldCheck check - ) { - for (Field field : FieldIterable.of(classAccessor.getType())) { - ObjectAccessor reference = classAccessor.getDefaultValuesAccessor( - typeTag, - isNullWarningSuppressed, - isZeroWarningSuppressed, - nonnullFields, - annotationCache - ); - ObjectAccessor changed = classAccessor.getDefaultValuesAccessor( - typeTag, - isNullWarningSuppressed, - isZeroWarningSuppressed, - nonnullFields, - annotationCache - ); - FieldAccessor fieldAccessor = FieldAccessor.of(field); - - check.execute(reference, changed, fieldAccessor); + for (Field field : FieldIterable.of(type)) { + FieldProbe fieldProbe = FieldProbe.of(field); + check.execute(fieldProbe); } } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldsChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldsChecker.java index ab958f5fe..263519800 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldsChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/FieldsChecker.java @@ -3,23 +3,23 @@ import java.util.function.Predicate; import nl.jqno.equalsverifier.Warning; import nl.jqno.equalsverifier.internal.checkers.fieldchecks.*; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.Context; public class FieldsChecker implements Checker { + private final Context context; private final Configuration config; private final ArrayFieldCheck arrayFieldCheck; private final FloatAndDoubleFieldCheck floatAndDoubleFieldCheck; private final MutableStateFieldCheck mutableStateFieldCheck; private final ReflexivityFieldCheck reflexivityFieldCheck; private final SignificantFieldCheck significantFieldCheck; - private final SignificantFieldCheck skippingSignificantFieldCheck; private final SymmetryFieldCheck symmetryFieldCheck; private final TransientFieldsCheck transientFieldsCheck; private final TransitivityFieldCheck transitivityFieldCheck; @@ -27,49 +27,52 @@ public class FieldsChecker implements Checker { private final BigDecimalFieldCheck bigDecimalFieldCheck; private final JpaLazyGetterFieldCheck jpaLazyGetterFieldCheck; - public FieldsChecker(Configuration config) { - this.config = config; + public FieldsChecker(Context context) { + this.context = context; + this.config = context.getConfiguration(); - final PrefabValues prefabValues = config.getPrefabValues(); final TypeTag typeTag = config.getTypeTag(); + final SubjectCreator subjectCreator = context.getSubjectCreator(); final String cachedHashCodeFieldName = config .getCachedHashCodeInitializer() .getCachedHashCodeFieldName(); - final Predicate isCachedHashCodeField = a -> - a.getFieldName().equals(cachedHashCodeFieldName); + final Predicate isCachedHashCodeField = p -> + p.getName().equals(cachedHashCodeFieldName); - this.arrayFieldCheck = new ArrayFieldCheck<>(config.getCachedHashCodeInitializer()); - this.floatAndDoubleFieldCheck = new FloatAndDoubleFieldCheck<>(); + this.arrayFieldCheck = + new ArrayFieldCheck<>(subjectCreator, config.getCachedHashCodeInitializer()); + this.floatAndDoubleFieldCheck = new FloatAndDoubleFieldCheck<>(subjectCreator); this.mutableStateFieldCheck = - new MutableStateFieldCheck<>(prefabValues, typeTag, isCachedHashCodeField); - this.reflexivityFieldCheck = new ReflexivityFieldCheck<>(config); - this.significantFieldCheck = - new SignificantFieldCheck<>(config, isCachedHashCodeField, false); - this.skippingSignificantFieldCheck = - new SignificantFieldCheck<>(config, isCachedHashCodeField, true); - this.symmetryFieldCheck = new SymmetryFieldCheck<>(prefabValues, typeTag); - this.transientFieldsCheck = new TransientFieldsCheck<>(config); - this.transitivityFieldCheck = new TransitivityFieldCheck<>(prefabValues, typeTag); + new MutableStateFieldCheck<>(subjectCreator, isCachedHashCodeField); + this.reflexivityFieldCheck = new ReflexivityFieldCheck<>(context); + this.significantFieldCheck = new SignificantFieldCheck<>(context, isCachedHashCodeField); + this.symmetryFieldCheck = new SymmetryFieldCheck<>(subjectCreator); + this.transientFieldsCheck = + new TransientFieldsCheck<>(subjectCreator, typeTag, config.getAnnotationCache()); + this.transitivityFieldCheck = new TransitivityFieldCheck<>(subjectCreator); this.stringFieldCheck = - new StringFieldCheck<>(config.getPrefabValues(), config.getCachedHashCodeInitializer()); + new StringFieldCheck<>( + subjectCreator, + context.getValueProvider(), + config.getCachedHashCodeInitializer() + ); this.bigDecimalFieldCheck = - new BigDecimalFieldCheck<>(config.getCachedHashCodeInitializer()); - this.jpaLazyGetterFieldCheck = new JpaLazyGetterFieldCheck<>(config); + new BigDecimalFieldCheck<>(subjectCreator, config.getCachedHashCodeInitializer()); + this.jpaLazyGetterFieldCheck = new JpaLazyGetterFieldCheck<>(context); } @Override public void check() { - ClassAccessor classAccessor = config.getClassAccessor(); - FieldInspector inspector = new FieldInspector<>(classAccessor, config.getTypeTag()); + FieldInspector inspector = new FieldInspector<>(context.getType()); - if (!classAccessor.isEqualsInheritedFromObject()) { + if (!context.getClassProbe().isEqualsInheritedFromObject()) { inspector.check(arrayFieldCheck); inspector.check(floatAndDoubleFieldCheck); inspector.check(reflexivityFieldCheck); } - if (!ignoreMutability(config.getType())) { + if (!ignoreMutability(context.getType())) { inspector.check(mutableStateFieldCheck); } @@ -82,14 +85,6 @@ public void check() { inspector.check(transitivityFieldCheck); inspector.check(stringFieldCheck); - inspector.checkWithNull( - config.getWarningsToSuppress().contains(Warning.NULL_FIELDS), - config.getWarningsToSuppress().contains(Warning.ZERO_FIELDS), - config.getNonnullFields(), - config.getAnnotationCache(), - skippingSignificantFieldCheck - ); - if (!config.getWarningsToSuppress().contains(Warning.BIGDECIMAL_EQUALITY)) { inspector.check(bigDecimalFieldCheck); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java index 10a3e083b..815370e31 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java @@ -7,20 +7,18 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.ClassProbe; +import nl.jqno.equalsverifier.internal.reflection.Instantiator; import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; -import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; -import nl.jqno.equalsverifier.internal.util.Configuration; -import nl.jqno.equalsverifier.internal.util.Formatter; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.util.*; public class HierarchyChecker implements Checker { private final Configuration config; private final Class type; - private final TypeTag typeTag; - private final ClassAccessor classAccessor; + private final SubjectCreator subjectCreator; + private final ClassProbe classProbe; private final Class redefinedSubclass; private final boolean strictnessSuppressed; private final boolean hasRedefinedSubclass; @@ -28,8 +26,8 @@ public class HierarchyChecker implements Checker { private final boolean typeIsSealed; private final CachedHashCodeInitializer cachedHashCodeInitializer; - public HierarchyChecker(Configuration config) { - this.config = config; + public HierarchyChecker(Context context) { + this.config = context.getConfiguration(); this.strictnessSuppressed = config.getWarningsToSuppress().contains(Warning.STRICT_INHERITANCE); @@ -42,12 +40,12 @@ public HierarchyChecker(Configuration config) { ); } - this.type = config.getType(); - this.typeTag = config.getTypeTag(); - this.classAccessor = config.getClassAccessor(); + this.type = context.getType(); + this.subjectCreator = context.getSubjectCreator(); + this.classProbe = context.getClassProbe(); this.redefinedSubclass = config.getRedefinedSubclass(); this.typeIsFinal = Modifier.isFinal(type.getModifiers()); - this.typeIsSealed = classAccessor.isSealed(); + this.typeIsSealed = classProbe.isSealed(); this.cachedHashCodeInitializer = config.getCachedHashCodeInitializer(); } @@ -61,13 +59,13 @@ public void check() { } private void checkSuperclass() { - ClassAccessor superAccessor = classAccessor.getSuperAccessor(); - if (superAccessor.isEqualsInheritedFromObject() || superAccessor.isSealed()) { + ClassProbe superProbe = classProbe.getSuperProbe(); + if (superProbe.isEqualsInheritedFromObject() || superProbe.isSealed()) { return; } if (config.hasRedefinedSuperclass() || config.isUsingGetClass()) { - T reference = classAccessor.getRedObject(typeTag); + T reference = subjectCreator.plain(); Object equalSuper = getEqualSuper(reference); Formatter formatter = Formatter.of( @@ -87,15 +85,13 @@ private void checkSuperclass() { // instance. } } else { - safelyCheckSuperProperties(classAccessor.getRedAccessor(typeTag)); safelyCheckSuperProperties( - classAccessor.getDefaultValuesAccessor( - typeTag, - config.getWarningsToSuppress().contains(Warning.NULL_FIELDS), - config.getWarningsToSuppress().contains(Warning.ZERO_FIELDS), - config.getNonnullFields(), - config.getAnnotationCache() - ) + subjectCreator.plain(), + subjectCreator.withAllFieldsShallowlyChanged() + ); + safelyCheckSuperProperties( + subjectCreator.withAllFieldsDefaulted(), + subjectCreator.withAllFieldsShallowlyChanged() ); } } @@ -104,21 +100,15 @@ private void checkSuperclass() { value = "DCN_NULLPOINTER_EXCEPTION", justification = "The equals method in a superclasses can throw an NPE, but it's a specific non-goal to do something with that here." ) - private void safelyCheckSuperProperties(ObjectAccessor referenceAccessor) { + private void safelyCheckSuperProperties(T reference, T shallowScrambled) { if (strictnessSuppressed) { return; } - T reference = referenceAccessor.get(); Object equalSuper = getEqualSuper(reference); - T shallowCopy = referenceAccessor.copy(); - ObjectAccessor scrambledAccessor = ObjectAccessor - .of(shallowCopy) - .shallowScramble(config.getPrefabValues(), typeTag); - try { - checkSuperProperties(reference, equalSuper, scrambledAccessor.get()); + checkSuperProperties(reference, equalSuper, shallowScrambled); } catch (AbstractMethodError | NullPointerException ignored) { // In these cases, we'll assume all super properties hold. // The problems we test for, can never occur anyway if you can't instantiate a super @@ -159,7 +149,7 @@ private void checkSuperProperties(T reference, Object equalSuper, T shallow) { } private Object getEqualSuper(T reference) { - return ObjectAccessor.of(reference, type.getSuperclass()).copy(); + return subjectCreator.copyIntoSuperclass(reference); } private void checkSubclass() { @@ -167,9 +157,13 @@ private void checkSubclass() { return; } - ObjectAccessor referenceAccessor = classAccessor.getRedAccessor(typeTag); - T reference = referenceAccessor.get(); - T equalSub = referenceAccessor.copyIntoAnonymousSubclass(); + T reference = subjectCreator.plain(); + + @SuppressWarnings("unchecked") + Class anonymousSubclass = (Class) Instantiator.giveDynamicSubclass( + reference.getClass() // don't use type directly, as reference may already be a subclass if type was abstract + ); + T equalSub = subjectCreator.copyIntoSubclass(reference, anonymousSubclass); if (config.isUsingGetClass()) { Formatter formatter = Formatter.of( @@ -204,9 +198,8 @@ private void checkRedefinedSubclass() { ); } - ObjectAccessor referenceAccessor = classAccessor.getRedAccessor(typeTag); - T reference = referenceAccessor.get(); - T redefinedSub = referenceAccessor.copyIntoSubclass(redefinedSubclass); + T reference = subjectCreator.plain(); + T redefinedSub = subjectCreator.copyIntoSubclass(reference, redefinedSubclass); assertFalse( Formatter.of( "Subclass:\n %%\nequals subclass instance\n %%", diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/MapEntryHashCodeRequirementChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/MapEntryHashCodeRequirementChecker.java index 23ba70a04..3ae750f99 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/MapEntryHashCodeRequirementChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/MapEntryHashCodeRequirementChecker.java @@ -4,30 +4,30 @@ import java.util.Map; import java.util.Objects; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; +import nl.jqno.equalsverifier.internal.reflection.instantiation.ValueProvider; import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.Context; import nl.jqno.equalsverifier.internal.util.Formatter; public class MapEntryHashCodeRequirementChecker implements Checker { private final Configuration config; - private final ClassAccessor classAccessor; - private final CachedHashCodeInitializer cachedHashCodeInitializer; + private final ValueProvider valueProvider; - public MapEntryHashCodeRequirementChecker(Configuration config) { - this.config = config; - this.classAccessor = config.getClassAccessor(); - this.cachedHashCodeInitializer = config.getCachedHashCodeInitializer(); + public MapEntryHashCodeRequirementChecker(Context context) { + this.config = context.getConfiguration(); + this.valueProvider = context.getValueProvider(); } @Override public void check() { - if (Map.Entry.class.isAssignableFrom(classAccessor.getType())) { - Map.Entry e = (Map.Entry) classAccessor.getRedObject(config.getTypeTag()); + if (Map.Entry.class.isAssignableFrom(config.getType())) { + Map.Entry e = valueProvider + .>provide(config.getTypeTag()) + .getRed(); int expectedHashCode = Objects.hashCode(e.getKey()) ^ Objects.hashCode(e.getValue()); - int actualHashCode = cachedHashCodeInitializer.getInitializedHashCode(e); + int actualHashCode = config.getCachedHashCodeInitializer().getInitializedHashCode(e); Formatter f = Formatter.of( "Map.Entry: hashCode for\n %%\nshould be %% but was %%.\n" + diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java index 006ff02a9..736a06c6a 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java @@ -2,25 +2,23 @@ import nl.jqno.equalsverifier.Warning; import nl.jqno.equalsverifier.internal.checkers.fieldchecks.NullPointerExceptionFieldCheck; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.Context; public class NullChecker implements Checker { - private final Configuration config; + private final Context context; - public NullChecker(Configuration config) { - this.config = config; + public NullChecker(Context context) { + this.context = context; } @Override public void check() { - if (config.getWarningsToSuppress().contains(Warning.NULL_FIELDS)) { + if (context.getConfiguration().getWarningsToSuppress().contains(Warning.NULL_FIELDS)) { return; } - ClassAccessor classAccessor = config.getClassAccessor(); - FieldInspector inspector = new FieldInspector<>(classAccessor, config.getTypeTag()); - inspector.check(new NullPointerExceptionFieldCheck<>(config)); + FieldInspector inspector = new FieldInspector<>(context.getType()); + inspector.check(new NullPointerExceptionFieldCheck<>(context)); } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/RecordChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/RecordChecker.java index aa466296c..30cd67409 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/RecordChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/RecordChecker.java @@ -1,6 +1,6 @@ package nl.jqno.equalsverifier.internal.checkers; -import static nl.jqno.equalsverifier.internal.util.Assert.*; +import static nl.jqno.equalsverifier.internal.util.Assert.fail; import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; import java.lang.reflect.Field; @@ -9,44 +9,36 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; +import nl.jqno.equalsverifier.internal.reflection.ClassProbe; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; -import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.util.Context; import nl.jqno.equalsverifier.internal.util.Formatter; public class RecordChecker implements Checker { - private final Configuration config; + private final Context context; + private final SubjectCreator subjectCreator; - public RecordChecker(Configuration config) { - this.config = config; + public RecordChecker(Context context) { + this.context = context; + this.subjectCreator = context.getSubjectCreator(); } @Override public void check() { - ClassAccessor accessor = config.getClassAccessor(); - if (!accessor.isRecord()) { + ClassProbe probe = context.getClassProbe(); + if (!probe.isRecord()) { return; } - verifyRecordPrecondition(accessor.getRedAccessor(config.getTypeTag())); - verifyRecordPrecondition( - accessor.getDefaultValuesAccessor( - config.getTypeTag(), - config.getWarningsToSuppress().contains(Warning.NULL_FIELDS), - config.getWarningsToSuppress().contains(Warning.ZERO_FIELDS), - config.getNonnullFields(), - config.getAnnotationCache() - ) - ); + verifyRecordPrecondition(subjectCreator.plain()); + verifyRecordPrecondition(subjectCreator.withAllFieldsDefaulted()); } - private void verifyRecordPrecondition(ObjectAccessor originalAccessor) { - Class type = config.getType(); - T original = originalAccessor.get(); - T copy = originalAccessor.copy(); + private void verifyRecordPrecondition(T original) { + Class type = context.getType(); + T copy = subjectCreator.copy(original); if (original.equals(copy)) { return; diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/SignatureChecker.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/SignatureChecker.java index d0c5542e5..15ae77d89 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/SignatureChecker.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/SignatureChecker.java @@ -8,20 +8,20 @@ import java.util.List; import java.util.Set; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.reflection.ClassProbe; +import nl.jqno.equalsverifier.internal.util.Context; import nl.jqno.equalsverifier.internal.util.Formatter; public class SignatureChecker implements Checker { private final Class type; - private final ClassAccessor classAccessor; + private final ClassProbe classProbe; private final Set warningsToSuppress; - public SignatureChecker(Configuration config) { - this.type = config.getType(); - this.classAccessor = config.getClassAccessor(); - this.warningsToSuppress = config.getWarningsToSuppress(); + public SignatureChecker(Context context) { + this.type = context.getType(); + this.classProbe = context.getClassProbe(); + this.warningsToSuppress = context.getConfiguration().getWarningsToSuppress(); } @Override @@ -78,7 +78,7 @@ private void checkEqualsIsDefined() { boolean dontAllowDirectlyInherited = !warningsToSuppress.contains( Warning.INHERITED_DIRECTLY_FROM_OBJECT ); - boolean isDirectlyInherited = classAccessor.isEqualsInheritedFromObject(); + boolean isDirectlyInherited = classProbe.isEqualsInheritedFromObject(); if (dontAllowDirectlyInherited && isDirectlyInherited) { fail( Formatter.of( diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ArrayFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ArrayFieldCheck.java index 02a98cc4f..506c37a19 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ArrayFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ArrayFieldCheck.java @@ -3,48 +3,47 @@ import static nl.jqno.equalsverifier.internal.util.Assert.assertEquals; import java.lang.reflect.Array; -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; import nl.jqno.equalsverifier.internal.util.Formatter; public class ArrayFieldCheck implements FieldCheck { - private CachedHashCodeInitializer cachedHashCodeInitializer; + private final SubjectCreator subjectCreator; + private final CachedHashCodeInitializer cachedHashCodeInitializer; - public ArrayFieldCheck(CachedHashCodeInitializer cachedHashCodeInitializer) { + public ArrayFieldCheck( + SubjectCreator subjectCreator, + CachedHashCodeInitializer cachedHashCodeInitializer + ) { + this.subjectCreator = subjectCreator; this.cachedHashCodeInitializer = cachedHashCodeInitializer; } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - Class arrayType = fieldAccessor.getFieldType(); + public void execute(FieldProbe fieldProbe) { + Class arrayType = fieldProbe.getType(); if (!arrayType.isArray()) { return; } - if (!fieldAccessor.canBeModifiedReflectively()) { + if (!fieldProbe.canBeModifiedReflectively()) { return; } - String fieldName = fieldAccessor.getFieldName(); - T reference = referenceAccessor.get(); - T changed = replaceInnermostArrayValue(copyAccessor, fieldAccessor.getField()).get(); + T reference = subjectCreator.plain(); + T changed = replaceInnermostArrayValue(reference, fieldProbe); if (arrayType.getComponentType().isArray()) { - assertDeep(fieldName, reference, changed); + assertDeep(fieldProbe.getName(), reference, changed); } else { - assertArray(fieldName, reference, changed); + assertArray(fieldProbe.getName(), reference, changed); } } - private ObjectAccessor replaceInnermostArrayValue(ObjectAccessor accessor, Field field) { - Object newArray = arrayCopy(accessor.getField(field)); - return accessor.withFieldSetTo(field, newArray); + private T replaceInnermostArrayValue(T reference, FieldProbe fieldProbe) { + Object newArray = arrayCopy(fieldProbe.getValue(reference)); + return subjectCreator.withFieldSetTo(fieldProbe.getField(), newArray); } private Object arrayCopy(Object array) { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/BigDecimalFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/BigDecimalFieldCheck.java index 9c3fda89b..d766321d8 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/BigDecimalFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/BigDecimalFieldCheck.java @@ -6,8 +6,8 @@ import java.math.BigDecimal; import java.math.RoundingMode; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; import nl.jqno.equalsverifier.internal.util.Formatter; @@ -15,39 +15,39 @@ public class BigDecimalFieldCheck implements FieldCheck { public static final String ERROR_DOC_TITLE = "BigDecimal equality"; + private final SubjectCreator subjectCreator; private final CachedHashCodeInitializer cachedHashCodeInitializer; - public BigDecimalFieldCheck(CachedHashCodeInitializer cachedHashCodeInitializer) { + public BigDecimalFieldCheck( + SubjectCreator subjectCreator, + CachedHashCodeInitializer cachedHashCodeInitializer + ) { + this.subjectCreator = subjectCreator; this.cachedHashCodeInitializer = cachedHashCodeInitializer; } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - if (BigDecimal.class.equals(fieldAccessor.getFieldType())) { - Field field = fieldAccessor.getField(); - BigDecimal referenceField = (BigDecimal) referenceAccessor.getField(field); - BigDecimal changedField = referenceField.setScale( - referenceField.scale() + 1, + public void execute(FieldProbe fieldProbe) { + if (BigDecimal.class.equals(fieldProbe.getType())) { + T left = subjectCreator.plain(); + + BigDecimal referenceValue = (BigDecimal) fieldProbe.getValue(left); + BigDecimal changedValue = referenceValue.setScale( + referenceValue.scale() + 1, RoundingMode.UNNECESSARY ); - ObjectAccessor changed = copyAccessor.withFieldSetTo(field, changedField); - T left = referenceAccessor.get(); - T right = changed.get(); + T right = subjectCreator.withFieldSetTo(fieldProbe.getField(), changedValue); - checkEquals(field, referenceField, changedField, left, right); - checkHashCode(field, referenceField, changedField, left, right); + checkEquals(fieldProbe.getField(), referenceValue, changedValue, left, right); + checkHashCode(fieldProbe.getField(), referenceValue, changedValue, left, right); } } private void checkEquals( Field field, - BigDecimal referenceField, - BigDecimal changedField, + BigDecimal referenceValue, + BigDecimal changedValue, T left, T right ) { @@ -58,8 +58,8 @@ private void checkEquals( "\nIf these values should be considered equal then use compareTo rather than equals for this field." + "\nIf these values should not be considered equal, suppress Warning.%% to disable this check.", field.getName(), - referenceField, - changedField, + referenceValue, + changedValue, Warning.BIGDECIMAL_EQUALITY ); assertEquals(f, left, right); @@ -67,8 +67,8 @@ private void checkEquals( private void checkHashCode( Field field, - BigDecimal referenceField, - BigDecimal changedField, + BigDecimal referenceValue, + BigDecimal changedValue, T left, T right ) { @@ -79,8 +79,8 @@ private void checkHashCode( "\nIf these values should be considered equal then make sure to derive the same constituent hashCode from this field." + "\nIf these values should not be considered equal, suppress Warning.%% to disable this check.", field.getName(), - referenceField, - changedField, + referenceValue, + changedValue, Warning.BIGDECIMAL_EQUALITY ); int leftHashCode = cachedHashCodeInitializer.getInitializedHashCode(left); diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FieldCheck.java index fd0cbd05f..34eca081b 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FieldCheck.java @@ -1,13 +1,8 @@ package nl.jqno.equalsverifier.internal.checkers.fieldchecks; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; @FunctionalInterface public interface FieldCheck { - void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ); + void execute(FieldProbe fieldProbe); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FloatAndDoubleFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FloatAndDoubleFieldCheck.java index 110e0d351..64841b5f5 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FloatAndDoubleFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/FloatAndDoubleFieldCheck.java @@ -2,40 +2,40 @@ import static nl.jqno.equalsverifier.internal.util.Assert.assertEquals; -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.Formatter; public class FloatAndDoubleFieldCheck implements FieldCheck { + private final SubjectCreator subjectCreator; + + public FloatAndDoubleFieldCheck(SubjectCreator subjectCreator) { + this.subjectCreator = subjectCreator; + } + @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - Class type = fieldAccessor.getFieldType(); - Field field = fieldAccessor.getField(); + public void execute(FieldProbe fieldProbe) { + Class type = fieldProbe.getType(); if (isFloat(type)) { - T reference = referenceAccessor.withFieldSetTo(field, Float.NaN).get(); - T copy = copyAccessor.withFieldSetTo(field, Float.NaN).get(); + T reference = subjectCreator.withFieldSetTo(fieldProbe.getField(), Float.NaN); + T copy = subjectCreator.withFieldSetTo(fieldProbe.getField(), Float.NaN); assertEquals( Formatter.of( "Float: equals doesn't use Float.compare for field %%.", - field.getName() + fieldProbe.getName() ), reference, copy ); } if (isDouble(type)) { - T reference = referenceAccessor.withFieldSetTo(field, Double.NaN).get(); - T copy = copyAccessor.withFieldSetTo(field, Double.NaN).get(); + T reference = subjectCreator.withFieldSetTo(fieldProbe.getField(), Double.NaN); + T copy = subjectCreator.withFieldSetTo(fieldProbe.getField(), Double.NaN); assertEquals( Formatter.of( "Double: equals doesn't use Double.compare for field %%.", - field.getName() + fieldProbe.getName() ), reference, copy diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/JpaLazyGetterFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/JpaLazyGetterFieldCheck.java index 5ecd18ca9..9727744bf 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/JpaLazyGetterFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/JpaLazyGetterFieldCheck.java @@ -4,63 +4,73 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import static nl.jqno.equalsverifier.internal.util.Assert.assertTrue; +import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.function.Function; import nl.jqno.equalsverifier.Warning; import nl.jqno.equalsverifier.internal.exceptions.EqualsVerifierInternalBugException; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; +import nl.jqno.equalsverifier.internal.reflection.ClassProbe; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; import nl.jqno.equalsverifier.internal.reflection.Instantiator; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.reflection.instantiation.ValueProvider; import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.Context; import nl.jqno.equalsverifier.internal.util.Formatter; public class JpaLazyGetterFieldCheck implements FieldCheck { + private final SubjectCreator subjectCreator; + private final ValueProvider valueProvider; private final Class type; - private final ClassAccessor accessor; - private final PrefabValues prefabValues; + private final ClassProbe classProbe; private final AnnotationCache annotationCache; private final Function fieldnameToGetter; - private final TypeTag typeTag; private final boolean strictHashcode; - public JpaLazyGetterFieldCheck(Configuration config) { - this.type = config.getType(); - this.accessor = config.getClassAccessor(); - this.prefabValues = config.getPrefabValues(); + public JpaLazyGetterFieldCheck(Context context) { + this.subjectCreator = context.getSubjectCreator(); + this.valueProvider = context.getValueProvider(); + this.type = context.getType(); + this.classProbe = context.getClassProbe(); + + Configuration config = context.getConfiguration(); this.annotationCache = config.getAnnotationCache(); this.fieldnameToGetter = config.getFieldnameToGetter(); - this.typeTag = config.getTypeTag(); this.strictHashcode = config.getWarningsToSuppress().contains(Warning.STRICT_HASHCODE); } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - String fieldName = fieldAccessor.getFieldName(); + public void execute(FieldProbe fieldProbe) { + String fieldName = fieldProbe.getName(); String getterName = fieldnameToGetter.apply(fieldName); if ( - !fieldIsUsed(referenceAccessor, copyAccessor, fieldAccessor, true) || - !fieldIsLazy(fieldAccessor) || + !fieldIsUsed(fieldProbe.getField(), true) || + !fieldIsLazy(fieldName) || Modifier.isFinal(type.getModifiers()) ) { return; } - assertEntity(fieldName, "equals", getterName, accessor.hasMethod(getterName)); - ClassAccessor subAccessor = throwingGetterAccessor(getterName); + assertTrue( + Formatter.of( + "Class %% doesn't contain getter %%() for field %%.", + classProbe.getType().getSimpleName(), + getterName, + fieldName + ), + classProbe.hasMethod(getterName) + ); - T red1 = subAccessor.getRedObject(TypeTag.NULL); - T red2 = subAccessor.getRedObject(TypeTag.NULL); + Class sub = throwingGetterCreator(getterName); + Tuple tuple = valueProvider.provide(new TypeTag(sub)); + T red1 = tuple.getRed(); + T red2 = tuple.getRedCopy(); boolean equalsExceptionCaught = false; try { @@ -70,8 +80,7 @@ public void execute( } assertEntity(fieldName, "equals", getterName, equalsExceptionCaught); - boolean usedInHashcode = - !strictHashcode || fieldIsUsed(referenceAccessor, copyAccessor, fieldAccessor, false); + boolean usedInHashcode = !strictHashcode || fieldIsUsed(fieldProbe.getField(), false); boolean hashCodeExceptionCaught = false; try { red1.hashCode(); @@ -81,16 +90,9 @@ public void execute( assertEntity(fieldName, "hashCode", getterName, hashCodeExceptionCaught || !usedInHashcode); } - private boolean fieldIsUsed( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor, - boolean forEquals - ) { - T red = referenceAccessor.get(); - T blue = copyAccessor - .withChangedField(fieldAccessor.getField(), prefabValues, typeTag) - .get(); + private boolean fieldIsUsed(Field field, boolean forEquals) { + T red = subjectCreator.plain(); + T blue = subjectCreator.withFieldChanged(field); if (forEquals) { return !red.equals(blue); @@ -99,23 +101,19 @@ private boolean fieldIsUsed( } } - private boolean fieldIsLazy(FieldAccessor fieldAccessor) { + private boolean fieldIsLazy(String fieldName) { return ( annotationCache.hasFieldAnnotation( type, - fieldAccessor.getFieldName(), + fieldName, SupportedAnnotations.JPA_LINKED_FIELD ) || - annotationCache.hasFieldAnnotation( - type, - fieldAccessor.getFieldName(), - SupportedAnnotations.JPA_LAZY_FIELD - ) + annotationCache.hasFieldAnnotation(type, fieldName, SupportedAnnotations.JPA_LAZY_FIELD) ); } - private ClassAccessor throwingGetterAccessor(String getterName) { - Class sub = Instantiator.giveDynamicSubclass( + private Class throwingGetterCreator(String getterName) { + return Instantiator.giveDynamicSubclass( type, getterName, builder -> @@ -123,7 +121,6 @@ private ClassAccessor throwingGetterAccessor(String getterName) { .method(named(getterName)) .intercept(throwing(EqualsVerifierInternalBugException.class)) ); - return ClassAccessor.of(sub, prefabValues); } private void assertEntity( diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/MutableStateFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/MutableStateFieldCheck.java index c886c06ed..13b0b576e 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/MutableStateFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/MutableStateFieldCheck.java @@ -2,61 +2,43 @@ import static nl.jqno.equalsverifier.internal.util.Assert.fail; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.lang.reflect.Field; import java.util.function.Predicate; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.Formatter; public class MutableStateFieldCheck implements FieldCheck { - private final PrefabValues prefabValues; - private final TypeTag typeTag; - private final Predicate isCachedHashCodeField; + private final SubjectCreator subjectCreator; + private final Predicate isCachedHashCodeField; - @SuppressFBWarnings( - value = "EI_EXPOSE_REP2", - justification = "PrefabValues is inherently mutable." - ) public MutableStateFieldCheck( - PrefabValues prefabValues, - TypeTag typeTag, - Predicate isCachedHashCodeField + SubjectCreator subjectCreator, + Predicate isCachedHashCodeField ) { - this.prefabValues = prefabValues; - this.typeTag = typeTag; + this.subjectCreator = subjectCreator; this.isCachedHashCodeField = isCachedHashCodeField; } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - if (isCachedHashCodeField.test(fieldAccessor)) { + public void execute(FieldProbe fieldProbe) { + if (isCachedHashCodeField.test(fieldProbe)) { return; } - Field field = fieldAccessor.getField(); - T reference = referenceAccessor.get(); - T copy = copyAccessor.get(); + T reference = subjectCreator.plain(); + T copy = subjectCreator.plain(); boolean equalBefore = reference.equals(copy); - T changed = copyAccessor.withChangedField(field, prefabValues, typeTag).get(); + T changed = subjectCreator.withFieldChanged(fieldProbe.getField()); boolean equalAfter = reference.equals(changed); - if (equalBefore && !equalAfter && !fieldAccessor.fieldIsFinal()) { + if (equalBefore && !equalAfter && !fieldProbe.isFinal()) { String message = "Mutability: equals depends on mutable field %%.\n" + "Make the field final, suppress Warning.NONFINAL_FIELDS or use" + " EqualsVerifier.simple()"; - fail(Formatter.of(message, field.getName())); + fail(Formatter.of(message, fieldProbe.getName())); } - - referenceAccessor.withChangedField(field, prefabValues, typeTag); } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/NullPointerExceptionFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/NullPointerExceptionFieldCheck.java index 1aab5cdca..0ad2fd058 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/NullPointerExceptionFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/NullPointerExceptionFieldCheck.java @@ -4,12 +4,10 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.FieldModifier; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; -import nl.jqno.equalsverifier.internal.reflection.annotations.NonnullAnnotationVerifier; -import nl.jqno.equalsverifier.internal.util.Configuration; -import nl.jqno.equalsverifier.internal.util.Formatter; +import nl.jqno.equalsverifier.internal.reflection.FieldMutator; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.util.*; @SuppressFBWarnings( value = "RV_RETURN_VALUE_IGNORED", @@ -17,40 +15,40 @@ ) public class NullPointerExceptionFieldCheck implements FieldCheck { - private Configuration config; + private final Configuration config; + private final SubjectCreator subjectCreator; - public NullPointerExceptionFieldCheck(Configuration config) { - this.config = config; + public NullPointerExceptionFieldCheck(Context context) { + this.config = context.getConfiguration(); + this.subjectCreator = context.getSubjectCreator(); } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - if (config.getNonnullFields().contains(fieldAccessor.getFieldName())) { + public void execute(FieldProbe fieldProbe) { + if (config.getNonnullFields().contains(fieldProbe.getName())) { return; } - if (fieldAccessor.getFieldType().isPrimitive()) { + if (fieldProbe.isPrimitive()) { return; } - Field field = fieldAccessor.getField(); - if (NonnullAnnotationVerifier.fieldIsNonnull(field, config.getAnnotationCache())) { + if (fieldProbe.isAnnotatedNonnull(config.getAnnotationCache())) { return; } - if (fieldAccessor.fieldIsStatic()) { - FieldModifier fieldModifier = FieldModifier.of(field, referenceAccessor.get()); - Object saved = referenceAccessor.getField(field); + if (fieldProbe.isStatic()) { + T reference = subjectCreator.plain(); + FieldMutator fieldMutator = new FieldMutator(fieldProbe); + Object saved = fieldProbe.getValue(reference); - fieldModifier.defaultStaticField(); - performTests(field, referenceAccessor.get(), copyAccessor.get()); - fieldModifier.set(saved); + fieldMutator.setNewValue( + reference, + PrimitiveMappers.DEFAULT_VALUE_MAPPER.get(fieldProbe.getType()) + ); + performTests(fieldProbe.getField(), subjectCreator.plain(), subjectCreator.plain()); + fieldMutator.setNewValue(reference, saved); } else { - ObjectAccessor changed = copyAccessor.withDefaultedField(field); - performTests(field, referenceAccessor.get(), changed.get()); - referenceAccessor.withDefaultedField(field); + T changed = subjectCreator.withFieldDefaulted(fieldProbe.getField()); + performTests(fieldProbe.getField(), subjectCreator.plain(), changed); } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ReflexivityFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ReflexivityFieldCheck.java index e9f221996..3d775008a 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ReflexivityFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/ReflexivityFieldCheck.java @@ -7,75 +7,69 @@ import java.util.EnumSet; import java.util.Set; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.*; import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; -import nl.jqno.equalsverifier.internal.reflection.annotations.NonnullAnnotationVerifier; import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.reflection.instantiation.ValueProvider; import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.Context; import nl.jqno.equalsverifier.internal.util.Formatter; public class ReflexivityFieldCheck implements FieldCheck { private final TypeTag typeTag; - private final PrefabValues prefabValues; + private final SubjectCreator subjectCreator; + private final ValueProvider valueProvider; private final EnumSet warningsToSuppress; private final Set nonnullFields; + private final Set prefabbedFields; private final AnnotationCache annotationCache; + private final FieldCache fieldCache; - public ReflexivityFieldCheck(Configuration config) { + public ReflexivityFieldCheck(Context context) { + this.subjectCreator = context.getSubjectCreator(); + this.valueProvider = context.getValueProvider(); + + Configuration config = context.getConfiguration(); this.typeTag = config.getTypeTag(); - this.prefabValues = config.getPrefabValues(); this.warningsToSuppress = config.getWarningsToSuppress(); this.nonnullFields = config.getNonnullFields(); + this.prefabbedFields = config.getPrefabbedFields(); this.annotationCache = config.getAnnotationCache(); + this.fieldCache = context.getFieldCache(); } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { + public void execute(FieldProbe fieldProbe) { if (warningsToSuppress.contains(Warning.IDENTICAL_COPY_FOR_VERSIONED_ENTITY)) { return; } - checkReferenceReflexivity(referenceAccessor, copyAccessor); - checkValueReflexivity(referenceAccessor, copyAccessor, fieldAccessor); - checkNullReflexivity(referenceAccessor, copyAccessor, fieldAccessor); + checkReferenceReflexivity(); + checkValueReflexivity(fieldProbe); + checkNullReflexivity(fieldProbe); } - private void checkReferenceReflexivity( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor - ) { - T left = referenceAccessor.get(); - T right = copyAccessor.get(); + private void checkReferenceReflexivity() { + T left = subjectCreator.plain(); + T right = subjectCreator.plain(); checkReflexivityFor(left, right); } - private void checkValueReflexivity( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - Field field = fieldAccessor.getField(); - Class fieldType = field.getType(); + private void checkValueReflexivity(FieldProbe probe) { + Class fieldType = probe.getType(); if (warningsToSuppress.contains(Warning.REFERENCE_EQUALITY)) { return; } if (fieldType.equals(Object.class) || fieldType.isInterface()) { return; } - if (fieldAccessor.fieldIsStatic()) { + if (probe.isStatic()) { return; } - ClassAccessor fieldTypeAccessor = ClassAccessor.of(fieldType, prefabValues); - if (!fieldTypeAccessor.declaresEquals()) { + ClassProbe fieldTypeProbe = new ClassProbe<>(fieldType); + if (!fieldTypeProbe.declaresEquals()) { return; } if (fieldType.isSynthetic()) { @@ -83,38 +77,40 @@ private void checkValueReflexivity( return; } + Field field = probe.getField(); + String fieldName = field.getName(); TypeTag tag = TypeTag.of(field, typeTag); - Object left = referenceAccessor.withFieldSetTo(field, prefabValues.giveRed(tag)).get(); - Object right = copyAccessor.withFieldSetTo(field, prefabValues.giveRedCopy(tag)).get(); + Tuple tuple = prefabbedFields.contains(fieldName) + ? fieldCache.get(fieldName) + : valueProvider.provide(tag); + + Object left = subjectCreator.withFieldSetTo(field, tuple.getRed()); + Object right = subjectCreator.withFieldSetTo(field, tuple.getRedCopy()); Formatter f = Formatter.of( "Reflexivity: == used instead of .equals() on field: %%" + "\nIf this is intentional, consider suppressing Warning.%%", - field.getName(), + probe.getName(), Warning.REFERENCE_EQUALITY.toString() ); assertEquals(f, left, right); } - private void checkNullReflexivity( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - if (fieldAccessor.fieldIsPrimitive() && warningsToSuppress.contains(Warning.ZERO_FIELDS)) { + private void checkNullReflexivity(FieldProbe fieldProbe) { + if (prefabbedFields.contains(fieldProbe.getName())) { return; } - Field field = fieldAccessor.getField(); + Field field = fieldProbe.getField(); boolean nullWarningIsSuppressed = warningsToSuppress.contains(Warning.NULL_FIELDS); - boolean fieldIsNonNull = NonnullAnnotationVerifier.fieldIsNonnull(field, annotationCache); + boolean fieldIsNonNull = fieldProbe.isAnnotatedNonnull(annotationCache); boolean fieldIsMentionedExplicitly = nonnullFields.contains(field.getName()); if (nullWarningIsSuppressed || fieldIsNonNull || fieldIsMentionedExplicitly) { return; } - T left = referenceAccessor.withDefaultedField(field).get(); - T right = copyAccessor.withDefaultedField(field).get(); + T left = subjectCreator.withFieldDefaulted(field); + T right = subjectCreator.withFieldDefaulted(field); checkReflexivityFor(left, right); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SignificantFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SignificantFieldCheck.java index 3199b058b..c459965c7 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SignificantFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SignificantFieldCheck.java @@ -3,70 +3,72 @@ import static nl.jqno.equalsverifier.internal.util.Assert.assertFalse; import static nl.jqno.equalsverifier.internal.util.Assert.assertTrue; -import java.lang.reflect.Field; import java.util.EnumSet; import java.util.Set; import java.util.function.Predicate; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; -import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; -import nl.jqno.equalsverifier.internal.util.Configuration; -import nl.jqno.equalsverifier.internal.util.Formatter; -import nl.jqno.equalsverifier.internal.util.PrimitiveMappers; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.util.*; public class SignificantFieldCheck implements FieldCheck { + private final SubjectCreator subjectCreator; + private final Configuration config; private final Class type; - private final TypeTag typeTag; - private final PrefabValues prefabValues; private final EnumSet warningsToSuppress; private final Set ignoredFields; private final CachedHashCodeInitializer cachedHashCodeInitializer; private final AnnotationCache annotationCache; - private final Predicate isCachedHashCodeField; - private final boolean skipCertainTestsThatDontMatterWhenValuesAreNull; + private final Predicate isCachedHashCodeField; - public SignificantFieldCheck( - Configuration config, - Predicate isCachedHashCodeField, - boolean skipCertainTestsThatDontMatterWhenValuesAreNull - ) { + public SignificantFieldCheck(Context context, Predicate isCachedHashCodeField) { + this.subjectCreator = context.getSubjectCreator(); + this.config = context.getConfiguration(); this.type = config.getType(); - this.typeTag = config.getTypeTag(); - this.prefabValues = config.getPrefabValues(); this.warningsToSuppress = config.getWarningsToSuppress(); this.ignoredFields = config.getIgnoredFields(); this.cachedHashCodeInitializer = config.getCachedHashCodeInitializer(); this.annotationCache = config.getAnnotationCache(); this.isCachedHashCodeField = isCachedHashCodeField; - this.skipCertainTestsThatDontMatterWhenValuesAreNull = - skipCertainTestsThatDontMatterWhenValuesAreNull; } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - if (isCachedHashCodeField.test(fieldAccessor)) { + public void execute(FieldProbe fieldProbe) { + if (isCachedHashCodeField.test(fieldProbe)) { return; } - Field field = fieldAccessor.getField(); - T reference = referenceAccessor.get(); - T copy = copyAccessor.get(); - String fieldName = field.getName(); - - boolean equalToItself = reference.equals(copy); + checkValues( + subjectCreator.plain(), + subjectCreator.plain(), + subjectCreator.withFieldChanged(fieldProbe.getField()), + fieldProbe, + false + ); + if (fieldProbe.canBeDefault(config)) { + checkValues( + subjectCreator.withAllFieldsDefaulted(), + subjectCreator.withAllFieldsDefaulted(), + subjectCreator.withAllFieldsDefaultedExcept(fieldProbe.getField()), + fieldProbe, + true + ); + } + } - T changed = copyAccessor.withChangedField(field, prefabValues, typeTag).get(); + private void checkValues( + T reference, + T copy, + T changed, + FieldProbe probe, + boolean testWithNull + ) { + String fieldName = probe.getField().getName(); + boolean equalToItself = reference.equals(copy); boolean equalsChanged = !reference.equals(changed); boolean hashCodeChanged = cachedHashCodeInitializer.getInitializedHashCode(reference) != @@ -77,17 +79,17 @@ public void execute( hashCodeChanged, reference, changed, - fieldName + fieldName, + testWithNull ); assertFieldShouldBeIgnored( equalToItself, equalsChanged, - referenceAccessor.get(), - fieldAccessor, - fieldName + reference, + probe, + fieldName, + testWithNull ); - - referenceAccessor.withChangedField(field, prefabValues, typeTag); } private void assertEqualsAndHashCodeRelyOnSameFields( @@ -95,12 +97,12 @@ private void assertEqualsAndHashCodeRelyOnSameFields( boolean hashCodeChanged, T reference, T changed, - String fieldName + String fieldName, + boolean testWithNull ) { if (equalsChanged != hashCodeChanged) { boolean skipEqualsHasMoreThanHashCodeTest = - warningsToSuppress.contains(Warning.STRICT_HASHCODE) || - skipCertainTestsThatDontMatterWhenValuesAreNull; + warningsToSuppress.contains(Warning.STRICT_HASHCODE) || testWithNull; if (!skipEqualsHasMoreThanHashCodeTest) { Formatter formatter = Formatter.of( "Significant fields: equals relies on %%, but hashCode does not." + @@ -128,10 +130,11 @@ private void assertFieldShouldBeIgnored( boolean equalToItself, boolean equalsChanged, T object, - FieldAccessor fieldAccessor, - String fieldName + FieldProbe fieldProbe, + String fieldName, + boolean testWithNull ) { - if (!shouldAllFieldsBeUsed(fieldAccessor) || !isFieldEligible(fieldAccessor)) { + if (!shouldAllFieldsBeUsed(fieldProbe) || !isFieldEligible(fieldProbe)) { return; } @@ -145,7 +148,7 @@ private void assertFieldShouldBeIgnored( !thisFieldIsMarkedAsId && annotationCache.hasClassAnnotation(type, SupportedAnnotations.ID); - if (!fieldIsEmptyAndItsOk(thisFieldIsMarkedAsId, fieldAccessor, object)) { + if (!fieldIsEmptyAndItsOk(thisFieldIsMarkedAsId, fieldProbe, object)) { if (!fieldShouldBeIgnored) { assertTrue( Formatter.of("Significant fields: equals does not use %%.", fieldName), @@ -165,26 +168,27 @@ private void assertFieldShouldBeIgnored( equalsChanged, fieldShouldBeIgnored, thisFieldIsMarkedAsId, - anotherFieldIsMarkedAsId + anotherFieldIsMarkedAsId, + testWithNull ); } - private boolean shouldAllFieldsBeUsed(FieldAccessor fieldAccessor) { + private boolean shouldAllFieldsBeUsed(FieldProbe fieldProbe) { return ( !warningsToSuppress.contains(Warning.ALL_FIELDS_SHOULD_BE_USED) && !(warningsToSuppress.contains(Warning.ALL_NONFINAL_FIELDS_SHOULD_BE_USED) && - !fieldAccessor.fieldIsFinal()) + !fieldProbe.isFinal()) ); } - private boolean isFieldEligible(FieldAccessor fieldAccessor) { + private boolean isFieldEligible(FieldProbe fieldProbe) { return ( - !fieldAccessor.fieldIsStatic() && - !fieldAccessor.fieldIsTransient() && - !fieldAccessor.fieldIsEmptyOrSingleValueEnum() && + !fieldProbe.isStatic() && + !fieldProbe.isTransient() && + !fieldProbe.isEmptyOrSingleValueEnum() && !annotationCache.hasFieldAnnotation( type, - fieldAccessor.getField().getName(), + fieldProbe.getField().getName(), SupportedAnnotations.TRANSIENT ) ); @@ -192,11 +196,11 @@ private boolean isFieldEligible(FieldAccessor fieldAccessor) { private boolean fieldIsEmptyAndItsOk( boolean thisFieldIsMarkedAsId, - FieldAccessor fieldAccessor, + FieldProbe fieldProbe, T object ) { - Object value = fieldAccessor.get(object); - Class fieldType = fieldAccessor.getFieldType(); + Object value = fieldProbe.getValue(object); + Class fieldType = fieldProbe.getType(); Object zero = PrimitiveMappers.DEFAULT_WRAPPED_VALUE_MAPPER.get(fieldType); boolean fieldIsEmpty = value == null || value.equals(zero); @@ -236,7 +240,8 @@ private void assertFieldShouldNotBeUsed( boolean equalsChanged, boolean fieldShouldBeIgnored, boolean thisFieldIsMarkedAsId, - boolean anotherFieldIsMarkedAsId + boolean anotherFieldIsMarkedAsId, + boolean testWithNull ) { final String message; if (thisFieldIsMarkedAsId) { @@ -255,9 +260,7 @@ private void assertFieldShouldNotBeUsed( assertTrue( Formatter.of(message, fieldName), - !fieldShouldBeIgnored || - !equalsChanged || - skipCertainTestsThatDontMatterWhenValuesAreNull + !fieldShouldBeIgnored || !equalsChanged || testWithNull ); } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/StringFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/StringFieldCheck.java index c2437c09d..e05e50ad5 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/StringFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/StringFieldCheck.java @@ -3,12 +3,11 @@ import static nl.jqno.equalsverifier.internal.util.Assert.fail; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.lang.reflect.Field; import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.reflection.instantiation.ValueProvider; import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; import nl.jqno.equalsverifier.internal.util.Formatter; @@ -16,7 +15,8 @@ public class StringFieldCheck implements FieldCheck { public static final String ERROR_DOC_TITLE = "String equality"; - private final PrefabValues prefabValues; + private final SubjectCreator subjectCreator; + private final ValueProvider valueProvider; private final CachedHashCodeInitializer cachedHashCodeInitializer; @SuppressFBWarnings( @@ -24,10 +24,12 @@ public class StringFieldCheck implements FieldCheck { justification = "PrefabValues is inherently mutable." ) public StringFieldCheck( - PrefabValues prefabValues, + SubjectCreator subjectCreator, + ValueProvider instanceCreator, CachedHashCodeInitializer cachedHashCodeInitializer ) { - this.prefabValues = prefabValues; + this.subjectCreator = subjectCreator; + this.valueProvider = instanceCreator; this.cachedHashCodeInitializer = cachedHashCodeInitializer; } @@ -36,20 +38,15 @@ public StringFieldCheck( value = "DM_CONVERT_CASE", justification = "String prefab values are probably not localized." ) - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - if (String.class.equals(fieldAccessor.getFieldType()) && !fieldAccessor.fieldIsStatic()) { - Field field = fieldAccessor.getField(); - String red = prefabValues.giveRed(new TypeTag(String.class)); + public void execute(FieldProbe fieldProbe) { + if (String.class.equals(fieldProbe.getType()) && !fieldProbe.isStatic()) { + String red = valueProvider.provide(new TypeTag(String.class)).getRed(); final T reference; final T copy; try { - reference = referenceAccessor.withFieldSetTo(field, red.toLowerCase()).get(); - copy = copyAccessor.withFieldSetTo(field, red.toUpperCase()).get(); + reference = subjectCreator.withFieldSetTo(fieldProbe.getField(), red.toLowerCase()); + copy = subjectCreator.withFieldSetTo(fieldProbe.getField(), red.toUpperCase()); } catch (ReflectionException ignored) { // Differently-cased String is not allowed, so cannot cause problems either. return; @@ -66,7 +63,7 @@ public void execute( ERROR_DOC_TITLE + ": class uses equalsIgnoreCase to compare String field %%, but hashCode is case-sensitive." + " Use toUpperCase() to determine the hashCode.", - field.getName() + fieldProbe.getName() ) ); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SymmetryFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SymmetryFieldCheck.java index b5318cff6..c3611eebc 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SymmetryFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/SymmetryFieldCheck.java @@ -3,58 +3,33 @@ import static nl.jqno.equalsverifier.internal.util.Assert.assertTrue; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.Formatter; public class SymmetryFieldCheck implements FieldCheck { - private final PrefabValues prefabValues; - private final TypeTag typeTag; + private final SubjectCreator subjectCreator; @SuppressFBWarnings( value = "EI_EXPOSE_REP2", justification = "PrefabValues is inherently mutable." ) - public SymmetryFieldCheck(PrefabValues prefabValues, TypeTag typeTag) { - this.prefabValues = prefabValues; - this.typeTag = typeTag; + public SymmetryFieldCheck(SubjectCreator subjectCreator) { + this.subjectCreator = subjectCreator; } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - Field field = fieldAccessor.getField(); - - checkSymmetry(referenceAccessor, copyAccessor); - - ObjectAccessor changedAccessor = copyAccessor.withChangedField( - field, - prefabValues, - typeTag - ); - checkSymmetry(referenceAccessor, changedAccessor); + public void execute(FieldProbe fieldProbe) { + T left = subjectCreator.plain(); + T right = subjectCreator.plain(); + T changedRight = subjectCreator.withFieldChanged(fieldProbe.getField()); - ObjectAccessor changedReferenceAccessor = referenceAccessor.withChangedField( - field, - prefabValues, - typeTag - ); - checkSymmetry(changedReferenceAccessor, changedAccessor); + checkSymmetry(left, right); + checkSymmetry(left, changedRight); } - private void checkSymmetry( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor - ) { - T left = referenceAccessor.get(); - T right = copyAccessor.get(); + private void checkSymmetry(T left, T right) { assertTrue( Formatter.of("Symmetry: objects are not symmetric:\n %%\nand\n %%", left, right), left.equals(right) == right.equals(left) diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransientFieldsCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransientFieldsCheck.java index 5e849ed4b..261d970a7 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransientFieldsCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransientFieldsCheck.java @@ -2,54 +2,50 @@ import static nl.jqno.equalsverifier.internal.util.Assert.fail; -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; -import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.Formatter; public class TransientFieldsCheck implements FieldCheck { - private final PrefabValues prefabValues; + private final SubjectCreator subjectCreator; private final TypeTag typeTag; private final AnnotationCache annotationCache; - public TransientFieldsCheck(Configuration config) { - this.prefabValues = config.getPrefabValues(); - this.typeTag = config.getTypeTag(); - this.annotationCache = config.getAnnotationCache(); + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "A cache is inherently mutable.") + public TransientFieldsCheck( + SubjectCreator subjectCreator, + TypeTag typeTag, + AnnotationCache annotationCache + ) { + this.subjectCreator = subjectCreator; + this.typeTag = typeTag; + this.annotationCache = annotationCache; } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - Field field = fieldAccessor.getField(); - T reference = referenceAccessor.get(); - T changed = copyAccessor.withChangedField(field, prefabValues, typeTag).get(); + public void execute(FieldProbe fieldProbe) { + T reference = subjectCreator.plain(); + T changed = subjectCreator.withFieldChanged(fieldProbe.getField()); boolean equalsChanged = !reference.equals(changed); boolean hasAnnotation = annotationCache.hasFieldAnnotation( typeTag.getType(), - field.getName(), + fieldProbe.getName(), SupportedAnnotations.TRANSIENT ); - boolean fieldIsTransient = fieldAccessor.fieldIsTransient() || hasAnnotation; + boolean fieldIsTransient = fieldProbe.isTransient() || hasAnnotation; if (equalsChanged && fieldIsTransient) { fail( Formatter.of( "Transient field %% should not be included in equals/hashCode contract.", - field.getName() + fieldProbe.getName() ) ); } - - referenceAccessor.withChangedField(field, prefabValues, typeTag); } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransitivityFieldCheck.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransitivityFieldCheck.java index 8bf74a553..6e20d3991 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransitivityFieldCheck.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/TransitivityFieldCheck.java @@ -3,38 +3,27 @@ import static nl.jqno.equalsverifier.internal.util.Assert.fail; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.FieldIterable; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; import nl.jqno.equalsverifier.internal.util.Formatter; public class TransitivityFieldCheck implements FieldCheck { - private final PrefabValues prefabValues; - private final TypeTag typeTag; + private final SubjectCreator subjectCreator; @SuppressFBWarnings( value = "EI_EXPOSE_REP2", justification = "PrefabValues is inherently mutable." ) - public TransitivityFieldCheck(PrefabValues prefabValues, TypeTag typeTag) { - this.prefabValues = prefabValues; - this.typeTag = typeTag; + public TransitivityFieldCheck(SubjectCreator subjectCreator) { + this.subjectCreator = subjectCreator; } @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - Field field = fieldAccessor.getField(); - T a1 = referenceAccessor.get(); - T b1 = buildB1(copyAccessor, field); - T b2 = buildB2(referenceAccessor, field); + public void execute(FieldProbe fieldProbe) { + T a1 = subjectCreator.plain(); + T b1 = subjectCreator.withFieldChanged(fieldProbe.getField()); + T b2 = subjectCreator.withAllFieldsChanged(); boolean x = a1.equals(b1); boolean y = b1.equals(b2); @@ -53,23 +42,6 @@ public void execute( } } - private T buildB1(ObjectAccessor accessor, Field field) { - accessor.withChangedField(field, prefabValues, typeTag); - return accessor.get(); - } - - private T buildB2(ObjectAccessor referenceAccessor, Field field) { - T copy = referenceAccessor.copy(); - ObjectAccessor objectAccessor = ObjectAccessor.of(copy); - objectAccessor = objectAccessor.withChangedField(field, prefabValues, typeTag); - for (Field f : FieldIterable.of(referenceAccessor.type())) { - if (!f.equals(field)) { - objectAccessor = objectAccessor.withChangedField(f, prefabValues, typeTag); - } - } - return objectAccessor.get(); - } - private int countFalses(boolean... bools) { int result = 0; for (boolean b : bools) { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/exceptions/RecursionException.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/exceptions/RecursionException.java index 1e48c5625..1221f1c0b 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/exceptions/RecursionException.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/exceptions/RecursionException.java @@ -3,7 +3,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Iterator; import java.util.LinkedHashSet; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; /** Signals that a recursion has been detected while traversing the fields of a data structure. */ @SuppressWarnings("serial") diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/Cache.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/Cache.java deleted file mode 100644 index 8a48145c8..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/Cache.java +++ /dev/null @@ -1,49 +0,0 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; - -import java.util.HashMap; -import java.util.Map; - -/** Contains a cache of prefabricated values, for {@link PrefabValues}. */ -class Cache { - - @SuppressWarnings("rawtypes") - private final Map cache = new HashMap<>(); - - /** - * Adds a prefabricated value to the cache for the given type. - * - * @param tag A description of the type. Takes generics into account. - * @param red A "red" value for the given type. - * @param blue A "blue" value for the given type. - * @param redCopy A shallow copy of the given red value. - * @param The type of given tag. - */ - public void put(TypeTag tag, T red, T blue, T redCopy) { - cache.put(tag, new Tuple<>(red, blue, redCopy)); - } - - /** - * Returns a {@link Tuple} of prefabricated values for the specified type. - * - *

What happens when there is no value, is undefined. Always call {@link #contains(TypeTag)} - * first. - * - * @param tag A description of the type. Takes generics into account. - * @param the type of the Tuple. - * @return Tuple of type T. - */ - @SuppressWarnings("unchecked") - public Tuple getTuple(TypeTag tag) { - return cache.get(tag); - } - - /** - * Returns whether prefabricated values are available for the given type. - * - * @param tag A description of the type. Takes generics into account. - * @return true if prefabricated values are available. - */ - public boolean contains(TypeTag tag) { - return cache.containsKey(tag); - } -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/FactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/FactoryProvider.java deleted file mode 100644 index c044ff744..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/FactoryProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; - -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; - -public interface FactoryProvider { - FactoryCache getFactoryCache(); -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessor.java deleted file mode 100644 index 247645456..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessor.java +++ /dev/null @@ -1,338 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Predicate; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; -import nl.jqno.equalsverifier.internal.reflection.annotations.NonnullAnnotationVerifier; - -/** - * Instantiates and populates objects of a given class. {@link ClassAccessor} can create two - * different instances of T, which are guaranteed not to be equal to each other, and which contain - * no null values. - * - * @param A class. - */ -public class ClassAccessor { - - private final Class type; - private final PrefabValues prefabValues; - - /** Private constructor. Call {@link #of(Class, PrefabValues)} instead. */ - ClassAccessor(Class type, PrefabValues prefabValues) { - this.type = type; - this.prefabValues = prefabValues; - } - - /** - * Factory method. - * - * @param The class on which {@link ClassAccessor} operates. - * @param type The class on which {@link ClassAccessor} operates. Should be the same as T. - * @param prefabValues Prefabricated values with which to fill instantiated objects. - * @return A {@link ClassAccessor} for T. - */ - public static ClassAccessor of(Class type, PrefabValues prefabValues) { - return new ClassAccessor<>(type, prefabValues); - } - - /** @return The class on which {@link ClassAccessor} operates. */ - public Class getType() { - return type; - } - - /** - * Determines whether T is a Java Record. - * - * @return true if T is a Java Record. - */ - public boolean isRecord() { - return RecordsHelper.isRecord(type); - } - - /** - * Determines whether T is a sealed class. - * - * @return true if T is a sealed class - */ - public boolean isSealed() { - return SealedTypesHelper.isSealed(type); - } - - /** - * Determines whether T declares a field. This does not include inherited fields. - * - * @param field The field that we want to detect. - * @return True if T declares the field. - */ - public boolean declaresField(Field field) { - try { - type.getDeclaredField(field.getName()); - return true; - } catch (NoSuchFieldException e) { - return false; - } - } - - /** - * Determines whether T has an {@code equals} method. - * - * @return True if T has an {@code equals} method. - */ - public boolean declaresEquals() { - return declaresMethod(type, "equals", Object.class); - } - - /** - * Determines whether T has an {@code hashCode} method. - * - * @return True if T has an {@code hashCode} method. - */ - public boolean declaresHashCode() { - return declaresMethod(type, "hashCode"); - } - - /** - * Determines whether T has a method with the given name and parameters. - * - * @param name The name of the method we're looking for. - * @return True if T has a method with the given name and parameters. - */ - public boolean hasMethod(String name) { - Class t = type; - while (t != null) { - if (declaresMethod(t, name)) { - return true; - } - t = t.getSuperclass(); - } - return false; - } - - private static boolean declaresMethod(Class type, String name, Class... parameterTypes) { - try { - type.getDeclaredMethod(name, parameterTypes); - return true; - } catch (NoSuchMethodException e) { - return false; - } - } - - /** - * Determines whether T's {@code equals} method is abstract. - * - * @return True if T's {@code equals} method is abstract. - */ - public boolean isEqualsAbstract() { - return isMethodAbstract("equals", Object.class); - } - - /** - * Determines whether T's {@code hashCode} method is abstract. - * - * @return True if T's {@code hashCode} method is abstract. - */ - public boolean isHashCodeAbstract() { - return isMethodAbstract("hashCode"); - } - - private boolean isMethodAbstract(String name, Class... parameterTypes) { - return rethrow(() -> - Modifier.isAbstract(type.getMethod(name, parameterTypes).getModifiers()) - ); - } - - /** - * Determines whether T's {@code equals} method is inherited from {@link Object}. - * - * @return true if T's {@code equals} method is inherited from {@link Object}; false if it is - * overridden in T or in any of its superclasses (except {@link Object}). - */ - public boolean isEqualsInheritedFromObject() { - ClassAccessor i = this; - while (i.getType() != Object.class) { - if (i.declaresEquals() && !i.isEqualsAbstract()) { - return false; - } - i = i.getSuperAccessor(); - } - return true; - } - - /** - * Returns an accessor for T's superclass. - * - * @return An accessor for T's superclass. - */ - public ClassAccessor getSuperAccessor() { - return ClassAccessor.of(type.getSuperclass(), prefabValues); - } - - /** - * Returns an instance of T that is not equal to the instance of T returned by {@link - * #getBlueObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @return An instance of T. - */ - public T getRedObject(TypeTag enclosingType) { - return getRedAccessor(enclosingType).get(); - } - - /** - * Returns an instance of T that is not equal to the instance of T returned by {@link - * #getBlueObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @param typeStack Keeps track of recursion in the type. - * @return An instance of T. - */ - public T getRedObject(TypeTag enclosingType, LinkedHashSet typeStack) { - return getRedAccessor(enclosingType, typeStack).get(); - } - - /** - * Returns an {@link ObjectAccessor} for {@link #getRedObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @return An {@link ObjectAccessor} for {@link #getRedObject(TypeTag)}. - */ - public ObjectAccessor getRedAccessor(TypeTag enclosingType) { - return getRedAccessor(enclosingType, new LinkedHashSet<>()); - } - - /** - * Returns an {@link ObjectAccessor} for {@link #getRedObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @param typeStack Keeps track of recursion in the type. - * @return An {@link ObjectAccessor} for {@link #getRedObject(TypeTag)}. - */ - public ObjectAccessor getRedAccessor( - TypeTag enclosingType, - LinkedHashSet typeStack - ) { - return buildObjectAccessor().scramble(prefabValues, enclosingType, typeStack); - } - - /** - * Returns an instance of T that is not equal to the instance of T returned by {@link - * #getRedObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @return An instance of T. - */ - public T getBlueObject(TypeTag enclosingType) { - return getBlueAccessor(enclosingType).get(); - } - - /** - * Returns an instance of T that is not equal to the instance of T returned by {@link - * #getRedObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @param typeStack Keeps track of recursion in the type. - * @return An instance of T. - */ - public T getBlueObject(TypeTag enclosingType, LinkedHashSet typeStack) { - return getBlueAccessor(enclosingType, typeStack).get(); - } - - /** - * Returns an {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @return An {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}. - */ - public ObjectAccessor getBlueAccessor(TypeTag enclosingType) { - return getBlueAccessor(enclosingType, new LinkedHashSet<>()); - } - - /** - * Returns an {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}. - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @param typeStack Keeps track of recursion in the type. - * @return An {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}. - */ - public ObjectAccessor getBlueAccessor( - TypeTag enclosingType, - LinkedHashSet typeStack - ) { - return buildObjectAccessor() - .scramble(prefabValues, enclosingType, typeStack) - .scramble(prefabValues, enclosingType, typeStack); - } - - /** - * Returns an {@link ObjectAccessor} for an instance of T where all the fields are initialized - * to their default values. I.e., 0 for ints, and null for objects (except when the field is - * marked with a NonNull annotation). - * - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @param isWarningNullSuppressed Whether reference fields must be non-null (a.k.a., whether - * Warnings.NULL_FIELDS is suppressed). - * @param isWarningZeroSuppressed Whether primitive fields must be non-0 (a.k.a., whether - * Warnings.ZERO_FIELDS is suppressed). - * @param nonnullFields Fields which are not allowed to be set to null. - * @param annotationCache To check for any NonNull annotations. - * @return An {@link ObjectAccessor} for an instance of T where all the fields are initialized - * to their default values. - */ - public ObjectAccessor getDefaultValuesAccessor( - TypeTag enclosingType, - boolean isWarningNullSuppressed, - boolean isWarningZeroSuppressed, - Set nonnullFields, - AnnotationCache annotationCache - ) { - Predicate canBeDefault = f -> - canBeDefault( - f, - enclosingType, - isWarningNullSuppressed, - isWarningZeroSuppressed, - nonnullFields, - annotationCache - ); - return buildObjectAccessor().clear(canBeDefault, prefabValues, enclosingType); - } - - private boolean canBeDefault( - Field f, - TypeTag enclosingType, - boolean isWarningNullSuppressed, - boolean isWarningZeroSuppressed, - Set nonnullFields, - AnnotationCache annotationCache - ) { - FieldAccessor accessor = FieldAccessor.of(f); - if (accessor.fieldIsPrimitive()) { - return !isWarningZeroSuppressed; - } - - boolean isAnnotated = NonnullAnnotationVerifier.fieldIsNonnull(f, annotationCache); - boolean isMentionedExplicitly = nonnullFields.contains(f.getName()); - return !isWarningNullSuppressed && !isAnnotated && !isMentionedExplicitly; - } - - private ObjectAccessor buildObjectAccessor() { - T object = Instantiator.of(type).instantiate(); - return ObjectAccessor.of(object); - } -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ClassProbe.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ClassProbe.java new file mode 100644 index 000000000..18eb2b208 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ClassProbe.java @@ -0,0 +1,134 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; + +import java.lang.reflect.Modifier; + +/** + * Provides read-only reflective access to a class. + */ +public class ClassProbe { + + private final Class type; + + public ClassProbe(Class type) { + this.type = type; + } + + /** @return The class on which {@link ClassProbe} operates. */ + public Class getType() { + return type; + } + + /** + * Determines whether T is a Java Record. + * + * @return true if T is a Java Record. + */ + public boolean isRecord() { + return RecordsHelper.isRecord(type); + } + + /** + * Determines whether T is a sealed class. + * + * @return true if T is a sealed class + */ + public boolean isSealed() { + return SealedTypesHelper.isSealed(type); + } + + /** + * Determines whether T has an {@code equals} method. + * + * @return True if T has an {@code equals} method. + */ + public boolean declaresEquals() { + return declaresMethod(type, "equals", Object.class); + } + + /** + * Determines whether T has an {@code hashCode} method. + * + * @return True if T has an {@code hashCode} method. + */ + public boolean declaresHashCode() { + return declaresMethod(type, "hashCode"); + } + + /** + * Determines whether T has a method with the given name and parameters. + * + * @param name The name of the method we're looking for. + * @return True if T has a method with the given name and parameters. + */ + public boolean hasMethod(String name) { + Class t = type; + while (t != null) { + if (declaresMethod(t, name)) { + return true; + } + t = t.getSuperclass(); + } + return false; + } + + private static boolean declaresMethod(Class type, String name, Class... parameterTypes) { + try { + type.getDeclaredMethod(name, parameterTypes); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Determines whether T's {@code equals} method is abstract. + * + * @return True if T's {@code equals} method is abstract. + */ + public boolean isEqualsAbstract() { + return isMethodAbstract("equals", Object.class); + } + + /** + * Determines whether T's {@code hashCode} method is abstract. + * + * @return True if T's {@code hashCode} method is abstract. + */ + public boolean isHashCodeAbstract() { + return isMethodAbstract("hashCode"); + } + + private boolean isMethodAbstract(String name, Class... parameterTypes) { + return rethrow(() -> + Modifier.isAbstract(type.getMethod(name, parameterTypes).getModifiers()) + ); + } + + /** + * Determines whether T's {@code equals} method is inherited from {@link Object}. + * + * @return true if T's {@code equals} method is inherited from {@link Object}; false if it is + * overridden in T or in any of its superclasses (except {@link Object}). + */ + public boolean isEqualsInheritedFromObject() { + ClassProbe i = this; + while (i.getType() != Object.class) { + if (i.declaresEquals() && !i.isEqualsAbstract()) { + return false; + } + i = i.getSuperProbe(); + } + return true; + } + + /** + * Returns a probe for T's superclass. + * + * @return A probe for T's superclass. + */ + public ClassProbe getSuperProbe() { + return new ClassProbe<>(type.getSuperclass()); + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/FactoryCache.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FactoryCache.java similarity index 83% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/FactoryCache.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FactoryCache.java index 3cc2e74a5..1d77eabdd 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/FactoryCache.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FactoryCache.java @@ -1,12 +1,12 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; -/** Contains a cache of factories, for {@link PrefabValues}. */ -public class FactoryCache implements Iterable>> { +/** Contains a cache of factories, for {@link VintageValueProvider}. */ +public class FactoryCache { /** * We store Strings instead of Classes, so that the cache can be lazy and initializers won't be @@ -81,14 +81,8 @@ public FactoryCache merge(FactoryCache other) { } private void copy(FactoryCache to, FactoryCache from) { - for (Map.Entry> entry : from) { + for (Map.Entry> entry : from.cache.entrySet()) { to.put(entry.getKey(), entry.getValue()); } } - - /** Provides an iterator over all available factories. */ - @Override - public Iterator>> iterator() { - return cache.entrySet().iterator(); - } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldAccessor.java deleted file mode 100644 index 49a3ab1a7..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldAccessor.java +++ /dev/null @@ -1,103 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; - -/** Provides reflective access to one field of an object. */ -public final class FieldAccessor { - - private final Field field; - - /** Private constructor. Call {@link #of(Field)} to instantiate. */ - private FieldAccessor(Field field) { - this.field = field; - } - - /** - * Factory method. - * - * @param field The field to access. - * @return A {@link FieldAccessor} for {@link #field}. - */ - public static FieldAccessor of(Field field) { - return new FieldAccessor(field); - } - - /** @return The field itself. */ - @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Can't defensively copy a Field.") - public Field getField() { - return field; - } - - /** @return The field's type. */ - public Class getFieldType() { - return field.getType(); - } - - /** @return The field's name. */ - public String getFieldName() { - return field.getName(); - } - - /** @return Whether the field is of a primitive type. */ - public boolean fieldIsPrimitive() { - return getFieldType().isPrimitive(); - } - - /** @return Whether the field is marked with the final modifier. */ - public boolean fieldIsFinal() { - return Modifier.isFinal(field.getModifiers()); - } - - /** @return Whether the field is marked with the static modifier. */ - public boolean fieldIsStatic() { - return Modifier.isStatic(field.getModifiers()); - } - - /** @return Whether the field is marked with the transient modifier. */ - public boolean fieldIsTransient() { - return Modifier.isTransient(field.getModifiers()); - } - - /** @return Whether the field is an enum with a single value. */ - public boolean fieldIsEmptyOrSingleValueEnum() { - Class type = field.getType(); - return type.isEnum() && type.getEnumConstants().length <= 1; - } - - /** - * Tries to get the field's value. - * - * @param object The object that contains the field whose value we want to get. - * @return The field's value. - * @throws ReflectionException If the operation fails. - */ - @SuppressFBWarnings( - value = "DP_DO_INSIDE_DO_PRIVILEGED", - justification = "Only called in test code, not production." - ) - public Object get(Object object) { - field.setAccessible(true); - return rethrow(() -> field.get(object)); - } - - /** - * Determines whether the field can be modified using reflection. - * - * @return Whether or not the field can be modified reflectively. - */ - public boolean canBeModifiedReflectively() { - if (field.isSynthetic()) { - return false; - } - int modifiers = field.getModifiers(); - if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) { - return false; - } - return true; - } -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldCache.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldCache.java new file mode 100644 index 000000000..455a786e5 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldCache.java @@ -0,0 +1,60 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import java.util.*; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; + +/** Contains a cache for values connected to specific fields, for {@link SubjectCreator}. */ +public class FieldCache { + + /** + * We store Strings instead of Fields, to make it easier to interact with when we don't + * actually have a reference to a Field. + */ + private final Map> cache = new HashMap<>(); + + /** + * Adds the given factory to the cache and associates it with the given type. + * + * @param The type of the values. + * @param fieldName The name of the field to associate with the values. + * @param tuple The tuple that contains the values. + */ + public void put(String fieldName, Tuple tuple) { + if (fieldName != null) { + cache.put(fieldName, tuple); + } + } + + /** + * Retrieves the values from the cache for the given field. + * + *

What happens when there are no values, is undefined. Always call {@link #contains(String)} + * first. + * + * @param The returned values will have this as generic type. + * @param fieldName The name of the field for which values are needed. + * @return A tuple of values for the given type, or {@code null} if none is available. + */ + @SuppressWarnings("unchecked") + public Tuple get(String fieldName) { + if (fieldName == null) { + return null; + } + return (Tuple) cache.get(fieldName); + } + + /** + * @param fieldName The name of the field for which values are needed. + * @return Whether values are available for the given field. + */ + public boolean contains(String fieldName) { + return cache.containsKey(fieldName); + } + + /** + * @return The fields preset in the cache. + */ + public Set getFieldNames() { + return new HashSet<>(cache.keySet()); + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldIterable.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldIterable.java index b50d3f57c..aa9ec66c6 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldIterable.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldIterable.java @@ -1,7 +1,6 @@ package nl.jqno.equalsverifier.internal.reflection; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -56,6 +55,17 @@ public static FieldIterable ofIgnoringStatic(Class type) { return new FieldIterable(type, true, false); } + /** + * Factory method for a FieldIterable that iterates over all declared fields of {@code type}, + * but that ignores its the declared fields of its superclasses, as well as its static fields. + * + * @param type The class that contains the fields over which to iterate. + * @return A FieldIterable. + */ + public static FieldIterable ofIgnoringSuperAndStatic(Class type) { + return new FieldIterable(type, false, false); + } + /** * Returns an iterator over all declared fields of the class and all of its superclasses. * @@ -90,7 +100,7 @@ private List addFieldsFor(Class c) { !"__cobertura_counters".equals(field.getName()) && !field.getName().startsWith("bitmap$init$") // Generated by Scala 2.x's -Xcheckinit flag ) { - boolean isStatic = Modifier.isStatic(field.getModifiers()); + boolean isStatic = FieldProbe.of(field).isStatic(); if (isStatic && includeStatic) { statics.add(field); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldMutator.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldMutator.java new file mode 100644 index 000000000..f27805935 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldMutator.java @@ -0,0 +1,39 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; + +import java.lang.reflect.Field; + +/** + * Allows for a field in an object reference to be set to another value. + */ +public class FieldMutator { + + private final FieldProbe probe; + private final Field field; + + /** + * Constructor. + * + * @param probe A field probe pointing to the field to mutate. + */ + public FieldMutator(FieldProbe probe) { + this.probe = probe; + this.field = probe.getField(); + } + + /** + * Assigns {@code newValue} to the current field in {@code object}. + * + * @param object The instance on which to re-assign the current field. + * @param newValue The value to assign to the field. + */ + public void setNewValue(Object object, Object newValue) { + rethrow(() -> { + if (probe.canBeModifiedReflectively()) { + field.setAccessible(true); + field.set(object, newValue); + } + }); + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldProbe.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldProbe.java new file mode 100644 index 000000000..56cd194b6 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldProbe.java @@ -0,0 +1,156 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations.*; +import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import nl.jqno.equalsverifier.Warning; +import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; +import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; +import nl.jqno.equalsverifier.internal.util.Configuration; + +/** + * Provides read-only reflective access to one field of an object. + */ +public final class FieldProbe { + + private final Field field; + + /** Private constructor. Call {@link #of(Field)} to instantiate. */ + private FieldProbe(Field field) { + this.field = field; + } + + /** + * Factory method. + * + * @param field The field to access. + * @return A {@link FieldProbe} for {@link #field}. + */ + public static FieldProbe of(Field field) { + return new FieldProbe(field); + } + + /** @return The field itself. */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Can't defensively copy a Field.") + public Field getField() { + return field; + } + + /** + * Tries to get the field's value. + * + * @param object The object that contains the field whose value we want to get. + * @return The field's value. + * @throws ReflectionException If the operation fails. + */ + @SuppressFBWarnings( + value = "DP_DO_INSIDE_DO_PRIVILEGED", + justification = "Only called in test code, not production." + ) + public Object getValue(Object object) { + field.setAccessible(true); + return rethrow(() -> field.get(object)); + } + + /** @return The field's type. */ + public Class getType() { + return field.getType(); + } + + /** @return The field's name. */ + public String getName() { + return field.getName(); + } + + /** @return Whether the field is of a primitive type. */ + public boolean isPrimitive() { + return getType().isPrimitive(); + } + + /** @return Whether the field is marked with the final modifier. */ + public boolean isFinal() { + return Modifier.isFinal(field.getModifiers()); + } + + /** @return Whether the field is marked with the static modifier. */ + public boolean isStatic() { + return Modifier.isStatic(field.getModifiers()); + } + + /** @return Whether the field is marked with the transient modifier. */ + public boolean isTransient() { + return Modifier.isTransient(field.getModifiers()); + } + + /** @return Whether the field is an enum with a single value. */ + public boolean isEmptyOrSingleValueEnum() { + Class type = field.getType(); + return type.isEnum() && type.getEnumConstants().length <= 1; + } + + /** + * @param config The configuration affects whether a given field van be default. + * @return Whether the field can be set to the default value for its type. + */ + public boolean canBeDefault(Configuration config) { + if (isPrimitive()) { + return !config.getPrefabbedFields().contains(getName()); + } + + boolean isAnnotated = isAnnotatedNonnull(config.getAnnotationCache()); + boolean isMentionedExplicitly = config.getNonnullFields().contains(field.getName()); + return ( + !config.getWarningsToSuppress().contains(Warning.NULL_FIELDS) && + !isAnnotated && + !isMentionedExplicitly + ); + } + + /** + * Checks whether the given field is marked with an Nonnull annotation, whether directly, or + * through some default annotation mechanism. + * + * @param annotationCache To retrieve annotations from. + * @return True if the field is to be treated as Nonnull. + */ + public boolean isAnnotatedNonnull(AnnotationCache annotationCache) { + Class type = field.getDeclaringClass(); + if (annotationCache.hasFieldAnnotation(type, field.getName(), NONNULL)) { + return true; + } + if (annotationCache.hasFieldAnnotation(type, field.getName(), NULLABLE)) { + return false; + } + boolean hasFindbugsAnnotation = annotationCache.hasClassAnnotation( + type, + FINDBUGS1X_DEFAULT_ANNOTATION_NONNULL + ); + boolean hasJsr305Annotation = annotationCache.hasClassAnnotation( + type, + JSR305_DEFAULT_ANNOTATION_NONNULL + ); + boolean hasDefaultAnnotation = annotationCache.hasClassAnnotation( + type, + DEFAULT_ANNOTATION_NONNULL + ); + return hasFindbugsAnnotation || hasJsr305Annotation || hasDefaultAnnotation; + } + + /** + * Determines whether the field can be modified using reflection. + * + * @return Whether or not the field can be modified reflectively. + */ + public boolean canBeModifiedReflectively() { + if (field.isSynthetic()) { + return false; + } + if (isFinal() && isStatic()) { + return false; + } + return true; + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessor.java deleted file mode 100644 index 63ce34a7e..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessor.java +++ /dev/null @@ -1,160 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import java.lang.reflect.Field; -import java.util.LinkedHashSet; -import java.util.function.Function; -import java.util.function.Predicate; -import nl.jqno.equalsverifier.internal.exceptions.ModuleException; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; - -/** - * Implementation of ObjectAccessor that modifies its wrapped object in-place through reflection. - * - * @param The specified object's class. - */ -final class InPlaceObjectAccessor extends ObjectAccessor { - - /** Package-private constructor. Call {@link ObjectAccessor#of(Object)} to instantiate. */ - /* default */InPlaceObjectAccessor(T object, Class type) { - super(object, type); - } - - /** {@inheritDoc} */ - @Override - public T copy() { - T copy = Instantiator.of(type()).instantiate(); - return copyInto(copy); - } - - /** {@inheritDoc} */ - @Override - public S copyIntoSubclass(Class subclass) { - S copy = Instantiator.of(subclass).instantiate(); - return copyInto(copy); - } - - /** {@inheritDoc} */ - @Override - public T copyIntoAnonymousSubclass() { - T copy = Instantiator.of(type()).instantiateAnonymousSubclass(); - return copyInto(copy); - } - - private S copyInto(S copy) { - for (Field field : FieldIterable.of(type())) { - fieldModifierFor(field).copyTo(copy); - } - return copy; - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor scramble( - PrefabValues prefabValues, - TypeTag enclosingType, - LinkedHashSet typeStack - ) { - return scrambleInternal(prefabValues, enclosingType, typeStack, FieldIterable::of); - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor shallowScramble(PrefabValues prefabValues, TypeTag enclosingType) { - return scrambleInternal( - prefabValues, - enclosingType, - new LinkedHashSet<>(), - FieldIterable::ofIgnoringSuper - ); - } - - private ObjectAccessor scrambleInternal( - PrefabValues prefabValues, - TypeTag enclosingType, - LinkedHashSet typeStack, - Function, FieldIterable> it - ) { - for (Field field : it.apply(type())) { - try { - fieldModifierFor(field).changeField(prefabValues, enclosingType, typeStack); - } catch (ModuleException e) { - handleInaccessibleObjectException(e.getCause(), field); - } catch (RuntimeException e) { - // InaccessibleObjectException is not yet available in Java 8 - if (e.getClass().getName().endsWith("InaccessibleObjectException")) { - handleInaccessibleObjectException(e, field); - } else { - throw e; - } - } - } - return this; - } - - private void handleInaccessibleObjectException(Throwable e, Field field) { - if (e.getMessage() != null && e.getMessage().contains(type().getCanonicalName())) { - throw new ModuleException( - "The class is not accessible via the Java Module system. Consider opening the module that contains it.", - e - ); - } else { - throw new ModuleException( - "Field " + - field.getName() + - " of type " + - field.getType().getName() + - " is not accessible via the Java Module System.\nConsider opening the module that contains it, or add prefab values for type " + - field.getType().getName() + - ".", - e - ); - } - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor clear( - Predicate canBeDefault, - PrefabValues prefabValues, - TypeTag enclosingType - ) { - for (Field field : FieldIterable.of(type())) { - FieldModifier modifier = fieldModifierFor(field); - modifier.defaultField(); - if (!canBeDefault.test(field)) { - modifier.changeField(prefabValues, enclosingType); - } - } - return this; - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor withDefaultedField(Field field) { - fieldModifierFor(field).defaultField(); - return this; - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor withChangedField( - Field field, - PrefabValues prefabValues, - TypeTag enclosingType - ) { - fieldModifierFor(field).changeField(prefabValues, enclosingType); - return this; - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor withFieldSetTo(Field field, Object newValue) { - fieldModifierFor(field).set(newValue); - return this; - } - - private FieldModifier fieldModifierFor(Field field) { - return FieldModifier.of(field, get()); - } -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java index f9d2472ee..894fd53c2 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java @@ -13,7 +13,7 @@ import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.dynamic.scaffold.TypeValidation; -import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper; +import org.objenesis.Objenesis; /** * Instantiates objects of a given class. @@ -33,10 +33,12 @@ public final class Instantiator { private static final String FALLBACK_PACKAGE_NAME = getPackageName(Instantiator.class); private final Class type; + private final Objenesis objenesis; /** Private constructor. Call {@link #of(Class)} to instantiate. */ - private Instantiator(Class type) { + private Instantiator(Class type, Objenesis objenesis) { this.type = type; + this.objenesis = objenesis; } /** @@ -44,17 +46,18 @@ private Instantiator(Class type) { * * @param The class on which {@link Instantiator} operates. * @param type The class on which {@link Instantiator} operates. Should be the same as T. + * @param objenesis To instantiate non-record classes. * @return An {@link Instantiator} for {@link #type}. */ - public static Instantiator of(Class type) { + public static Instantiator of(Class type, Objenesis objenesis) { if (SealedTypesHelper.isSealed(type)) { Class concrete = SealedTypesHelper.findInstantiableSubclass(type).get(); - return Instantiator.of(concrete); + return Instantiator.of(concrete, objenesis); } if (Modifier.isAbstract(type.getModifiers())) { - return new Instantiator<>(giveDynamicSubclass(type, "", b -> b)); + return new Instantiator<>(giveDynamicSubclass(type, "", b -> b), objenesis); } - return new Instantiator<>(type); + return new Instantiator<>(type, objenesis); } /** @@ -66,7 +69,7 @@ public static Instantiator of(Class type) { * @return An object of type T. */ public T instantiate() { - return ObjenesisWrapper.getObjenesis().newInstance(type); + return objenesis.newInstance(type); } /** @@ -75,8 +78,12 @@ public T instantiate() { * @return An instance of an anonymous subclass of T. */ public T instantiateAnonymousSubclass() { - Class proxyClass = giveDynamicSubclass(type, "", b -> b); - return ObjenesisWrapper.getObjenesis().newInstance(proxyClass); + Class proxyClass = giveDynamicSubclass(type); + return objenesis.newInstance(proxyClass); + } + + public static Class giveDynamicSubclass(Class superclass) { + return giveDynamicSubclass(superclass, "", b -> b); } @SuppressWarnings("unchecked") diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/JavaApiPrefabValues.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/JavaApiPrefabValues.java similarity index 97% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/JavaApiPrefabValues.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/JavaApiPrefabValues.java index abfe63efc..0105c4b26 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/JavaApiPrefabValues.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/JavaApiPrefabValues.java @@ -1,8 +1,8 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.*; import static nl.jqno.equalsverifier.internal.reflection.Util.classes; import static nl.jqno.equalsverifier.internal.reflection.Util.objects; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.*; import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -28,14 +28,14 @@ import java.util.concurrent.locks.StampedLock; import java.util.function.Supplier; import java.util.regex.Pattern; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.EnumMapFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.EnumSetFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.ExternalFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; -import nl.jqno.equalsverifier.internal.reflection.ConditionalInstantiator; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.EnumMapFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.EnumSetFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.ExternalFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; /** - * Creates instances of classes for use in a {@link PrefabValues} object. + * Creates instances of classes for use in a {@link VintageValueProvider} object. * *

Contains hand-made instances of well-known Java API classes that cannot be instantiated * dynamically because of an internal infinite recursion of types, or other issues. @@ -432,7 +432,7 @@ private void addSets() { addFactory(CopyOnWriteArraySet.class, collection(CopyOnWriteArraySet::new)); addFactory(HashSet.class, collection(HashSet::new)); addFactory(TreeSet.class, collection(() -> new TreeSet<>(OBJECT_COMPARATOR))); - addFactory(EnumSet.class, new EnumSetFactory<>(EnumSet::copyOf)); + addFactory(EnumSet.class, new EnumSetFactory<>(c -> EnumSet.copyOf(c))); addValues( BitSet.class, BitSet.valueOf(new byte[] { 0 }), diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ObjectAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ObjectAccessor.java deleted file mode 100644 index e3ec543fa..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/ObjectAccessor.java +++ /dev/null @@ -1,239 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import java.lang.reflect.Field; -import java.util.LinkedHashSet; -import java.util.function.Predicate; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; - -/** - * Wraps an object to provide access to it. ObjectAccessor can copy and scramble the wrapped object. - * - * @param The specified object's class. - */ -public abstract class ObjectAccessor { - - private final T object; - private final Class type; - - /** Package private constructor. Call {@link #of(Object)} to instantiate. */ - /* default */ObjectAccessor(T object, Class type) { - this.object = object; - this.type = type; - } - - /** - * Factory method. - * - * @param {@link #object}'s type. - * @param object The object to wrap. - * @return An {@link ObjectAccessor} for {@link #object}. - */ - public static ObjectAccessor of(T object) { - @SuppressWarnings("unchecked") - Class type = (Class) object.getClass(); - return of(object, type); - } - - /** - * Factory method. - * - * @param {@link #object}'s type, or a supertype. - * @param object The object to wrap. - * @param type Superclass of {@link #object}'s type, as which it will be treated by {@link - * ObjectAccessor}. - * @return An {@link ObjectAccessor} for {@link #object}. - */ - public static ObjectAccessor of(T object, Class type) { - if (RecordsHelper.isRecord(type)) { - return new RecordObjectAccessor(object, type); - } - return new InPlaceObjectAccessor<>(object, type); - } - - /** - * Returns the wrapped object. - * - * @return The wrapped object. - */ - public T get() { - return object; - } - - /** - * Returns the type of the object. - * - * @return The type of the object. - */ - public Class type() { - return type; - } - - /** - * Returns the value of the given field. - * - * @param field The field whose value we want to get. - * @return The value of the given field. - */ - @SuppressWarnings("unchecked") - public T getField(Field field) { - return (T) FieldAccessor.of(field).get(object); - } - - /** - * Creates a copy of the wrapped object. - * - *

Note: it does a "shallow" copy. Reference fields are not copied recursively. - * - * @return A shallow copy. - */ - public abstract T copy(); - - /** - * Creates a copy of the wrapped object, where the copy's type is a specified subclass of the - * wrapped object's class. - * - *

Note: it does a "shallow" copy. Reference fields are not copied recursively. - * - * @param subclass A subclass of the wrapped object's class. - * @param The subclass. - * @return A shallow copy. - */ - public abstract S copyIntoSubclass(Class subclass); - - /** - * Creates a copy of the wrapped object, where the copy type is an anonymous subclass of the - * wrapped object's class. - * - *

Note: it does a "shallow" copy. Reference fields are not copied recursively. - * - * @return A shallow copy. - */ - public abstract T copyIntoAnonymousSubclass(); - - /** - * Modifies all fields of the wrapped object that are declared in T and in its superclasses. It - * may or may not mutate the object of the current ObjectAccessor. Either way, the current - * ObjectAccessor and any reference to its object should be considered 'spent' after calling - * this method. The returned ObjectAccessor can safely be used. - * - *

This method is consistent: given two equal objects; after scrambling both objects, they - * remain equal to each other. - * - *

It may not be able to modify: 1. static final fields, and 2. final fields that are - * initialized to a compile-time constant in the field declaration. These fields may be left - * unmodified. - * - * @param prefabValues Prefabricated values to take values from. - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @param typeStack Keeps track of recursion in the type. - * @return An accessor to the scrambled object. - */ - public abstract ObjectAccessor scramble( - PrefabValues prefabValues, - TypeTag enclosingType, - LinkedHashSet typeStack - ); - - /** - * Modifies all fields of the wrapped object that are declared in T, but not those inherited - * from superclasses. It may or may not mutate the object of the current ObjectAccessor. Either - * way, the current ObjectAccessor and any reference to its object should be considered 'spent' - * after calling this method. The returned ObjectAccessor can safely be used. - * - *

This method is consistent: given two equal objects; after scrambling both objects, they - * remain equal to each other. - * - *

It may not be able to modify: 1. static final fields, and 2. final fields that are - * initialized to a compile-time constant in the field declaration. These fields may be left - * unmodified. - * - * @param prefabValues Prefabricated values to take values from. - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @return An accessor to the scrambled object. - */ - public abstract ObjectAccessor shallowScramble( - PrefabValues prefabValues, - TypeTag enclosingType - ); - - /** - * Clears all fields of the wrapped object to their default values, but only if {@code - * canBeDefault} for the given field returns true. Otherwise, leaves the value intact. It may or - * may not mutate the object of the current ObjectAccessor. Either way, the current - * ObjectAccessor and any reference to its object should be considered 'spent' after calling - * this method. The returned ObjectAccessor can safely be used. - * - *

It may not be able to modify: 1. static final fields, and 2. final fields that are - * initialized to a compile-time constant in the field declaration. These fields may be left - * unmodified. - * - * @param canBeDefault A predicate that determines for the wrapped object's fields whether or - * not they are allowed to be 'default', i.e. 0 or null. If a field is marked with @NonNull, - * for example, it may not be default. - * @param prefabValues Prefabricated values to take values from. - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @return An accessor to the cleared object. - */ - public abstract ObjectAccessor clear( - Predicate canBeDefault, - PrefabValues prefabValues, - TypeTag enclosingType - ); - - /** - * Clears the given field of the wrapped object to its default value. It may or may not mutate - * the object of the current ObjectAccessor. Either way, the current ObjectAccessor and any - * reference to its object should be considered 'spent' after calling this method. The returned - * ObjectAccessor can safely be used. - * - *

It may not be able to modify: 1. static final fields, and 2. final fields that are - * initialized to a compile-time constant in the field declaration. These fields may be left - * unmodified. - * - * @param field The field to set to its default value. - * @return An accessor to the object with the defaulted field. - */ - public abstract ObjectAccessor withDefaultedField(Field field); - - /** - * Changes the given field of the wrapped object to some unspecified, but different value. It - * may or may not mutate the object of the current ObjectAccessor. Either way, the current - * ObjectAccessor and any reference to its object should be considered 'spent' after calling - * this method. The returned ObjectAccessor can safely be used. - * - *

It may not be able to modify: 1. static final fields, and 2. final fields that are - * initialized to a compile-time constant in the field declaration. These fields may be left - * unmodified. - * - * @param field The field to set to a different value. - * @param prefabValues Prefabricated values to take values from. - * @param enclosingType Describes the type that contains this object as a field, to determine - * any generic parameters it may contain. - * @return An accessor to the object with the changed field. - */ - public abstract ObjectAccessor withChangedField( - Field field, - PrefabValues prefabValues, - TypeTag enclosingType - ); - - /** - * Changes the given field of the wrapped object to the given value. It may or may not mutate - * the object of the current ObjectAccessor. Either way, the current ObjectAccessor and any - * reference to its object should be considered 'spent' after calling this method. The returned - * ObjectAccessor can safely be used. - * - *

It may not be able to modify: 1. static final fields, and 2. final fields that are - * initialized to a compile-time constant in the field declaration. These fields may be left - * unmodified. - * - * @param field The field to set to the given value. - * @param newValue The value to set the field to. - * @return An accessor to the object with the defaulted field. - */ - public abstract ObjectAccessor withFieldSetTo(Field field, Object newValue); -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessor.java deleted file mode 100644 index 600586fad..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessor.java +++ /dev/null @@ -1,190 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import nl.jqno.equalsverifier.internal.exceptions.EqualsVerifierInternalBugException; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.util.PrimitiveMappers; - -/** - * Implementation of ObjectAccessor that returns modified copies of its wrapped object, through - * calling its class's constructor. This only works when the constructor matches the class's fields - * exactly, such as in a record. - * - * @param The specified object's class. - */ -final class RecordObjectAccessor extends ObjectAccessor { - - private final Constructor constructor; - - /** Package-private constructor. Call {@link ObjectAccessor#of(Object)} to instantiate. */ - /* default */RecordObjectAccessor(T object, Class type) { - super(object, type); - this.constructor = getRecordConstructor(); - } - - /** {@inheritDoc} */ - @Override - public T copy() { - List params = fields().map(this::getField).collect(Collectors.toList()); - return callRecordConstructor(params); - } - - /** {@inheritDoc} */ - @Override - public S copyIntoSubclass(Class subclass) { - throw new EqualsVerifierInternalBugException( - "Can't copy a record into a subclass of itself." - ); - } - - /** {@inheritDoc} */ - @Override - public T copyIntoAnonymousSubclass() { - throw new EqualsVerifierInternalBugException( - "Can't copy a record into an anonymous subclass of itself." - ); - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor scramble( - PrefabValues prefabValues, - TypeTag enclosingType, - LinkedHashSet typeStack - ) { - return makeAccessor(f -> { - Object value = getField(f); - TypeTag tag = TypeTag.of(f, enclosingType); - return prefabValues.giveOther(tag, value, typeStack); - }); - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor shallowScramble(PrefabValues prefabValues, TypeTag enclosingType) { - throw new EqualsVerifierInternalBugException("Record: can't shallow-scramble a record."); - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor clear( - Predicate canBeDefault, - PrefabValues prefabValues, - TypeTag enclosingType - ) { - return makeAccessor(f -> - canBeDefault.test(f) - ? PrimitiveMappers.DEFAULT_VALUE_MAPPER.get(f.getType()) - : prefabValues.giveRed(TypeTag.of(f, enclosingType)) - ); - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor withDefaultedField(Field field) { - return modify(field, PrimitiveMappers.DEFAULT_VALUE_MAPPER.get(field.getType())); - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor withChangedField( - Field field, - PrefabValues prefabValues, - TypeTag enclosingType - ) { - TypeTag tag = TypeTag.of(field, enclosingType); - Object currentValue = getField(field); - Object newValue = prefabValues.giveOther(tag, currentValue); - return modify(field, newValue); - } - - /** {@inheritDoc} */ - @Override - public ObjectAccessor withFieldSetTo(Field field, Object newValue) { - return modify(field, newValue); - } - - private ObjectAccessor modify(Field field, Object value) { - return makeAccessor(f -> f.equals(field) ? value : getField(f)); - } - - private ObjectAccessor makeAccessor(Function determineValue) { - List params = fields().map(determineValue).collect(Collectors.toList()); - T newObject = callRecordConstructor(params); - return ObjectAccessor.of(newObject); - } - - private Stream fields() { - return StreamSupport.stream(FieldIterable.ofIgnoringStatic(type()).spliterator(), false); - } - - private Constructor getRecordConstructor() { - return rethrow(() -> { - List> constructorTypes = fields() - .map(Field::getType) - .collect(Collectors.toList()); - Constructor result = type() - .getDeclaredConstructor(constructorTypes.toArray(new Class[0])); - result.setAccessible(true); - return result; - }); - } - - private T callRecordConstructor(List params) { - return rethrow( - () -> constructor.newInstance(params.toArray(new Object[0])), - e -> buildMessage(e, constructor.getDeclaringClass(), params) - ); - } - - private String buildMessage(Throwable e, Class type, List params) { - String prefix = - "Record: failed to run constructor for record type " + - type.getName() + - "\n These were the values passed to the constructor: " + - params; - - if (e.getCause() instanceof NullPointerException) { - return ( - prefix + - "\n If the record does not accept null values for its constructor parameters," + - " consider suppressing Warning.NULL_FIELDS." - ); - } - - boolean hasZeros = false; - boolean hasSomethingElse = false; - for (Object p : params) { - if (PrimitiveMappers.ZEROS.contains(p)) { - hasZeros = true; - } else { - // nulls are already eliminated - hasSomethingElse = true; - } - } - - String msg = prefix; - if (hasZeros) { - msg += - "\n If the record does not accept 0 or false as a value for its constructor parameters," + - " consider suppressing Warning.ZERO_FIELDS."; - } - if (hasSomethingElse) { - msg += - "\n If the record does not accept any of the given values for its constructor parameters," + - " consider providing prefab values for the types of those fields."; - } - return msg; - } -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordProbe.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordProbe.java new file mode 100644 index 000000000..4cbaecbf7 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordProbe.java @@ -0,0 +1,85 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import nl.jqno.equalsverifier.internal.util.PrimitiveMappers; + +public class RecordProbe { + + private final Class type; + + public RecordProbe(Class type) { + this.type = type; + } + + public Stream fields() { + return StreamSupport.stream(FieldIterable.ofIgnoringStatic(type).spliterator(), false); + } + + public T callRecordConstructor(List params) { + Constructor constructor = getRecordConstructor(); + return rethrow( + () -> constructor.newInstance(params.toArray(new Object[0])), + e -> buildMessage(e, params) + ); + } + + private Constructor getRecordConstructor() { + return rethrow(() -> { + List> constructorTypes = fields() + .map(Field::getType) + .collect(Collectors.toList()); + Constructor result = type.getDeclaredConstructor( + constructorTypes.toArray(new Class[0]) + ); + result.setAccessible(true); + return result; + }); + } + + private String buildMessage(Throwable e, List params) { + String prefix = + "Record: failed to run constructor for record type " + + type.getName() + + "\n These were the values passed to the constructor: " + + params; + + if (e.getCause() instanceof NullPointerException) { + return ( + prefix + + "\n If the record does not accept null values for its constructor parameters," + + " consider suppressing Warning.NULL_FIELDS." + ); + } + + boolean hasZeros = false; + boolean hasSomethingElse = false; + for (Object p : params) { + if (PrimitiveMappers.ZEROS.contains(p)) { + hasZeros = true; + } else { + // nulls are already eliminated + hasSomethingElse = true; + } + } + + String msg = prefix; + if (hasZeros) { + msg += + "\n If the record does not accept 0 or false as a value for its constructor parameters," + + " consider suppressing Warning.ZERO_FIELDS."; + } + if (hasSomethingElse) { + msg += + "\n If the record does not accept any of the given values for its constructor parameters," + + " consider providing prefab values for the types of those fields."; + } + return msg; + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/Tuple.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Tuple.java similarity index 97% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/Tuple.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Tuple.java index 3eb85fbae..c276f3fac 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/Tuple.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Tuple.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; /** * Container for three values of the same type: a "red" one, a "blue" one, and a shallow copy of the diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTag.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/TypeTag.java similarity index 99% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTag.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/TypeTag.java index a8bf32662..09b30a6e8 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTag.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/TypeTag.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; import static nl.jqno.equalsverifier.internal.reflection.Util.classForName; diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/NonnullAnnotationVerifier.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/NonnullAnnotationVerifier.java deleted file mode 100644 index f51469ffb..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/NonnullAnnotationVerifier.java +++ /dev/null @@ -1,35 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection.annotations; - -import static nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations.*; - -import java.lang.reflect.Field; - -/** Utility class that checks whether a field is marked with an Nonnull annotation of some sort. */ -public final class NonnullAnnotationVerifier { - - private NonnullAnnotationVerifier() {} - - /** - * Checks whether the given field is marked with an Nonnull annotation, whether directly, or - * through some default annotation mechanism. - * - * @param field The field to be checked. - * @param annotationCache To provide access to the annotations on the field and the field's - * class - * @return True if the field is to be treated as Nonnull. - */ - public static boolean fieldIsNonnull(Field field, AnnotationCache annotationCache) { - Class type = field.getDeclaringClass(); - if (annotationCache.hasFieldAnnotation(type, field.getName(), NONNULL)) { - return true; - } - if (annotationCache.hasFieldAnnotation(type, field.getName(), NULLABLE)) { - return false; - } - return ( - annotationCache.hasClassAnnotation(type, FINDBUGS1X_DEFAULT_ANNOTATION_NONNULL) || - annotationCache.hasClassAnnotation(type, JSR305_DEFAULT_ANNOTATION_NONNULL) || - annotationCache.hasClassAnnotation(type, DEFAULT_ANNOTATION_NONNULL) - ); - } -} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/InstanceCreator.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/InstanceCreator.java new file mode 100644 index 000000000..b26966398 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/InstanceCreator.java @@ -0,0 +1,88 @@ +package nl.jqno.equalsverifier.internal.reflection.instantiation; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.BiConsumer; +import nl.jqno.equalsverifier.internal.reflection.*; +import nl.jqno.equalsverifier.internal.util.PrimitiveMappers; +import org.objenesis.Objenesis; + +/** + * Creates an instance of a class or record. + */ +public class InstanceCreator { + + private final Class type; + private final ClassProbe probe; + private final Objenesis objenesis; + + /** + * Constructor. + * + * @param probe Represents the class to instantiate. + * @param objenesis To instantiate non-record classes. + */ + public InstanceCreator(ClassProbe probe, Objenesis objenesis) { + this.type = probe.getType(); + this.probe = probe; + this.objenesis = objenesis; + } + + /** + * Creates an instance of the given type, with its field set to the given values. If no value + * is given for a specific field, the field will be set to its default value: null for object + * references, 0 for numbers, false for booleans. + * + * @param values Values to assign to the instance's fields. + * @return An instance with assigned values. + */ + public T instantiate(Map values) { + return probe.isRecord() ? createRecordInstance(values) : createClassInstance(values); + } + + /** + * Creates a new instance with all fields set to the same value as their counterparts from + * {@code original}. + * + * @param original The instance to copy. + * @return A copy of the given original. + */ + public T copy(Object original) { + Map values = new HashMap<>(); + for (Field f : fields(original.getClass())) { + Object value = FieldProbe.of(f).getValue(original); + values.put(f, value); + } + return instantiate(values); + } + + private T createRecordInstance(Map values) { + List params = new ArrayList<>(); + traverseFields(values, (f, v) -> params.add(v)); + RecordProbe recordProbe = new RecordProbe<>(type); + return recordProbe.callRecordConstructor(params); + } + + private T createClassInstance(Map values) { + T instance = Instantiator.of(type, objenesis).instantiate(); + traverseFields( + values, + (f, v) -> new FieldMutator(FieldProbe.of(f)).setNewValue(instance, v) + ); + return instance; + } + + private void traverseFields(Map values, BiConsumer setValue) { + for (Field f : fields(type)) { + Object value = values.get(f); + if (value == null) { + value = PrimitiveMappers.DEFAULT_VALUE_MAPPER.get(f.getType()); + } + setValue.accept(f, value); + } + } + + private FieldIterable fields(Class typeWithFields) { + return FieldIterable.ofIgnoringStatic(typeWithFields); + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/SubjectCreator.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/SubjectCreator.java new file mode 100644 index 000000000..f77ca14a0 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/SubjectCreator.java @@ -0,0 +1,263 @@ +package nl.jqno.equalsverifier.internal.reflection.instantiation; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import nl.jqno.equalsverifier.internal.exceptions.ModuleException; +import nl.jqno.equalsverifier.internal.reflection.*; +import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.Rethrow; +import org.objenesis.Objenesis; + +/** + * Creates a subject, i.e. an instance of the class that is currently being tested by + * EqualsVerifier. + */ +public class SubjectCreator { + + private final TypeTag typeTag; + private final Class type; + private final Configuration config; + private final ValueProvider valueProvider; + private final ClassProbe classProbe; + private final FieldCache fieldCache; + private final Objenesis objenesis; + + /** + * Constructor. + * + * @param config A configuration object. + * @param valueProvider To provide values for the fields of the subject. + * @param fieldCache Prepared values for the fields of the subject. + * @param objenesis Needed by InstanceCreator to instantiate non-record classes. + */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "A cache is inherently mutable") + public SubjectCreator( + Configuration config, + ValueProvider valueProvider, + FieldCache fieldCache, + Objenesis objenesis + ) { + this.typeTag = config.getTypeTag(); + this.type = typeTag.getType(); + this.config = config; + this.valueProvider = valueProvider; + this.classProbe = new ClassProbe<>(type); + this.fieldCache = fieldCache; + this.objenesis = objenesis; + } + + /** + * Creates an instance with all values set with prefab values assigned to their respective field + * names or types, or, if no prefab values are given, values created by the + * {@link ValueProvider}. + * + * @return A plain instance. + */ + public T plain() { + return createInstance(empty()); + } + + /** + * Creates a {@link #plain()} instance, but with the given field set to its type's default + * value: null for object references, 0 for numbers, false for booleans. + * + * @param field The field to default. + * @return A plain instance with a field defaulted. + */ + public T withFieldDefaulted(Field field) { + return createInstance(with(field, null)); + } + + /** + * Creates an instance with all values set to their type's default value: null for object + * references, 0 for numbers, false for booleans. + * + * @return An instance with all fields defaulted. + */ + public T withAllFieldsDefaulted() { + Map values = empty(); + for (Field f : fields()) { + values.put(f, null); + } + return createInstance(values); + } + + /** + * Creates an instance with all values set to their type's default value: null for object + * references, 0 for numbers, false for booleans; except for the given field which is set + * to its {@link #plain()} value. + * + * @param field The field that should not be defaulted. + * @return An instance with all fields defaulted except for {@code field}. + */ + public T withAllFieldsDefaultedExcept(Field field) { + Map values = empty(); + for (Field f : fields()) { + if (!f.equals(field)) { + values.put(f, null); + } + } + return createInstance(values); + } + + /** + * Creates a {@link #plain()} instance, but with the given field set to the given value. + * + * @param field The field to assign the given value. + * @param value The value to assign to the given field. + * @return A plain instance with one field assigned the given value. + */ + public T withFieldSetTo(Field field, Object value) { + return createInstance(with(field, value)); + } + + /** + * Creates a {@link #plain()} instance, but with the given field set to another value. + * + * @param field The field to change. + * @return A plain instance with a field changed. + */ + public T withFieldChanged(Field field) { + if (FieldProbe.of(field).isStatic()) { + return plain(); + } + Object value = valuesFor(field).getBlue(); + return createInstance(with(field, value)); + } + + /** + * Creates a {@link #plain()} instance, but with all fields set to another value. + * + * @return A plain instance with all fields changed. + */ + public T withAllFieldsChanged() { + Map values = empty(); + for (Field f : fields()) { + Object value = valuesFor(f).getBlue(); + values.put(f, value); + } + return createInstance(values); + } + + /** + * Creates a {@link #plain()} instance, but with all fields that are declared in the current + * class set to another value. Fields coming from the superclasses get their {@link #plain()} + * value. + * + * @return A plain instance with all non-inherited fields changed. + */ + public T withAllFieldsShallowlyChanged() { + Map values = empty(); + for (Field f : nonSuperFields()) { + Object value = valuesFor(f).getBlue(); + values.put(f, value); + } + return createInstance(values); + } + + /** + * Creates a new instance with all fields set to the same value as their counterparts from + * {@code original}. + * + * @param original The instance to copy. + * @return A copy of the given original. + */ + public T copy(T original) { + InstanceCreator instanceCreator = new InstanceCreator<>(classProbe, objenesis); + return Rethrow.rethrow(() -> instanceCreator.copy(original)); + } + + /** + * Creates a new instance of the superclass of the current class, with all fields that exist + * within that superclass set to the same value as their counterparts from {@code original}. + * + * @param original The instance to copy. + * @return An instance of the givenoriginal's superclass, but otherwise a copy of the original. + */ + public Object copyIntoSuperclass(T original) { + InstanceCreator superCreator = new InstanceCreator<>( + new ClassProbe<>(type.getSuperclass()), + objenesis + ); + return superCreator.copy(original); + } + + /** + * Creates a new instance of the given subclass of the current class, with all fields that also + * exist in the current class set to the same value as their counterparts from + * {@code original}. All fields declared in the subclass are set to their {@link #plain()} + * values. + * + * @param A subtype of original's type. + * @param original The instance to copy. + * @param subType A subtype of original's type. + * @return An instance of the given subType, but otherwise a copy of the given original. + */ + public S copyIntoSubclass(T original, Class subType) { + InstanceCreator subCreator = new InstanceCreator<>(new ClassProbe<>(subType), objenesis); + return subCreator.copy(original); + } + + private T createInstance(Map givens) { + Map values = determineValues(givens); + InstanceCreator instanceCreator = new InstanceCreator<>(classProbe, objenesis); + return Rethrow.rethrow(() -> instanceCreator.instantiate(values)); + } + + private Map determineValues(Map givens) { + Map values = new HashMap<>(givens); + for (Field f : fields()) { + boolean fieldIsAbsent = !values.containsKey(f); + boolean fieldCannotBeNull = + values.get(f) == null && !FieldProbe.of(f).canBeDefault(config); + if (fieldIsAbsent || fieldCannotBeNull) { + Object value = valuesFor(f).getRed(); + values.put(f, value); + } + } + return values; + } + + private Map empty() { + return new HashMap<>(); + } + + private Map with(Field f, Object v) { + Map result = empty(); + result.put(f, v); + return result; + } + + private FieldIterable fields() { + return FieldIterable.ofIgnoringStatic(type); + } + + private FieldIterable nonSuperFields() { + return FieldIterable.ofIgnoringSuperAndStatic(type); + } + + private Tuple valuesFor(Field f) { + String fieldName = f.getName(); + if (fieldCache.contains(fieldName)) { + return fieldCache.get(fieldName); + } + try { + Tuple tuple = valueProvider.provide(TypeTag.of(f, typeTag)); + fieldCache.put(fieldName, tuple); + return tuple; + } catch (ModuleException e) { + throw new ModuleException( + "Field " + + f.getName() + + " of type " + + f.getType().getName() + + " is not accessible via the Java Module System.\nConsider opening the module that contains it, or add prefab values for type " + + f.getType().getName() + + ".", + e + ); + } + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/ValueProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/ValueProvider.java new file mode 100644 index 000000000..eef125f5c --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/ValueProvider.java @@ -0,0 +1,24 @@ +package nl.jqno.equalsverifier.internal.reflection.instantiation; + +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; + +/** + * Creator of prefabricated instances of classes. + * + *

These instances are intended to be used to populate a subject, i.e. the class that is + * currently being tested by EqualsVerifier. + * + *

Only creates values ones, and caches them once they've been created. Takes generics into + * account; i.e., {@code List} is different from {@code List}. + */ +public interface ValueProvider { + /** + * Returns a tuple of two different prefabricated values of the specified type. + * + * @param The returned tuple will have this generic type. + * @param tag A description of the desired type, including generic parameters. + * @return A tuple of two different values of the given type. + */ + Tuple provide(TypeTag tag); +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValues.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProvider.java similarity index 68% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValues.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProvider.java index dde09c903..4af2fbd1b 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValues.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProvider.java @@ -1,34 +1,49 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection.instantiation; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.Arrays; -import java.util.LinkedHashSet; +import java.util.*; import nl.jqno.equalsverifier.internal.exceptions.RecursionException; import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.FallbackFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.FallbackFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; import nl.jqno.equalsverifier.internal.util.PrimitiveMappers; +import nl.jqno.equalsverifier.internal.util.Rethrow; +import org.objenesis.Objenesis; /** - * Container and creator of prefabricated instances of objects and classes. + * Creator of prefabricated instances of classes, using a "vintage" strategy for doing so. * - *

Only creates values ones, and caches them once they've been created. Takes generics into - * account; i.e., {@code List} is different from {@code List}. + * Vintage in this case means that it employs the creation strategy that EqualsVerifier has been + * using since its inception. This strategy is quite hacky and messy, and other strategies might + * be preferable. */ -public class PrefabValues { +public class VintageValueProvider implements ValueProvider { + + // I'd like to remove this, but that affects recursion detection it a way I can't yet explain + private final Map> valueCache = new HashMap<>(); - private final Cache cache = new Cache(); private final FactoryCache factoryCache; - private final PrefabValueFactory fallbackFactory = new FallbackFactory<>(); + private final PrefabValueFactory fallbackFactory; /** * Constructor. * * @param factoryCache The factories that can be used to create values. + * @param objenesis To instantiate non-record classes. */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "A cache is inherently mutable.") - public PrefabValues(FactoryCache factoryCache) { + public VintageValueProvider(FactoryCache factoryCache, Objenesis objenesis) { this.factoryCache = factoryCache; + this.fallbackFactory = new FallbackFactory<>(objenesis); + } + + /** {@inheritDoc} */ + @Override + public Tuple provide(TypeTag tag) { + return Rethrow.rethrow(() -> giveTuple(tag)); } /** @@ -70,43 +85,6 @@ public T giveRedCopy(TypeTag tag) { return this.giveTuple(tag).getRedCopy(); } - /** - * Returns a tuple of two different prefabricated values of the specified type. - * - * @param The returned tuple will have this generic type. - * @param tag A description of the desired type, including generic parameters. - * @return A tuple of two different values of the given type. - */ - public Tuple giveTuple(TypeTag tag) { - return giveTuple(tag, new LinkedHashSet<>()); - } - - /** - * Returns a tuple of two different prefabricated values of the specified type. - * - * @param The returned tuple will have this generic type. - * @param tag A description of the desired type, including generic parameters. - * @param typeStack Keeps track of recursion in the type. - * @return A tuple of two different values of the given type. - */ - public Tuple giveTuple(TypeTag tag, LinkedHashSet typeStack) { - realizeCacheFor(tag, typeStack); - return cache.getTuple(tag); - } - - /** - * Returns a prefabricated value of the specified type, that is different from the specified - * value. - * - * @param The type of the value. - * @param tag A description of the desired type, including generic parameters. - * @param value A value that is different from the value that will be returned. - * @return A value that is different from {@code value}. - */ - public T giveOther(TypeTag tag, T value) { - return giveOther(tag, value, new LinkedHashSet<>()); - } - /** * Returns a prefabricated value of the specified type, that is different from the specified * value. @@ -168,12 +146,22 @@ private boolean arraysAreDeeplyEqual(Object x, Object y) { * @param typeStack Keeps track of recursion in the type. */ public void realizeCacheFor(TypeTag tag, LinkedHashSet typeStack) { - if (!cache.contains(tag)) { + if (!valueCache.containsKey(tag)) { Tuple tuple = createTuple(tag, typeStack); - addToCache(tag, tuple); + valueCache.put(tag, tuple); } } + private Tuple giveTuple(TypeTag tag) { + return giveTuple(tag, new LinkedHashSet<>()); + } + + @SuppressWarnings("unchecked") + private Tuple giveTuple(TypeTag tag, LinkedHashSet typeStack) { + realizeCacheFor(tag, typeStack); + return (Tuple) valueCache.get(tag); + } + private Tuple createTuple(TypeTag tag, LinkedHashSet typeStack) { if (typeStack.contains(tag)) { throw new RecursionException(typeStack); @@ -189,8 +177,4 @@ private Tuple createTuple(TypeTag tag, LinkedHashSet typeStack) Tuple result = (Tuple) fallbackFactory.createValues(tag, this, typeStack); return result; } - - private void addToCache(TypeTag tag, Tuple tuple) { - cache.put(tag, tuple.getRed(), tuple.getBlue(), tuple.getRedCopy()); - } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/ClassAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/ClassAccessor.java new file mode 100644 index 000000000..e19fde95d --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/ClassAccessor.java @@ -0,0 +1,108 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import java.util.LinkedHashSet; +import nl.jqno.equalsverifier.internal.reflection.Instantiator; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import org.objenesis.Objenesis; + +/** + * Instantiates and populates objects of a given class. {@link ClassAccessor} can create two + * different instances of T, which are guaranteed not to be equal to each other, and which contain + * no null values. + * + * @param A class. + */ +public class ClassAccessor { + + private final Class type; + private final VintageValueProvider valueProvider; + private final Objenesis objenesis; + + /** Private constructor. Call {@link #of(Class, PrefabValues)} instead. */ + ClassAccessor(Class type, VintageValueProvider valueProvider, Objenesis objenesis) { + this.type = type; + this.valueProvider = valueProvider; + this.objenesis = objenesis; + } + + /** + * Factory method. + * + * @param The class on which {@link ClassAccessor} operates. + * @param type The class on which {@link ClassAccessor} operates. Should be the same as T. + * @param valueProvider Prefabricated values with which to fill instantiated objects. + * @param objenesis To instantiate non-record classes. + * @return A {@link ClassAccessor} for T. + */ + public static ClassAccessor of( + Class type, + VintageValueProvider valueProvider, + Objenesis objenesis + ) { + return new ClassAccessor<>(type, valueProvider, objenesis); + } + + /** + * Returns an instance of T that is not equal to the instance of T returned by {@link + * #getBlueObject(TypeTag, LinkedHashSet)}. + * + * @param enclosingType Describes the type that contains this object as a field, to determine + * any generic parameters it may contain. + * @param typeStack Keeps track of recursion in the type. + * @return An instance of T. + */ + public T getRedObject(TypeTag enclosingType, LinkedHashSet typeStack) { + return getRedAccessor(enclosingType, typeStack).get(); + } + + /** + * Returns an {@link ObjectAccessor} for {@link #getRedObject(TypeTag, LinkedHashSet)}. + * + * @param enclosingType Describes the type that contains this object as a field, to determine + * any generic parameters it may contain. + * @param typeStack Keeps track of recursion in the type. + * @return An {@link ObjectAccessor} for {@link #getRedObject}. + */ + public ObjectAccessor getRedAccessor( + TypeTag enclosingType, + LinkedHashSet typeStack + ) { + return buildObjectAccessor().scramble(valueProvider, enclosingType, typeStack); + } + + /** + * Returns an instance of T that is not equal to the instance of T returned by {@link + * #getRedObject(TypeTag, LinkedHashSet)}. + * + * @param enclosingType Describes the type that contains this object as a field, to determine + * any generic parameters it may contain. + * @param typeStack Keeps track of recursion in the type. + * @return An instance of T. + */ + public T getBlueObject(TypeTag enclosingType, LinkedHashSet typeStack) { + return getBlueAccessor(enclosingType, typeStack).get(); + } + + /** + * Returns an {@link ObjectAccessor} for {@link #getBlueObject(TypeTag, LinkedHashSet)}. + * + * @param enclosingType Describes the type that contains this object as a field, to determine + * any generic parameters it may contain. + * @param typeStack Keeps track of recursion in the type. + * @return An {@link ObjectAccessor} for {@link #getBlueObject(TypeTag, LinkedHashSet)}. + */ + public ObjectAccessor getBlueAccessor( + TypeTag enclosingType, + LinkedHashSet typeStack + ) { + return buildObjectAccessor() + .scramble(valueProvider, enclosingType, typeStack) + .scramble(valueProvider, enclosingType, typeStack); + } + + private ObjectAccessor buildObjectAccessor() { + T object = Instantiator.of(type, objenesis).instantiate(); + return ObjectAccessor.of(object); + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldModifier.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/FieldModifier.java similarity index 55% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldModifier.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/FieldModifier.java index 8dd428481..0b91e5f87 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldModifier.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/FieldModifier.java @@ -1,13 +1,13 @@ -package nl.jqno.equalsverifier.internal.reflection; +package nl.jqno.equalsverifier.internal.reflection.vintage; import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow; import java.lang.reflect.Field; import java.util.LinkedHashSet; import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.util.PrimitiveMappers; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; public final class FieldModifier { @@ -31,43 +31,6 @@ public static FieldModifier of(Field field, Object object) { return new FieldModifier(field, object); } - /** - * Tries to set the field to the specified value. - * - *

Includes static fields but ignores fields that can't be modified reflectively. - * - * @param value The value that the field should get. - * @throws ReflectionException If the operation fails. - */ - public void set(Object value) { - change(() -> field.set(object, value), true); - } - - /** - * Tries to make the field null. Ignores static fields and fields that can't be modified - * reflectively. - * - * @throws ReflectionException If the operation fails. - */ - public void defaultField() { - change(this::setFieldToDefault, false); - } - - /** - * Tries to make the field null. Includes static fields but ignores fields that can't be - * modified reflectively. - * - * @throws ReflectionException If the operation fails. - */ - public void defaultStaticField() { - change(this::setFieldToDefault, true); - } - - private void setFieldToDefault() throws IllegalAccessException { - Class type = field.getType(); - field.set(object, PrimitiveMappers.DEFAULT_VALUE_MAPPER.get(type)); - } - /** * Copies field's value to the corresponding field in the specified object. * @@ -86,23 +49,7 @@ public void copyTo(Object to) { * *

Ignores static fields and fields that can't be modified reflectively. * - * @param prefabValues If the field is of a type contained within prefabValues, the new value - * will be taken from it. - * @param enclosingType A tag for the type that contains the field. Needed to determine a - * generic type, if it has one.. - * @throws ReflectionException If the operation fails. - */ - public void changeField(PrefabValues prefabValues, TypeTag enclosingType) { - changeField(prefabValues, enclosingType, new LinkedHashSet<>()); - } - - /** - * Changes the field's value to something else. The new value will never be null. Other than - * that, the precise value is undefined. - * - *

Ignores static fields and fields that can't be modified reflectively. - * - * @param prefabValues If the field is of a type contained within prefabValues, the new value + * @param valueProvider If the field is of a type contained within prefabValues, the new value * will be taken from it. * @param enclosingType A tag for the type that contains the field. Needed to determine a * generic type, if it has one.. @@ -110,24 +57,24 @@ public void changeField(PrefabValues prefabValues, TypeTag enclosingType) { * @throws ReflectionException If the operation fails. */ public void changeField( - PrefabValues prefabValues, + VintageValueProvider valueProvider, TypeTag enclosingType, LinkedHashSet typeStack ) { FieldChanger fm = () -> { TypeTag tag = TypeTag.of(field, enclosingType); - Object newValue = prefabValues.giveOther(tag, field.get(object), typeStack); + Object newValue = valueProvider.giveOther(tag, field.get(object), typeStack); field.set(object, newValue); }; change(fm, false); } private void change(FieldChanger changer, boolean includeStatic) { - FieldAccessor accessor = FieldAccessor.of(field); - if (!accessor.canBeModifiedReflectively()) { + FieldProbe probe = FieldProbe.of(field); + if (!probe.canBeModifiedReflectively()) { return; } - if (!includeStatic && accessor.fieldIsStatic()) { + if (!includeStatic && probe.isStatic()) { return; } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessor.java new file mode 100644 index 000000000..c9cf1c573 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessor.java @@ -0,0 +1,49 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import java.lang.reflect.Field; +import java.util.LinkedHashSet; +import nl.jqno.equalsverifier.internal.reflection.FieldIterable; +import nl.jqno.equalsverifier.internal.reflection.Instantiator; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import org.objenesis.Objenesis; + +/** + * Implementation of ObjectAccessor that modifies its wrapped object in-place through reflection. + * + * @param The specified object's class. + */ +final class InPlaceObjectAccessor extends ObjectAccessor { + + /** Package-private constructor. Call {@link ObjectAccessor#of(Object)} to instantiate. */ + /* default */InPlaceObjectAccessor(T object, Class type) { + super(object, type); + } + + /** {@inheritDoc} */ + @Override + public T copy(Objenesis objenesis) { + T copy = Instantiator.of(type(), objenesis).instantiate(); + for (Field field : FieldIterable.of(type())) { + fieldModifierFor(field).copyTo(copy); + } + return copy; + } + + /** {@inheritDoc} */ + @Override + public ObjectAccessor scramble( + VintageValueProvider valueProvider, + TypeTag enclosingType, + LinkedHashSet typeStack + ) { + for (Field field : FieldIterable.of(type())) { + fieldModifierFor(field).changeField(valueProvider, enclosingType, typeStack); + } + return this; + } + + private FieldModifier fieldModifierFor(Field field) { + return FieldModifier.of(field, get()); + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/ObjectAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/ObjectAccessor.java new file mode 100644 index 000000000..480e30224 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/ObjectAccessor.java @@ -0,0 +1,106 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import java.util.LinkedHashSet; +import nl.jqno.equalsverifier.internal.reflection.RecordsHelper; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import org.objenesis.Objenesis; + +/** + * Wraps an object to provide access to it. ObjectAccessor can copy and scramble the wrapped object. + * + * @param The specified object's class. + */ +public abstract class ObjectAccessor { + + private final T object; + private final Class type; + + /** Package private constructor. Call {@link #of(Object)} to instantiate. */ + /* default */ObjectAccessor(T object, Class type) { + this.object = object; + this.type = type; + } + + /** + * Factory method. + * + * @param {@link #object}'s type. + * @param object The object to wrap. + * @return An {@link ObjectAccessor} for {@link #object}. + */ + public static ObjectAccessor of(T object) { + @SuppressWarnings("unchecked") + Class type = (Class) object.getClass(); + return of(object, type); + } + + /** + * Factory method. + * + * @param {@link #object}'s type, or a supertype. + * @param object The object to wrap. + * @param type Superclass of {@link #object}'s type, as which it will be treated by {@link + * ObjectAccessor}. + * @return An {@link ObjectAccessor} for {@link #object}. + */ + public static ObjectAccessor of(T object, Class type) { + if (RecordsHelper.isRecord(type)) { + return new RecordObjectAccessor(object, type); + } + return new InPlaceObjectAccessor<>(object, type); + } + + /** + * Returns the wrapped object. + * + * @return The wrapped object. + */ + public T get() { + return object; + } + + /** + * Returns the type of the object. + * + * @return The type of the object. + */ + public Class type() { + return type; + } + + /** + * Creates a copy of the wrapped object. + * + *

Note: it does a "shallow" copy. Reference fields are not copied recursively. + * + * @param objenesis Needed to instantiate the copy. + * @return A shallow copy. + */ + public abstract T copy(Objenesis objenesis); + + /** + * Modifies all fields of the wrapped object that are declared in T and in its superclasses. It + * may or may not mutate the object of the current ObjectAccessor. Either way, the current + * ObjectAccessor and any reference to its object should be considered 'spent' after calling + * this method. The returned ObjectAccessor can safely be used. + * + *

This method is consistent: given two equal objects; after scrambling both objects, they + * remain equal to each other. + * + *

It may not be able to modify: 1. static final fields, and 2. final fields that are + * initialized to a compile-time constant in the field declaration. These fields may be left + * unmodified. + * + * @param valueProvider Prefabricated values to take values from. + * @param enclosingType Describes the type that contains this object as a field, to determine + * any generic parameters it may contain. + * @param typeStack Keeps track of recursion in the type. + * @return An accessor to the scrambled object. + */ + public abstract ObjectAccessor scramble( + VintageValueProvider valueProvider, + TypeTag enclosingType, + LinkedHashSet typeStack + ); +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessor.java new file mode 100644 index 000000000..c1f63ea44 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/RecordObjectAccessor.java @@ -0,0 +1,67 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import java.lang.reflect.Field; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.RecordProbe; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import org.objenesis.Objenesis; + +/** + * Implementation of ObjectAccessor that returns modified copies of its wrapped object, through + * calling its class's constructor. This only works when the constructor matches the class's fields + * exactly, such as in a record. + * + * @param The specified object's class. + */ +final class RecordObjectAccessor extends ObjectAccessor { + + private final RecordProbe probe; + + /** Package-private constructor. Call {@link ObjectAccessor#of(Object)} to instantiate. */ + /* default */RecordObjectAccessor(T object, Class type) { + super(object, type); + this.probe = new RecordProbe<>(type); + } + + /** {@inheritDoc} */ + @Override + public T copy(Objenesis objenesis) { + List params = probe.fields().map(this::getField).collect(Collectors.toList()); + return callRecordConstructor(params); + } + + /** {@inheritDoc} */ + @Override + public ObjectAccessor scramble( + VintageValueProvider valueProvider, + TypeTag enclosingType, + LinkedHashSet typeStack + ) { + return makeAccessor(f -> { + Object value = getField(f); + TypeTag tag = TypeTag.of(f, enclosingType); + return valueProvider.giveOther(tag, value, typeStack); + }); + } + + private ObjectAccessor makeAccessor(Function determineValue) { + List params = probe.fields().map(determineValue).collect(Collectors.toList()); + T newObject = callRecordConstructor(params); + return ObjectAccessor.of(newObject); + } + + private T callRecordConstructor(List params) { + RecordProbe p = new RecordProbe<>(type()); + return p.callRecordConstructor(params); + } + + @SuppressWarnings("unchecked") + public T getField(Field field) { + return (T) FieldProbe.of(field).getValue(get()); + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/AbstractGenericFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/AbstractGenericFactory.java similarity index 85% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/AbstractGenericFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/AbstractGenericFactory.java index abf2c24bc..0058867ad 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/AbstractGenericFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/AbstractGenericFactory.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.reflect.InvocationTargetException; @@ -7,8 +7,8 @@ import java.util.LinkedHashSet; import java.util.List; import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; /** * Abstract implementation of {@link PrefabValueFactory} that provides helper functions for dealing @@ -36,16 +36,16 @@ protected TypeTag copyGenericTypesInto(Class type, TypeTag source) { protected TypeTag determineAndCacheActualTypeTag( int n, TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { - return determineAndCacheActualTypeTag(n, tag, prefabValues, typeStack, null); + return determineAndCacheActualTypeTag(n, tag, valueProvider, typeStack, null); } protected TypeTag determineAndCacheActualTypeTag( int n, TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack, Class bottomType ) { @@ -53,7 +53,7 @@ protected TypeTag determineAndCacheActualTypeTag( if (bottomType != null && result.getType().equals(Object.class)) { result = new TypeTag(bottomType); } - prefabValues.realizeCacheFor(result, typeStack); + valueProvider.realizeCacheFor(result, typeStack); return result; } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/CopyFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/CopyFactory.java similarity index 52% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/CopyFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/CopyFactory.java index 1ab9660b1..1b1222861 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/CopyFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/CopyFactory.java @@ -1,15 +1,15 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.LinkedHashSet; import java.util.function.Function; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; public class CopyFactory extends AbstractGenericFactory { private final Class source; - private Function copy; + private final Function copy; public CopyFactory(Class source, Function copy) { this.source = source; @@ -19,16 +19,16 @@ public CopyFactory(Class source, Function copy) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { LinkedHashSet clone = cloneWith(typeStack, tag); TypeTag sourceTag = copyGenericTypesInto(source, tag); - prefabValues.realizeCacheFor(sourceTag, clone); + valueProvider.realizeCacheFor(sourceTag, clone); - S redSource = prefabValues.giveRed(sourceTag); - S blueSource = prefabValues.giveBlue(sourceTag); - S redCopySource = prefabValues.giveRedCopy(sourceTag); + S redSource = valueProvider.giveRed(sourceTag); + S blueSource = valueProvider.giveBlue(sourceTag); + S redCopySource = valueProvider.giveRedCopy(sourceTag); return Tuple.of(copy.apply(redSource), copy.apply(blueSource), copy.apply(redCopySource)); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/EnumMapFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/EnumMapFactory.java similarity index 60% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/EnumMapFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/EnumMapFactory.java index 0ed28a257..5b1424694 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/EnumMapFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/EnumMapFactory.java @@ -1,12 +1,12 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.function.Function; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; @SuppressWarnings({ "unchecked", "rawtypes" }) public final class EnumMapFactory extends AbstractGenericFactory { @@ -20,19 +20,19 @@ public EnumMapFactory(Function factory) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { LinkedHashSet clone = cloneWith(typeStack, tag); - TypeTag keyTag = determineAndCacheActualTypeTag(0, tag, prefabValues, clone, Enum.class); - TypeTag valueTag = determineAndCacheActualTypeTag(1, tag, prefabValues, clone, Enum.class); + TypeTag keyTag = determineAndCacheActualTypeTag(0, tag, valueProvider, clone, Enum.class); + TypeTag valueTag = determineAndCacheActualTypeTag(1, tag, valueProvider, clone, Enum.class); Map red = new HashMap<>(); Map blue = new HashMap<>(); Map redCopy = new HashMap<>(); - red.put(prefabValues.giveRed(keyTag), prefabValues.giveBlue(valueTag)); - blue.put(prefabValues.giveBlue(keyTag), prefabValues.giveBlue(valueTag)); - redCopy.put(prefabValues.giveRed(keyTag), prefabValues.giveBlue(valueTag)); + red.put(valueProvider.giveRed(keyTag), valueProvider.giveBlue(valueTag)); + blue.put(valueProvider.giveBlue(keyTag), valueProvider.giveBlue(valueTag)); + redCopy.put(valueProvider.giveRed(keyTag), valueProvider.giveBlue(valueTag)); return Tuple.of(factory.apply(red), factory.apply(blue), factory.apply(redCopy)); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/EnumSetFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/EnumSetFactory.java similarity index 66% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/EnumSetFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/EnumSetFactory.java index 58abd7fe7..61a59e447 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/EnumSetFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/EnumSetFactory.java @@ -1,12 +1,12 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.function.Function; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; /** * Implementation of {@link PrefabValueFactory} that instantiates EnumSets using reflection, while @@ -24,18 +24,18 @@ public EnumSetFactory(Function factory) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { LinkedHashSet clone = cloneWith(typeStack, tag); - TypeTag entryTag = determineAndCacheActualTypeTag(0, tag, prefabValues, clone, Enum.class); + TypeTag entryTag = determineAndCacheActualTypeTag(0, tag, valueProvider, clone, Enum.class); Collection red = new HashSet<>(); Collection blue = new HashSet<>(); Collection redCopy = new HashSet<>(); - red.add(prefabValues.giveRed(entryTag)); - blue.add(prefabValues.giveBlue(entryTag)); - redCopy.add(prefabValues.giveRed(entryTag)); + red.add(valueProvider.giveRed(entryTag)); + blue.add(valueProvider.giveBlue(entryTag)); + redCopy.add(valueProvider.giveRed(entryTag)); return new Tuple<>(factory.apply(red), factory.apply(blue), factory.apply(redCopy)); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/ExternalFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/ExternalFactory.java similarity index 61% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/ExternalFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/ExternalFactory.java index d09076162..0bea3340b 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/ExternalFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/ExternalFactory.java @@ -1,20 +1,20 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import static nl.jqno.equalsverifier.internal.reflection.Util.classes; import static nl.jqno.equalsverifier.internal.reflection.Util.objects; import java.util.LinkedHashSet; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders.FactoryProvider; import nl.jqno.equalsverifier.internal.reflection.ConditionalInstantiator; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders.FactoryProvider; public class ExternalFactory implements PrefabValueFactory { private static final String EXTERNAL_FACTORIES_PACKAGE = - "nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders."; + "nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders."; private final String factoryName; private FactoryCache factoryCache; @@ -26,7 +26,7 @@ public ExternalFactory(String factoryName) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { if (factoryCache == null) { @@ -36,6 +36,6 @@ public Tuple createValues( } PrefabValueFactory factory = factoryCache.get(tag.getType()); - return factory.createValues(tag, prefabValues, typeStack); + return factory.createValues(tag, valueProvider, typeStack); } } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/Factories.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/Factories.java similarity index 94% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/Factories.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/Factories.java index b532e8e2c..42cf962cd 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/Factories.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/Factories.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.Collection; import java.util.Map; diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FallbackFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FallbackFactory.java similarity index 63% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FallbackFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FallbackFactory.java index 5ee82eed2..eb69ea6b3 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FallbackFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FallbackFactory.java @@ -1,27 +1,34 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.LinkedHashSet; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.vintage.ClassAccessor; +import org.objenesis.Objenesis; /** * Implementation of {@link PrefabValueFactory} that instantiates types "by force". * *

It instantiates the type using bytecode magic, bypassing the constructor. Then it uses {@link - * PrefabValues} to fill up all the fields, recursively. + * VintageValueProvider} to fill up all the fields, recursively. */ public class FallbackFactory implements PrefabValueFactory { + private final Objenesis objenesis; + + public FallbackFactory(Objenesis objenesis) { + this.objenesis = objenesis; + } + @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { @SuppressWarnings("unchecked") @@ -33,11 +40,11 @@ public Tuple createValues( return giveEnumInstances(tag); } if (type.isArray()) { - return giveArrayInstances(tag, prefabValues, clone); + return giveArrayInstances(tag, valueProvider, clone); } - traverseFields(tag, prefabValues, clone); - return giveInstances(tag, prefabValues, clone); + traverseFields(tag, valueProvider, clone); + return giveInstances(tag, valueProvider, clone); } private Tuple giveEnumInstances(TypeTag tag) { @@ -57,45 +64,45 @@ private Tuple giveEnumInstances(TypeTag tag) { @SuppressWarnings("unchecked") private Tuple giveArrayInstances( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { Class type = tag.getType(); Class componentType = type.getComponentType(); TypeTag componentTag = new TypeTag(componentType); - prefabValues.realizeCacheFor(componentTag, typeStack); + valueProvider.realizeCacheFor(componentTag, typeStack); T red = (T) Array.newInstance(componentType, 1); - Array.set(red, 0, prefabValues.giveRed(componentTag)); + Array.set(red, 0, valueProvider.giveRed(componentTag)); T blue = (T) Array.newInstance(componentType, 1); - Array.set(blue, 0, prefabValues.giveBlue(componentTag)); + Array.set(blue, 0, valueProvider.giveBlue(componentTag)); T redCopy = (T) Array.newInstance(componentType, 1); - Array.set(redCopy, 0, prefabValues.giveRed(componentTag)); + Array.set(redCopy, 0, valueProvider.giveRed(componentTag)); return new Tuple<>(red, blue, redCopy); } private void traverseFields( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { Class type = tag.getType(); for (Field field : FieldIterable.of(type)) { - int modifiers = field.getModifiers(); - boolean isStaticAndFinal = Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers); + FieldProbe probe = FieldProbe.of(field); + boolean isStaticAndFinal = probe.isStatic() && probe.isFinal(); if (!isStaticAndFinal) { - prefabValues.realizeCacheFor(TypeTag.of(field, tag), typeStack); + valueProvider.realizeCacheFor(TypeTag.of(field, tag), typeStack); } } } private Tuple giveInstances( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { - ClassAccessor accessor = ClassAccessor.of(tag.getType(), prefabValues); + ClassAccessor accessor = ClassAccessor.of(tag.getType(), valueProvider, objenesis); T red = accessor.getRedObject(tag, typeStack); T blue = accessor.getBlueObject(tag, typeStack); T redCopy = accessor.getRedObject(tag, typeStack); diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/MapFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/MapFactory.java similarity index 68% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/MapFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/MapFactory.java index d402e4d71..f835cfe90 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/MapFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/MapFactory.java @@ -1,11 +1,11 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.LinkedHashSet; import java.util.Map; import java.util.function.Supplier; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; /** * Implementation of {@link PrefabValueFactory} that specializes in creating implementations of @@ -23,27 +23,27 @@ public MapFactory(Supplier createEmpty) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { LinkedHashSet clone = cloneWith(typeStack, tag); - TypeTag keyTag = determineAndCacheActualTypeTag(0, tag, prefabValues, clone); - TypeTag valueTag = determineAndCacheActualTypeTag(1, tag, prefabValues, clone); + TypeTag keyTag = determineAndCacheActualTypeTag(0, tag, valueProvider, clone); + TypeTag valueTag = determineAndCacheActualTypeTag(1, tag, valueProvider, clone); // Use red for key and blue for value in the Red map to avoid having identical keys and // values. // But don't do it in the Blue map, or they may cancel each other out again. - Object redKey = prefabValues.giveRed(keyTag); - Object blueKey = prefabValues.giveBlue(keyTag); - Object blueValue = prefabValues.giveBlue(valueTag); + Object redKey = valueProvider.giveRed(keyTag); + Object blueKey = valueProvider.giveBlue(keyTag); + Object blueValue = valueProvider.giveBlue(valueTag); T red = createEmpty.get(); red.put(redKey, blueValue); T blue = createEmpty.get(); if (!redKey.equals(blueKey)) { // This happens with single-element enums - blue.put(prefabValues.giveBlue(keyTag), blueValue); + blue.put(valueProvider.giveBlue(keyTag), blueValue); } T redCopy = createEmpty.get(); diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/PrefabValueFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/PrefabValueFactory.java similarity index 50% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/PrefabValueFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/PrefabValueFactory.java index 2ee983e1e..4616c2660 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/PrefabValueFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/PrefabValueFactory.java @@ -1,9 +1,9 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.LinkedHashSet; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; /** * Creates instances of generic types for use as prefab value. @@ -16,10 +16,14 @@ public interface PrefabValueFactory { * Creates a tuple of two prefab values. * * @param tag The typetag of the type for which to create values. - * @param prefabValues Repository for querying instances of generic types of the type tag. + * @param valueProvider Repository for querying instances of generic types of the type tag. * @param typeStack A stack of {@link TypeTag}s that require tag in order to be created. Used * for recursion detection. * @return A "red" instance of {@code T}. */ - Tuple createValues(TypeTag tag, PrefabValues prefabValues, LinkedHashSet typeStack); + Tuple createValues( + TypeTag tag, + VintageValueProvider valueProvider, + LinkedHashSet typeStack + ); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleFactory.java similarity index 61% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleFactory.java index b764cf67d..44a7ae041 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleFactory.java @@ -1,9 +1,9 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.LinkedHashSet; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; /** * Implementation of {@link PrefabValueFactory} that holds on to two instances that have already @@ -20,7 +20,7 @@ public SimpleFactory(T red, T blue, T redCopy) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { return tuple; diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleGenericFactory.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleGenericFactory.java similarity index 75% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleGenericFactory.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleGenericFactory.java index baf0223b4..748f64f7f 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleGenericFactory.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleGenericFactory.java @@ -1,13 +1,13 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.function.Supplier; import nl.jqno.equalsverifier.Func; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; public class SimpleGenericFactory extends AbstractGenericFactory { @@ -22,7 +22,7 @@ public SimpleGenericFactory(Func factory, Supplier emptyFactory) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { LinkedHashSet clone = cloneWith(typeStack, tag); @@ -33,10 +33,10 @@ public Tuple createValues( boolean useEmpty = false; int n = tag.getType().getTypeParameters().length; for (int i = 0; i < n; i++) { - TypeTag paramTag = determineAndCacheActualTypeTag(i, tag, prefabValues, clone); + TypeTag paramTag = determineAndCacheActualTypeTag(i, tag, valueProvider, clone); - Object redValue = prefabValues.giveRed(paramTag); - Object blueValue = prefabValues.giveBlue(paramTag); + Object redValue = valueProvider.giveRed(paramTag); + Object blueValue = valueProvider.giveBlue(paramTag); if (redValue.equals(blueValue)) { // This happens with single-element enums useEmpty = true; } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/AwtFactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/AwtFactoryProvider.java similarity index 87% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/AwtFactoryProvider.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/AwtFactoryProvider.java index a8096d9c6..2312d3b5a 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/AwtFactoryProvider.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/AwtFactoryProvider.java @@ -1,13 +1,13 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_Profile; import java.awt.image.BufferedImage; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; public final class AwtFactoryProvider implements FactoryProvider { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/FactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/FactoryProvider.java new file mode 100644 index 000000000..c81cda452 --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/FactoryProvider.java @@ -0,0 +1,7 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; + +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; + +public interface FactoryProvider { + FactoryCache getFactoryCache(); +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/GuavaFactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/GuavaFactoryProvider.java similarity index 79% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/GuavaFactoryProvider.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/GuavaFactoryProvider.java index f955aadab..4416de38c 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/GuavaFactoryProvider.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/GuavaFactoryProvider.java @@ -1,19 +1,19 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.*; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.*; import com.google.common.collect.*; import com.google.common.reflect.TypeToken; import java.util.*; import java.util.function.Supplier; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.AbstractGenericFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.EnumMapFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.EnumSetFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.AbstractGenericFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.EnumMapFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.EnumSetFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories; public final class GuavaFactoryProvider implements FactoryProvider { @@ -43,7 +43,7 @@ private void putMultisets(FactoryCache cache) { cache.put(TreeMultiset.class, collection(() -> TreeMultiset.create(OBJECT_COMPARATOR))); cache.put(LinkedHashMultiset.class, collection(LinkedHashMultiset::create)); cache.put(ConcurrentHashMultiset.class, collection(ConcurrentHashMultiset::create)); - cache.put(EnumMultiset.class, new EnumSetFactory<>(EnumMultiset::create)); + cache.put(EnumMultiset.class, new EnumSetFactory<>(c -> EnumMultiset.create(c))); cache.put(ImmutableMultiset.class, copy(Set.class, ImmutableMultiset::copyOf)); cache.put( ImmutableSortedMultiset.class, @@ -79,7 +79,7 @@ private void putBiMaps(FactoryCache cache) { cache.put(HashBiMap.class, map(HashBiMap::create)); cache.put(EnumHashBiMap.class, copy(EnumMap.class, EnumHashBiMap::create)); cache.put(ImmutableBiMap.class, copy(Map.class, ImmutableBiMap::copyOf)); - cache.put(EnumBiMap.class, new EnumMapFactory<>(EnumBiMap::create)); + cache.put(EnumBiMap.class, new EnumMapFactory<>(c -> EnumBiMap.create(c))); } @SuppressWarnings("unchecked") @@ -159,19 +159,19 @@ private MultimapFactory(Supplier factory) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { LinkedHashSet clone = cloneWith(typeStack, tag); - TypeTag keyTag = determineAndCacheActualTypeTag(0, tag, prefabValues, clone); - TypeTag valueTag = determineAndCacheActualTypeTag(1, tag, prefabValues, clone); + TypeTag keyTag = determineAndCacheActualTypeTag(0, tag, valueProvider, clone); + TypeTag valueTag = determineAndCacheActualTypeTag(1, tag, valueProvider, clone); T red = factory.get(); T blue = factory.get(); T redCopy = factory.get(); - red.put(prefabValues.giveRed(keyTag), prefabValues.giveBlue(valueTag)); - blue.put(prefabValues.giveBlue(keyTag), prefabValues.giveBlue(valueTag)); - redCopy.put(prefabValues.giveRed(keyTag), prefabValues.giveBlue(valueTag)); + red.put(valueProvider.giveRed(keyTag), valueProvider.giveBlue(valueTag)); + blue.put(valueProvider.giveBlue(keyTag), valueProvider.giveBlue(valueTag)); + redCopy.put(valueProvider.giveRed(keyTag), valueProvider.giveBlue(valueTag)); return Tuple.of(red, blue, redCopy); } @@ -189,31 +189,31 @@ private TableFactory(Supplier factory) { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { LinkedHashSet clone = cloneWith(typeStack, tag); - TypeTag columnTag = determineAndCacheActualTypeTag(0, tag, prefabValues, clone); - TypeTag rowTag = determineAndCacheActualTypeTag(1, tag, prefabValues, clone); - TypeTag valueTag = determineAndCacheActualTypeTag(2, tag, prefabValues, clone); + TypeTag columnTag = determineAndCacheActualTypeTag(0, tag, valueProvider, clone); + TypeTag rowTag = determineAndCacheActualTypeTag(1, tag, valueProvider, clone); + TypeTag valueTag = determineAndCacheActualTypeTag(2, tag, valueProvider, clone); T red = factory.get(); T blue = factory.get(); T redCopy = factory.get(); red.put( - prefabValues.giveRed(columnTag), - prefabValues.giveRed(rowTag), - prefabValues.giveBlue(valueTag) + valueProvider.giveRed(columnTag), + valueProvider.giveRed(rowTag), + valueProvider.giveBlue(valueTag) ); blue.put( - prefabValues.giveBlue(columnTag), - prefabValues.giveBlue(rowTag), - prefabValues.giveBlue(valueTag) + valueProvider.giveBlue(columnTag), + valueProvider.giveBlue(rowTag), + valueProvider.giveBlue(valueTag) ); redCopy.put( - prefabValues.giveRed(columnTag), - prefabValues.giveRed(rowTag), - prefabValues.giveBlue(valueTag) + valueProvider.giveRed(columnTag), + valueProvider.giveRed(rowTag), + valueProvider.giveBlue(valueTag) ); return Tuple.of(red, blue, redCopy); diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaFxFactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaFxFactoryProvider.java similarity index 80% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaFxFactoryProvider.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaFxFactoryProvider.java index d23466674..aa2b6a3ca 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaFxFactoryProvider.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaFxFactoryProvider.java @@ -1,20 +1,16 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.copy; -import static nl.jqno.equalsverifier.internal.reflection.Util.*; +import static nl.jqno.equalsverifier.internal.reflection.Util.classForName; +import static nl.jqno.equalsverifier.internal.reflection.Util.classes; +import static nl.jqno.equalsverifier.internal.reflection.Util.objects; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.copy; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.AbstractGenericFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; -import nl.jqno.equalsverifier.internal.reflection.ConditionalInstantiator; +import nl.jqno.equalsverifier.internal.reflection.*; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.AbstractGenericFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; public final class JavaFxFactoryProvider implements FactoryProvider { @@ -111,7 +107,7 @@ static final class PropertyFactory extends AbstractGenericFactory { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { ConditionalInstantiator ci = new ConditionalInstantiator(fullyQualifiedTypeName); @@ -119,15 +115,15 @@ public Tuple createValues( T red = ci.instantiate( classes(parameterRawType), - objects(prefabValues.giveRed(singleParameterTag)) + objects(valueProvider.giveRed(singleParameterTag)) ); T blue = ci.instantiate( classes(parameterRawType), - objects(prefabValues.giveBlue(singleParameterTag)) + objects(valueProvider.giveBlue(singleParameterTag)) ); T redCopy = ci.instantiate( classes(parameterRawType), - objects(prefabValues.giveRed(singleParameterTag)) + objects(valueProvider.giveRed(singleParameterTag)) ); return Tuple.of(red, blue, redCopy); diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaxFactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaxFactoryProvider.java similarity index 72% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaxFactoryProvider.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaxFactoryProvider.java index 8d761dfe9..386bb67ae 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaxFactoryProvider.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaxFactoryProvider.java @@ -1,10 +1,10 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; import javax.naming.Reference; import javax.swing.tree.DefaultMutableTreeNode; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; public final class JavaxFactoryProvider implements FactoryProvider { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JodaFactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JodaFactoryProvider.java similarity index 82% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JodaFactoryProvider.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JodaFactoryProvider.java index c56ccda29..da40b542d 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JodaFactoryProvider.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JodaFactoryProvider.java @@ -1,8 +1,8 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; import org.joda.time.*; import org.joda.time.chrono.GregorianChronology; import org.joda.time.chrono.ISOChronology; diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/RmiFactoryProvider.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/RmiFactoryProvider.java similarity index 63% rename from equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/RmiFactoryProvider.java rename to equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/RmiFactoryProvider.java index 8749a01a1..edb25d411 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/RmiFactoryProvider.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/RmiFactoryProvider.java @@ -1,10 +1,10 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; import java.rmi.dgc.VMID; import java.rmi.server.UID; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; public final class RmiFactoryProvider implements FactoryProvider { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/testhelpers/Util.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/testhelpers/Util.java index 5a99ae69b..9b67fd1e1 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/testhelpers/Util.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/testhelpers/Util.java @@ -4,8 +4,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Objects; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; public final class Util { @@ -57,7 +57,7 @@ public static int defaultHashCode(Object x) { } private static boolean isRelevant(Field f) { - return FieldAccessor.of(f).canBeModifiedReflectively(); + return FieldProbe.of(f).canBeModifiedReflectively(); } public static void coverThePrivateConstructor(Class type) { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java index 2924db52c..72c06ef98 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java @@ -1,24 +1,13 @@ package nl.jqno.equalsverifier.internal.util; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.reflection.annotations.Annotation; -import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; -import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCacheBuilder; -import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.annotations.*; public final class Configuration { @@ -27,6 +16,7 @@ public final class Configuration { private final Class type; private final Set nonnullFields; + private final Set prefabbedFields; private final CachedHashCodeInitializer cachedHashCodeInitializer; private final boolean hasRedefinedSuperclass; private final Class redefinedSubclass; @@ -35,8 +25,6 @@ public final class Configuration { private final Function fieldnameToGetter; private final TypeTag typeTag; - private final PrefabValues prefabValues; - private final ClassAccessor classAccessor; private final AnnotationCache annotationCache; private final Set ignoredFields; @@ -47,10 +35,9 @@ public final class Configuration { private Configuration( Class type, TypeTag typeTag, - ClassAccessor classAccessor, - PrefabValues prefabValues, Set ignoredFields, Set nonnullFields, + Set prefabbedFields, AnnotationCache annotationCache, CachedHashCodeInitializer cachedHashCodeInitializer, boolean hasRedefinedSuperclass, @@ -63,10 +50,9 @@ private Configuration( ) { this.type = type; this.typeTag = typeTag; - this.classAccessor = classAccessor; - this.prefabValues = prefabValues; this.ignoredFields = ignoredFields; this.nonnullFields = nonnullFields; + this.prefabbedFields = prefabbedFields; this.annotationCache = annotationCache; this.cachedHashCodeInitializer = cachedHashCodeInitializer; this.hasRedefinedSuperclass = hasRedefinedSuperclass; @@ -83,22 +69,19 @@ public static Configuration build( Set excludedFields, Set includedFields, Set nonnullFields, + Set prefabbedFields, CachedHashCodeInitializer cachedHashCodeInitializer, boolean hasRedefinedSuperclass, Class redefinedSubclass, boolean usingGetClass, EnumSet warningsToSuppress, Function fieldnameToGetter, - FactoryCache factoryCache, Set ignoredAnnotationClassNames, Set actualFields, List equalExamples, List unequalExamples ) { TypeTag typeTag = new TypeTag(type); - FactoryCache cache = JavaApiPrefabValues.build().merge(factoryCache); - PrefabValues prefabValues = new PrefabValues(cache); - ClassAccessor classAccessor = ClassAccessor.of(type, prefabValues); AnnotationCache annotationCache = buildAnnotationCache(type, ignoredAnnotationClassNames); Set ignoredFields = determineIgnoredFields( type, @@ -111,15 +94,13 @@ public static Configuration build( Function converter = fieldnameToGetter != null ? fieldnameToGetter : DEFAULT_FIELDNAME_TO_GETTER_CONVERTER; - List unequals = ensureUnequalExamples(typeTag, classAccessor, unequalExamples); return new Configuration<>( type, typeTag, - classAccessor, - prefabValues, ignoredFields, nonnullFields, + prefabbedFields, annotationCache, cachedHashCodeInitializer, hasRedefinedSuperclass, @@ -128,7 +109,7 @@ public static Configuration build( warningsToSuppress, converter, equalExamples, - unequals + unequalExamples ); } @@ -206,21 +187,6 @@ private static Set determineAnnotationlessIgnoredFields( return excludedFields; } - private static List ensureUnequalExamples( - TypeTag typeTag, - ClassAccessor classAccessor, - List examples - ) { - if (examples.size() > 0) { - return examples; - } - - List result = new ArrayList<>(); - result.add(classAccessor.getRedObject(typeTag)); - result.add(classAccessor.getBlueObject(typeTag)); - return result; - } - public Class getType() { return type; } @@ -229,6 +195,10 @@ public Set getNonnullFields() { return Collections.unmodifiableSet(nonnullFields); } + public Set getPrefabbedFields() { + return Collections.unmodifiableSet(prefabbedFields); + } + public CachedHashCodeInitializer getCachedHashCodeInitializer() { return cachedHashCodeInitializer; } @@ -265,18 +235,6 @@ public TypeTag getTypeTag() { return typeTag; } - @SuppressFBWarnings( - value = "EI_EXPOSE_REP", - justification = "PrefabValues is inherently mutable." - ) - public PrefabValues getPrefabValues() { - return prefabValues; - } - - public ClassAccessor getClassAccessor() { - return classAccessor; - } - @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "A cache is inherently mutable.") public AnnotationCache getAnnotationCache() { return annotationCache; diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Context.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Context.java new file mode 100644 index 000000000..e70f1620d --- /dev/null +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Context.java @@ -0,0 +1,69 @@ +package nl.jqno.equalsverifier.internal.util; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import nl.jqno.equalsverifier.internal.reflection.*; +import nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator; +import nl.jqno.equalsverifier.internal.reflection.instantiation.ValueProvider; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import org.objenesis.Objenesis; + +public final class Context { + + private final Class type; + private final Configuration configuration; + private final ClassProbe classProbe; + private final FieldCache fieldCache; + + private final SubjectCreator subjectCreator; + private final ValueProvider valueProvider; + + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "FieldCache is inherently mutable" + ) + public Context( + Configuration configuration, + FactoryCache factoryCache, + FieldCache fieldCache, + Objenesis objenesis + ) { + this.type = configuration.getType(); + this.configuration = configuration; + this.classProbe = new ClassProbe<>(configuration.getType()); + this.fieldCache = fieldCache; + + FactoryCache cache = JavaApiPrefabValues.build().merge(factoryCache); + this.valueProvider = new VintageValueProvider(cache, objenesis); + this.subjectCreator = + new SubjectCreator<>(configuration, valueProvider, fieldCache, objenesis); + } + + public Class getType() { + return type; + } + + public Configuration getConfiguration() { + return configuration; + } + + public ClassProbe getClassProbe() { + return classProbe; + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "A cache is inherently mutable") + public FieldCache getFieldCache() { + return fieldCache; + } + + @SuppressFBWarnings( + value = "EI_EXPOSE_REP", + justification = "VintageValueProvider can use a mutable cache." + ) + public ValueProvider getValueProvider() { + return valueProvider; + } + + public SubjectCreator getSubjectCreator() { + return subjectCreator; + } +} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Formatter.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Formatter.java index ca1ad1d81..a94bf4582 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Formatter.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Formatter.java @@ -3,7 +3,7 @@ import java.lang.reflect.Field; import java.util.regex.Matcher; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FieldProbe; /** * Formats a string with the contents of one or more objects. @@ -83,9 +83,7 @@ private String stringify(Object obj) { private String stringifyByReflection(Object obj) { StringBuilder result = new StringBuilder(); - Class type = obj.getClass(); - ObjectAccessor accessor = ObjectAccessor.of(obj); result.append("["); String typeName = type.getSimpleName().replaceAll("\\$\\$DynamicSubclass.*", ""); @@ -98,7 +96,9 @@ private String stringifyByReflection(Object obj) { result.append(" "); result.append(fieldName); result.append("="); - Object value = accessor.getField(field); + + FieldProbe probe = FieldProbe.of(field); + Object value = probe.getValue(obj); result.append(stringify(value)); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/ObjenesisWrapper.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/ObjenesisWrapper.java deleted file mode 100644 index 9b4e55942..000000000 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/ObjenesisWrapper.java +++ /dev/null @@ -1,29 +0,0 @@ -package nl.jqno.equalsverifier.internal.util; - -import org.objenesis.Objenesis; -import org.objenesis.ObjenesisStd; - -/** - * A wrapper around Objenesis. Objenesis keeps caches of objects it has instantiated, so we want a - * way to easily re-use the same instance of `Objenesis`. This class reflects the usage in - * {@link org.objenesis.ObjenesisHelper}, but with the added benefit that now we can reset the - * caches if needed (for instance if the test framework used does some "clever" tricks with - * ClassLoaders) by re-initializing the Objenesis instance. - * - * Note: I realise that a wrapper around a static reference is not very architecturally sound; - * however, doing it properly would require major re-writes. Maybe some other time. - */ -public final class ObjenesisWrapper { - - private static Objenesis objenesis = new ObjenesisStd(); - - private ObjenesisWrapper() {} - - public static Objenesis getObjenesis() { - return objenesis; - } - - public static void reset() { - objenesis = new ObjenesisStd(); - } -} 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 a8ddc3ab4..8bc5c9c0c 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,13 +1,16 @@ package nl.jqno.equalsverifier.internal.util; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.simple; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.simple; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; import nl.jqno.equalsverifier.Func.Func1; import nl.jqno.equalsverifier.Func.Func2; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FieldCache; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.vintage.ObjectAccessor; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; +import org.objenesis.Objenesis; public final class PrefabValuesApi { @@ -15,6 +18,7 @@ private PrefabValuesApi() {} public static void addPrefabValues( FactoryCache factoryCache, + Objenesis objenesis, Class otherType, T red, T blue @@ -25,7 +29,7 @@ public static void addPrefabValues( factoryCache.put(otherType, values(red, blue, red)); } else { try { - T redCopy = ObjectAccessor.of(red).copy(); + T redCopy = ObjectAccessor.of(red).copy(objenesis); factoryCache.put(otherType, values(red, blue, redCopy)); } catch (RuntimeException ignored) { /* specifically, on Java 9+: InacessibleObjectException */ @@ -34,6 +38,31 @@ public static void addPrefabValues( } } + @SuppressWarnings("unchecked") + public static void addPrefabValuesForField( + FieldCache fieldCache, + Objenesis objenesis, + Class type, + String fieldName, + T red, + T blue + ) { + Validations.validateRedAndBluePrefabValues((Class) red.getClass(), red, blue); + Validations.validateFieldTypeMatches(type, fieldName, red.getClass()); + + if (red.getClass().isArray()) { + fieldCache.put(fieldName, new Tuple<>(red, blue, red)); + } else { + try { + T redCopy = ObjectAccessor.of(red).copy(objenesis); + fieldCache.put(fieldName, new Tuple<>(red, blue, redCopy)); + } catch (RuntimeException ignored) { + /* specifically, on Java 9+: InacessibleObjectException */ + fieldCache.put(fieldName, new Tuple<>(red, blue, red)); + } + } + } + public static void addGenericPrefabValues( FactoryCache factoryCache, Class otherType, diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Rethrow.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Rethrow.java index 5d61c788c..1c07f2e18 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Rethrow.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/util/Rethrow.java @@ -2,6 +2,7 @@ import java.util.function.Function; import nl.jqno.equalsverifier.internal.exceptions.EqualsVerifierInternalBugException; +import nl.jqno.equalsverifier.internal.exceptions.ModuleException; import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; /** @@ -33,7 +34,14 @@ public static T rethrow( try { return supplier.get(); } catch (RuntimeException e) { - throw e; + if (e.getClass().getName().endsWith("InaccessibleObjectException")) { + throw new ModuleException( + "The class is not accessible via the Java Module system. Consider opening the module that contains it.", + e + ); + } else { + throw e; + } } catch (ReflectiveOperationException e) { throw new ReflectionException(errorMessage.apply(e), e); } catch (Exception e) { 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 0a67506c0..0adb4f04d 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 @@ -9,10 +9,10 @@ import java.util.Set; import java.util.stream.Collectors; import nl.jqno.equalsverifier.Warning; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; import nl.jqno.equalsverifier.internal.reflection.FieldIterable; import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; public final class Validations { @@ -33,7 +33,7 @@ public static void validateFieldNamesExist( givenFields.forEach(f -> validateFieldNameExists(type, f, actualFields)); } - private static void validateFieldNameExists( + public static void validateFieldNameExists( Class type, String field, Set actualFields @@ -89,6 +89,35 @@ 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 sameFields = f.getType().equals(fieldType); + boolean compatibleFields = fieldType.equals( + PrimitiveMappers.PRIMITIVE_OBJECT_MAPPER.get(f.getType()) + ); + validate( + !sameFields && !compatibleFields, + "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 validateGenericPrefabValues( Class type, PrefabValueFactory factory, diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/ModulesTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/ModulesTest.java index 274da8af9..b58a876a6 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/ModulesTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/ModulesTest.java @@ -3,6 +3,7 @@ import java.text.AttributedString; import java.util.Objects; import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; @@ -19,7 +20,12 @@ public class ModulesTest { @DisabledForJreRange(max = JRE.JAVA_11) public void giveProperErrorMessage_whenClassUnderTestIsInaccessible() { ExpectedException - .when(() -> EqualsVerifier.forClass(AttributedString.class).verify()) + .when(() -> + EqualsVerifier + .forClass(AttributedString.class) + .suppress(Warning.INHERITED_DIRECTLY_FROM_OBJECT) + .verify() + ) .assertFailure() .assertMessageContains("The class", "Consider opening"); } diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extra_features/JpaLazyEntityTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extra_features/JpaLazyEntityTest.java index 4c926eae1..e55f2c3eb 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extra_features/JpaLazyEntityTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extra_features/JpaLazyEntityTest.java @@ -17,6 +17,14 @@ public void gettersAreUsed() { EqualsVerifier.forClass(CorrectJpaLazyFieldContainer.class).verify(); } + @Test + public void basicGetterAbsent() { + ExpectedException + .when(() -> EqualsVerifier.forClass(LazyFieldWithoutGetterContainer.class).verify()) + .assertFailure() + .assertMessageContains("doesn't contain getter getBasic() for field basic"); + } + @Test public void basicGetterNotUsed_givenEagerLoading() { EqualsVerifier.forClass(CorrectBasicJpaEagerFieldContainer.class).verify(); @@ -260,6 +268,27 @@ public int hashCode() { } } + @Entity + static class LazyFieldWithoutGetterContainer { + + @OneToMany + private String basic; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof LazyFieldWithoutGetterContainer)) { + return false; + } + LazyFieldWithoutGetterContainer other = (LazyFieldWithoutGetterContainer) obj; + return Objects.equals(basic, other.basic); + } + + @Override + public int hashCode() { + return Objects.hash(basic); + } + } + @Entity static class CorrectBasicJpaEagerFieldContainer { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/RecursionTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/RecursionTest.java index a83cace23..952b32f0f 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/RecursionTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/RecursionTest.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Objects; import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; 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 new file mode 100644 index 000000000..0d39f7c59 --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithPrefabValuesForFieldTest.java @@ -0,0 +1,242 @@ +package nl.jqno.equalsverifier.integration.operational; + +import java.time.LocalDate; +import java.time.Month; +import java.util.Objects; +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; +import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; +import nl.jqno.equalsverifier.testhelpers.types.FinalPoint; +import org.junit.jupiter.api.Test; + +public class WithPrefabValuesForFieldTest { + + private final FinalPoint pRed = new FinalPoint(3, 42); + private final FinalPoint pBlue = new FinalPoint(3, 1337); + private final int iRed = 111; + private final int iBlue = 142; + + @Test + public void fail_whenClassHasSinglePrecondition() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(SinglePrecondition.class) + .suppress(Warning.NULL_FIELDS) + .verify() + ) + .assertFailure() + .assertMessageContains("x coordinate must be"); + } + + @Test + public void succeed_whenClassHasSinglePrecondition_givenPrefabValuesForField() { + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("point", pRed, pBlue) + .verify(); + } + + @Test + public void fail_whenClassHasDualPrecondition() { + ExpectedException + .when(() -> EqualsVerifier.forClass(DualPrecondition.class).verify()) + .assertFailure() + .assertMessageContains("x must be between"); + } + + @Test + public void fail_whenClassHasDualPrecondition_givenPrefabValuesForOnlyOneField() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(DualPrecondition.class) + .withPrefabValuesForField("x", iRed, iBlue) + .verify() + ) + .assertFailure() + .assertMessageContains("y must be between"); + } + + @Test + public void succeed_whenClassHasDualPrecondition_givenPrefabValueForBothFields() { + EqualsVerifier + .forClass(DualPrecondition.class) + .withPrefabValuesForField("x", iRed, iBlue) + .withPrefabValuesForField("y", 505, 555) + .verify(); + } + + @Test + public void throw_whenFieldDoesNotExistInClass() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("doesnt_exist", 1, 2) + ) + .assertThrows(IllegalStateException.class) + .assertMessageContains("Precondition:", "does not contain field doesnt_exist"); + } + + @Test + public void throw_whenFirstPrefabValueIsNull() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("point", null, pBlue) + ) + .assertThrows(NullPointerException.class); + } + + @Test + public void throw_whenSecondPrefabValueIsNull() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("point", pRed, null) + ) + .assertThrows(NullPointerException.class); + } + + @Test + public void throw_whenThePrefabValuesAreTheSame() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("point", pRed, pRed) + ) + .assertThrows(IllegalStateException.class) + .assertMessageContains( + "Precondition", + "both prefab values of type FinalPoint are equal" + ); + } + + @Test + public void throw_whenThePrefabValuesAreEqual() { + FinalPoint red1 = new FinalPoint(3, 4); + FinalPoint red2 = new FinalPoint(3, 4); + + ExpectedException + .when(() -> + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("point", red1, red2) + ) + .assertThrows(IllegalStateException.class) + .assertMessageContains( + "Precondition", + "both prefab values of type FinalPoint are equal" + ); + } + + @Test + public void throw_whenFieldsDontMatch() { + ExpectedException + .when(() -> + EqualsVerifier + .forClass(SinglePrecondition.class) + .withPrefabValuesForField("point", 1, 2) + ) + .assertThrows(IllegalStateException.class) + .assertMessageContains( + "Precondition", + "for field point should be of type FinalPoint but are" + ); + } + + @Test + public void dontThrow_whenAddingPrefabValuesFromAnotherModuleAndThereforeARedCopyCantBeMade() { + EqualsVerifier + .forClass(OtherModuleContainer.class) + .withPrefabValuesForField("date", LocalDate.of(2024, 9, 18), LocalDate.of(2024, 9, 19)) + .verify(); + } + + static final class SinglePrecondition { + + private final FinalPoint point; + + public SinglePrecondition(FinalPoint point) { + this.point = point; + } + + @Override + public boolean equals(Object obj) { + if (point == null || point.getX() != 3) { + throw new IllegalArgumentException("x coordinate must be 3! But was " + point); + } + if (!(obj instanceof SinglePrecondition)) { + return false; + } + SinglePrecondition other = (SinglePrecondition) obj; + return Objects.equals(point, other.point); + } + + @Override + public int hashCode() { + return Objects.hash(point); + } + } + + static final class DualPrecondition { + + private final int x; + private final int y; + + public DualPrecondition(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object obj) { + if (x < 100 || x > 200) { + throw new IllegalArgumentException("x must be between 100 and 200! But was " + x); + } + if (y < 500 || y > 600) { + throw new IllegalArgumentException("y must be between 500 and 600! But was " + y); + } + if (!(obj instanceof DualPrecondition)) { + return false; + } + DualPrecondition other = (DualPrecondition) obj; + return Objects.equals(x, other.x) && Objects.equals(y, other.y); + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + } + + static final class OtherModuleContainer { + + private final LocalDate date; + + public OtherModuleContainer(LocalDate date) { + this.date = date; + } + + @Override + public boolean equals(Object obj) { + if (date == null || date.getMonth() != Month.SEPTEMBER) { + throw new IllegalArgumentException("date must be in September! But was " + date); + } + if (!(obj instanceof OtherModuleContainer)) { + return false; + } + OtherModuleContainer other = (OtherModuleContainer) obj; + return Objects.equals(date, other.date); + } + + @Override + public int hashCode() { + return Objects.hash(date); + } + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithResetCachesTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithResetCachesTest.java deleted file mode 100644 index 8803c0a40..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithResetCachesTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package nl.jqno.equalsverifier.integration.operational; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper; -import nl.jqno.equalsverifier.testhelpers.packages.correct.A; -import org.junit.jupiter.api.Test; -import org.objenesis.Objenesis; - -public class WithResetCachesTest { - - @Test - public void resetObjenesisCacheOnEachRun() { - Objenesis original = ObjenesisWrapper.getObjenesis(); - EqualsVerifier.forClass(A.class).verify(); - Objenesis reset = ObjenesisWrapper.getObjenesis(); - assertNotEquals(original, reset); - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/architecture/ArchitectureTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/architecture/ArchitectureTest.java index 4b396b798..b7461f4e9 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/architecture/ArchitectureTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/architecture/ArchitectureTest.java @@ -5,10 +5,55 @@ import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; +import nl.jqno.equalsverifier.internal.reflection.FactoryCacheTest; +import nl.jqno.equalsverifier.internal.reflection.JavaApiPrefabValues; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProviderCreatorTest; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProviderTest; +import nl.jqno.equalsverifier.internal.reflection.vintage.ClassAccessor; +import nl.jqno.equalsverifier.internal.reflection.vintage.FieldModifier; +import nl.jqno.equalsverifier.internal.reflection.vintage.ObjectAccessor; +import nl.jqno.equalsverifier.internal.util.Context; +import nl.jqno.equalsverifier.internal.util.PrefabValuesApi; +import nl.jqno.equalsverifier.testhelpers.FactoryCacheFactory; @AnalyzeClasses(packages = "nl.jqno.equalsverifier") public final class ArchitectureTest { + @ArchTest + public static final ArchRule ONLY_VINTAGE_INSTANTIATORS_CAN_USE_VINTAGE_REFLECTION = noClasses() + .that() + .doNotBelongToAnyOf( + VintageValueProvider.class, + Context.class, + PrefabValuesApi.class, + JavaApiPrefabValues.class, + // 👇 Test classes + FactoryCacheFactory.class, + FactoryCacheTest.class, + VintageValueProviderTest.class, + VintageValueProviderCreatorTest.class + ) + .and() + .resideOutsideOfPackage("nl.jqno.equalsverifier.internal.reflection.vintage..") + .should() + .accessClassesThat() + .resideInAPackage("nl.jqno.equalsverifier.internal.reflection.vintage.."); + + @ArchTest + public static final ArchRule DONT_USE_VINTAGE_REFLECTION_DIRECTLY = noClasses() + .that() + .resideInAPackage("nl.jqno.equalsverifier.internal.checkers..") + .should() + .accessClassesThat() + .areAssignableTo(ClassAccessor.class) + .orShould() + .accessClassesThat() + .areAssignableTo(ObjectAccessor.class) + .orShould() + .accessClassesThat() + .areAssignableTo(FieldModifier.class); + @ArchTest public static final ArchRule APACHE_COMMONS = noClasses() .that() @@ -38,7 +83,7 @@ public final class ArchitectureTest { ); private static final String FACTORYPROVIDER_PATTERN = - "nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders.."; + "nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders.."; private static final String TEST_CLASS_PATTERN = ".*Test(\\$.*)?$"; private ArchitectureTest() { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/checkers/FieldInspectorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/checkers/FieldInspectorTest.java deleted file mode 100644 index 1b0454a41..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/checkers/FieldInspectorTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package nl.jqno.equalsverifier.internal.checkers; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.lang.reflect.Field; -import java.util.HashSet; -import java.util.Set; -import nl.jqno.equalsverifier.internal.checkers.fieldchecks.FieldCheck; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.ClassAccessor; -import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; -import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; -import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; -import nl.jqno.equalsverifier.testhelpers.FactoryCacheFactory; -import nl.jqno.equalsverifier.testhelpers.types.Point; -import org.junit.jupiter.api.Test; - -public class FieldInspectorTest { - - private final PrefabValues prefabValues = new PrefabValues( - FactoryCacheFactory.withPrimitiveFactories() - ); - private final ClassAccessor accessor = ClassAccessor.of(Point.class, prefabValues); - - @Test - public void objectsAreReset_whenEachIterationBegins() { - FieldInspector inspector = new FieldInspector<>(accessor, TypeTag.NULL); - - inspector.check(new ResetObjectForEachIterationCheck<>()); - } - - @Test - public void objectsAreReset_whenEachIterationBegins_givenNullObjects() { - FieldInspector inspector = new FieldInspector<>(accessor, TypeTag.NULL); - Set nullFields = new HashSet<>(); - AnnotationCache annotationCache = new AnnotationCache(); - - inspector.checkWithNull( - false, - false, - nullFields, - annotationCache, - new ResetObjectForEachIterationCheck<>() - ); - } - - private final class ResetObjectForEachIterationCheck implements FieldCheck { - - private Object originalReference; - private Object originalChanged; - - @Override - public void execute( - ObjectAccessor referenceAccessor, - ObjectAccessor copyAccessor, - FieldAccessor fieldAccessor - ) { - if (originalReference == null) { - originalReference = referenceAccessor.copy(); - originalChanged = copyAccessor.copy(); - } else { - assertEquals(originalReference, referenceAccessor.get()); - assertEquals(originalChanged, copyAccessor.get()); - } - Field field = fieldAccessor.getField(); - - referenceAccessor.withChangedField(field, prefabValues, TypeTag.NULL); - copyAccessor.withChangedField(field, prefabValues, TypeTag.NULL); - } - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/exceptions/RecursionExceptionTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/exceptions/RecursionExceptionTest.java index 5e4c59587..6b118672c 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/exceptions/RecursionExceptionTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/exceptions/RecursionExceptionTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.LinkedHashSet; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; import nl.jqno.equalsverifier.testhelpers.types.Point; import org.junit.jupiter.api.Test; diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/CacheTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/CacheTest.java deleted file mode 100644 index 2d1ad46f0..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/CacheTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class CacheTest { - - private static final TypeTag STRING_TAG = new TypeTag(String.class); - private static final Tuple STRING_TUPLE = new Tuple<>("red", "blue", new String("red")); - private static final TypeTag INT_TAG = new TypeTag(int.class); - private static final Tuple INT_TUPLE = new Tuple<>(42, 1337, 42); - - private Cache cache = new Cache(); - - @Test - public void putAndGetTuple() { - cache.put( - STRING_TAG, - STRING_TUPLE.getRed(), - STRING_TUPLE.getBlue(), - STRING_TUPLE.getRedCopy() - ); - assertEquals(STRING_TUPLE, cache.getTuple(STRING_TAG)); - } - - @Test - public void putTwiceAndGetBoth() { - cache.put( - STRING_TAG, - STRING_TUPLE.getRed(), - STRING_TUPLE.getBlue(), - STRING_TUPLE.getRedCopy() - ); - cache.put(INT_TAG, INT_TUPLE.getRed(), INT_TUPLE.getBlue(), INT_TUPLE.getRedCopy()); - - assertEquals(INT_TUPLE, cache.getTuple(INT_TAG)); - assertEquals(STRING_TUPLE, cache.getTuple(STRING_TAG)); - } - - @Test - public void contains() { - cache.put( - STRING_TAG, - STRING_TUPLE.getRed(), - STRING_TUPLE.getBlue(), - STRING_TUPLE.getRedCopy() - ); - assertTrue(cache.contains(STRING_TAG)); - } - - @Test - public void doesntContain() { - assertFalse(cache.contains(STRING_TAG)); - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValuesTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValuesTest.java deleted file mode 100644 index 1602dc32b..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValuesTest.java +++ /dev/null @@ -1,364 +0,0 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; - -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; -import static nl.jqno.equalsverifier.internal.testhelpers.Util.defaultEquals; -import static nl.jqno.equalsverifier.internal.testhelpers.Util.defaultHashCode; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; -import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; -import nl.jqno.equalsverifier.testhelpers.types.Point; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class PrefabValuesTest { - - private static final TypeTag STRING_TAG = new TypeTag(String.class); - private static final TypeTag POINT_TAG = new TypeTag(Point.class); - private static final TypeTag INT_TAG = new TypeTag(int.class); - private static final TypeTag STRING_ARRAY_TAG = new TypeTag(String[].class); - - private FactoryCache factoryCache = new FactoryCache(); - private PrefabValues pv; - - @BeforeEach - public void setUp() { - factoryCache.put(String.class, new AppendingStringTestFactory()); - factoryCache.put(int.class, values(42, 1337, 42)); - pv = new PrefabValues(factoryCache); - } - - @Test - public void sanityTestFactoryIncreasesStringLength() { - AppendingStringTestFactory f = new AppendingStringTestFactory(); - assertEquals("r", f.createValues(null, null, null).getRed()); - assertEquals("rr", f.createValues(null, null, null).getRed()); - assertEquals("rrr", f.createValues(null, null, null).getRed()); - } - - @Test - public void giveRedFromFactory() { - assertEquals("r", pv.giveRed(STRING_TAG)); - } - - @Test - public void giveRedFromCache() { - pv.giveRed(STRING_TAG); - assertEquals("r", pv.giveRed(STRING_TAG)); - } - - @Test - public void giveBlueFromFactory() { - assertEquals("b", pv.giveBlue(STRING_TAG)); - } - - @Test - public void giveBlueFromCache() { - pv.giveBlue(STRING_TAG); - assertEquals("b", pv.giveBlue(STRING_TAG)); - } - - @Test - public void giveRedCopyFromFactory() { - assertEquals("r", pv.giveRedCopy(STRING_TAG)); - assertNotSame(pv.giveRed(STRING_TAG), pv.giveRedCopy(STRING_TAG)); - } - - @Test - public void giveRedCopyFromCache() { - pv.giveRedCopy(STRING_TAG); - assertEquals("r", pv.giveRedCopy(STRING_TAG)); - assertNotSame(pv.giveRed(STRING_TAG), pv.giveRedCopy(STRING_TAG)); - } - - @Test - public void giveRedFromFallbackFactory() { - Point actual = pv.giveRed(POINT_TAG); - assertEquals(new Point(42, 42), actual); - } - - @Test - public void giveBlueFromFallbackFactory() { - Point actual = pv.giveBlue(POINT_TAG); - assertEquals(new Point(1337, 1337), actual); - } - - @Test - public void giveRedCopyFromFallbackFactory() { - Point actual = pv.giveRedCopy(POINT_TAG); - assertEquals(new Point(42, 42), actual); - assertNotSame(pv.giveRed(POINT_TAG), actual); - } - - @Test - public void giveTuple() { - Tuple actual = pv.giveTuple(POINT_TAG); - assertEquals(Tuple.of(new Point(42, 42), new Point(1337, 1337), new Point(42, 42)), actual); - } - - @Test - public void giveOtherWhenValueIsKnown() { - Point red = pv.giveRed(POINT_TAG); - Point blue = pv.giveBlue(POINT_TAG); - assertEquals(blue, pv.giveOther(POINT_TAG, red)); - assertEquals(red, pv.giveOther(POINT_TAG, blue)); - } - - @Test - public void giveOtherWhenValueIsCloneOfKnown() { - Point red = new Point(42, 42); - Point blue = new Point(1337, 1337); - assertEquals(blue, pv.giveOther(POINT_TAG, red)); - assertEquals(red, pv.giveOther(POINT_TAG, blue)); - - // Sanity check - assertEquals(red, pv.giveRed(POINT_TAG)); - assertEquals(blue, pv.giveBlue(POINT_TAG)); - } - - @Test - public void giveOtherWhenValueIsUnknown() { - Point value = new Point(-1, -1); - Point expected = pv.giveRed(POINT_TAG); - assertEquals(expected, pv.giveOther(POINT_TAG, value)); - } - - @Test - public void giveOtherWhenValueIsPrimitive() { - int expected = pv.giveRed(INT_TAG); - assertEquals(expected, (int) pv.giveOther(INT_TAG, -10)); - } - - @Test - public void giveOtherWhenValueIsNull() { - Point expected = pv.giveRed(POINT_TAG); - assertEquals(expected, pv.giveOther(POINT_TAG, null)); - } - - @Test - public void giveOtherWhenValueIsNullAndTypeWouldThrowNpe() { - TypeTag tag = new TypeTag(NpeThrowing.class); - NpeThrowing expected = pv.giveRed(tag); - assertEquals(expected, pv.giveOther(tag, null)); - } - - @Test - public void giveOtherWhenValueIsKnownArray() { - String[] red = pv.giveRed(STRING_ARRAY_TAG); - String[] blue = pv.giveBlue(STRING_ARRAY_TAG); - assertArrayEquals(blue, pv.giveOther(STRING_ARRAY_TAG, red)); - assertArrayEquals(red, pv.giveOther(STRING_ARRAY_TAG, blue)); - } - - @Test - public void giveOtherWhenValueIsCloneOfKnownArray() { - String[] red = { "r" }; - String[] blue = { "b" }; - assertArrayEquals(blue, pv.giveOther(STRING_ARRAY_TAG, red)); - assertArrayEquals(red, pv.giveOther(STRING_ARRAY_TAG, blue)); - - // Sanity check - assertArrayEquals(red, pv.giveRed(STRING_ARRAY_TAG)); - assertArrayEquals(blue, pv.giveBlue(STRING_ARRAY_TAG)); - } - - @Test - public void giveOtherWhenValueIsUnknownArray() { - String[] value = { "hello world" }; - String[] expected = pv.giveRed(STRING_ARRAY_TAG); - assertArrayEquals(expected, pv.giveOther(STRING_ARRAY_TAG, value)); - } - - @Test - public void giveOtherWhenTagDoesntMatchValue() { - ExpectedException - .when(() -> pv.giveOther(POINT_TAG, "not a point")) - .assertThrows(ReflectionException.class) - .assertMessageContains("TypeTag does not match value."); - } - - @Test - public void fallbackDoesNotAffectStaticFields() { - int expected = StaticContainer.staticInt; - pv.giveRed(new TypeTag(StaticContainer.class)); - assertEquals(expected, StaticContainer.staticInt); - } - - @Test - public void stringListIsSeparateFromIntegerList() { - factoryCache.put(List.class, new ListTestFactory()); - pv = new PrefabValues(factoryCache); - - List strings = pv.giveRed(new TypeTag(List.class, STRING_TAG)); - List ints = pv.giveRed(new TypeTag(List.class, INT_TAG)); - - assertEquals("r", strings.get(0)); - assertEquals(42, (int) ints.get(0)); - } - - @Test - public void addingNullDoesntBreakAnything() { - factoryCache.put((Class) null, new ListTestFactory()); - } - - @Test - public void addingATypeTwiceOverrulesTheExistingOne() { - factoryCache.put(int.class, values(-1, -2, -1)); - pv = new PrefabValues(factoryCache); - assertEquals(-1, (int) pv.giveRed(INT_TAG)); - assertEquals(-2, (int) pv.giveBlue(INT_TAG)); - } - - @Test - public void addLazyFactoryWorks() { - TypeTag lazyTag = new TypeTag(Lazy.class); - factoryCache.put(Lazy.class.getName(), values(Lazy.X, Lazy.Y, Lazy.X)); - pv = new PrefabValues(factoryCache); - assertEquals(Lazy.X, pv.giveRed(lazyTag)); - assertEquals(Lazy.Y, pv.giveBlue(lazyTag)); - assertEquals(Lazy.X, pv.giveRedCopy(lazyTag)); - } - - @Test - public void addLazyFactoryIsLazy() { - TypeTag throwingLazyTag = new TypeTag(ThrowingLazy.class); - - // Shouldn't throw, because constructing PrefabValues doesn't instantiate objects: - factoryCache.put( - ThrowingLazy.class.getName(), - (t, p, ts) -> Tuple.of(ThrowingLazy.X, ThrowingLazy.Y, ThrowingLazy.X) - ); - pv = new PrefabValues(factoryCache); - - // Should throw, because `giveRed` does instantiate objects: - try { - pv.giveRed(throwingLazyTag); - fail("Expected an exception"); - } catch (Error e) { - // succeed - } - } - - public static class NpeThrowing { - - private final int i; - - public NpeThrowing(int i) { - this.i = i; - } - - public boolean equals(Object obj) { - if (obj == null) { - throw new NullPointerException(); - } - if (!(obj instanceof NpeThrowing)) { - return false; - } - return i == ((NpeThrowing) obj).i; - } - - public int hashCode() { - return i; - } - } - - private static class AppendingStringTestFactory implements PrefabValueFactory { - - private String red; - private String blue; - - public AppendingStringTestFactory() { - red = ""; - blue = ""; - } - - @Override - public Tuple createValues( - TypeTag tag, - PrefabValues prefabValues, - LinkedHashSet typeStack - ) { - red += "r"; - blue += "b"; - return new Tuple<>(red, blue, new String(red)); - } - } - - @SuppressWarnings("rawtypes") - private static final class ListTestFactory implements PrefabValueFactory { - - @Override - @SuppressWarnings("unchecked") - public Tuple createValues( - TypeTag tag, - PrefabValues prefabValues, - LinkedHashSet typeStack - ) { - TypeTag subtag = tag.getGenericTypes().get(0); - - List red = new ArrayList<>(); - red.add(prefabValues.giveRed(subtag)); - - List blue = new ArrayList<>(); - blue.add(prefabValues.giveBlue(subtag)); - - List redCopy = new ArrayList<>(); - redCopy.add(prefabValues.giveRed(subtag)); - - return new Tuple<>(red, blue, redCopy); - } - } - - private static final class StaticContainer { - - static int staticInt = 2; - - @SuppressWarnings("unused") - int regularInt = 3; - } - - @SuppressWarnings("unused") - public static class Lazy { - - public static final Lazy X = new Lazy(1); - public static final Lazy Y = new Lazy(2); - - private final int i; - - public Lazy(int i) { - this.i = i; - } - - @Override - public boolean equals(Object obj) { - return defaultEquals(this, obj); - } - - @Override - public int hashCode() { - return defaultHashCode(this); - } - - @Override - public String toString() { - return "Lazy: " + i; - } - } - - @SuppressWarnings("unused") - public static class ThrowingLazy { - { - // Throwing something that will immediately be thrown when the class is constructed. - if (true) { - throw new IllegalStateException("initializing"); - } - } - - public static final ThrowingLazy X = new ThrowingLazy(); - public static final ThrowingLazy Y = new ThrowingLazy(); - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorTest.java deleted file mode 100644 index dd3d6e7da..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassAccessorTest.java +++ /dev/null @@ -1,378 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; -import static org.junit.jupiter.api.Assertions.*; - -import java.lang.reflect.Field; -import java.util.HashSet; -import java.util.Set; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCache; -import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationCacheBuilder; -import nl.jqno.equalsverifier.internal.reflection.annotations.SupportedAnnotations; -import nl.jqno.equalsverifier.testhelpers.annotations.NonNull; -import nl.jqno.equalsverifier.testhelpers.types.ColorPoint3D; -import nl.jqno.equalsverifier.testhelpers.types.Point3D; -import nl.jqno.equalsverifier.testhelpers.types.PointContainer; -import nl.jqno.equalsverifier.testhelpers.types.RecursiveTypeHelper.TwoStepNodeA; -import nl.jqno.equalsverifier.testhelpers.types.RecursiveTypeHelper.TwoStepNodeB; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class ClassAccessorTest { - - private FactoryCache factoryCache; - private PrefabValues prefabValues; - private ClassAccessor pointContainerAccessor; - private ClassAccessor abstractEqualsAndHashCodeAccessor; - private ClassAccessor defaultValuesClassAccessor; - private AnnotationCache defaultValuesAnnotationCache; - - @BeforeEach - public void setup() { - factoryCache = JavaApiPrefabValues.build(); - prefabValues = new PrefabValues(factoryCache); - pointContainerAccessor = ClassAccessor.of(PointContainer.class, prefabValues); - abstractEqualsAndHashCodeAccessor = - ClassAccessor.of(AbstractEqualsAndHashCode.class, prefabValues); - defaultValuesClassAccessor = ClassAccessor.of(DefaultValues.class, prefabValues); - - defaultValuesAnnotationCache = new AnnotationCache(); - new AnnotationCacheBuilder(SupportedAnnotations.values(), new HashSet<>()) - .build(DefaultValues.class, defaultValuesAnnotationCache); - } - - /* Tests the false case. The true case is tested in {@link ClassAccessorCompilerTest}. */ - @Test - public void isRecord() { - assertFalse(pointContainerAccessor.isRecord()); - } - - /* Tests the false case. The true case is tested in {@link ClassAccessorSealedTest}. */ - @Test - public void isSealed() { - assertFalse(pointContainerAccessor.isSealed()); - } - - @Test - public void getType() { - assertSame(PointContainer.class, pointContainerAccessor.getType()); - } - - @Test - public void declaresField() throws Exception { - Field field = PointContainer.class.getDeclaredField("point"); - assertTrue(pointContainerAccessor.declaresField(field)); - } - - @Test - public void doesNotDeclareField() throws Exception { - ClassAccessor accessor = ClassAccessor.of(ColorPoint3D.class, prefabValues); - Field field = Point3D.class.getDeclaredField("z"); - assertFalse(accessor.declaresField(field)); - } - - @Test - public void declaresEquals() { - assertTrue(pointContainerAccessor.declaresEquals()); - assertTrue(abstractEqualsAndHashCodeAccessor.declaresEquals()); - } - - @Test - public void doesNotDeclareEquals() { - ClassAccessor accessor = ClassAccessor.of(Empty.class, prefabValues); - assertFalse(accessor.declaresEquals()); - } - - @Test - public void declaresHashCode() { - assertTrue(pointContainerAccessor.declaresHashCode()); - assertTrue(abstractEqualsAndHashCodeAccessor.declaresHashCode()); - } - - @Test - public void doesNotDeclareHashCode() { - ClassAccessor accessor = ClassAccessor.of(Empty.class, prefabValues); - assertFalse(accessor.declaresHashCode()); - } - - @Test - public void hasMethod() { - ClassAccessor accessor = ClassAccessor.of(MethodContainer.class, prefabValues); - assertTrue(accessor.hasMethod("m")); - } - - @Test - public void hasProtectedMethod() { - ClassAccessor accessor = ClassAccessor.of(MethodContainer.class, prefabValues); - assertTrue(accessor.hasMethod("m_protected")); - } - - @Test - public void hasMethodInSuper() { - ClassAccessor accessor = ClassAccessor.of(ChildOfMethodContainer.class, prefabValues); - assertTrue(accessor.hasMethod("m")); - } - - @Test - public void hasProtectedMethodInSuper() { - ClassAccessor accessor = ClassAccessor.of(ChildOfMethodContainer.class, prefabValues); - assertTrue(accessor.hasMethod("m_protected")); - } - - @Test - public void doesNotHaveMethod() { - ClassAccessor accessor = ClassAccessor.of(MethodContainer.class, prefabValues); - assertFalse(accessor.hasMethod("doesNotExist")); - } - - @Test - public void equalsIsNotAbstract() { - assertFalse(pointContainerAccessor.isEqualsAbstract()); - } - - @Test - public void equalsIsAbstract() { - assertTrue(abstractEqualsAndHashCodeAccessor.isEqualsAbstract()); - } - - @Test - public void hashCodeIsNotAbstract() { - assertFalse(pointContainerAccessor.isHashCodeAbstract()); - } - - @Test - public void hashCodeIsAbstract() { - assertTrue(abstractEqualsAndHashCodeAccessor.isHashCodeAbstract()); - } - - @Test - public void equalsIsInheritedFromObject() { - ClassAccessor accessor = ClassAccessor.of( - NoFieldsSubWithFields.class, - prefabValues - ); - assertTrue(accessor.isEqualsInheritedFromObject()); - } - - @Test - public void equalsIsNotInheritedFromObject() { - assertFalse(pointContainerAccessor.isEqualsInheritedFromObject()); - } - - @Test - public void getSuperAccessorForPojo() { - ClassAccessor superAccessor = - pointContainerAccessor.getSuperAccessor(); - assertEquals(Object.class, superAccessor.getType()); - } - - @Test - public void getSuperAccessorInHierarchy() { - ClassAccessor accessor = ClassAccessor.of(ColorPoint3D.class, prefabValues); - ClassAccessor superAccessor = accessor.getSuperAccessor(); - assertEquals(Point3D.class, superAccessor.getType()); - } - - @Test - public void getRedObject() { - assertObjectHasNoNullFields(pointContainerAccessor.getRedObject(TypeTag.NULL)); - } - - @Test - @SuppressWarnings("rawtypes") - public void getRedObjectGeneric() { - ClassAccessor accessor = ClassAccessor.of( - GenericTypeVariableListContainer.class, - prefabValues - ); - GenericTypeVariableListContainer foo = accessor.getRedObject( - new TypeTag(GenericTypeVariableListContainer.class, new TypeTag(String.class)) - ); - assertEquals(String.class, foo.tList.get(0).getClass()); - } - - @Test - public void getRedAccessor() { - PointContainer foo = pointContainerAccessor.getRedObject(TypeTag.NULL); - ObjectAccessor objectAccessor = pointContainerAccessor.getRedAccessor( - TypeTag.NULL - ); - assertEquals(foo, objectAccessor.get()); - } - - @Test - public void getBlueObject() { - assertObjectHasNoNullFields(pointContainerAccessor.getBlueObject(TypeTag.NULL)); - } - - @Test - @SuppressWarnings("rawtypes") - public void getBlueObjectGeneric() { - ClassAccessor accessor = ClassAccessor.of( - GenericTypeVariableListContainer.class, - prefabValues - ); - GenericTypeVariableListContainer foo = accessor.getBlueObject( - new TypeTag(GenericTypeVariableListContainer.class, new TypeTag(String.class)) - ); - assertEquals(String.class, foo.tList.get(0).getClass()); - } - - @Test - public void getBlueAccessor() { - PointContainer foo = pointContainerAccessor.getBlueObject(TypeTag.NULL); - ObjectAccessor objectAccessor = pointContainerAccessor.getBlueAccessor( - TypeTag.NULL - ); - assertEquals(foo, objectAccessor.get()); - } - - @Test - public void redAndBlueNotEqual() { - PointContainer red = pointContainerAccessor.getRedObject(TypeTag.NULL); - PointContainer blue = pointContainerAccessor.getBlueObject(TypeTag.NULL); - assertFalse(red.equals(blue)); - } - - @Test - public void getDefaultValuesAccessor_withNoNonnullValues() { - ObjectAccessor objectAccessor = - defaultValuesClassAccessor.getDefaultValuesAccessor( - TypeTag.NULL, - false, - false, - new HashSet<>(), - defaultValuesAnnotationCache - ); - DefaultValues foo = objectAccessor.get(); - assertNull(foo.s); - // The rest is tested in getDefaultValuesObject - } - - @Test - public void getDefaultValuesAccessor_withOneNonnullValue() { - Set nonnullFields = new HashSet<>(); - nonnullFields.add("s"); - ObjectAccessor objectAccessor = - defaultValuesClassAccessor.getDefaultValuesAccessor( - TypeTag.NULL, - false, - false, - nonnullFields, - defaultValuesAnnotationCache - ); - DefaultValues foo = objectAccessor.get(); - assertNotNull(foo.s); - // The rest is tested in getDefaultValuesObject - } - - @Test - public void getDefaultValuesAccessor_whenNullWarningIsSuppressed() { - ObjectAccessor objectAccessor = - defaultValuesClassAccessor.getDefaultValuesAccessor( - TypeTag.NULL, - true, - false, - new HashSet<>(), - defaultValuesAnnotationCache - ); - DefaultValues foo = objectAccessor.get(); - assertNotNull(foo.s); - // The rest is tested in getDefaultValuesObject - } - - @Test - public void getDefaultValuesAccessor_whenZeroWarningIsSuppressed() { - ObjectAccessor objectAccessor = - defaultValuesClassAccessor.getDefaultValuesAccessor( - TypeTag.NULL, - false, - true, - new HashSet<>(), - defaultValuesAnnotationCache - ); - DefaultValues foo = objectAccessor.get(); - assertNotEquals(0, foo.i); - // The rest is tested in getDefaultValuesObject - } - - @Test - public void getDefaultValuesAccessor_objectContent() { - ClassAccessor accessor = ClassAccessor.of(DefaultValues.class, prefabValues); - DefaultValues foo = accessor - .getDefaultValuesAccessor( - TypeTag.NULL, - false, - false, - new HashSet<>(), - defaultValuesAnnotationCache - ) - .get(); - assertEquals(0, foo.i); - assertNull(foo.s); - assertNotNull(foo.t); - } - - @Test - public void instantiateAllTypes() { - ClassAccessor.of(AllTypesContainer.class, prefabValues).getRedObject(TypeTag.NULL); - } - - @Test - public void instantiateArrayTypes() { - ClassAccessor.of(AllArrayTypesContainer.class, prefabValues).getRedObject(TypeTag.NULL); - } - - @Test - public void instantiateRecursiveTypeUsingPrefabValue() { - factoryCache.put( - TwoStepNodeB.class, - values(new TwoStepNodeB(), new TwoStepNodeB(), new TwoStepNodeB()) - ); - prefabValues = new PrefabValues(factoryCache); - ClassAccessor.of(TwoStepNodeA.class, prefabValues).getRedObject(TypeTag.NULL); - } - - @Test - public void instantiateInterfaceField() { - ClassAccessor.of(InterfaceContainer.class, prefabValues).getRedObject(TypeTag.NULL); - } - - @Test - public void instantiateAbstractClassField() { - ClassAccessor.of(AbstractClassContainer.class, prefabValues).getRedObject(TypeTag.NULL); - } - - @Test - public void anInvalidTypeShouldNotThrowAnExceptionUponCreation() { - ClassAccessor.of(null, prefabValues); - } - - private void assertObjectHasNoNullFields(PointContainer foo) { - assertNotNull(foo); - assertNotNull(foo.getPoint()); - } - - static class DefaultValues { - - int i; - String s; - - @NonNull - String t; - } - - static class MethodContainer { - - public void m() {} - - protected void m_protected() {} - } - - static class ChildOfMethodContainer extends MethodContainer {} -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassProbeTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassProbeTest.java new file mode 100644 index 000000000..4c5ed75db --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/ClassProbeTest.java @@ -0,0 +1,148 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static org.junit.jupiter.api.Assertions.*; + +import nl.jqno.equalsverifier.testhelpers.types.ColorPoint3D; +import nl.jqno.equalsverifier.testhelpers.types.Point3D; +import nl.jqno.equalsverifier.testhelpers.types.PointContainer; +import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.AbstractEqualsAndHashCode; +import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.Empty; +import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.NoFieldsSubWithFields; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ClassProbeTest { + + private ClassProbe pointProbe; + private ClassProbe abstractProbe; + + @BeforeEach + public void setup() { + pointProbe = new ClassProbe<>(PointContainer.class); + abstractProbe = new ClassProbe<>(AbstractEqualsAndHashCode.class); + } + + @Test + public void getType() { + assertSame(PointContainer.class, pointProbe.getType()); + } + + /* Tests the false case. The true case is tested in {@link ClassProbeCompilerTest}. */ + @Test + public void isRecord() { + assertFalse(pointProbe.isRecord()); + } + + /* Tests the false case. The true case is tested in {@link ClassProbeSealedTest}. */ + @Test + public void isSealed() { + assertFalse(pointProbe.isSealed()); + } + + @Test + public void declaresEquals() { + assertTrue(pointProbe.declaresEquals()); + assertTrue(abstractProbe.declaresEquals()); + } + + @Test + public void doesNotDeclareEquals() { + ClassProbe accessor = new ClassProbe<>(Empty.class); + assertFalse(accessor.declaresEquals()); + } + + @Test + public void declaresHashCode() { + assertTrue(pointProbe.declaresHashCode()); + assertTrue(abstractProbe.declaresHashCode()); + } + + @Test + public void doesNotDeclareHashCode() { + ClassProbe accessor = new ClassProbe<>(Empty.class); + assertFalse(accessor.declaresHashCode()); + } + + @Test + public void hasMethod() { + ClassProbe accessor = new ClassProbe<>(MethodContainer.class); + assertTrue(accessor.hasMethod("m")); + } + + @Test + public void hasProtectedMethod() { + ClassProbe accessor = new ClassProbe<>(MethodContainer.class); + assertTrue(accessor.hasMethod("m_protected")); + } + + @Test + public void hasMethodInSuper() { + ClassProbe accessor = new ClassProbe<>(ChildOfMethodContainer.class); + assertTrue(accessor.hasMethod("m")); + } + + @Test + public void hasProtectedMethodInSuper() { + ClassProbe accessor = new ClassProbe<>(ChildOfMethodContainer.class); + assertTrue(accessor.hasMethod("m_protected")); + } + + @Test + public void doesNotHaveMethod() { + ClassProbe accessor = new ClassProbe<>(MethodContainer.class); + assertFalse(accessor.hasMethod("doesNotExist")); + } + + @Test + public void equalsIsNotAbstract() { + assertFalse(pointProbe.isEqualsAbstract()); + } + + @Test + public void equalsIsAbstract() { + assertTrue(abstractProbe.isEqualsAbstract()); + } + + @Test + public void hashCodeIsNotAbstract() { + assertFalse(pointProbe.isHashCodeAbstract()); + } + + @Test + public void hashCodeIsAbstract() { + assertTrue(abstractProbe.isHashCodeAbstract()); + } + + @Test + public void equalsIsInheritedFromObject() { + ClassProbe accessor = new ClassProbe<>(NoFieldsSubWithFields.class); + assertTrue(accessor.isEqualsInheritedFromObject()); + } + + @Test + public void equalsIsNotInheritedFromObject() { + assertFalse(pointProbe.isEqualsInheritedFromObject()); + } + + @Test + public void getSuperAccessorForPojo() { + ClassProbe superAccessor = pointProbe.getSuperProbe(); + assertEquals(Object.class, superAccessor.getType()); + } + + @Test + public void getSuperAccessorInHierarchy() { + ClassProbe accessor = new ClassProbe<>(ColorPoint3D.class); + ClassProbe superAccessor = accessor.getSuperProbe(); + assertEquals(Point3D.class, superAccessor.getType()); + } + + static class MethodContainer { + + public void m() {} + + protected void m_protected() {} + } + + static class ChildOfMethodContainer extends MethodContainer {} +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/FactoryCacheTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FactoryCacheTest.java similarity index 65% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/FactoryCacheTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FactoryCacheTest.java index 42fdad4a7..bbe20d280 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/FactoryCacheTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FactoryCacheTest.java @@ -1,9 +1,9 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; import static org.junit.jupiter.api.Assertions.*; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.SimpleFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.SimpleFactory; import org.junit.jupiter.api.Test; public class FactoryCacheTest { @@ -54,4 +54,22 @@ public void contains() { public void doesntContain() { assertFalse(cache.contains(STRING_CLASS)); } + + @Test + public void merge() { + FactoryCache a = new FactoryCache(); + a.put(STRING_CLASS, STRING_FACTORY); + + FactoryCache b = new FactoryCache(); + b.put(INT_CLASS, INT_FACTORY); + + FactoryCache combined = a.merge(b); + + assertTrue(combined.contains(STRING_CLASS)); + assertTrue(combined.contains(INT_CLASS)); + + assertFalse(a == combined); + assertFalse(a.contains(INT_CLASS)); + assertFalse(b.contains(STRING_CLASS)); + } } diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldAccessorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldAccessorTest.java deleted file mode 100644 index 0eaa74b37..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldAccessorTest.java +++ /dev/null @@ -1,154 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static org.junit.jupiter.api.Assertions.*; - -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.*; -import org.junit.jupiter.api.Test; - -public class FieldAccessorTest { - - private static final String FIELD_NAME = "field"; - - @Test - public void getField() throws NoSuchFieldException { - ObjectContainer foo = new ObjectContainer(); - Field field = foo.getClass().getDeclaredField(FIELD_NAME); - FieldAccessor fieldAccessor = FieldAccessor.of(field); - assertSame(field, fieldAccessor.getField()); - } - - @Test - public void getFieldType() { - ObjectContainer foo = new ObjectContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertEquals(Object.class, fieldAccessor.getFieldType()); - } - - @Test - public void getFieldName() { - ObjectContainer foo = new ObjectContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertEquals(FIELD_NAME, fieldAccessor.getFieldName()); - } - - @Test - public void isNotPrimitive() { - ObjectContainer foo = new ObjectContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertFalse(fieldAccessor.fieldIsPrimitive()); - } - - @Test - public void isPrimitive() { - PrimitiveContainer foo = new PrimitiveContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertTrue(fieldAccessor.fieldIsPrimitive()); - } - - @Test - public void isNotFinal() { - ObjectContainer foo = new ObjectContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertFalse(fieldAccessor.fieldIsFinal()); - } - - @Test - public void isFinal() { - FinalContainer foo = new FinalContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertTrue(fieldAccessor.fieldIsFinal()); - } - - @Test - public void isNotStatic() { - ObjectContainer foo = new ObjectContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertFalse(fieldAccessor.fieldIsStatic()); - } - - @Test - public void isStatic() { - StaticContainer foo = new StaticContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertTrue(fieldAccessor.fieldIsStatic()); - } - - @Test - public void isNotTransient() { - ObjectContainer foo = new ObjectContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertFalse(fieldAccessor.fieldIsTransient()); - } - - @Test - public void isTransient() { - TransientContainer foo = new TransientContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertTrue(fieldAccessor.fieldIsTransient()); - } - - @Test - public void isNotEnum() { - PrimitiveContainer foo = new PrimitiveContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, FIELD_NAME); - assertFalse(fieldAccessor.fieldIsEmptyOrSingleValueEnum()); - } - - @Test - public void isEnumButNotSingleValue() { - EnumContainer foo = new EnumContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, "twoElementEnum"); - assertFalse(fieldAccessor.fieldIsEmptyOrSingleValueEnum()); - } - - @Test - public void isSingleValueEnum() { - EnumContainer foo = new EnumContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, "oneElementEnum"); - assertTrue(fieldAccessor.fieldIsEmptyOrSingleValueEnum()); - } - - @Test - public void isEmptyEnum() { - EnumContainer foo = new EnumContainer(); - FieldAccessor fieldAccessor = getAccessorFor(foo, "emptyEnum"); - assertTrue(fieldAccessor.fieldIsEmptyOrSingleValueEnum()); - } - - @Test - public void getValuePrimitive() { - PrimitiveContainer foo = new PrimitiveContainer(); - foo.field = 10; - Object value = getValue(foo, "field"); - assertEquals(10, value); - } - - @Test - public void getValueObject() { - Object object = new Object(); - ObjectContainer foo = new ObjectContainer(); - foo.field = object; - Object value = getValue(foo, FIELD_NAME); - assertEquals(object, value); - } - - @Test - public void getPrivateValue() { - PrivateObjectContainer foo = new PrivateObjectContainer(); - getValue(foo, FIELD_NAME); - } - - private Object getValue(Object object, String fieldName) { - return getAccessorFor(object, fieldName).get(object); - } - - private FieldAccessor getAccessorFor(Object object, String fieldName) { - try { - Field field = object.getClass().getDeclaredField(fieldName); - return FieldAccessor.of(field); - } catch (NoSuchFieldException e) { - throw new IllegalArgumentException("fieldName: " + fieldName); - } - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldCacheTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldCacheTest.java new file mode 100644 index 000000000..5d09df19a --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldCacheTest.java @@ -0,0 +1,61 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; + +public class FieldCacheTest { + + private String stringField = "string"; + private final Tuple stringValues = new Tuple<>("red", "blue", "red"); + + private String intField = "int"; + private final Tuple intValues = new Tuple<>(1, 2, 1); + + private FieldCache cache = new FieldCache(); + + @Test + public void putAndGetTuple() { + cache.put(stringField, stringValues); + assertEquals(stringValues, cache.get(stringField)); + } + + @Test + public void putTwiceAndGetBoth() { + cache.put(stringField, stringValues); + cache.put(intField, intValues); + + assertEquals(intValues, cache.get(intField)); + assertEquals(stringValues, cache.get(stringField)); + } + + @Test + public void putNullAndGetNothingBack() { + cache.put(null, stringValues); + assertNull(cache.get(null)); + } + + @Test + public void contains() { + cache.put(stringField, stringValues); + assertTrue(cache.contains(stringField)); + } + + @Test + public void doesntContain() { + assertFalse(cache.contains(stringField)); + } + + @Test + public void getFieldNames() { + assertEquals(Collections.emptySet(), cache.getFieldNames()); + + cache.put(stringField, stringValues); + Set expected = new HashSet<>(); + expected.add(stringField); + assertEquals(expected, cache.getFieldNames()); + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldModifierTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldModifierTest.java deleted file mode 100644 index 544b73ad3..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldModifierTest.java +++ /dev/null @@ -1,371 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.testhelpers.types.Point; -import nl.jqno.equalsverifier.testhelpers.types.PointContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.AbstractAndInterfaceArrayContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.AbstractClassContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.AllArrayTypesContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.AllTypesContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.GenericListContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.GenericTypeVariableListContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.InterfaceContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.ObjectContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.Outer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.Outer.Inner; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.PointArrayContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.PrimitiveContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.PrivateObjectContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.StaticContainer; -import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.StaticFinalContainer; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class FieldModifierTest { - - private static final Point RED_NEW_POINT = new Point(10, 20); - private static final Point BLUE_NEW_POINT = new Point(20, 10); - private static final Point REDCOPY_NEW_POINT = new Point(10, 20); - private static final String FIELD_NAME = "field"; - - private PrefabValues prefabValues; - - @BeforeEach - public void setup() { - FactoryCache factoryCache = JavaApiPrefabValues.build(); - factoryCache.put(Point.class, values(RED_NEW_POINT, BLUE_NEW_POINT, REDCOPY_NEW_POINT)); - prefabValues = new PrefabValues(factoryCache); - } - - @Test - public void setValuePrimitive() { - PrimitiveContainer foo = new PrimitiveContainer(); - setField(foo, FIELD_NAME, 20); - assertEquals(20, foo.field); - } - - @Test - public void setValueObject() { - Object object = new Object(); - ObjectContainer foo = new ObjectContainer(); - setField(foo, FIELD_NAME, object); - assertEquals(object, foo.field); - } - - @Test - public void defaultFieldOnObjectSetsNull() { - ObjectContainer foo = new ObjectContainer(); - foo.field = new Object(); - doNullField(foo, FIELD_NAME); - assertNull(foo.field); - } - - @Test - public void defaultFieldOnArraySetsNull() { - AllTypesContainer foo = new AllTypesContainer(); - foo._array = new int[] { 1, 2, 3 }; - doNullField(foo, "_array"); - assertNull(foo._array); - } - - @Test - public void defaultFieldOnBooleanSetsFalse() { - AllTypesContainer foo = new AllTypesContainer(); - foo._boolean = true; - doNullField(foo, "_boolean"); - assertEquals(false, foo._boolean); - } - - @Test - public void defaultFieldOnByteSetsZero() { - AllTypesContainer foo = new AllTypesContainer(); - foo._byte = 10; - doNullField(foo, "_byte"); - assertEquals(0, foo._byte); - } - - @Test - public void defaultFieldOnCharSetsZero() { - AllTypesContainer foo = new AllTypesContainer(); - foo._char = 'a'; - doNullField(foo, "_char"); - assertEquals('\u0000', foo._char); - } - - @Test - public void defaultFieldOnDoubleSetsZero() { - AllTypesContainer foo = new AllTypesContainer(); - foo._double = 1.1; - doNullField(foo, "_double"); - assertEquals(0.0, foo._double, 0.0000001); - } - - @Test - public void defaultFieldOnFloatSetsZero() { - AllTypesContainer foo = new AllTypesContainer(); - foo._float = 1.1f; - doNullField(foo, "_float"); - assertEquals(0.0f, foo._float, 0.0000001); - } - - @Test - public void defaultFieldOnIntSetsZero() { - AllTypesContainer foo = new AllTypesContainer(); - foo._int = 10; - doNullField(foo, "_int"); - assertEquals(0, foo._int); - } - - @Test - public void defaultFieldOnLongSetsZero() { - AllTypesContainer foo = new AllTypesContainer(); - foo._long = 10; - doNullField(foo, "_long"); - assertEquals(0, foo._long); - } - - @Test - public void defaultFieldOnShortSetsZero() { - AllTypesContainer foo = new AllTypesContainer(); - foo._short = 10; - doNullField(foo, "_short"); - assertEquals(0, foo._short); - } - - @SuppressWarnings("static-access") - @Test - public void defaultFieldOnPrimitiveStaticFinalIsNoOp() { - StaticFinalContainer foo = new StaticFinalContainer(); - doNullField(foo, "CONST"); - assertEquals(42, foo.CONST); - } - - @SuppressWarnings("static-access") - @Test - public void defaultFieldOnObjectStaticFinalIsNoOp() { - StaticFinalContainer foo = new StaticFinalContainer(); - Object original = foo.OBJECT; - doNullField(foo, "OBJECT"); - assertSame(original, foo.OBJECT); - } - - @Test - public void defaultFieldOnSyntheticIsNoOp() { - Outer outer = new Outer(); - Inner inner = outer.new Inner(); - String fieldName = getSyntheticFieldName(inner, "this$"); - doNullField(inner, fieldName); - assertSame(outer, inner.getOuter()); - } - - @Test - public void defaultPrivateField() { - PrivateObjectContainer foo = new PrivateObjectContainer(); - doNullField(foo, FIELD_NAME); - assertNull(foo.get()); - } - - @Test - public void defaultStaticField() { - StaticContainer foo = new StaticContainer(); - getAccessorFor(foo, "field").defaultStaticField(); - assertNull(StaticContainer.field); - } - - @Test - public void copyToPrimitiveField() { - int value = 10; - - PrimitiveContainer from = new PrimitiveContainer(); - from.field = value; - - PrimitiveContainer to = new PrimitiveContainer(); - doCopyField(to, from, FIELD_NAME); - - assertEquals(value, to.field); - } - - @Test - public void copyToObjectField() { - Object value = new Object(); - - ObjectContainer from = new ObjectContainer(); - from.field = value; - - ObjectContainer to = new ObjectContainer(); - doCopyField(to, from, FIELD_NAME); - - assertSame(value, to.field); - } - - @Test - public void changeField() { - AllTypesContainer reference = new AllTypesContainer(); - AllTypesContainer changed = new AllTypesContainer(); - assertTrue(reference.equals(changed)); - - for (Field field : FieldIterable.of(AllTypesContainer.class)) { - FieldModifier.of(field, changed).changeField(prefabValues, TypeTag.NULL); - assertFalse(reference.equals(changed), "On field: " + field.getName()); - FieldModifier.of(field, reference).changeField(prefabValues, TypeTag.NULL); - assertTrue(reference.equals(changed), "On field: " + field.getName()); - } - } - - @SuppressWarnings("static-access") - @Test - public void changeFieldOnPrimitiveStaticFinalIsNoOp() { - StaticFinalContainer foo = new StaticFinalContainer(); - doChangeField(foo, "CONST"); - assertEquals(42, foo.CONST); - } - - @SuppressWarnings("static-access") - @Test - public void changeFieldStaticFinal() throws SecurityException { - StaticFinalContainer foo = new StaticFinalContainer(); - Object original = foo.OBJECT; - doChangeField(foo, "OBJECT"); - assertEquals(original, foo.OBJECT); - } - - @Test - public void changeAbstractField() { - AbstractClassContainer foo = new AbstractClassContainer(); - doChangeField(foo, FIELD_NAME); - assertNotNull(foo.field); - } - - @Test - public void changeInterfaceField() { - InterfaceContainer foo = new InterfaceContainer(); - doChangeField(foo, FIELD_NAME); - assertNotNull(foo.field); - } - - @Test - public void changeArrayField() { - AllArrayTypesContainer reference = new AllArrayTypesContainer(); - AllArrayTypesContainer changed = new AllArrayTypesContainer(); - assertTrue(reference.equals(changed)); - - for (Field field : FieldIterable.of(AllArrayTypesContainer.class)) { - FieldModifier.of(field, changed).changeField(prefabValues, TypeTag.NULL); - assertFalse(reference.equals(changed), "On field: " + field.getName()); - FieldModifier.of(field, reference).changeField(prefabValues, TypeTag.NULL); - assertTrue(reference.equals(changed), "On field: " + field.getName()); - } - } - - @Test - public void changeAbstractArrayField() { - AbstractAndInterfaceArrayContainer foo = new AbstractAndInterfaceArrayContainer(); - doChangeField(foo, "abstractClasses"); - assertNotNull(foo.abstractClasses[0]); - } - - @Test - public void changeInterfaceArrayField() { - AbstractAndInterfaceArrayContainer foo = new AbstractAndInterfaceArrayContainer(); - doChangeField(foo, "interfaces"); - assertNotNull(foo.interfaces[0]); - } - - @Test - public void changeGenericField() { - GenericListContainer foo = new GenericListContainer(); - doChangeField(foo, "stringList"); - doChangeField(foo, "integerList"); - assertNotEquals(foo.stringList, foo.integerList); - } - - @Test - public void changeTypeVariableGenericField() { - GenericTypeVariableListContainer foo = new GenericTypeVariableListContainer<>(); - doChangeField( - foo, - "tList", - new TypeTag(GenericTypeVariableListContainer.class, new TypeTag(String.class)) - ); - assertFalse(foo.tList.isEmpty()); - } - - @Test - public void addPrefabValues() { - PointContainer foo = new PointContainer(new Point(1, 2)); - - doChangeField(foo, "point"); - assertEquals(RED_NEW_POINT, foo.getPoint()); - - doChangeField(foo, "point"); - assertEquals(BLUE_NEW_POINT, foo.getPoint()); - - doChangeField(foo, "point"); - assertEquals(RED_NEW_POINT, foo.getPoint()); - } - - @Test - public void addPrefabArrayValues() { - PointArrayContainer foo = new PointArrayContainer(); - - doChangeField(foo, "points"); - assertEquals(RED_NEW_POINT, foo.points[0]); - - doChangeField(foo, "points"); - assertEquals(BLUE_NEW_POINT, foo.points[0]); - - doChangeField(foo, "points"); - assertEquals(RED_NEW_POINT, foo.points[0]); - } - - private void setField(Object object, String fieldName, Object value) { - getAccessorFor(object, fieldName).set(value); - } - - private void doNullField(Object object, String fieldName) { - getAccessorFor(object, fieldName).defaultField(); - } - - private void doCopyField(Object to, Object from, String fieldName) { - getAccessorFor(from, fieldName).copyTo(to); - } - - private void doChangeField(Object object, String fieldName) { - doChangeField(object, fieldName, TypeTag.NULL); - } - - private void doChangeField(Object object, String fieldName, TypeTag enclosingType) { - getAccessorFor(object, fieldName).changeField(prefabValues, enclosingType); - } - - private FieldModifier getAccessorFor(Object object, String fieldName) { - try { - Field field = object.getClass().getDeclaredField(fieldName); - return FieldModifier.of(field, object); - } catch (NoSuchFieldException e) { - throw new IllegalArgumentException("fieldName: " + fieldName); - } - } - - private String getSyntheticFieldName(Object object, String prefix) { - for (Field field : object.getClass().getDeclaredFields()) { - if (field.getName().startsWith(prefix)) { - return field.getName(); - } - } - throw new IllegalStateException("Cannot find internal field starting with " + prefix); - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldMutatorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldMutatorTest.java new file mode 100644 index 000000000..d6ec87c5d --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldMutatorTest.java @@ -0,0 +1,65 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class FieldMutatorTest { + + private FieldProbe p; + private FieldMutator sut; + private Container o = new Container(); + + @Test + public void setPrimitive() throws NoSuchFieldException { + p = FieldProbe.of(Container.class.getDeclaredField("i")); + sut = new FieldMutator(p); + + assertEquals(10, o.i); + sut.setNewValue(o, 1337); + assertEquals(1337, o.i); + } + + @Test + public void setObject() throws NoSuchFieldException { + p = FieldProbe.of(Container.class.getDeclaredField("s")); + sut = new FieldMutator(p); + + assertEquals("NON-FINAL", o.s); + sut.setNewValue(o, "changed"); + assertEquals("changed", o.s); + } + + @Test + public void dontSetConstantPrimitive() throws NoSuchFieldException { + p = FieldProbe.of(Container.class.getDeclaredField("FINAL_INT")); + sut = new FieldMutator(p); + + assertEquals(42, Container.FINAL_INT); + sut.setNewValue(o, 1337); + assertEquals(42, Container.FINAL_INT); + } + + @Test + public void dontSetConstantObject() throws NoSuchFieldException { + p = FieldProbe.of(Container.class.getDeclaredField("FINAL_STRING")); + sut = new FieldMutator(p); + + assertEquals("FINAL", Container.FINAL_STRING); + sut.setNewValue(o, "changed"); + assertEquals("FINAL", Container.FINAL_STRING); + } + + static class Container { + + private static final int FINAL_INT = 42; + private static final String FINAL_STRING = "FINAL"; + private final int i; + private final String s; + + public Container() { + this.i = 10; + this.s = "NON-FINAL"; + } + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldProbeTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldProbeTest.java new file mode 100644 index 000000000..c7f23c7a1 --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldProbeTest.java @@ -0,0 +1,208 @@ +package nl.jqno.equalsverifier.internal.reflection; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Field; +import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.ConfigurationHelper; +import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.*; +import org.junit.jupiter.api.Test; + +/* + * FieldProbe.isAnnotatedNonnull() is tested in AnnotationNonnullTest + */ +public class FieldProbeTest { + + private static final String FIELD_NAME = "field"; + private Configuration config = ConfigurationHelper.emptyConfiguration(ObjectContainer.class); + + @Test + public void getField() throws NoSuchFieldException { + ObjectContainer foo = new ObjectContainer(); + Field field = foo.getClass().getDeclaredField(FIELD_NAME); + FieldProbe probe = FieldProbe.of(field); + assertSame(field, probe.getField()); + } + + @Test + public void getType() { + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertEquals(Object.class, probe.getType()); + } + + @Test + public void getName() { + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertEquals(FIELD_NAME, probe.getName()); + } + + @Test + public void isNotPrimitive() { + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertFalse(probe.isPrimitive()); + } + + @Test + public void isPrimitive() { + FieldProbe probe = getProbeFor(PrimitiveContainer.class, FIELD_NAME); + assertTrue(probe.isPrimitive()); + } + + @Test + public void isNotFinal() { + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertFalse(probe.isFinal()); + } + + @Test + public void isFinal() { + FieldProbe probe = getProbeFor(FinalContainer.class, FIELD_NAME); + assertTrue(probe.isFinal()); + } + + @Test + public void isNotStatic() { + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertFalse(probe.isStatic()); + } + + @Test + public void isStatic() { + FieldProbe probe = getProbeFor(StaticContainer.class, FIELD_NAME); + assertTrue(probe.isStatic()); + } + + @Test + public void isNotTransient() { + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertFalse(probe.isTransient()); + } + + @Test + public void isTransient() { + FieldProbe probe = getProbeFor(TransientContainer.class, FIELD_NAME); + assertTrue(probe.isTransient()); + } + + @Test + public void isNotEnum() { + FieldProbe probe = getProbeFor(PrimitiveContainer.class, FIELD_NAME); + assertFalse(probe.isEmptyOrSingleValueEnum()); + } + + @Test + public void isEnumButNotSingleValue() { + FieldProbe probe = getProbeFor(EnumContainer.class, "twoElementEnum"); + assertFalse(probe.isEmptyOrSingleValueEnum()); + } + + @Test + public void isSingleValueEnum() { + FieldProbe probe = getProbeFor(EnumContainer.class, "oneElementEnum"); + assertTrue(probe.isEmptyOrSingleValueEnum()); + } + + @Test + public void isEmptyEnum() { + FieldProbe probe = getProbeFor(EnumContainer.class, "emptyEnum"); + assertTrue(probe.isEmptyOrSingleValueEnum()); + } + + @Test + public void canBeDefault_forObject() { + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertTrue(probe.canBeDefault(config)); + } + + @Test + public void canBeDefault_primitive() { + FieldProbe probe = getProbeFor(PrimitiveContainer.class, FIELD_NAME); + assertTrue(probe.canBeDefault(config)); + } + + @Test + public void canBeDefault_primitiveWithPrefabbedField() { + config = + ConfigurationHelper.emptyConfigurationWithPrefabbedFields( + PrimitiveContainer.class, + FIELD_NAME + ); + FieldProbe probe = getProbeFor(PrimitiveContainer.class, FIELD_NAME); + assertFalse(probe.canBeDefault(config)); + } + + @Test + public void canBeDefault_isMentionedExplicitly() { + config = + ConfigurationHelper.emptyConfigurationWithNonnullFields( + ObjectContainer.class, + FIELD_NAME + ); + FieldProbe probe = getProbeFor(ObjectContainer.class, FIELD_NAME); + assertFalse(probe.canBeDefault(config)); + } + + @Test + public void canBeDefault_annotated() { + config = + ConfigurationHelper.emptyConfigurationWithNonnullFields( + NonNullContainer.class, + FIELD_NAME + ); + FieldProbe probe = getProbeFor(NonNullContainer.class, FIELD_NAME); + assertFalse(probe.canBeDefault(config)); + } + + @Test + public void canBeModifiedReflectively_synthetic() { + FieldProbe probe = getProbeFor(NonStaticInner.class, "this$0"); + assertFalse(probe.canBeModifiedReflectively()); + } + + @Test + public void canBeModifiedReflectively_staticFinal() { + FieldProbe publicStaticFinal = getProbeFor(ModifierMix.class, "PUBLIC_STATIC_FINAL"); + assertFalse(publicStaticFinal.canBeModifiedReflectively()); + } + + @Test + public void canBeModifiedReflectively_static() { + FieldProbe publicStatic = getProbeFor(ModifierMix.class, "publicStatic"); + assertTrue(publicStatic.canBeModifiedReflectively()); + } + + @Test + public void canBeModifiedReflectively_final() { + FieldProbe publicFinal = getProbeFor(ModifierMix.class, "publicFinal"); + assertTrue(publicFinal.canBeModifiedReflectively()); + } + + @Test + public void canBeModifiedReflectively_noModifiers() { + FieldProbe publicNothingElse = getProbeFor(ModifierMix.class, "publicNothingElse"); + assertTrue(publicNothingElse.canBeModifiedReflectively()); + } + + private FieldProbe getProbeFor(Class type, String fieldName) { + try { + Field field = type.getDeclaredField(fieldName); + return FieldProbe.of(field); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException("fieldName: " + fieldName); + } + } + + class NonStaticInner {} + + static class ModifierMix { + + public static final int PUBLIC_STATIC_FINAL = -1; + public static int publicStatic = 1; + public final int publicFinal; + public int publicNothingElse; + + public ModifierMix(int alsoYes) { + this.publicFinal = alsoYes; + } + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorModificationTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorModificationTest.java deleted file mode 100644 index 90a6ca690..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorModificationTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; - -import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class InPlaceObjectAccessorModificationTest { - - private static final int INITIAL_INT = 42; - private static final boolean INITIAL_BOOLEAN = true; - private static final String INITIAL_STRING = "hello"; - private static final Object INITIAL_OBJECT = new Object(); - - private PrefabValues prefabValues; - private Modifiable m; - private ObjectAccessor accessor; - private Field stringField; - - @BeforeEach - public void setUp() throws Exception { - prefabValues = new PrefabValues(JavaApiPrefabValues.build()); - m = new Modifiable(INITIAL_INT, INITIAL_BOOLEAN, INITIAL_STRING, INITIAL_OBJECT); - accessor = new InPlaceObjectAccessor<>(m, Modifiable.class); - stringField = Modifiable.class.getDeclaredField("s"); - } - - @Test - public void clearClears() { - accessor.clear(f -> true, prefabValues, TypeTag.NULL); - assertEquals(0, m.i); - assertEquals(false, m.b); - assertNull(m.s); - assertNull(m.o); - } - - @Test - public void clearClearsExcept() { - accessor.clear(f -> !f.getName().equals("s"), prefabValues, TypeTag.NULL); - assertEquals(prefabValues.giveRed(new TypeTag(String.class)), m.s); - assertEquals(0, m.i); - assertEquals(false, m.b); - assertNull(m.o); - } - - @Test - public void clearWorksInPlace() { - ObjectAccessor modified = accessor.clear(f -> true, prefabValues, TypeTag.NULL); - assertSame(m, modified.get()); - } - - @Test - public void withDefaultedField() { - accessor.withDefaultedField(stringField); - assertNull(m.s); - assertEquals(INITIAL_INT, m.i); - assertEquals(INITIAL_BOOLEAN, m.b); - assertEquals(INITIAL_OBJECT, m.o); - } - - @Test - public void withDefaultedFieldWorksInPlace() { - ObjectAccessor modified = accessor.withDefaultedField(stringField); - assertSame(m, modified.get()); - } - - @Test - public void withChangedField() { - accessor.withChangedField(stringField, prefabValues, TypeTag.NULL); - assertEquals(prefabValues.giveRed(new TypeTag(String.class)), m.s); - assertEquals(INITIAL_INT, m.i); - assertEquals(INITIAL_BOOLEAN, m.b); - assertEquals(INITIAL_OBJECT, m.o); - } - - @Test - public void withChangedFieldWorksInPlace() { - ObjectAccessor modified = accessor.withChangedField( - stringField, - prefabValues, - TypeTag.NULL - ); - assertSame(m, modified.get()); - } - - @Test - public void withFieldSetTo() { - accessor.withFieldSetTo(stringField, "something else"); - assertEquals("something else", m.s); - assertEquals(INITIAL_INT, m.i); - assertEquals(INITIAL_BOOLEAN, m.b); - assertEquals(INITIAL_OBJECT, m.o); - } - - @Test - public void withFieldSetToWorksInPlace() { - ObjectAccessor modified = accessor.withFieldSetTo( - stringField, - "something else" - ); - assertSame(m, modified.get()); - } - - static class Modifiable { - - private final int i; - private final boolean b; - private final String s; - private final Object o; - - public Modifiable(int i, boolean b, String s, Object o) { - this.i = i; - this.b = b; - this.s = s; - this.o = o; - } - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InstantiatorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InstantiatorTest.java index 95c1f5376..be7a5357a 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InstantiatorTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InstantiatorTest.java @@ -17,51 +17,57 @@ import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.ArrayContainer; import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.Interface; import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; import org.w3c.dom.Element; public class InstantiatorTest { + private Objenesis objenesis = new ObjenesisStd(); + @Test public void instantiateClass() { - Instantiator instantiator = Instantiator.of(Point.class); + Instantiator instantiator = Instantiator.of(Point.class, objenesis); Point p = instantiator.instantiate(); assertEquals(Point.class, p.getClass()); } @Test public void fieldsOfInstantiatedObjectHaveDefaultValues() { - ColorBlindColorPoint p = Instantiator.of(ColorBlindColorPoint.class).instantiate(); + ColorBlindColorPoint p = Instantiator + .of(ColorBlindColorPoint.class, objenesis) + .instantiate(); assertEquals(0, p.x); assertEquals(null, p.color); } @Test public void instantiateInterface() { - Instantiator instantiator = Instantiator.of(Interface.class); + Instantiator instantiator = Instantiator.of(Interface.class, objenesis); Interface i = instantiator.instantiate(); assertTrue(Interface.class.isAssignableFrom(i.getClass())); } @Test public void instantiateFinalClass() { - Instantiator.of(FinalPoint.class); + Instantiator.of(FinalPoint.class, objenesis); } @Test public void instantiateArrayContainer() { - Instantiator.of(ArrayContainer.class); + Instantiator.of(ArrayContainer.class, objenesis); } @Test public void instantiateAbstractClass() { - Instantiator instantiator = Instantiator.of(AbstractClass.class); + Instantiator instantiator = Instantiator.of(AbstractClass.class, objenesis); AbstractClass ac = instantiator.instantiate(); assertTrue(AbstractClass.class.isAssignableFrom(ac.getClass())); } @Test public void instantiateSubclass() { - Instantiator instantiator = Instantiator.of(Point.class); + Instantiator instantiator = Instantiator.of(Point.class, objenesis); Point p = instantiator.instantiateAnonymousSubclass(); assertFalse(p.getClass() == Point.class); assertTrue(Point.class.isAssignableFrom(p.getClass())); @@ -70,26 +76,26 @@ public void instantiateSubclass() { @Test public void instantiateAnNonToplevelClass() { class Something {} - Instantiator instantiator = Instantiator.of(Something.class); + Instantiator instantiator = Instantiator.of(Something.class, objenesis); instantiator.instantiateAnonymousSubclass(); } @Test @SuppressWarnings("rawtypes") public void instantiateJavaApiClassWhichHasBootstrapClassLoader() { - Instantiator instantiator = Instantiator.of(List.class); + Instantiator instantiator = Instantiator.of(List.class, objenesis); instantiator.instantiateAnonymousSubclass(); } @Test public void instantiateOrgW3cDomClassWhichHasBootstrapClassLoader() { - Instantiator instantiator = Instantiator.of(Element.class); + Instantiator instantiator = Instantiator.of(Element.class, objenesis); instantiator.instantiateAnonymousSubclass(); } @Test public void instantiateTheSameSubclass() { - Instantiator instantiator = Instantiator.of(Point.class); + Instantiator instantiator = Instantiator.of(Point.class, objenesis); Class expected = instantiator.instantiateAnonymousSubclass().getClass(); Class actual = instantiator.instantiateAnonymousSubclass().getClass(); assertEquals(expected, actual); diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TupleTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TupleTest.java similarity index 94% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TupleTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TupleTest.java index 572484ce1..ec04bba0b 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TupleTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TupleTest.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTagParameterizedTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TypeTagParameterizedTest.java similarity index 98% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTagParameterizedTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TypeTagParameterizedTest.java index eb5a66e2c..424ecef38 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTagParameterizedTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TypeTagParameterizedTest.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTagTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TypeTagTest.java similarity index 99% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTagTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TypeTagTest.java index 26083bfab..f74a6cc5a 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/TypeTagTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/TypeTagTest.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java index 20d227c10..6eb8fcce7 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java @@ -20,6 +20,7 @@ import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; public class AnnotationCacheBuilderTest { @@ -407,7 +408,7 @@ public void loadedBySystemClassLoaderDoesNotThrowNullPointerException() { @Test public void dynamicClassDoesntGetProcessed_butDoesntThrowEither() { Class type = Instantiator - .of(AnnotatedWithRuntime.class) + .of(AnnotatedWithRuntime.class, new ObjenesisStd()) .instantiateAnonymousSubclass() .getClass(); build(type); diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/NonnullAnnotationVerifierTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/NonnullAnnotationVerifierTest.java deleted file mode 100644 index 90752a880..000000000 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/NonnullAnnotationVerifierTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package nl.jqno.equalsverifier.internal.reflection.annotations; - -import static nl.jqno.equalsverifier.internal.testhelpers.Util.coverThePrivateConstructor; - -import org.junit.jupiter.api.Test; - -public class NonnullAnnotationVerifierTest { - - @Test - public void coverConstructor() { - coverThePrivateConstructor(NonnullAnnotationVerifier.class); - } -} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/InstanceCreatorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/InstanceCreatorTest.java new file mode 100644 index 000000000..4cd563b6f --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/InstanceCreatorTest.java @@ -0,0 +1,71 @@ +package nl.jqno.equalsverifier.internal.reflection.instantiation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import nl.jqno.equalsverifier.internal.reflection.ClassProbe; +import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; + +public class InstanceCreatorTest { + + @Test + public void instantiate() throws NoSuchFieldException { + ClassProbe probe = new ClassProbe<>(SomeClass.class); + Objenesis objenesis = new ObjenesisStd(); + InstanceCreator sut = new InstanceCreator<>(probe, objenesis); + + Field x = SomeClass.class.getDeclaredField("x"); + Field z = SomeClass.class.getDeclaredField("z"); + Map values = new HashMap<>(); + values.put(x, 42); + values.put(z, "42"); + + SomeClass actual = sut.instantiate(values); + + assertEquals(42, actual.x); + assertEquals(0, actual.y); + assertEquals("42", actual.z); + } + + @Test + public void copy() throws NoSuchFieldException { + ClassProbe probe = new ClassProbe<>(SomeSubClass.class); + Objenesis objenesis = new ObjenesisStd(); + InstanceCreator sut = new InstanceCreator<>(probe, objenesis); + + SomeClass original = new SomeClass(42, 1337, "yeah"); + SomeSubClass copy = sut.copy(original); + + assertEquals(original.x, copy.x); + assertEquals(original.y, copy.y); + assertEquals(original.z, copy.z); + assertEquals(0, copy.a); + } + + static class SomeClass { + + final int x; + final int y; + final String z; + + public SomeClass(int x, int y, String z) { + this.x = x; + this.y = y; + this.z = z; + } + } + + static class SomeSubClass extends SomeClass { + + final int a; + + public SomeSubClass(int x, int y, String z, int a) { + super(x, y, z); + this.a = a; + } + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/SubjectCreatorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/SubjectCreatorTest.java new file mode 100644 index 000000000..d9e9ba8cd --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/SubjectCreatorTest.java @@ -0,0 +1,316 @@ +package nl.jqno.equalsverifier.internal.reflection.instantiation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import java.lang.reflect.Field; +import java.util.Objects; +import nl.jqno.equalsverifier.internal.reflection.FieldCache; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.util.Configuration; +import nl.jqno.equalsverifier.internal.util.ConfigurationHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; + +public class SubjectCreatorTest { + + private static final int I_RED = 42; + private static final int I_BLUE = 1337; + private static final String S_RED = "abc"; + private static final String S_BLUE = "xyz"; + + private Configuration config = ConfigurationHelper.emptyConfiguration( + SomeClass.class + ); + private ValueProvider valueProvider = new SubjectCreatorTestValueProvider(); + private FieldCache fieldCache = new FieldCache(); + private Objenesis objenesis = new ObjenesisStd(); + private SubjectCreator sut = new SubjectCreator<>( + config, + valueProvider, + fieldCache, + objenesis + ); + + private Field fieldX; + private Field fieldI; + private Field fieldS; + + private SomeClass expected; + private SomeClass actual; + + @BeforeEach + public void setup() throws NoSuchFieldException { + fieldX = SomeSuper.class.getDeclaredField("x"); + fieldI = SomeClass.class.getDeclaredField("i"); + fieldS = SomeClass.class.getDeclaredField("s"); + } + + @Test + public void sanity() {} + + @Test + public void plain() { + expected = new SomeClass(I_RED, I_RED, S_RED); + actual = sut.plain(); + + assertEquals(expected, actual); + } + + @Test + public void withFieldDefaulted_super() { + expected = new SomeClass(0, I_RED, S_RED); + actual = sut.withFieldDefaulted(fieldX); + + assertEquals(expected, actual); + } + + @Test + public void withFieldDefaulted_primitive() { + expected = new SomeClass(I_RED, 0, S_RED); + actual = sut.withFieldDefaulted(fieldI); + + assertEquals(expected, actual); + } + + @Test + public void withFieldDefaulted_object() { + expected = new SomeClass(I_RED, I_RED, null); + actual = sut.withFieldDefaulted(fieldS); + + assertEquals(expected, actual); + } + + @Test + public void withAllFieldsDefaulted() { + expected = new SomeClass(0, 0, null); + actual = sut.withAllFieldsDefaulted(); + + assertEquals(expected, actual); + } + + @Test + public void withAllFieldsDefaultedExcept() { + expected = new SomeClass(0, I_RED, null); + actual = sut.withAllFieldsDefaultedExcept(fieldI); + + assertEquals(expected, actual); + } + + @Test + public void withFieldSetTo_super() { + expected = new SomeClass(99, I_RED, S_RED); + actual = sut.withFieldSetTo(fieldX, 99); + + assertEquals(expected, actual); + } + + @Test + public void withFieldSetTo_primitive() { + expected = new SomeClass(I_RED, 99, S_RED); + actual = sut.withFieldSetTo(fieldI, 99); + + assertEquals(expected, actual); + } + + @Test + public void withFieldSetTo_object() { + expected = new SomeClass(I_RED, I_RED, "value"); + actual = sut.withFieldSetTo(fieldS, "value"); + + assertEquals(expected, actual); + } + + @Test + public void withFieldChanged_super() { + expected = new SomeClass(I_BLUE, I_RED, S_RED); + actual = sut.withFieldChanged(fieldX); + + assertEquals(expected, actual); + } + + @Test + public void withFieldChanged_primitive() { + expected = new SomeClass(I_RED, I_BLUE, S_RED); + actual = sut.withFieldChanged(fieldI); + + assertEquals(expected, actual); + } + + @Test + public void withFieldChanged_object() { + expected = new SomeClass(I_RED, I_RED, S_BLUE); + actual = sut.withFieldChanged(fieldS); + + assertEquals(expected, actual); + } + + @Test + public void withAllFieldsChanged() { + expected = new SomeClass(I_BLUE, I_BLUE, S_BLUE); + actual = sut.withAllFieldsChanged(); + + assertEquals(expected, actual); + } + + @Test + public void withAllFieldsShallowlyChanged() { + expected = new SomeClass(I_RED, I_BLUE, S_BLUE); + actual = sut.withAllFieldsShallowlyChanged(); + + assertEquals(expected, actual); + } + + @Test + public void copy() { + expected = new SomeClass(I_RED, I_RED, S_RED); + SomeClass original = new SomeClass(I_RED, I_RED, S_RED); + actual = sut.copy(original); + + assertEquals(expected, actual); + assertNotSame(expected, actual); + } + + @Test + public void copyIntoSuperclass() { + SomeSuper superExpected = new SomeSuper(I_RED); + SomeClass original = new SomeClass(I_RED, I_RED, S_RED); + Object superActual = sut.copyIntoSuperclass(original); + + assertEquals(superExpected, superActual); + assertEquals(SomeSuper.class, superActual.getClass()); + } + + @Test + public void copyIntoSubclass() { + expected = new SomeSub(I_RED, I_RED, S_RED, null); + SomeClass original = new SomeClass(I_RED, I_RED, S_RED); + actual = sut.copyIntoSubclass(original, SomeSub.class); + + assertEquals(expected, actual); + assertEquals(SomeSub.class, actual.getClass()); + } + + static class SubjectCreatorTestValueProvider implements ValueProvider { + + public Tuple provide(TypeTag tag) { + if (int.class.equals(tag.getType())) { + return Tuple.of(I_RED, I_BLUE, I_RED); + } + if (String.class.equals(tag.getType())) { + return Tuple.of(S_RED, S_BLUE, new String(S_RED)); + } + throw new IllegalStateException(); + } + } + + static class SomeSuper { + + final int x; + + public SomeSuper(int x) { + this.x = x; + } + + public boolean canEqual(Object obj) { + return obj instanceof SomeSuper; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SomeSuper)) { + return false; + } + SomeSuper other = (SomeSuper) obj; + return other.canEqual(this) && x == other.x; + } + + @Override + public int hashCode() { + return Objects.hash(x); + } + + @Override + public String toString() { + return "SomeSuper: " + x; + } + } + + static class SomeClass extends SomeSuper { + + final int i; + final String s; + + public SomeClass(int x, int i, String s) { + super(x); + this.i = i; + this.s = s; + } + + @Override + public boolean canEqual(Object obj) { + return obj instanceof SomeClass; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SomeClass)) { + return false; + } + SomeClass other = (SomeClass) obj; + return ( + other.canEqual(this) && + super.equals(other) && + Objects.equals(i, other.i) && + Objects.equals(s, other.s) + ); + } + + @Override + public int hashCode() { + return Objects.hash(x, i, s); + } + + @Override + public String toString() { + return "SomeClass: " + x + "," + i + "," + s; + } + } + + static class SomeSub extends SomeClass { + + final String q; + + public SomeSub(int x, int i, String s, String q) { + super(x, i, s); + this.q = q; + } + + @Override + public boolean canEqual(Object obj) { + return obj instanceof SomeSub; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SomeSub)) { + return false; + } + SomeSub other = (SomeSub) obj; + return other.canEqual(this) && super.equals(other) && Objects.equals(q, other.q); + } + + @Override + public int hashCode() { + return Objects.hash(x, i, s, q); + } + + @Override + public String toString() { + return "SomeSub: " + x + "," + i + "," + s + "," + q; + } + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValuesCreatorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProviderCreatorTest.java similarity index 62% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValuesCreatorTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProviderCreatorTest.java index 21c8f317b..45bba76bc 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/PrefabValuesCreatorTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProviderCreatorTest.java @@ -1,9 +1,11 @@ -package nl.jqno.equalsverifier.internal.prefabvalues; +package nl.jqno.equalsverifier.internal.reflection.instantiation; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; import static org.junit.jupiter.api.Assertions.*; import nl.jqno.equalsverifier.internal.exceptions.RecursionException; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; import nl.jqno.equalsverifier.testhelpers.FactoryCacheFactory; import nl.jqno.equalsverifier.testhelpers.types.Point; @@ -13,8 +15,10 @@ import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.OneElementEnum; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; -public class PrefabValuesCreatorTest { +public class VintageValueProviderCreatorTest { private static final TypeTag POINT_TAG = new TypeTag(Point.class); private static final TypeTag ENUM_TAG = new TypeTag(Enum.class); @@ -25,60 +29,62 @@ public class PrefabValuesCreatorTest { private static final TypeTag TWOSTEP_NODE_A_TAG = new TypeTag(TwoStepNodeA.class); private static final TypeTag TWOSTEP_NODE_ARRAY_A_TAG = new TypeTag(TwoStepNodeArrayA.class); + private Objenesis objenesis; private FactoryCache factoryCache; - private PrefabValues prefabValues; + private VintageValueProvider valueProvider; @BeforeEach public void setup() { + objenesis = new ObjenesisStd(); factoryCache = FactoryCacheFactory.withPrimitiveFactories(); - prefabValues = new PrefabValues(factoryCache); + valueProvider = new VintageValueProvider(factoryCache, objenesis); } @Test public void simple() { - Point red = prefabValues.giveRed(POINT_TAG); - Point blue = prefabValues.giveBlue(POINT_TAG); + Point red = valueProvider.giveRed(POINT_TAG); + Point blue = valueProvider.giveBlue(POINT_TAG); assertFalse(red.equals(blue)); } @Test public void createSecondTimeIsNoOp() { - Point red = prefabValues.giveRed(POINT_TAG); - Point blue = prefabValues.giveBlue(POINT_TAG); + Point red = valueProvider.giveRed(POINT_TAG); + Point blue = valueProvider.giveBlue(POINT_TAG); - assertSame(red, prefabValues.giveRed(POINT_TAG)); - assertSame(blue, prefabValues.giveBlue(POINT_TAG)); + assertSame(red, valueProvider.giveRed(POINT_TAG)); + assertSame(blue, valueProvider.giveBlue(POINT_TAG)); } @Test public void createEnum() { - assertNotNull(prefabValues.giveRed(ENUM_TAG)); - assertNotNull(prefabValues.giveBlue(ENUM_TAG)); + assertNotNull(valueProvider.giveRed(ENUM_TAG)); + assertNotNull(valueProvider.giveBlue(ENUM_TAG)); } @Test public void createOneElementEnum() { - assertNotNull(prefabValues.giveRed(ONE_ELT_ENUM_TAG)); - assertNotNull(prefabValues.giveBlue(ONE_ELT_ENUM_TAG)); + assertNotNull(valueProvider.giveRed(ONE_ELT_ENUM_TAG)); + assertNotNull(valueProvider.giveBlue(ONE_ELT_ENUM_TAG)); } @Test public void createEmptyEnum() { - assertNull(prefabValues.giveRed(EMPTY_ENUM_TAG)); - assertNull(prefabValues.giveBlue(EMPTY_ENUM_TAG)); + assertNull(valueProvider.giveRed(EMPTY_ENUM_TAG)); + assertNull(valueProvider.giveBlue(EMPTY_ENUM_TAG)); } @Test public void oneStepRecursiveType() { factoryCache.put(Node.class, values(new Node(), new Node(), new Node())); - prefabValues = new PrefabValues(factoryCache); - prefabValues.giveRed(NODE_TAG); + valueProvider = new VintageValueProvider(factoryCache, objenesis); + valueProvider.giveRed(NODE_TAG); } @Test public void dontAddOneStepRecursiveType() { ExpectedException - .when(() -> prefabValues.giveRed(NODE_TAG)) + .when(() -> valueProvider.giveRed(NODE_TAG)) .assertThrows(RecursionException.class); } @@ -88,14 +94,14 @@ public void oneStepRecursiveArrayType() { NodeArray.class, values(new NodeArray(), new NodeArray(), new NodeArray()) ); - prefabValues = new PrefabValues(factoryCache); - prefabValues.giveRed(NODE_ARRAY_TAG); + valueProvider = new VintageValueProvider(factoryCache, objenesis); + valueProvider.giveRed(NODE_ARRAY_TAG); } @Test public void dontAddOneStepRecursiveArrayType() { ExpectedException - .when(() -> prefabValues.giveRed(NODE_ARRAY_TAG)) + .when(() -> valueProvider.giveRed(NODE_ARRAY_TAG)) .assertThrows(RecursionException.class); } @@ -105,14 +111,14 @@ public void addTwoStepRecursiveType() { TwoStepNodeB.class, values(new TwoStepNodeB(), new TwoStepNodeB(), new TwoStepNodeB()) ); - prefabValues = new PrefabValues(factoryCache); - prefabValues.giveRed(TWOSTEP_NODE_A_TAG); + valueProvider = new VintageValueProvider(factoryCache, objenesis); + valueProvider.giveRed(TWOSTEP_NODE_A_TAG); } @Test public void dontAddTwoStepRecursiveType() { ExpectedException - .when(() -> prefabValues.giveRed(TWOSTEP_NODE_A_TAG)) + .when(() -> valueProvider.giveRed(TWOSTEP_NODE_A_TAG)) .assertThrows(RecursionException.class); } @@ -122,26 +128,26 @@ public void twoStepRecursiveArrayType() { TwoStepNodeArrayB.class, values(new TwoStepNodeArrayB(), new TwoStepNodeArrayB(), new TwoStepNodeArrayB()) ); - prefabValues = new PrefabValues(factoryCache); - prefabValues.giveRed(TWOSTEP_NODE_ARRAY_A_TAG); + valueProvider = new VintageValueProvider(factoryCache, objenesis); + valueProvider.giveRed(TWOSTEP_NODE_ARRAY_A_TAG); } @Test public void dontAddTwoStepRecursiveArrayType() { ExpectedException - .when(() -> prefabValues.giveRed(TWOSTEP_NODE_ARRAY_A_TAG)) + .when(() -> valueProvider.giveRed(TWOSTEP_NODE_ARRAY_A_TAG)) .assertThrows(RecursionException.class); } @Test public void sameClassTwiceButNoRecursion() { - prefabValues.giveRed(new TypeTag(NotRecursiveA.class)); + valueProvider.giveRed(new TypeTag(NotRecursiveA.class)); } @Test public void recursiveWithAnotherFieldFirst() { ExpectedException - .when(() -> prefabValues.giveRed(new TypeTag(RecursiveWithAnotherFieldFirst.class))) + .when(() -> valueProvider.giveRed(new TypeTag(RecursiveWithAnotherFieldFirst.class))) .assertThrows(RecursionException.class) .assertDescriptionContains(RecursiveWithAnotherFieldFirst.class.getSimpleName()) .assertDescriptionDoesNotContain(RecursiveThisIsTheOtherField.class.getSimpleName()); @@ -150,7 +156,7 @@ public void recursiveWithAnotherFieldFirst() { @Test public void exceptionMessage() { ExpectedException - .when(() -> prefabValues.giveRed(TWOSTEP_NODE_A_TAG)) + .when(() -> valueProvider.giveRed(TWOSTEP_NODE_A_TAG)) .assertThrows(RecursionException.class) .assertDescriptionContains( TwoStepNodeA.class.getSimpleName(), @@ -160,7 +166,7 @@ public void exceptionMessage() { @Test public void skipStaticFinal() { - prefabValues.giveRed(new TypeTag(StaticFinalContainer.class)); + valueProvider.giveRed(new TypeTag(StaticFinalContainer.class)); } static class StaticFinalContainer { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProviderTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProviderTest.java new file mode 100644 index 000000000..2fa15353e --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/instantiation/VintageValueProviderTest.java @@ -0,0 +1,277 @@ +package nl.jqno.equalsverifier.internal.reflection.instantiation; + +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.testhelpers.Util.defaultEquals; +import static nl.jqno.equalsverifier.internal.testhelpers.Util.defaultHashCode; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; +import nl.jqno.equalsverifier.testhelpers.types.Point; +import nl.jqno.equalsverifier.testhelpers.types.ThrowingInitializer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; + +public class VintageValueProviderTest { + + private static final TypeTag STRING_TAG = new TypeTag(String.class); + private static final TypeTag POINT_TAG = new TypeTag(Point.class); + private static final TypeTag INT_TAG = new TypeTag(int.class); + + private Objenesis objenesis = new ObjenesisStd(); + private FactoryCache factoryCache = new FactoryCache(); + private VintageValueProvider vp; + + @BeforeEach + public void setUp() { + factoryCache.put(String.class, new AppendingStringTestFactory()); + factoryCache.put(int.class, values(42, 1337, 42)); + vp = new VintageValueProvider(factoryCache, objenesis); + } + + @Test + public void sanityTestFactoryIncreasesStringLength() { + AppendingStringTestFactory f = new AppendingStringTestFactory(); + assertEquals("r", f.createValues(null, null, null).getRed()); + assertEquals("rr", f.createValues(null, null, null).getRed()); + assertEquals("rrr", f.createValues(null, null, null).getRed()); + } + + @Test + public void provide() { + Tuple actual = vp.provide(POINT_TAG); + assertEquals(Tuple.of(new Point(42, 42), new Point(1337, 1337), new Point(42, 42)), actual); + } + + @Test + public void giveRedFromFactory() { + assertEquals("r", vp.giveRed(STRING_TAG)); + } + + @Test + public void giveRedFromCache() { + vp.giveRed(STRING_TAG); + assertEquals("r", vp.giveRed(STRING_TAG)); + } + + @Test + public void giveBlueFromFactory() { + assertEquals("b", vp.giveBlue(STRING_TAG)); + } + + @Test + public void giveBlueFromCache() { + vp.giveBlue(STRING_TAG); + assertEquals("b", vp.giveBlue(STRING_TAG)); + } + + @Test + public void giveRedCopyFromFactory() { + assertEquals("r", vp.giveRedCopy(STRING_TAG)); + assertNotSame(vp.giveRed(STRING_TAG), vp.giveRedCopy(STRING_TAG)); + } + + @Test + public void giveRedCopyFromCache() { + vp.giveRedCopy(STRING_TAG); + assertEquals("r", vp.giveRedCopy(STRING_TAG)); + assertNotSame(vp.giveRed(STRING_TAG), vp.giveRedCopy(STRING_TAG)); + } + + @Test + public void giveRedFromFallbackFactory() { + Point actual = vp.giveRed(POINT_TAG); + assertEquals(new Point(42, 42), actual); + } + + @Test + public void giveBlueFromFallbackFactory() { + Point actual = vp.giveBlue(POINT_TAG); + assertEquals(new Point(1337, 1337), actual); + } + + @Test + public void giveRedCopyFromFallbackFactory() { + Point actual = vp.giveRedCopy(POINT_TAG); + assertEquals(new Point(42, 42), actual); + assertNotSame(vp.giveRed(POINT_TAG), actual); + } + + @Test + public void fallbackDoesNotAffectStaticFields() { + int expected = StaticContainer.staticInt; + vp.giveRed(new TypeTag(StaticContainer.class)); + assertEquals(expected, StaticContainer.staticInt); + } + + @Test + public void stringListIsSeparateFromIntegerList() { + factoryCache.put(List.class, new ListTestFactory()); + vp = new VintageValueProvider(factoryCache, objenesis); + + List strings = vp.giveRed(new TypeTag(List.class, STRING_TAG)); + List ints = vp.giveRed(new TypeTag(List.class, INT_TAG)); + + assertEquals("r", strings.get(0)); + assertEquals(42, (int) ints.get(0)); + } + + @Test + public void addingNullDoesntBreakAnything() { + factoryCache.put((Class) null, new ListTestFactory()); + } + + @Test + public void addingATypeTwiceOverrulesTheExistingOne() { + factoryCache.put(int.class, values(-1, -2, -1)); + vp = new VintageValueProvider(factoryCache, objenesis); + assertEquals(-1, (int) vp.giveRed(INT_TAG)); + assertEquals(-2, (int) vp.giveBlue(INT_TAG)); + } + + @Test + public void addLazyFactoryWorks() { + TypeTag lazyTag = new TypeTag(Lazy.class); + factoryCache.put(Lazy.class.getName(), values(Lazy.X, Lazy.Y, Lazy.X)); + vp = new VintageValueProvider(factoryCache, objenesis); + assertEquals(Lazy.X, vp.giveRed(lazyTag)); + assertEquals(Lazy.Y, vp.giveBlue(lazyTag)); + assertEquals(Lazy.X, vp.giveRedCopy(lazyTag)); + } + + @Test + public void addLazyFactoryIsLazy() { + TypeTag throwingInitializerTag = new TypeTag(ThrowingInitializer.class); + + // Shouldn't throw, because constructing PrefabValues doesn't instantiate objects: + factoryCache.put( + ThrowingInitializer.class.getName(), + (t, p, ts) -> + Tuple.of(ThrowingInitializer.X, ThrowingInitializer.Y, ThrowingInitializer.X) + ); + vp = new VintageValueProvider(factoryCache, objenesis); + + // Should throw, because `giveRed` does instantiate objects: + try { + vp.giveRed(throwingInitializerTag); + fail("Expected an exception"); + } catch (Error e) { + // succeed + } + } + + public static class NpeThrowing { + + private final int i; + + public NpeThrowing(int i) { + this.i = i; + } + + public boolean equals(Object obj) { + if (obj == null) { + throw new NullPointerException(); + } + if (!(obj instanceof NpeThrowing)) { + return false; + } + return i == ((NpeThrowing) obj).i; + } + + public int hashCode() { + return i; + } + } + + private static class AppendingStringTestFactory implements PrefabValueFactory { + + private String red; + private String blue; + + public AppendingStringTestFactory() { + red = ""; + blue = ""; + } + + @Override + public Tuple createValues( + TypeTag tag, + VintageValueProvider valueProvider, + LinkedHashSet typeStack + ) { + red += "r"; + blue += "b"; + return new Tuple<>(red, blue, new String(red)); + } + } + + @SuppressWarnings("rawtypes") + private static final class ListTestFactory implements PrefabValueFactory { + + @Override + @SuppressWarnings("unchecked") + public Tuple createValues( + TypeTag tag, + VintageValueProvider valueProvider, + LinkedHashSet typeStack + ) { + TypeTag subtag = tag.getGenericTypes().get(0); + + List red = new ArrayList<>(); + red.add(valueProvider.giveRed(subtag)); + + List blue = new ArrayList<>(); + blue.add(valueProvider.giveBlue(subtag)); + + List redCopy = new ArrayList<>(); + redCopy.add(valueProvider.giveRed(subtag)); + + return new Tuple<>(red, blue, redCopy); + } + } + + private static final class StaticContainer { + + static int staticInt = 2; + + @SuppressWarnings("unused") + int regularInt = 3; + } + + @SuppressWarnings("unused") + public static class Lazy { + + public static final Lazy X = new Lazy(1); + public static final Lazy Y = new Lazy(2); + + private final int i; + + public Lazy(int i) { + this.i = i; + } + + @Override + public boolean equals(Object obj) { + return defaultEquals(this, obj); + } + + @Override + public int hashCode() { + return defaultHashCode(this); + } + + @Override + public String toString() { + return "Lazy: " + i; + } + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/ClassAccessorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/ClassAccessorTest.java new file mode 100644 index 000000000..3fbfcb6cb --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/ClassAccessorTest.java @@ -0,0 +1,164 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.LinkedHashSet; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.JavaApiPrefabValues; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.testhelpers.types.PointContainer; +import nl.jqno.equalsverifier.testhelpers.types.RecursiveTypeHelper.TwoStepNodeA; +import nl.jqno.equalsverifier.testhelpers.types.RecursiveTypeHelper.TwoStepNodeB; +import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; + +public class ClassAccessorTest { + + private LinkedHashSet empty; + private Objenesis objenesis; + private FactoryCache factoryCache; + private VintageValueProvider valueProvider; + private ClassAccessor pointContainerAccessor; + + @BeforeEach + public void setup() { + empty = new LinkedHashSet<>(); + objenesis = new ObjenesisStd(); + factoryCache = JavaApiPrefabValues.build(); + valueProvider = new VintageValueProvider(factoryCache, objenesis); + pointContainerAccessor = ClassAccessor.of(PointContainer.class, valueProvider, objenesis); + } + + @Test + public void getRedObject() { + assertObjectHasNoNullFields(pointContainerAccessor.getRedObject(TypeTag.NULL, empty)); + } + + @Test + @SuppressWarnings("rawtypes") + public void getRedObjectGeneric() { + ClassAccessor accessor = ClassAccessor.of( + GenericTypeVariableListContainer.class, + valueProvider, + objenesis + ); + GenericTypeVariableListContainer foo = accessor.getRedObject( + new TypeTag(GenericTypeVariableListContainer.class, new TypeTag(String.class)), + empty + ); + assertEquals(String.class, foo.tList.get(0).getClass()); + } + + @Test + public void getRedAccessor() { + PointContainer foo = pointContainerAccessor.getRedObject(TypeTag.NULL, empty); + ObjectAccessor objectAccessor = pointContainerAccessor.getRedAccessor( + TypeTag.NULL, + empty + ); + assertEquals(foo, objectAccessor.get()); + } + + @Test + public void getBlueObject() { + assertObjectHasNoNullFields(pointContainerAccessor.getBlueObject(TypeTag.NULL, empty)); + } + + @Test + @SuppressWarnings("rawtypes") + public void getBlueObjectGeneric() { + ClassAccessor accessor = ClassAccessor.of( + GenericTypeVariableListContainer.class, + valueProvider, + objenesis + ); + GenericTypeVariableListContainer foo = accessor.getBlueObject( + new TypeTag(GenericTypeVariableListContainer.class, new TypeTag(String.class)), + empty + ); + assertEquals(String.class, foo.tList.get(0).getClass()); + } + + @Test + public void getBlueAccessor() { + PointContainer foo = pointContainerAccessor.getBlueObject(TypeTag.NULL, empty); + ObjectAccessor objectAccessor = pointContainerAccessor.getBlueAccessor( + TypeTag.NULL, + empty + ); + assertEquals(foo, objectAccessor.get()); + } + + @Test + public void redAndBlueNotEqual() { + PointContainer red = pointContainerAccessor.getRedObject(TypeTag.NULL, empty); + PointContainer blue = pointContainerAccessor.getBlueObject(TypeTag.NULL, empty); + assertFalse(red.equals(blue)); + } + + @Test + public void instantiateAllTypes() { + ClassAccessor + .of(AllTypesContainer.class, valueProvider, objenesis) + .getRedObject(TypeTag.NULL, empty); + } + + @Test + public void instantiateArrayTypes() { + ClassAccessor + .of(AllArrayTypesContainer.class, valueProvider, objenesis) + .getRedObject(TypeTag.NULL, empty); + } + + @Test + public void instantiateRecursiveTypeUsingPrefabValue() { + factoryCache.put( + TwoStepNodeB.class, + values(new TwoStepNodeB(), new TwoStepNodeB(), new TwoStepNodeB()) + ); + valueProvider = new VintageValueProvider(factoryCache, objenesis); + ClassAccessor + .of(TwoStepNodeA.class, valueProvider, objenesis) + .getRedObject(TypeTag.NULL, empty); + } + + @Test + public void instantiateInterfaceField() { + ClassAccessor + .of(InterfaceContainer.class, valueProvider, objenesis) + .getRedObject(TypeTag.NULL, empty); + } + + @Test + public void instantiateAbstractClassField() { + ClassAccessor + .of(AbstractClassContainer.class, valueProvider, objenesis) + .getRedObject(TypeTag.NULL, empty); + } + + @Test + public void anInvalidTypeShouldNotThrowAnExceptionUponCreation() { + ClassAccessor.of(null, valueProvider, objenesis); + } + + private void assertObjectHasNoNullFields(PointContainer foo) { + assertNotNull(foo); + assertNotNull(foo.getPoint()); + } + + static class MethodContainer { + + public void m() {} + + protected void m_protected() {} + } + + static class ChildOfMethodContainer extends MethodContainer {} +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/FieldModifierTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/FieldModifierTest.java new file mode 100644 index 000000000..e0b2562b2 --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/FieldModifierTest.java @@ -0,0 +1,53 @@ +package nl.jqno.equalsverifier.internal.reflection.vintage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.lang.reflect.Field; +import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.ObjectContainer; +import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.PrimitiveContainer; +import org.junit.jupiter.api.Test; + +public class FieldModifierTest { + + private static final String FIELD_NAME = "field"; + + @Test + public void copyToPrimitiveField() { + int value = 10; + + PrimitiveContainer from = new PrimitiveContainer(); + from.field = value; + + PrimitiveContainer to = new PrimitiveContainer(); + doCopyField(to, from, FIELD_NAME); + + assertEquals(value, to.field); + } + + @Test + public void copyToObjectField() { + Object value = new Object(); + + ObjectContainer from = new ObjectContainer(); + from.field = value; + + ObjectContainer to = new ObjectContainer(); + doCopyField(to, from, FIELD_NAME); + + assertSame(value, to.field); + } + + private void doCopyField(Object to, Object from, String fieldName) { + getAccessorFor(from, fieldName).copyTo(to); + } + + private FieldModifier getAccessorFor(Object object, String fieldName) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + return FieldModifier.of(field, object); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException("fieldName: " + fieldName); + } + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorCopyingTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessorCopyingTest.java similarity index 54% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorCopyingTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessorCopyingTest.java index 072d2cba1..a224280df 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorCopyingTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessorCopyingTest.java @@ -1,17 +1,23 @@ -package nl.jqno.equalsverifier.internal.reflection; +package nl.jqno.equalsverifier.internal.reflection.vintage; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Field; -import nl.jqno.equalsverifier.testhelpers.types.ColorPoint3D; +import nl.jqno.equalsverifier.internal.reflection.FieldIterable; import nl.jqno.equalsverifier.testhelpers.types.Point; import nl.jqno.equalsverifier.testhelpers.types.Point3D; import nl.jqno.equalsverifier.testhelpers.types.PointContainer; import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.StaticFinalContainer; import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; public class InPlaceObjectAccessorCopyingTest { + private Objenesis objenesis = new ObjenesisStd(); + @Test public void copyHappyPath() { Point original = new Point(2, 3); @@ -53,59 +59,17 @@ public void copyFromSub() { assertAllFieldsEqual(original, copy, Point.class); } - @Test - public void copyToSub() { - Point original = new Point(2, 3); - Point3D copy = copyIntoSubclass(original, Point3D.class); - - assertAllFieldsEqual(original, copy, Point.class); - } - - @Test - public void shallowCopyToSub() { - PointContainer original = new PointContainer(new Point(1, 2)); - SubPointContainer copy = copyIntoSubclass(original, SubPointContainer.class); - - assertNotSame(original, copy); - assertTrue(original.getPoint() == copy.getPoint()); - } - - @Test - public void inheritanceCopyToSub() { - Point3D original = new Point3D(2, 3, 4); - Point3D copy = copyIntoSubclass(original, ColorPoint3D.class); - - assertAllFieldsEqual(original, copy, Point.class); - assertAllFieldsEqual(original, copy, Point3D.class); - } - - @Test - public void copyToAnonymousSub() { - Point original = new Point(2, 3); - InPlaceObjectAccessor accessor = create(original); - Point copy = accessor.copyIntoAnonymousSubclass(); - - assertAllFieldsEqual(original, copy, Point.class); - - assertNotSame(original.getClass(), copy.getClass()); - assertTrue(original.getClass().isAssignableFrom(copy.getClass())); - } - @SuppressWarnings("unchecked") private InPlaceObjectAccessor create(T object) { return new InPlaceObjectAccessor(object, (Class) object.getClass()); } private T copyOf(T from) { - return create(from).copy(); + return create(from).copy(objenesis); } private T copyOf(T from, Class type) { - return new InPlaceObjectAccessor(from, type).copy(); - } - - private S copyIntoSubclass(T from, Class subclass) { - return create(from).copyIntoSubclass(subclass); + return new InPlaceObjectAccessor(from, type).copy(objenesis); } private static void assertAllFieldsEqual(T original, T copy, Class type) { @@ -118,11 +82,4 @@ private static void assertAllFieldsEqual(T original, T copy, Class EMPTY_TYPE_STACK = new LinkedHashSet<>(); - private PrefabValues prefabValues; + private Objenesis objenesis; + private VintageValueProvider valueProviderTest; @BeforeEach public void setup() { FactoryCache factoryCache = JavaApiPrefabValues.build(); factoryCache.put(Point.class, values(new Point(1, 2), new Point(2, 3), new Point(1, 2))); - prefabValues = new PrefabValues(factoryCache); + objenesis = new ObjenesisStd(); + valueProviderTest = new VintageValueProvider(factoryCache, objenesis); } @Test @@ -61,18 +68,6 @@ public void deepScramble() { assertFalse(modified.equals(reference)); } - @Test - public void shallowScramble() { - Point3D modified = new Point3D(2, 3, 4); - Point3D reference = copy(modified); - - create(modified).shallowScramble(prefabValues, TypeTag.NULL); - - assertFalse(modified.equals(reference)); - modified.z = 4; - assertTrue(modified.equals(reference)); - } - @SuppressWarnings("static-access") @Test public void scrambleStaticFinal() { @@ -136,8 +131,9 @@ public void scrambleSutInaccessible() { ExpectedException .when(() -> doScramble(as)) - .assertThrows(ModuleException.class) - .assertDescriptionContains("The class", "Consider opening"); + // InaccessibleObjectException, but it's not available in Java 8 + .assertThrows(RuntimeException.class) + .assertMessageContains("accessible: module", "does not \"opens"); } @Test @@ -145,10 +141,7 @@ public void scrambleSutInaccessible() { public void scrambleFieldInaccessible() { InaccessibleContainer ic = new InaccessibleContainer(new AttributedString("x")); - ExpectedException - .when(() -> doScramble(ic)) - .assertThrows(ModuleException.class) - .assertDescriptionContains("Field as", "Consider opening"); + ExpectedException.when(() -> doScramble(ic)).assertThrows(ModuleException.class); } @SuppressWarnings("unchecked") @@ -157,11 +150,11 @@ private InPlaceObjectAccessor create(T object) { } private T copy(T object) { - return create(object).copy(); + return create(object).copy(objenesis); } private ObjectAccessor doScramble(Object object) { - return create(object).scramble(prefabValues, TypeTag.NULL, EMPTY_TYPE_STACK); + return create(object).scramble(valueProviderTest, TypeTag.NULL, EMPTY_TYPE_STACK); } static final class StringContainer { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessorTest.java similarity index 66% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessorTest.java index 9b3d8fce0..85152215a 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/InPlaceObjectAccessorTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/InPlaceObjectAccessorTest.java @@ -1,10 +1,8 @@ -package nl.jqno.equalsverifier.internal.reflection; +package nl.jqno.equalsverifier.internal.reflection.vintage; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.lang.reflect.Field; import nl.jqno.equalsverifier.testhelpers.types.Point; import org.junit.jupiter.api.Test; @@ -24,14 +22,6 @@ public void get() { assertSame(foo, accessor.get()); } - @Test - public void getField() throws Exception { - Point p = new Point(1, 2); - Field f = Point.class.getDeclaredField("y"); - InPlaceObjectAccessor accessor = create(p); - assertEquals(2, accessor.getField(f)); - } - @SuppressWarnings("unchecked") private InPlaceObjectAccessor create(T object) { return new InPlaceObjectAccessor(object, (Class) object.getClass()); diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/AbstractGenericFactoryTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/AbstractGenericFactoryTest.java similarity index 80% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/AbstractGenericFactoryTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/AbstractGenericFactoryTest.java index 31c06361a..e0a2263fd 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/AbstractGenericFactoryTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/AbstractGenericFactoryTest.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import static nl.jqno.equalsverifier.internal.reflection.Util.classes; import static nl.jqno.equalsverifier.internal.reflection.Util.objects; @@ -6,9 +6,9 @@ import java.util.LinkedHashSet; import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,7 +25,7 @@ public void setUp() { @Override public Tuple createValues( TypeTag tag, - PrefabValues prefabValues, + VintageValueProvider valueProvider, LinkedHashSet typeStack ) { return Tuple.of("red", "blue", new String("red")); diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FactoriesTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FactoriesTest.java similarity index 76% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FactoriesTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FactoriesTest.java index 4b951f4de..98337dd5a 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FactoriesTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FactoriesTest.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import static nl.jqno.equalsverifier.internal.testhelpers.Util.coverThePrivateConstructor; diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FallbackFactoryTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FallbackFactoryTest.java similarity index 81% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FallbackFactoryTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FallbackFactoryTest.java index 85731d91b..ff91709c4 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/FallbackFactoryTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/FallbackFactoryTest.java @@ -1,6 +1,6 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; import static nl.jqno.equalsverifier.internal.testhelpers.Util.defaultEquals; import static nl.jqno.equalsverifier.internal.testhelpers.Util.defaultHashCode; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -8,10 +8,10 @@ import java.util.LinkedHashSet; import nl.jqno.equalsverifier.internal.exceptions.RecursionException; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; import nl.jqno.equalsverifier.testhelpers.types.RecursiveTypeHelper.Node; import nl.jqno.equalsverifier.testhelpers.types.RecursiveTypeHelper.NodeArray; @@ -21,19 +21,22 @@ import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.TwoElementEnum; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; public class FallbackFactoryTest { private FallbackFactory factory; - private PrefabValues prefabValues; + private VintageValueProvider valueProvider; private LinkedHashSet typeStack; @BeforeEach public void setUp() { - factory = new FallbackFactory<>(); + Objenesis objenesis = new ObjenesisStd(); + factory = new FallbackFactory<>(objenesis); FactoryCache factoryCache = new FactoryCache(); factoryCache.put(int.class, values(42, 1337, 42)); - prefabValues = new PrefabValues(factoryCache); + valueProvider = new VintageValueProvider(factoryCache, objenesis); typeStack = new LinkedHashSet<>(); } @@ -54,7 +57,7 @@ public void giveMultiElementEnum() { @Test public void giveArray() { - Tuple tuple = factory.createValues(new TypeTag(int[].class), prefabValues, typeStack); + Tuple tuple = factory.createValues(new TypeTag(int[].class), valueProvider, typeStack); assertArrayEquals(new int[] { 42 }, (int[]) tuple.getRed()); assertArrayEquals(new int[] { 1337 }, (int[]) tuple.getBlue()); } @@ -74,7 +77,7 @@ public void giveClassWithFields() { @Test public void dontGiveRecursiveClass() { ExpectedException - .when(() -> factory.createValues(new TypeTag(Node.class), prefabValues, typeStack)) + .when(() -> factory.createValues(new TypeTag(Node.class), valueProvider, typeStack)) .assertThrows(RecursionException.class); } @@ -82,7 +85,7 @@ public void dontGiveRecursiveClass() { public void dontGiveTwoStepRecursiveClass() { ExpectedException .when(() -> - factory.createValues(new TypeTag(TwoStepNodeA.class), prefabValues, typeStack) + factory.createValues(new TypeTag(TwoStepNodeA.class), valueProvider, typeStack) ) .assertThrows(RecursionException.class) .assertDescriptionContains("TwoStepNodeA", "TwoStepNodeB"); @@ -91,12 +94,13 @@ public void dontGiveTwoStepRecursiveClass() { @Test public void dontGiveRecursiveArray() { ExpectedException - .when(() -> factory.createValues(new TypeTag(NodeArray.class), prefabValues, typeStack)) + .when(() -> factory.createValues(new TypeTag(NodeArray.class), valueProvider, typeStack) + ) .assertThrows(RecursionException.class); } private void assertCorrectTuple(Class type, T expectedRed, T expectedBlue) { - Tuple tuple = factory.createValues(new TypeTag(type), prefabValues, typeStack); + Tuple tuple = factory.createValues(new TypeTag(type), valueProvider, typeStack); assertEquals(expectedRed, tuple.getRed()); assertEquals(expectedBlue, tuple.getBlue()); } diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/MapFactoryTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/MapFactoryTest.java similarity index 74% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/MapFactoryTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/MapFactoryTest.java index 2076e9082..afd23e9d7 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/MapFactoryTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/MapFactoryTest.java @@ -1,17 +1,18 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.JavaApiPrefabValues; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.OneElementEnum; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; @SuppressWarnings("rawtypes") public class MapFactoryTest { @@ -39,7 +40,7 @@ public class MapFactoryTest { private static final MapFactory MAP_FACTORY = new MapFactory<>(HashMap::new); private final LinkedHashSet typeStack = new LinkedHashSet<>(); - private PrefabValues prefabValues; + private VintageValueProvider valueProvider; private String red; private String blue; private Object redObject; @@ -48,19 +49,19 @@ public class MapFactoryTest { @BeforeEach public void setUp() { - prefabValues = new PrefabValues(JavaApiPrefabValues.build()); - red = prefabValues.giveRed(STRING_TYPETAG); - blue = prefabValues.giveBlue(STRING_TYPETAG); - redObject = prefabValues.giveRed(OBJECT_TYPETAG); - blueObject = prefabValues.giveBlue(OBJECT_TYPETAG); - redEnum = prefabValues.giveBlue(ONEELEMENTENUM_TYPETAG); + valueProvider = new VintageValueProvider(JavaApiPrefabValues.build(), new ObjenesisStd()); + red = valueProvider.giveRed(STRING_TYPETAG); + blue = valueProvider.giveBlue(STRING_TYPETAG); + redObject = valueProvider.giveRed(OBJECT_TYPETAG); + blueObject = valueProvider.giveBlue(OBJECT_TYPETAG); + redEnum = valueProvider.giveBlue(ONEELEMENTENUM_TYPETAG); } @Test public void createMapsOfStringToString() { Tuple tuple = MAP_FACTORY.createValues( STRINGSTRINGMAP_TYPETAG, - prefabValues, + valueProvider, typeStack ); assertEquals(mapOf(red, blue), tuple.getRed()); @@ -69,14 +70,14 @@ public void createMapsOfStringToString() { @Test public void createMapsOfWildcard() { - Tuple tuple = MAP_FACTORY.createValues(WILDCARDMAP_TYPETAG, prefabValues, typeStack); + Tuple tuple = MAP_FACTORY.createValues(WILDCARDMAP_TYPETAG, valueProvider, typeStack); assertEquals(mapOf(redObject, blueObject), tuple.getRed()); assertEquals(mapOf(blueObject, blueObject), tuple.getBlue()); } @Test public void createRawMaps() { - Tuple tuple = MAP_FACTORY.createValues(RAWMAP_TYPETAG, prefabValues, typeStack); + Tuple tuple = MAP_FACTORY.createValues(RAWMAP_TYPETAG, valueProvider, typeStack); assertEquals(mapOf(redObject, blueObject), tuple.getRed()); assertEquals(mapOf(blueObject, blueObject), tuple.getBlue()); } @@ -85,7 +86,7 @@ public void createRawMaps() { public void createMapOfOneElementEnumKey() { Tuple tuple = MAP_FACTORY.createValues( ONEELEMENTENUMKEYMAP_TYPETAG, - prefabValues, + valueProvider, typeStack ); assertEquals(mapOf(redEnum, blueObject), tuple.getRed()); diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleFactoryTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleFactoryTest.java similarity index 90% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleFactoryTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleFactoryTest.java index 1dc6650dc..3adabc58f 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleFactoryTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleFactoryTest.java @@ -1,4 +1,4 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleGenericFactoryTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleGenericFactoryTest.java similarity index 73% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleGenericFactoryTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleGenericFactoryTest.java index be6a1b7d9..ff5f32d6e 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factories/SimpleGenericFactoryTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factories/SimpleGenericFactoryTest.java @@ -1,16 +1,17 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factories; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.LinkedHashSet; import java.util.Optional; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.JavaApiPrefabValues; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; import nl.jqno.equalsverifier.testhelpers.types.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; @SuppressWarnings("rawtypes") public class SimpleGenericFactoryTest { @@ -40,7 +41,7 @@ public class SimpleGenericFactoryTest { private static final PrefabValueFactory PAIR_FACTORY = Factories.simple(Pair::new, null); private final LinkedHashSet typeStack = new LinkedHashSet<>(); - private PrefabValues prefabValues; + private VintageValueProvider valueProvider; private String redString; private String blueString; private Integer redInt; @@ -50,20 +51,20 @@ public class SimpleGenericFactoryTest { @BeforeEach public void setUp() { - prefabValues = new PrefabValues(JavaApiPrefabValues.build()); - redString = prefabValues.giveRed(STRING_TYPETAG); - blueString = prefabValues.giveBlue(STRING_TYPETAG); - redInt = prefabValues.giveRed(INTEGER_TYPETAG); - blueInt = prefabValues.giveBlue(INTEGER_TYPETAG); - redObject = prefabValues.giveRed(OBJECT_TYPETAG); - blueObject = prefabValues.giveBlue(OBJECT_TYPETAG); + valueProvider = new VintageValueProvider(JavaApiPrefabValues.build(), new ObjenesisStd()); + redString = valueProvider.giveRed(STRING_TYPETAG); + blueString = valueProvider.giveBlue(STRING_TYPETAG); + redInt = valueProvider.giveRed(INTEGER_TYPETAG); + blueInt = valueProvider.giveBlue(INTEGER_TYPETAG); + redObject = valueProvider.giveRed(OBJECT_TYPETAG); + blueObject = valueProvider.giveBlue(OBJECT_TYPETAG); } @Test public void createOptionalsOfMapOfString() { Tuple tuple = OPTIONAL_FACTORY.createValues( STRINGOPTIONAL_TYPETAG, - prefabValues, + valueProvider, typeStack ); assertEquals(Optional.of(redString), tuple.getRed()); @@ -74,7 +75,7 @@ public void createOptionalsOfMapOfString() { public void createOptionalsOfWildcard() { Tuple tuple = OPTIONAL_FACTORY.createValues( WILDCARDOPTIONAL_TYPETAG, - prefabValues, + valueProvider, typeStack ); assertEquals(Optional.of(redObject), tuple.getRed()); @@ -85,7 +86,7 @@ public void createOptionalsOfWildcard() { public void createRawOptionals() { Tuple tuple = OPTIONAL_FACTORY.createValues( RAWOPTIONAL_TYPETAG, - prefabValues, + valueProvider, typeStack ); assertEquals(Optional.of(redObject), tuple.getRed()); @@ -94,7 +95,7 @@ public void createRawOptionals() { @Test public void createSomethingWithMoreThanOneTypeParameter() { - Tuple tuple = PAIR_FACTORY.createValues(PAIR_TYPETAG, prefabValues, typeStack); + Tuple tuple = PAIR_FACTORY.createValues(PAIR_TYPETAG, valueProvider, typeStack); assertEquals(new Pair<>(redString, redInt), tuple.getRed()); assertEquals(new Pair<>(blueString, blueInt), tuple.getBlue()); } diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaFxFactoryProviderTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaFxFactoryProviderTest.java similarity index 68% rename from equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaFxFactoryProviderTest.java rename to equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaFxFactoryProviderTest.java index 5e8e89474..06681caeb 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/prefabvalues/factoryproviders/JavaFxFactoryProviderTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/vintage/prefabvalues/factoryproviders/JavaFxFactoryProviderTest.java @@ -1,28 +1,29 @@ -package nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders; +package nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.List; import java.util.Map; -import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; -import nl.jqno.equalsverifier.internal.prefabvalues.Tuple; -import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; -import nl.jqno.equalsverifier.internal.prefabvalues.factories.PrefabValueFactory; -import nl.jqno.equalsverifier.internal.prefabvalues.factoryproviders.JavaFxFactoryProvider.PropertyFactory; +import nl.jqno.equalsverifier.internal.reflection.JavaApiPrefabValues; +import nl.jqno.equalsverifier.internal.reflection.Tuple; +import nl.jqno.equalsverifier.internal.reflection.TypeTag; +import nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.PrefabValueFactory; +import nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factoryproviders.JavaFxFactoryProvider.PropertyFactory; import nl.jqno.equalsverifier.testhelpers.types.Point; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.objenesis.ObjenesisStd; @SuppressWarnings("rawtypes") public class JavaFxFactoryProviderTest { - private PrefabValues prefabValues; + private VintageValueProvider valueProvider; @BeforeEach public void setUp() { - prefabValues = new PrefabValues(JavaApiPrefabValues.build()); + valueProvider = new VintageValueProvider(JavaApiPrefabValues.build(), new ObjenesisStd()); } @Test @@ -39,10 +40,10 @@ public void createInstancesWithCorrectSingleGenericParameter() { GenericContainer.class.getName(), List.class ); - Tuple tuple = factory.createValues(tag, prefabValues, null); + Tuple tuple = factory.createValues(tag, valueProvider, null); - assertEquals(prefabValues.giveRed(listTag), tuple.getRed().t); - assertEquals(prefabValues.giveBlue(listTag), tuple.getBlue().t); + assertEquals(valueProvider.giveRed(listTag), tuple.getRed().t); + assertEquals(valueProvider.giveBlue(listTag), tuple.getBlue().t); assertEquals(String.class, tuple.getRed().t.get(0).getClass()); } @@ -63,10 +64,10 @@ public void createInstancesWithCorrectMultipleGenericParameter() { GenericMultiContainer.class.getName(), Map.class ); - Tuple tuple = factory.createValues(tag, prefabValues, null); + Tuple tuple = factory.createValues(tag, valueProvider, null); - assertEquals(prefabValues.giveRed(mapTag), tuple.getRed().t); - assertEquals(prefabValues.giveBlue(mapTag), tuple.getBlue().t); + assertEquals(valueProvider.giveRed(mapTag), tuple.getRed().t); + assertEquals(valueProvider.giveBlue(mapTag), tuple.getBlue().t); Map.Entry next = (Map.Entry) tuple.getRed().t.entrySet().iterator().next(); assertEquals(String.class, next.getKey().getClass()); diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/ConfigurationHelper.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/ConfigurationHelper.java new file mode 100644 index 000000000..5480ed441 --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/ConfigurationHelper.java @@ -0,0 +1,80 @@ +package nl.jqno.equalsverifier.internal.util; + +import java.util.*; +import nl.jqno.equalsverifier.Warning; + +public final class ConfigurationHelper { + + private ConfigurationHelper() {} + + public static final Configuration emptyConfiguration( + Class type, + Warning... warnings + ) { + return Configuration.build( + type, + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + null, + false, + null, + false, + warnings.length == 0 + ? EnumSet.noneOf(Warning.class) + : EnumSet.copyOf(Arrays.asList(warnings)), + null, + Collections.emptySet(), + Collections.emptySet(), + Collections.emptyList(), + Collections.emptyList() + ); + } + + public static final Configuration emptyConfigurationWithNonnullFields( + Class type, + String... fieldNames + ) { + return Configuration.build( + type, + Collections.emptySet(), + Collections.emptySet(), + new HashSet<>(Arrays.asList(fieldNames)), + Collections.emptySet(), + null, + false, + null, + false, + EnumSet.noneOf(Warning.class), + null, + Collections.emptySet(), + Collections.emptySet(), + Collections.emptyList(), + Collections.emptyList() + ); + } + + public static final Configuration emptyConfigurationWithPrefabbedFields( + Class type, + String... fieldNames + ) { + return Configuration.build( + type, + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + new HashSet<>(Arrays.asList(fieldNames)), + null, + false, + null, + false, + EnumSet.noneOf(Warning.class), + null, + Collections.emptySet(), + Collections.emptySet(), + Collections.emptyList(), + Collections.emptyList() + ); + } +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/FormatterTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/FormatterTest.java index 8570afd0b..ac26e1721 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/FormatterTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/util/FormatterTest.java @@ -8,9 +8,13 @@ import nl.jqno.equalsverifier.internal.reflection.Instantiator; import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; import org.junit.jupiter.api.Test; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; public class FormatterTest { + private final Objenesis objenesis = new ObjenesisStd(); + @Test public void noParameters() { Formatter f = Formatter.of("No parameters"); @@ -69,35 +73,38 @@ public void oneParameterWithNoFieldsAndThrowsWithNullMessage() { @Test public void oneAbstractParameter() { - Instantiator i = Instantiator.of(Abstract.class); + Instantiator i = Instantiator.of(Abstract.class, objenesis); Formatter f = Formatter.of("Abstract: %%", i.instantiate()); assertThat(f.format(), containsString("Abstract: [Abstract x=0]")); } @Test public void oneConcreteSubclassParameter() { - Instantiator i = Instantiator.of(AbstractImpl.class); + Instantiator i = Instantiator.of(AbstractImpl.class, objenesis); Formatter f = Formatter.of("Concrete: %%", i.instantiate()); assertThat(f.format(), containsString("Concrete: something concrete")); } @Test public void oneDelegatedAbstractParameter() { - Instantiator i = Instantiator.of(AbstractDelegation.class); + Instantiator i = Instantiator.of(AbstractDelegation.class, objenesis); Formatter f = Formatter.of("Abstract: %%", i.instantiate()); assertThat(f.format(), containsString("Abstract: [AbstractDelegation y=0]")); } @Test public void oneDelegatedConcreteSubclassParameter() { - Instantiator i = Instantiator.of(AbstractDelegationImpl.class); + Instantiator i = Instantiator.of( + AbstractDelegationImpl.class, + objenesis + ); Formatter f = Formatter.of("Concrete: %%", i.instantiate()); assertThat(f.format(), containsString("Concrete: something concrete")); } @Test public void oneThrowingContainerParameter() { - Instantiator i = Instantiator.of(Throwing.class); + Instantiator i = Instantiator.of(Throwing.class, objenesis); ThrowingContainer tc = new ThrowingContainer(i.instantiate()); Formatter f = Formatter.of("TC: %%", tc); String expected = @@ -107,7 +114,7 @@ public void oneThrowingContainerParameter() { @Test public void oneAbstractContainerParameter() { - Instantiator i = Instantiator.of(AbstractDelegation.class); + Instantiator i = Instantiator.of(AbstractDelegation.class, objenesis); AbstractContainer ac = new AbstractContainer(i.instantiate()); Formatter f = Formatter.of("AC: %%", ac); diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/FactoryCacheFactory.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/FactoryCacheFactory.java index c12e7ca33..9f9871c6e 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/FactoryCacheFactory.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/FactoryCacheFactory.java @@ -1,8 +1,8 @@ package nl.jqno.equalsverifier.testhelpers; -import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values; +import static nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.Factories.values; -import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; +import nl.jqno.equalsverifier.internal.reflection.FactoryCache; public final class FactoryCacheFactory { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/FinalPoint.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/FinalPoint.java index ac3cb73de..41e081c18 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/FinalPoint.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/FinalPoint.java @@ -10,6 +10,14 @@ public FinalPoint(int x, int y) { this.y = y; } + public int getX() { + return x; + } + + public int getY() { + return y; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof FinalPoint)) { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/ThrowingInitializer.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/ThrowingInitializer.java new file mode 100644 index 000000000..87946f4e8 --- /dev/null +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/ThrowingInitializer.java @@ -0,0 +1,15 @@ +package nl.jqno.equalsverifier.testhelpers.types; + +public final class ThrowingInitializer { + { + // Throwing something that will immediately be thrown when the class is constructed. + if (true) { + throw new IllegalStateException("initializing"); + } + } + + private ThrowingInitializer() {} + + public static final ThrowingInitializer X = new ThrowingInitializer(); + public static final ThrowingInitializer Y = new ThrowingInitializer(); +} diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/TypeHelper.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/TypeHelper.java index 3a69c625e..d953a9294 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/TypeHelper.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/testhelpers/types/TypeHelper.java @@ -266,6 +266,12 @@ public static final class PrimitiveContainer { public int field; } + public static final class NonNullContainer { + + @NonNull + public Object field = new Object(); + } + public static final class StaticFinalContainer { public static final int CONST = 42; diff --git a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java index 6ad902ab4..2da2a26f3 100644 --- a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java +++ b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java @@ -23,8 +23,8 @@ public JarAsserter(JarReader reader) { public void assertPresenceOfCoreClasses() { assertPresenceOf( EV + "/EqualsVerifier.class", - EV + "/internal/reflection/ClassAccessor.class", - EV + "/internal/prefabvalues/PrefabValues.class" + EV + "/internal/reflection/vintage/ClassAccessor.class", + EV + "/internal/checkers/HierarchyChecker.class" ); } diff --git a/pom.xml b/pom.xml index 4a5a69a64..ca94acb02 100644 --- a/pom.xml +++ b/pom.xml @@ -214,6 +214,18 @@ ${version.bytebuddy} test + + joda-time + joda-time + ${version.joda-time} + provided + + + com.google.guava + guava + ${version.guava} + provided +