ObjectContracts

Provides fluent composition for Objects' equals, hashCode and toString.

API

ObjectContracts.java
class ObjectContracts {
  ToString<T> toString(String name, Function<T, ?> getter)
  Equality<T> checkEquals(Function<T, ?> getter)
  Hashing<T> hashing(Function<T, ?> getter)
  ObjectContract<T> contract(Class<T> objectClass)
  ObjectContract<T> parse(Class<T> target, String propertyNames)
  String toString(T obj, String propertyNames)
  boolean equals(T obj, Object other, String propertyNames)
  int hashCode(Object obj, String propertyNames)
  int compare(T obj, T other, String propertyNames)
}

Example Usage

For example:

@RequiredArgsConstructor(staticName = "of")
public static class ComplexNumber implements Comparable<ComplexNumber> {

    @Getter private final int real;
    @Getter private final int imaginary;

    private ObjectContracts.ObjectContract<ComplexNumber> contract
            = ObjectContracts.contract(ComplexNumber.class)
                .thenUse("real", ComplexNumber::getReal)
                .thenUse("imaginary", ComplexNumber::getImaginary);


    @Override
    public boolean equals(Object o) {
        return contract.equals(this, o);
    }

    @Override
    public int hashCode() {
        return contract.hashCode(this);
    }

    @Override
    public int compareTo(final ComplexNumber other) {
        return contract.compare(this, other);
    }

    @Override
    public String toString() {
        return contract.toString(this);
    }
}

There are a number of deprecated methods that identify property names as strings. These should not be use, as they use are not type-safe and also use reflection heavily and so impose a performance hit.

Usage Notes

Be aware of ORM loading issues

ObjectContracts implementation can cause DataNucleus to recursively rehydrate a larger number of associated entities (More detail below).

We therefore recommend that you disable persistence-by-reachability by adding:

application.properties
datanucleus.persistenceByReachabilityAtCommit=false
Diagram

In the course of a transaction, the Agreement entity is loaded into memory (not necessarily modified), and then new AgreementRoles are associated to it.

All these entities implement Comparable using ObjectContracts, so that the implementation of AgreementRole's (simplified) is:

public class AgreementRole {
    ...
    public int compareTo(AgreementRole other) {
        return ObjectContracts.compareTo(this, other, "agreement","startDate","party");
    }
    ...
}

while Agreement's is implemented as:

    public class Agreement {
        ...
        public int compareTo(Agreement other) {
            return ObjectContracts.compareTo(this, other, "reference");
        }
        ...
    }

and Party's is similarly implemented as:

public class Party {
    ...
    public int compareTo(Party other) {
        return ObjectContracts.compareTo(this, other, "reference");
    }
    ...
}

DataNucleus’s persistence-by-reachability algorithm adds the AgreementRoles into a SortedSet, which causes AgreementRole#compareTo() to fire:

  • the evaluation of the "agreement" property delegates back to the Agreement, whose own Agreement#compareTo() uses the scalar reference property. As the Agreement is already in-memory, this does not trigger any further database queries

  • the evaluation of the "startDate" property is just a scalar property of the AgreementRole, so will already in-memory

  • the evaluation of the "party" property delegates back to the Party, whose own Party#compareTo() requires the uses the scalar reference property. However, since the Party is not yet in-memory, using the reference property triggers a database query to "rehydrate" the Party instance.

In other words, figuring out whether AgreementRole is comparable requires the persistence-by-reachability algorithm to run, causing the adjacent associated entity Party to also be retrieved.