We are currently writing a code generation tool to generate hashCode
, equals
and toString
methods. (For those interested, it is a JAXB XJC plugin to generate reflection-free runtime-free hashCode
/equals
/toString
methods.)
We've started with the hashCode
and got it working so far. We've written a hierarchy of code generators which cover all the required cases - from primitives to arrays, lists and some other special classes.
Here's how a simple code generator looks like:
public class CastToIntHashCodeCodeGenerator extends
ValueBasedHashCodeCodeGenerator {
public CastToIntHashCodeCodeGenerator(
TypedHashCodeCodeGeneratorFactory factory, JCodeModel codeModel) {
super(factory, codeModel);
}
@Override
public JExpression generateHashCode(JType type, JVar value) {
return JExpr.cast(getCodeModel().INT, value);
}
}
Here's a list generator which is somewhat more complex:
public class ListHashCodeCodeGenerator extends BlockHashCodeCodeGenerator {
public ListHashCodeCodeGenerator(TypedHashCodeCodeGeneratorFactory factory,
JCodeModel codeModel) {
super(factory, codeModel);
}
@Override
protected void append(JBlock block, JVar currentHashCode, JType type,
Collection<JType> possibleTypes, JVar value) {
Validate.isInstanceOf(JClass.class, type);
final JClass _class = (JClass) type;
final JClass jaxbElementClass = getCodeModel().ref(JAXBElement.class);
final Set<JType> arrays = new HashSet<JType>();
final Collection<JClass> jaxbElements = new HashSet<JClass>();
final Set<JType> otherTypes = new HashSet<JType>();
for (final JType possibleType : possibleTypes) {
if (possibleType.isArray()) {
arrays.add(possibleType);
} else if (possibleType instanceof JClass
&& jaxbElementClass
.isAssignableFrom(((JClass) possibleType).erasure())) {
jaxbElements.add((JClass) possibleType);
} else {
otherTypes.add(possibleType);
}
}
// If list items are not arrays or JAXBElements, just delegate to the
// hashCode of the list
if (arrays.isEmpty() && jaxbElements.isEmpty()) {
block.assignPlus(currentHashCode, value.invoke("hashCode"));
} else {
appendElements(block, currentHashCode, possibleTypes, value, _class);
}
}
private void appendElements(JBlock block, JVar currentHashCode,
Collection<JType> possibleTypes, JVar value, final JClass type) {
final JClass elementType = getElementType(type);
final JVar iterator = block.decl(JMod.FINAL,
getCodeModel().ref(Iterator.class).narrow(elementType),
value.name() + "Iterator", value.invoke("iterator"));
final JBlock subBlock = block._while(iterator.invoke("hasNext")).body();
final JVar elementValue = subBlock.decl(JMod.FINAL, elementType,
value.name() + "Element", iterator.invoke("next"));
subBlock.assign(currentHashCode, currentHashCode.mul(JExpr
.lit(getCodeGeneratorFactory().getMultiplier())));
appendElement(subBlock, currentHashCode, elementType, possibleTypes,
elementValue);
}
private void appendElement(final JBlock subBlock, JVar currentHashCode,
final JClass type, Collection<JType> possibleTypes, final JVar value) {
final boolean isAlwaysSet = type.isPrimitive();
final JExpression hasSetValue = isAlwaysSet ? JExpr.TRUE : value
.ne(JExpr._null());
getCodeGeneratorFactory().getCodeGenerator(type).append(subBlock,
currentHashCode, type, possibleTypes, value, hasSetValue,
isAlwaysSet);
}
private JClass getElementType(final JClass _class) {
final JClass elementType;
if (_class.getTypeParameters().size() == 1) {
elementType = _class.getTypeParameters().get(0);
} else {
elementType = getCodeModel().ref(Object.class);
}
return elementType;
}
}
Now after hashCode
is done, I'd like to implement the equals
code generation (and then toString
). What I have noticed is that generation of these methods follows the same "pattern". It is a bit hard to explain, but if we compare hashCode
and equals
we may note that, for instance:
- Most primitive types are handled as-is, only
Double
andFloat
are handled via theirdoubleToLongBits
/floatToIntBits
. - If a list may not contain arrays or special classes (like
JAXBElement
) then it is Ok to callhashCode()
orequals(...)
, otherwise we'd have to iterate and process elements. - For a special class like
JAXBElement
we'll have to process properties (name
,value
and so on).
You may have probably noticed that this is basically the same algorithm but in one case we have to calculate the hash code and in the other - to compare the values. But the pattern is basically the same.
Frankly, I don't want to copy-modify the code three times. I'm looking for a way to make the processing algorithm generic and only write the specific hashCode
/equals
/toString
payload/generation. For instance, I don't want to write the ListEqualsCodeGenerator
or ListToStringCodeGenerator
in parallel to the ListHashCodeGenerator
.
I think the Bridge pattern may be applicable for this task. From the description it could help to separate the algorithm (the "pattern" of generation) from the specific operations (how do I calculate hash code for integers/compare two integers).
However I have never used it and need help to get started.
To make it more specific here are two interfaces I have at the moment:
public interface HashCodeCodeGenerator extends CodeGenerator {
public void append(JBlock block, JVar currentHashCode, JType type,
Collection<JType> possibleTypes, JVar value,
JExpression hasSetValue, boolean isAlwaysSet);
}
public interface EqualsCodeGenerator extends CodeGenerator {
public void append(JBlock block, JType type,
Collection<JType> possibleTypes, JVar left,
JExpression leftHasSetValue, JVar right,
JExpression rightHasSetValue, boolean isAlwaysSet);
}
How could I "generalize" these two generators so that I wouldn't have to duplicate ListHashCodeCodeGenerator
for equals
?