Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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 and Float are handled via their doubleToLongBits/floatToIntBits.
  • If a list may not contain arrays or special classes (like JAXBElement) then it is Ok to call hashCode() or equals(...), 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 ListHashCodeCodeGeneratorfor equals?

share|improve this question

closed as off-topic by Vogel612, Quill, rolfl, SuperBiasedMan, 200_success Sep 7 at 7:37

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Questions containing broken code or asking for advice about code not yet written are off-topic, as the code is not ready for review. After the question has been edited to contain working code, we will consider reopening it." – Vogel612, Quill, rolfl, SuperBiasedMan, 200_success
If this question can be reworded to fit the rules in the help center, please edit the question.