package org.springframework.roo.classpath.itd;
import static java.lang.reflect.Modifier.PRIVATE;
import static java.lang.reflect.Modifier.PUBLIC;
import java.util.ArrayList;
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.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.BeanInfoUtils;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.ItdTypeDetails;
import org.springframework.roo.classpath.details.ItdTypeDetailsBuilder;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.metadata.AbstractMetadataItem;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
/**
* Abstract implementation of {@link ItdTypeDetailsProvidingMetadataItem}, which
* assumes the subclass will require a non-null
* {@link ClassOrInterfaceTypeDetails} representing the governor and wishes to
* build an ITD via the {@link ItdTypeDetailsBuilder} mechanism.
*
* @author Ben Alex
* @since 1.0
*/
public abstract class AbstractItdTypeDetailsProvidingMetadataItem extends
AbstractMetadataItem implements ItdTypeDetailsProvidingMetadataItem {
protected JavaType aspectName;
protected ItdTypeDetailsBuilder builder;
protected JavaType destination;
protected PhysicalTypeMetadata governorPhysicalTypeMetadata;
protected ClassOrInterfaceTypeDetails governorTypeDetails;
protected ItdTypeDetails itdTypeDetails;
/**
* Validates input and constructs a superclass that implements
* {@link ItdTypeDetailsProvidingMetadataItem}.
* <p>
* Exposes the {@link ClassOrInterfaceTypeDetails} of the governor, if
* available. If they are not available, ensures {@link #isValid()} returns
* false.
* <p>
* Subclasses should generally return immediately if {@link #isValid()} is
* false. Subclasses should also attempt to set the {@link #itdTypeDetails}
* to contain the output of their ITD where {@link #isValid()} is true.
*
* @param identifier the identifier for this item of metadata (required)
* @param aspectName the Java type of the ITD (required)
* @param governorPhysicalTypeMetadata the governor, which is expected to
* contain a {@link ClassOrInterfaceTypeDetails} (required)
*/
protected AbstractItdTypeDetailsProvidingMetadataItem(
final String identifier, final JavaType aspectName,
final PhysicalTypeMetadata governorPhysicalTypeMetadata) {
super(identifier);
Validate.notNull(aspectName, "Aspect name required");
Validate.notNull(governorPhysicalTypeMetadata,
"Governor physical type metadata required");
this.aspectName = aspectName;
this.governorPhysicalTypeMetadata = governorPhysicalTypeMetadata;
final Object physicalTypeDetails = governorPhysicalTypeMetadata
.getMemberHoldingTypeDetails();
if (physicalTypeDetails instanceof ClassOrInterfaceTypeDetails) {
// We have reliable physical type details
governorTypeDetails = (ClassOrInterfaceTypeDetails) physicalTypeDetails;
}
else {
// There is a problem
valid = false;
}
destination = governorTypeDetails.getName();
// Provide the subclass a builder, to make preparing an ITD even easier
builder = new ItdTypeDetailsBuilder(getId(), governorTypeDetails,
aspectName, true);
}
private void addToImports(final List<JavaType> parameterTypes) {
if (parameterTypes != null) {
final List<JavaType> typesToImport = new ArrayList<JavaType>();
for (final JavaType parameterType : parameterTypes) {
if (!JdkJavaType.isPartOfJavaLang(parameterType)) {
typesToImport.add(parameterType);
}
}
builder.getImportRegistrationResolver().addImports(typesToImport);
}
}
/**
* Generates the {@link ItdTypeDetails} from the current contents of this
* instance's {@link ItdTypeDetailsBuilder}.
*
* @since 1.2.0
*/
protected void buildItd() {
itdTypeDetails = builder.build();
}
/**
* Ensures that the governor extends the given type, i.e. introduces that
* type as a supertype iff it's not already one
*
* @param javaType the type to extend (required)
* @since 1.2.0
*/
protected final void ensureGovernorExtends(final JavaType javaType) {
if (!governorTypeDetails.extendsType(javaType)) {
builder.addExtendsTypes(javaType);
}
}
/**
* Ensures that the governor implements the given type.
*
* @param javaType the type to implement (required)
* @since 1.2.0
*/
protected final void ensureGovernorImplements(final JavaType javaType) {
if (!governorTypeDetails.implementsType(javaType)) {
builder.addImplementsType(javaType);
}
}
protected MethodMetadataBuilder getAccessorMethod(final FieldMetadata field) {
return getAccessorMethod(
field,
InvocableMemberBodyBuilder.getInstance().appendFormalLine(
"return " + field.getFieldName().getSymbolName() + ";"));
}
protected MethodMetadataBuilder getAccessorMethod(
final FieldMetadata field,
final InvocableMemberBodyBuilder bodyBuilder) {
return getMethod(PUBLIC, BeanInfoUtils.getAccessorMethodName(field),
field.getFieldType(), null, null, bodyBuilder);
}
protected MethodMetadataBuilder getAccessorMethod(
final JavaSymbolName fieldName, final JavaType fieldType) {
return getAccessorMethod(
fieldName,
fieldType,
InvocableMemberBodyBuilder.getInstance().appendFormalLine(
"return " + fieldName + ";"));
}
protected MethodMetadataBuilder getAccessorMethod(
final JavaSymbolName fieldName, final JavaType fieldType,
final InvocableMemberBodyBuilder bodyBuilder) {
return getMethod(PUBLIC,
BeanInfoUtils.getAccessorMethodName(fieldName, fieldType),
fieldType, null, null, bodyBuilder);
}
/**
* Convenience method for returning a simple private field based on the
* field name, type, and initializer.
*
* @param fieldName the field name
* @param fieldType the field type
* @param fieldInitializer the string to initialize the field with
* @return null if the field exists on the governor, otherwise a new field
* with the given field name and type
*/
protected FieldMetadataBuilder getField(final int modifier,
final JavaSymbolName fieldName, final JavaType fieldType,
final String fieldInitializer) {
if (governorTypeDetails.getField(fieldName) != null) {
return null;
}
addToImports(Arrays.asList(fieldType));
return new FieldMetadataBuilder(getId(), modifier, fieldName,
fieldType, fieldInitializer);
}
protected FieldMetadataBuilder getField(final JavaSymbolName fieldName,
final JavaType fieldType) {
return getField(PRIVATE, fieldName, fieldType, null);
}
/**
* Returns the given method of the governor.
*
* @param methodName the name of the method for which to search
* @param parameterTypes the method's parameter types
* @return null if there was no such method
* @see MemberFindingUtils#getDeclaredMethod(org.springframework.roo.classpath.details.MemberHoldingTypeDetails,
* JavaSymbolName, List)
* @since 1.2.0
*/
protected MethodMetadata getGovernorMethod(final JavaSymbolName methodName,
final JavaType... parameterTypes) {
return getGovernorMethod(methodName, Arrays.asList(parameterTypes));
}
/**
* Returns the given method of the governor.
*
* @param methodName the name of the method for which to search
* @param parameterTypes the method's parameter types
* @return null if there was no such method
* @see MemberFindingUtils#getDeclaredMethod(org.springframework.roo.classpath.details.MemberHoldingTypeDetails,
* JavaSymbolName, List)
* @since 1.2.0 (previously called methodExists)
*/
protected MethodMetadata getGovernorMethod(final JavaSymbolName methodName,
final List<JavaType> parameterTypes) {
return MemberFindingUtils.getDeclaredMethod(governorTypeDetails,
methodName, parameterTypes);
}
public final ItdTypeDetails getMemberHoldingTypeDetails() {
return itdTypeDetails;
}
/**
* Returns a public method given the method name, return type, parameter
* types, parameter names, and method body.
*
* @param methodName the method name
* @param returnType the return type
* @param parameterTypes a list of parameter types
* @param parameterNames a list of parameter names
* @param bodyBuilder the method body
* @return null if the method exists on the governor, otherwise a new method
* is returned
*/
protected MethodMetadataBuilder getMethod(final int modifier,
final JavaSymbolName methodName, final JavaType returnType,
final List<JavaType> parameterTypes,
final List<JavaSymbolName> parameterNames,
final InvocableMemberBodyBuilder bodyBuilder) {
final MethodMetadata method = getGovernorMethod(methodName,
parameterTypes);
if (method != null) {
return null;
}
addToImports(parameterTypes);
return new MethodMetadataBuilder(getId(), modifier, methodName,
returnType,
AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
parameterNames, bodyBuilder);
}
protected MethodMetadataBuilder getMutatorMethod(
final JavaSymbolName fieldName, final JavaType parameterType) {
return getMutatorMethod(
fieldName,
parameterType,
InvocableMemberBodyBuilder.getInstance().appendFormalLine(
"this." + fieldName.getSymbolName() + " = "
+ fieldName.getSymbolName() + ";"));
}
protected MethodMetadataBuilder getMutatorMethod(
final JavaSymbolName fieldName, final JavaType parameterType,
final InvocableMemberBodyBuilder bodyBuilder) {
return getMethod(PUBLIC, BeanInfoUtils.getMutatorMethodName(fieldName),
JavaType.VOID_PRIMITIVE, Arrays.asList(parameterType),
Arrays.asList(fieldName), bodyBuilder);
}
/**
* Returns the metadata for an annotation of the given type if the governor
* does not already have one.
*
* @param annotationType the type of annotation to generate (required)
* @return <code>null</code> if the governor already has that annotation
*/
protected AnnotationMetadata getTypeAnnotation(final JavaType annotationType) {
if (governorTypeDetails.getAnnotation(annotationType) != null) {
return null;
}
return new AnnotationMetadataBuilder(annotationType).build();
}
/**
* Indicates whether the governor has a method with the given signature.
*
* @param methodName the name of the method for which to search
* @param parameterTypes the method's parameter types
* @return see above
* @since 1.2.0
*/
protected boolean governorHasMethod(final JavaSymbolName methodName,
final JavaType... parameterTypes) {
return getGovernorMethod(methodName, parameterTypes) != null;
}
/**
* Indicates whether the governor has a method with the given signature.
*
* @param methodName the name of the method for which to search
* @param parameterTypes the method's parameter types
* @return see above
* @since 1.2.1
*/
protected boolean governorHasMethod(final JavaSymbolName methodName,
final List<JavaType> parameterTypes) {
return getGovernorMethod(methodName, parameterTypes) != null;
}
/**
* Indicates whether the governor has a method with the given method name
* regardless of method parameters.
*
* @param methodName the name of the method for which to search
* @return see above
* @since 1.2.0
*/
protected boolean governorHasMethodWithSameName(
final JavaSymbolName methodName) {
return MemberFindingUtils.getDeclaredMethod(governorTypeDetails,
methodName) != null;
}
@Override
public int hashCode() {
return builder.build().hashCode();
}
/**
* Determines if the presented class (or any of its superclasses) implements
* the target interface.
*
* @param clazz the cid to search
* @param interfaceTarget the interface to locate
* @return true if the class or any of its superclasses contains the
* specified interface
*/
protected boolean isImplementing(final ClassOrInterfaceTypeDetails clazz,
final JavaType interfaceTarget) {
if (clazz.getImplementsTypes().contains(interfaceTarget)) {
return true;
}
if (clazz.getSuperclass() != null) {
return isImplementing(clazz.getSuperclass(), interfaceTarget);
}
return false;
}
@Override
public String toString() {
final ToStringBuilder builder = new ToStringBuilder(this);
builder.append("identifier", getId());
builder.append("valid", valid);
builder.append("aspectName", aspectName);
builder.append("governor", governorPhysicalTypeMetadata.getId());
builder.append("itdTypeDetails", itdTypeDetails);
return builder.toString();
}
/**
* Return current aspect name
* @return
*/
public JavaType getAspectName() {
return aspectName;
}
}