I've been thinking some more about equals and inheritance; things are not
quite as simple as they seem. The strategy on trunk currently is (in part):
- If other has the same class as instance, proceed.
- Else, if other's class is a proper subclass of instance's class, then
call other.equals(instance)
- Else, if other is a subclass of the most specific class contributing
equals properties to instance, then proceed.
- Else, return false.
There is a problem with this, exposed in the most recent unit test commit to
trunk. Suppose that Child1 and Child2 are subclasses of Parent. Child1
contributes no new properties, but Child2 does. In this case, if instance
is of class Child1 and other of class Child2, the algorithm will consider
them candidates for equality. However, if instance is of class Child2 and
other of class Child1, then they are not considered equal, thus breaking
symmetry.
The solution that's been considered for this is to modify the above
algorithm in the case where other is not a proper subclass of instance to
check the most specific contributing class for both equals and other, based
on the assumption that both are strictly relying on Pojomatic, by means of
looking at their respective pojomators. This runs afoul of interfaces,
however; if Intf is a pojomated interface, then implementing classes will
not have a pojomator.
One solution is to change the rules for interface pojomators. I think a
more general solution might be to make this user selectable, with defaults
determined by whether the pojomated class is an interface or not.
Specifically, I'm proposing adding a new class-level annotation, say,
@InheritanceStrategy. It's value would by typed to an InheritanceStrategy
enum with three values: SUBCLASSABLE, NOT_SUBCLASSABLE and DEFAULT; DEFAULT
would map to SUBCLASSABLE for non-interfaces and NOT_SUBCLASSABLE for
interfaces.
Additionally, it would be useful to add a no-arg annotation, say,
@NotEqualToParent, to handle cases where the child knows that it cannot be
considered equal to the parent. This would be useful for at least two cases
I can think of. One is where a child class is semantically distinct from
it's parent, even if there are no new properties (the difference might lie
in method implementations). A second would be where a child class needs to
introduce custom equals logic that cannot be expressed via pojomatic. The
impact of this annotation would be to ensure that ClassProperties
getEqualsParentClass would return a class no higher in the hierarchy than
the annotated class.
Thoughts?
|