2009-04-23

Adds equals() and hashCode() to Ibator's example classes.

Ibator includes EqualsHashCodePlugin by default.
It adds equals() and hashCode() methods to the domain classes.
As I want to add these methods to example classes, I wrote another plugin.

import java.util.Iterator;
import java.util.List;

import org.apache.ibatis.ibator.api.IbatorPluginAdapter;
import org.apache.ibatis.ibator.api.IntrospectedTable;
import org.apache.ibatis.ibator.api.dom.OutputUtilities;
import org.apache.ibatis.ibator.api.dom.java.Field;
import org.apache.ibatis.ibator.api.dom.java.FullyQualifiedJavaType;
import org.apache.ibatis.ibator.api.dom.java.InnerClass;
import org.apache.ibatis.ibator.api.dom.java.JavaVisibility;
import org.apache.ibatis.ibator.api.dom.java.Method;
import org.apache.ibatis.ibator.api.dom.java.Parameter;
import org.apache.ibatis.ibator.api.dom.java.TopLevelClass;

public class EqualsHashCodePlugin extends IbatorPluginAdapter
{
 public boolean validate(List<String> warnings)
 {
  return true;
 }

 @Override
 public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass,
  IntrospectedTable introspectedTable)
 {
  generateEquals(introspectedTable, topLevelClass);
  generateHashCode(introspectedTable, topLevelClass);
  return true;
 }

 @Override
 public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass,
  IntrospectedTable introspectedTable)
 {
  generateEquals(introspectedTable, topLevelClass);
  generateHashCode(introspectedTable, topLevelClass);
  return true;
 }

 @Override
 public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass,
  IntrospectedTable introspectedTable)
 {
  generateEquals(introspectedTable, topLevelClass);
  generateHashCode(introspectedTable, topLevelClass);
  return true;
 }

 @Override
 public boolean modelExampleClassGenerated(TopLevelClass topLevelClass,
  IntrospectedTable introspectedTable)
 {
  generateEquals(introspectedTable, topLevelClass);
  generateHashCode(introspectedTable, topLevelClass);

  InnerClass innerClass = topLevelClass.getInnerClasses().get(0);
  generateEquals(introspectedTable, innerClass);
  generateHashCode(introspectedTable, innerClass);

  return true;
 }

 private void generateHashCode(IntrospectedTable introspectedTable, InnerClass innerClass)
 {
  Iterator<Field> iter;
  Method method = new Method();
  method.setVisibility(JavaVisibility.PUBLIC);
  method.setReturnType(FullyQualifiedJavaType.getIntInstance());
  method.setName("hashCode");
  if (introspectedTable.isJava5Targeted())
  {
   method.addAnnotation("@Override");
  }

  if (innerClass instanceof TopLevelClass)
  {
   ibatorContext.getCommentGenerator().addGeneralMethodComment(method,
    introspectedTable);
  }

  method.addBodyLine("final int prime = 17;");
  if (hasSuperClass(innerClass))
   method.addBodyLine("int result = super.hashCode();");
  else
   method.addBodyLine("int result = 1;");

  StringBuilder sb = new StringBuilder();

  sb.setLength(0);
  boolean bitsDeclared = false;
  iter = innerClass.getFields().iterator();
  while (iter.hasNext())
  {
   Field field = iter.next();
   sb.setLength(0);

   String property = field.getName();
   FullyQualifiedJavaType type = field.getType();
   if (type.isPrimitive())
   {
    String typeStr = type.getFullyQualifiedName();
    if ("int".equals(typeStr))
    {
     sb.append("result = prime * result + ");
     sb.append(property);
     sb.append(";");
    }
    else if ("byte".equals(typeStr) || "char".equals(typeStr)
     || "short".equals(typeStr))
    {
     sb.append("result = prime * result + (int)");
     sb.append(property);
     sb.append(";");
    }
    else if ("long".equals(typeStr))
    {
     sb.append("result = prime * result + (int)(");
     sb.append(property);
     sb.append(" ^ (");
     sb.append(property);
     sb.append(" >>> 32));");
    }
    else if ("float".equals(typeStr))
    {
     sb.append("result = prime * result + Float.floatToIntBits(");
     sb.append(property);
     sb.append(");");
    }
    else if ("double".equals(typeStr))
    {
     if (!bitsDeclared)
     {
      sb.append("long ");
      bitsDeclared = true;
     }
     sb.append("bits = Double.doubleToLongBits(");
     sb.append(property);
     sb.append(");");
     sb.append("result = prime * result + (int)(bits ^ (bits >>> 32));");
    }
    else if ("boolean".equals(typeStr))
    {
     sb.append("result = prime * result + (");
     sb.append(property);
     sb.append(" ? 1 : 0);");
    }
   }
   else
   {
    sb.append("result = prime * result + (null == ");
    sb.append(property);
    sb.append(" ? 0 : ");
    sb.append(property);
    sb.append(".hashCode());");
   }

   method.addBodyLine(sb.toString());
  }

  method.addBodyLine("return result;");

  innerClass.addMethod(method);
 }

 /**
  * @param introspectedTable
  * @param innerClass
  */
 private void generateEquals(IntrospectedTable introspectedTable, InnerClass innerClass)
 {
  Method method;
  StringBuilder sb;
  boolean first;
  Iterator<Field> iter;
  method = new Method();
  method.setVisibility(JavaVisibility.PUBLIC);
  method.setReturnType(FullyQualifiedJavaType.getBooleanPrimitiveInstance());
  method.setName("equals");
  method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), "that"));
  if (introspectedTable.isJava5Targeted())
  {
   method.addAnnotation("@Override");
  }

  if (innerClass instanceof TopLevelClass)
  {
   ibatorContext.getCommentGenerator().addGeneralMethodComment(method,
    introspectedTable);
  }

  method.addBodyLine("if (this == that)");
  method.addBodyLine("return true;");

  if (hasSuperClass(innerClass))
  {
   method.addBodyLine("if (!super.equals(that))");
   method.addBodyLine("return false;");
  }

  sb = new StringBuilder();
  sb.append("if((that == null) || (that.getClass() != this.getClass()))");
  method.addBodyLine(sb.toString());
  method.addBodyLine("return false;");

  sb.setLength(0);
  sb.append(innerClass.getType().getShortName());
  sb.append(" other = (");
  sb.append(innerClass.getType().getShortName());
  sb.append(") that;");
  method.addBodyLine(sb.toString());

  sb.setLength(0);
  first = true;
  iter = innerClass.getFields().iterator();
  while (iter.hasNext())
  {
   Field field = iter.next();
   sb.setLength(0);

   if (first)
   {
    sb.append("return ");
    first = false;
   }
   else
   {
    OutputUtilities.javaIndent(sb, 1);
    sb.append("&& ");
   }

   String property = field.getName();
   FullyQualifiedJavaType type = field.getType();
   if (type.isPrimitive())
   {
    sb.append(property);
    sb.append(" == other.");
    sb.append(property);
   }
   else
   {
    sb.append("(");
    sb.append(property);
    sb.append(" == other.");
    sb.append(property);
    sb.append(" || (");
    sb.append(property);
    sb.append(" != null && ");
    sb.append(property);
    sb.append(".equals(other.");
    sb.append(property);
    sb.append(")))");
   }

   if (!iter.hasNext())
   {
    sb.append(';');
   }

   method.addBodyLine(sb.toString());
  }
  innerClass.addMethod(method);
 }

 private boolean hasSuperClass(InnerClass innerClass)
 {
  return null != innerClass.getSuperClass();
 }
}

It is useful when I use those classes with Mockito.
Note that this plugin generates the methods for domain classes as well, so don't use it with the default EqualsHashCodePlugin.