/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jboss.forge.roaster.model.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jboss.forge.roaster.model.source.FieldSource;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import org.jboss.forge.roaster.model.source.MethodSource;
/**
* Utility refactory methods for {@link JavaClassSource} objects
*
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
* @author <a href="mailto:ggastald@redhat.com">George Gastaldi</a>
* @author <a href="mailto:vreynolds@redhat.com">Vineet Reynolds</a>
*/
public class Refactory
{
private static final String RETURN_FALSE = " return false;";
private Refactory() {}
/**
* Generates a getXXX and setXXX method for the supplied field
*
* @param clazz
* @param field
*/
public static void createGetterAndSetter(final JavaClassSource clazz, final FieldSource<JavaClassSource> field)
{
if (!clazz.hasField(field))
{
throw new IllegalArgumentException("Entity did not contain the given field [" + field + "]");
}
clazz.getMethods();
String fieldName = field.getName();
String methodNameSuffix = Strings.capitalize(fieldName);
clazz.addMethod().setReturnType(field.getType().toString()).setName("get" + methodNameSuffix)
.setPublic()
.setBody("return this." + fieldName + ";");
if (!field.isFinal())
{
clazz.addMethod().setReturnTypeVoid().setName("set" + methodNameSuffix).setPublic()
.setParameters("final " + field.getType().toString() + " " + fieldName)
.setBody("this." + fieldName + " = " + fieldName + ";");
}
}
/**
* Create a <i>hashCode</i> and <i>equals</i> implementation for the given class and fields
*
* @deprecated Use {@link Refactory#createHashCodeAndEquals(JavaClass, Field<O>...)} instead, since this method
* relies on the existence of the id field
*/
@Deprecated
public static void createHashCodeAndEquals(final JavaClassSource clazz)
{
clazz.addMethod(
"public boolean equals(Object that) { " +
"if (this == that) { return true; } " +
"if (that == null) { return false; } " +
"if (getClass() != that.getClass()) { return false; } " +
"if (id != null) { return id.equals((("
+ clazz.getName() + ") that).id); } " +
"return super.equals(that); " +
"}")
.addAnnotation(Override.class);
clazz.addMethod(
"public int hashCode() { " +
"if (id != null) { return id.hashCode(); } " +
"return super.hashCode(); }")
.addAnnotation(Override.class);
}
/**
* Create a <i>hashCode</i> and <i>equals</i> implementation for the given class and fields. Callers must verify that
* the types of the fields override the default identity based equals and hashcode implementations. No warnings are
* issued in an event where the field type uses the implementation of java.lang.Object.
*
* This method ignores static fields for generating the equals and hashCode methods, since they are ideally not meant
* to be used in these cases. Although transient fields could also be ignored, they are not since there is no
* mechanism to convey warnings (not errors) in this case.
*
* @param clazz class to be changed
* @param fields fields to be used in the equals/hashCode methods
*/
public static void createHashCodeAndEquals(final JavaClassSource clazz, final FieldSource<?>... fields)
{
createEquals(clazz, fields);
createHashCode(clazz, fields);
}
/**
* Create an <i>equals</i> implementation for the given class and fields. Callers must verify that the types of the
* fields override the default identity based equals implementation. No warnings are issued in an event where the
* field type uses the implementation of java.lang.Object.
*
* This method ignores static fields for generating the equals method, since they are ideally not meant to be used in
* these cases. Although transient fields could also be ignored, they are not since there is no mechanism to convey
* warnings (not errors) in this case.
*
* @param clazz class to be changed
* @param fields fields to be used in the equals/hashCode methods
*/
public static void createEquals(final JavaClassSource clazz, final FieldSource<?>... fields)
{
if (clazz == null)
{
throw new IllegalArgumentException("The provided class cannot be null.");
}
if (fields == null || fields.length < 1)
{
throw new IllegalArgumentException("The provided fields cannot be null or empty.");
}
String superEqualsCheck = "";
if (!clazz.getSuperType().equals("java.lang.Object"))
{
superEqualsCheck = "if (!super.equals(obj)) { return false;} ";
}
StringBuilder fieldEqualityChecks = new StringBuilder();
for (FieldSource<?> field : fields)
{
if (field == null)
{
throw new IllegalArgumentException(
"A supplied field was null. The equals and hashCode computation will be aborted.");
}
if (field.isStatic())
{
throw new IllegalArgumentException(
"A static field was detected. The equals and hashCode computation will be aborted.");
}
String fieldName = field.getName();
if (field.getType().isArray())
{
fieldEqualityChecks.append("if (!java.util.Arrays.equals(").append(fieldName).append(", other.")
.append(fieldName)
.append(")) {");
fieldEqualityChecks.append(" return false; }");
}
else if (field.getType().isPrimitive())
{
if (field.getType().isType("float"))
{
fieldEqualityChecks.append("if (Float.floatToIntBits(").append(fieldName)
.append(") != Float.floatToIntBits(other.").append(fieldName)
.append(")) { ");
fieldEqualityChecks.append(RETURN_FALSE);
fieldEqualityChecks.append("} ");
}
else if (field.getType().isType("double"))
{
fieldEqualityChecks.append("if (Double.doubleToLongBits(").append(fieldName)
.append(") != Double.doubleToLongBits(other.").append(fieldName)
.append(")) { ");
fieldEqualityChecks.append(RETURN_FALSE);
fieldEqualityChecks.append("} ");
}
else
{
fieldEqualityChecks.append("if (").append(fieldName).append(" != other.").append(fieldName)
.append(") { ");
fieldEqualityChecks.append(RETURN_FALSE);
fieldEqualityChecks.append("} ");
}
}
else
{
fieldEqualityChecks.append("if (").append(fieldName).append(" != null) { ");
fieldEqualityChecks.append(" if(!").append(fieldName).append(".equals(");
fieldEqualityChecks.append("other.").append(fieldName);
fieldEqualityChecks.append(")) { return false;} } ");
}
}
if (fieldEqualityChecks.length() < 1)
{
throw new IllegalArgumentException(
"A failure was detected when generating the equals and hashCode methods. Verify the type and modifiers of the provided fields.");
}
StringBuilder typeCheckAndAssignment = new StringBuilder();
String klassName = clazz.getName();
typeCheckAndAssignment.append("if (!(obj instanceof ").append(klassName).append(")) {");
typeCheckAndAssignment.append(" return false;}");
typeCheckAndAssignment.append(klassName).append(" other = (").append(klassName).append(") obj;");
clazz.addMethod(
"public boolean equals(Object obj) { " +
"if (this == obj) { return true; } " +
superEqualsCheck +
typeCheckAndAssignment.toString() +
fieldEqualityChecks.toString() +
"return true; " +
"}")
.addAnnotation(Override.class);
}
/**
* Create a <i>hashCode</i> implementation for the given class and fields. Callers must verify that the types of the
* fields override the default identity based hashcode implementation. No warnings are issued in an event where the
* field type uses the implementation of java.lang.Object.
*
* This method ignores static fields for generating the equals method, since they are ideally not meant to be used in
* these cases. Although transient fields could also be ignored, they are not since there is no mechanism to convey
* warnings (not errors) in this case.
*
* @param clazz class to be changed
* @param fields fields to be used in the equals/hashCode methods
*/
public static void createHashCode(final JavaClassSource clazz, final FieldSource<?>... fields)
{
if (clazz == null)
{
throw new IllegalArgumentException("The provided class cannot be null.");
}
if (fields == null || fields.length < 1)
{
throw new IllegalArgumentException("The provided fields cannot be null or empty.");
}
String defaultHashcode = "int result = 1;";
if (!clazz.getSuperType().equals("java.lang.Object"))
{
defaultHashcode = "int result = super.hashCode();";
}
boolean isTempFieldCreated = false;
StringBuilder hashCodeComputation = new StringBuilder();
for (FieldSource<?> field : fields)
{
if (field == null)
{
throw new IllegalArgumentException(
"A supplied field was null. The equals and hashCode computation will be aborted.");
}
if (field.isStatic())
{
throw new IllegalArgumentException(
"A static field was detected. The equals and hashCode computation will be aborted.");
}
String fieldName = field.getName();
if (field.getType().isArray())
{
hashCodeComputation.append("result = prime * result + java.util.Arrays.hashCode(").append(fieldName)
.append(");");
}
else if (field.getType().isPrimitive())
{
if (field.getType().isType("float"))
{
hashCodeComputation.append("result = prime * result + ").append("Float.floatToIntBits(")
.append(fieldName).append(");");
}
else if (field.getType().isType("double"))
{
if (!isTempFieldCreated)
{
hashCodeComputation.append("long temp;");
isTempFieldCreated = true;
}
hashCodeComputation.append("temp = Double.doubleToLongBits(").append(fieldName).append(");");
hashCodeComputation.append("result = prime * result + (int) (temp ^ (temp >>> 32));");
}
else
{
if (field.getType().isType("long"))
{
// result = prime * result + (int) (longValue ^ (longValue >>> 32));
hashCodeComputation.append("result = prime * result + (int) (").append(fieldName).append(" ^ (")
.append(fieldName).append(" >>> 32));");
}
else if (field.getType().isType("boolean"))
{
// result = prime * result + (booleanValue : 1231 : 1237);
hashCodeComputation.append("result = prime * result + (").append(fieldName)
.append(" ? 1231 : 1237);");
}
else
{
// For byte, char, short, int
// result = prime * result + fieldValue;
hashCodeComputation.append("result = prime * result + ").append(fieldName).append(";");
}
}
}
else
{
hashCodeComputation.append("result = prime * result + ((").append(fieldName).append(" == null) ? 0 : ")
.append(fieldName).append(".hashCode());");
}
}
if (hashCodeComputation.length() < 1)
{
throw new IllegalArgumentException(
"A failure was detected when generating the equals and hashCode methods. Verify the type and modifiers of the provided fields.");
}
clazz.addMethod(
"public int hashCode() { " +
"final int prime = 31;" +
defaultHashcode +
hashCodeComputation.toString() +
"return result; }")
.addAnnotation(Override.class);
}
/**
* Create a <i>toString</i> implementation using all the fields in this class
*
* @param clazz
*/
public static void createToStringFromFields(final JavaClassSource clazz)
{
List<FieldSource<JavaClassSource>> fields = clazz.getFields();
createToStringFromFields(clazz, fields);
}
/**
* Create a <i>toString</i> implementation using the supplied fields
*
* @param clazz
* @param fields
*/
public static void createToStringFromFields(final JavaClassSource clazz,
final FieldSource<JavaClassSource>... fields)
{
createToStringFromFields(clazz, Arrays.asList(fields));
}
/**
* Create a <i>toString</i> implementation using the supplied fields
*
* @param clazz
* @param fields
*/
public static void createToStringFromFields(final JavaClassSource clazz,
final List<FieldSource<JavaClassSource>> fields)
{
MethodSource<JavaClassSource> method = clazz.addMethod().setName("toString").setReturnType(String.class)
.setPublic();
method.addAnnotation(Override.class);
List<String> list = new ArrayList<String>();
String delimeter = "\n";
for (FieldSource<JavaClassSource> field : fields)
{
if (clazz.hasField(field))
{
StringBuilder line = new StringBuilder();
if (!field.getType().isPrimitive())
if (field.getType().isType(String.class))
{
line.append("if(").append(field.getName()).append(" != null && !").append(field.getName())
.append(".trim().isEmpty())\n");
}
else
{
line.append("if(").append(field.getName()).append(" != null)\n");
}
boolean isFirst = list.isEmpty();
line.append(" result += ").append(isFirst ? "\"" : "\", ");
line.append(field.getName()).append(": \" + ").append(field.getName()).append(";");
list.add(line.toString());
}
}
String body = "String result = getClass().getSimpleName()+\" \";\n" +
Strings.join(list, delimeter) + "\n" +
"return result;";
method.setBody(body);
}
}