package org.springframework.roo.addon.equals; import static org.springframework.roo.model.JavaType.BOOLEAN_PRIMITIVE; import static org.springframework.roo.model.JavaType.INT_PRIMITIVE; import static org.springframework.roo.model.JavaType.OBJECT; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem; import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder; import org.springframework.roo.metadata.MetadataIdentificationUtils; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.support.util.CollectionUtils; /** * Metadata for {@link RooEquals}. * * @author Alan Stewart * @since 1.2.0 */ public class EqualsMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { private static final JavaType EQUALS_BUILDER = new JavaType( "org.apache.commons.lang3.builder.EqualsBuilder"); private static final JavaSymbolName EQUALS_METHOD_NAME = new JavaSymbolName( "equals"); private static final JavaType HASH_CODE_BUILDER = new JavaType( "org.apache.commons.lang3.builder.HashCodeBuilder"); private static final JavaSymbolName HASH_CODE_METHOD_NAME = new JavaSymbolName( "hashCode"); private static final String OBJECT_NAME = "obj"; private static final String PROVIDES_TYPE_STRING = EqualsMetadata.class .getName(); private static final String PROVIDES_TYPE = MetadataIdentificationUtils .create(PROVIDES_TYPE_STRING); public static String createIdentifier(final JavaType javaType, final LogicalPath path) { return PhysicalTypeIdentifierNamingUtils.createIdentifier( PROVIDES_TYPE_STRING, javaType, path); } public static JavaType getJavaType(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getJavaType( PROVIDES_TYPE_STRING, metadataIdentificationString); } /** * Returns the class-level ID of this type of metadata * * @return a valid class-level MID */ public static String getMetadataIdentiferType() { return PROVIDES_TYPE; } public static LogicalPath getPath(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING, metadataIdentificationString); } public static boolean isValid(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING, metadataIdentificationString); } private final EqualsAnnotationValues annotationValues; private final List<FieldMetadata> locatedFields; /** * Constructor * * @param identifier the ID of this piece of metadata (required) * @param aspectName the name of the ITD to generate (required) * @param governorPhysicalTypeMetadata the details of the governor * (required) * @param annotationValues the values of the @RooEquals annotation * (required) * @param equalityFields the fields to be compared by the * <code>equals</code> method (can be <code>null</code> or empty) */ public EqualsMetadata(final String identifier, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, final EqualsAnnotationValues annotationValues, final List<FieldMetadata> equalityFields) { super(identifier, aspectName, governorPhysicalTypeMetadata); Validate.isTrue(isValid(identifier), "Metadata id '" + identifier + "' is invalid"); Validate.notNull(annotationValues, "Annotation values required"); this.annotationValues = annotationValues; locatedFields = equalityFields; if (!CollectionUtils.isEmpty(equalityFields)) { builder.addMethod(getEqualsMethod()); builder.addMethod(getHashCodeMethod()); } // Create a representation of the desired output ITD itdTypeDetails = builder.build(); } /** * Returns the <code>equals</code> method to be generated * * @return <code>null</code> if no generation is required */ private MethodMetadataBuilder getEqualsMethod() { final JavaType parameterType = OBJECT; if (governorHasMethod(EQUALS_METHOD_NAME, parameterType)) { return null; } final List<JavaSymbolName> parameterNames = Arrays .asList(new JavaSymbolName(OBJECT_NAME)); builder.getImportRegistrationResolver().addImport(EQUALS_BUILDER); final String typeName = destination.getSimpleTypeName(); // Create the method final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("if (!(" + OBJECT_NAME + " instanceof " + typeName + ")) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("return false;"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("if (this == " + OBJECT_NAME + ") {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("return true;"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine(typeName + " rhs = (" + typeName + ") " + OBJECT_NAME + ";"); final StringBuilder builder = new StringBuilder( "return new EqualsBuilder()"); if (annotationValues.isAppendSuper()) { builder.append(".appendSuper(super.equals(" + OBJECT_NAME + "))"); } for (final FieldMetadata field : locatedFields) { builder.append(".append(" + field.getFieldName() + ", rhs." + field.getFieldName() + ")"); } builder.append(".isEquals();"); bodyBuilder.appendFormalLine(builder.toString()); return new MethodMetadataBuilder(getId(), Modifier.PUBLIC, EQUALS_METHOD_NAME, BOOLEAN_PRIMITIVE, AnnotatedJavaType.convertFromJavaTypes(parameterType), parameterNames, bodyBuilder); } /** * Returns the <code>hashCode</code> method to be generated * * @return <code>null</code> if no generation is required */ private MethodMetadataBuilder getHashCodeMethod() { if (governorHasMethod(HASH_CODE_METHOD_NAME)) { return null; } builder.getImportRegistrationResolver().addImport(HASH_CODE_BUILDER); // Create the method final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); final StringBuilder builder = new StringBuilder( "return new HashCodeBuilder()"); if (annotationValues.isAppendSuper()) { builder.append(".appendSuper(super.hashCode())"); } for (final FieldMetadata field : locatedFields) { builder.append(".append(" + field.getFieldName() + ")"); } builder.append(".toHashCode();"); bodyBuilder.appendFormalLine(builder.toString()); return new MethodMetadataBuilder(getId(), Modifier.PUBLIC, HASH_CODE_METHOD_NAME, INT_PRIMITIVE, bodyBuilder); } @Override public String toString() { final ToStringBuilder builder = new ToStringBuilder(this); builder.append("identifier", getId()); builder.append("valid", valid); builder.append("aspectName", aspectName); builder.append("destinationType", destination); builder.append("governor", governorPhysicalTypeMetadata.getId()); builder.append("itdTypeDetails", itdTypeDetails); return builder.toString(); } }