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 f1e5a6b6e..edddea870 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java @@ -3,6 +3,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.function.Function; import nl.jqno.equalsverifier.Func.Func1; import nl.jqno.equalsverifier.Func.Func2; import nl.jqno.equalsverifier.api.EqualsVerifierApi; @@ -20,21 +21,24 @@ public final class ConfiguredEqualsVerifier implements EqualsVerifierApi { private final EnumSet warningsToSuppress; private final FactoryCache factoryCache; private boolean usingGetClass; + private Function fieldnameToGetter; /** Constructor. */ public ConfiguredEqualsVerifier() { - this(EnumSet.noneOf(Warning.class), new FactoryCache(), false); + this(EnumSet.noneOf(Warning.class), new FactoryCache(), false, null); } /** Private constructor. For internal use only. */ private ConfiguredEqualsVerifier( EnumSet warningsToSuppress, FactoryCache factoryCache, - boolean usingGetClass + boolean usingGetClass, + Function fieldnameToGetter ) { this.warningsToSuppress = warningsToSuppress; this.factoryCache = factoryCache; this.usingGetClass = usingGetClass; + this.fieldnameToGetter = fieldnameToGetter; } /** @@ -46,7 +50,8 @@ public ConfiguredEqualsVerifier copy() { return new ConfiguredEqualsVerifier( EnumSet.copyOf(warningsToSuppress), new FactoryCache().merge(factoryCache), - usingGetClass + usingGetClass, + fieldnameToGetter ); } @@ -91,6 +96,14 @@ public ConfiguredEqualsVerifier usingGetClass() { return this; } + @Override + public ConfiguredEqualsVerifier withFieldnameToGetterConverter( + Function converter + ) { + this.fieldnameToGetter = converter; + return this; + } + /** {@inheritDoc} */ @Override public ConfiguredEqualsVerifier withResetCaches() { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java index 70a753954..fbbc5e1e4 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java @@ -1,5 +1,6 @@ package nl.jqno.equalsverifier.api; +import java.util.function.Function; import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.Func.Func1; import nl.jqno.equalsverifier.Func.Func2; @@ -70,6 +71,19 @@ public interface EqualsVerifierApi { */ EqualsVerifierApi usingGetClass(); + /** + * Determines how a getter name can be derived from a field name. + * + * The default behavior is to uppercase the field's first letter and prepend 'get'. For + * instance, a field name 'employee' would correspond to getter name 'getEmployee'. + * + * This method can be used if your project has a different naming convention. + * + * @param converter A function that converts from field name to getter name. + * @return {@code this}, for easy method chaining. + */ + EqualsVerifierApi withFieldnameToGetterConverter(Function converter); + /** * Signals that all internal caches need to be reset. This is useful when the test framework * uses multiple ClassLoaders to run tests, causing {@link java.lang.Class} instances diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java index a4a88a74b..b178689e7 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import nl.jqno.equalsverifier.ConfiguredEqualsVerifier; @@ -70,6 +71,15 @@ public MultipleTypeEqualsVerifierApi usingGetClass() { return this; } + /** {@inheritDoc} */ + @Override + public MultipleTypeEqualsVerifierApi withFieldnameToGetterConverter( + Function converter + ) { + ev.withFieldnameToGetterConverter(converter); + return this; + } + /** {@inheritDoc} */ @Override public MultipleTypeEqualsVerifierApi withResetCaches() { 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 881a0c32c..14fbb3c4e 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,16 +1,38 @@ package nl.jqno.equalsverifier.api; -import java.util.*; +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.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.*; +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.exceptions.MessagingException; import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; -import nl.jqno.equalsverifier.internal.util.*; +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.util.Formatter; +import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper; +import nl.jqno.equalsverifier.internal.util.PrefabValuesApi; +import nl.jqno.equalsverifier.internal.util.Validations; /** * Helps to construct an {@link EqualsVerifier} test with a fluent API. @@ -29,6 +51,7 @@ public class SingleTypeEqualsVerifierApi implements EqualsVerifierApi { private FactoryCache factoryCache = new FactoryCache(); private CachedHashCodeInitializer cachedHashCodeInitializer = CachedHashCodeInitializer.passthrough(); + private Function fieldnameToGetter = null; private Set allExcludedFields = new HashSet<>(); private Set allIncludedFields = new HashSet<>(); private Set nonnullFields = new HashSet<>(); @@ -129,6 +152,15 @@ public SingleTypeEqualsVerifierApi usingGetClass() { return this; } + /** {@inheritDoc} */ + @Override + public SingleTypeEqualsVerifierApi withFieldnameToGetterConverter( + Function converter + ) { + this.fieldnameToGetter = converter; + return this; + } + /** * Signals that all given fields are not relevant for the {@code equals} contract. {@code * EqualsVerifier} will not fail if one of these fields does not affect the outcome of {@code @@ -396,6 +428,7 @@ private Configuration buildConfig() { redefinedSubclass, usingGetClass, warningsToSuppress, + fieldnameToGetter, factoryCache, ignoredAnnotationClassNames, actualFields, 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 f97da7c4a..d4ad11fd4 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 @@ -5,6 +5,7 @@ import static nl.jqno.equalsverifier.internal.util.Assert.assertTrue; import java.util.Set; +import java.util.function.Function; import nl.jqno.equalsverifier.internal.exceptions.EqualsVerifierInternalBugException; import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag; @@ -24,6 +25,7 @@ public class JpaLazyGetterFieldCheck implements FieldCheck { private final Set ignoredFields; private final PrefabValues prefabValues; private final AnnotationCache annotationCache; + private final Function fieldnameToGetter; public JpaLazyGetterFieldCheck(Configuration config) { this.type = config.getType(); @@ -31,6 +33,7 @@ public JpaLazyGetterFieldCheck(Configuration config) { this.ignoredFields = config.getIgnoredFields(); this.prefabValues = config.getPrefabValues(); this.annotationCache = config.getAnnotationCache(); + this.fieldnameToGetter = config.getFieldnameToGetter(); } @Override @@ -40,8 +43,7 @@ public void execute( FieldAccessor fieldAccessor ) { String fieldName = fieldAccessor.getFieldName(); - String getterName = - "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + String getterName = fieldnameToGetter.apply(fieldName); if (ignoredFields.contains(fieldName) || !fieldIsLazy(fieldAccessor)) { return; 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 fc1ea2e0a..2924db52c 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,8 +1,13 @@ package nl.jqno.equalsverifier.internal.util; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; 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; @@ -17,6 +22,9 @@ public final class Configuration { + private static final Function DEFAULT_FIELDNAME_TO_GETTER_CONVERTER = fn -> + "get" + Character.toUpperCase(fn.charAt(0)) + fn.substring(1); + private final Class type; private final Set nonnullFields; private final CachedHashCodeInitializer cachedHashCodeInitializer; @@ -24,6 +32,7 @@ public final class Configuration { private final Class redefinedSubclass; private final boolean usingGetClass; private final EnumSet warningsToSuppress; + private final Function fieldnameToGetter; private final TypeTag typeTag; private final PrefabValues prefabValues; @@ -48,6 +57,7 @@ private Configuration( Class redefinedSubclass, boolean usingGetClass, EnumSet warningsToSuppress, + Function fieldnameToGetter, List equalExamples, List unequalExamples ) { @@ -63,6 +73,7 @@ private Configuration( this.redefinedSubclass = redefinedSubclass; this.usingGetClass = usingGetClass; this.warningsToSuppress = warningsToSuppress; + this.fieldnameToGetter = fieldnameToGetter; this.equalExamples = equalExamples; this.unequalExamples = unequalExamples; } @@ -77,6 +88,7 @@ public static Configuration build( Class redefinedSubclass, boolean usingGetClass, EnumSet warningsToSuppress, + Function fieldnameToGetter, FactoryCache factoryCache, Set ignoredAnnotationClassNames, Set actualFields, @@ -96,6 +108,9 @@ public static Configuration build( includedFields, actualFields ); + Function converter = fieldnameToGetter != null + ? fieldnameToGetter + : DEFAULT_FIELDNAME_TO_GETTER_CONVERTER; List unequals = ensureUnequalExamples(typeTag, classAccessor, unequalExamples); return new Configuration<>( @@ -111,6 +126,7 @@ public static Configuration build( redefinedSubclass, usingGetClass, warningsToSuppress, + converter, equalExamples, unequals ); @@ -233,6 +249,10 @@ public EnumSet getWarningsToSuppress() { return EnumSet.copyOf(warningsToSuppress); } + public Function getFieldnameToGetter() { + return fieldnameToGetter; + } + public List getEqualExamples() { return Collections.unmodifiableList(equalExamples); } 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 e373062cf..8c1ab9299 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 @@ -89,6 +89,17 @@ public void lazyGettersPickedUpInSuper() { EqualsVerifier.forClass(ChildOfLazyGetterContainer.class).usingGetClass().verify(); } + @Test + public void differentCodingStyle() { + EqualsVerifier + .forClass(DifferentCodingStyleContainer.class) + .suppress(Warning.NONFINAL_FIELDS) + .withFieldnameToGetterConverter(fn -> + "get" + Character.toUpperCase(fn.charAt(2)) + fn.substring(3) + ) + .verify(); + } + private void getterNotUsed(Class type, String method) { ExpectedException .when(() -> EqualsVerifier.forClass(type).suppress(Warning.NONFINAL_FIELDS).verify()) @@ -470,4 +481,39 @@ public boolean equals(Object obj) { return super.equals(obj); } } + + @Entity + static class DifferentCodingStyleContainer { + + @OneToMany(fetch = FetchType.LAZY) + private String m_oneToMany; + + @ManyToOne(fetch = FetchType.LAZY) + private String m_manyToOne; + + public String getOneToMany() { + return m_oneToMany; + } + + public String getManyToOne() { + return m_manyToOne; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DifferentCodingStyleContainer)) { + return false; + } + DifferentCodingStyleContainer other = (DifferentCodingStyleContainer) obj; + return ( + Objects.equals(getOneToMany(), other.getOneToMany()) && + Objects.equals(getManyToOne(), other.getManyToOne()) + ); + } + + @Override + public int hashCode() { + return Objects.hash(getOneToMany(), getManyToOne()); + } + } }