package org.springframework.roo.classpath.itd;
import static java.lang.reflect.Modifier.PRIVATE;
import static java.lang.reflect.Modifier.PUBLIC;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.ConstructorMetadataBuilder;
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;
import org.springframework.roo.model.SpringJavaType;
/**
* 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
* @author Juan Carlos GarcĂa
* @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;
protected Map<FieldMetadata, MethodMetadataBuilder> accessorMethods;
protected Map<FieldMetadata, MethodMetadataBuilder> mutatorMethods;
/**
* 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;
accessorMethods = new HashMap<FieldMetadata, MethodMetadataBuilder>();
mutatorMethods = new HashMap<FieldMetadata, MethodMetadataBuilder>();
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);
}
}
/**
* Ensures that the governor is annotated with the given annotation
*
* @param AnnotationMetadataBuilder the annotation to use(required)
* @since 2.0
*/
protected final void ensureGovernorIsAnnotated(final AnnotationMetadataBuilder annotationMetadata) {
if (governorTypeDetails.getAnnotation(annotationMetadata.getAnnotationType()) == null) {
builder.addAnnotation(annotationMetadata);
}
}
/**
* Ensures that the governor has provided field. If the provided field is private,
* this method always includes the accessor method. The mutator method will not be
* generated if the provided field is annotated with @Autowired annotation
*
* @param FieldMetadataBuilder the field to include(required)
* @since 2.0
*/
protected final void ensureGovernorHasField(final FieldMetadataBuilder fieldMetadata) {
boolean includeAccessor = false;
boolean includeMutator = false;
int modifier = fieldMetadata.getModifier();
if (modifier >= Modifier.PRIVATE
&& modifier <= Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL && modifier % 2 == 0) {
includeAccessor = true;
if (fieldMetadata.getDeclaredTypeAnnotation(SpringJavaType.AUTOWIRED) == null
&& modifier < Modifier.FINAL) {
includeMutator = true;
}
}
ensureGovernorHasField(fieldMetadata, includeAccessor, includeMutator);
}
/**
* Ensures that the governor has provided field. You could indicates if
* is necessary to generate the accessor and the mutator methods for this new field manually.
*
* @param FieldMetadataBuilder the field to include(required)
* @since 2.0
*/
protected final void ensureGovernorHasField(final FieldMetadataBuilder fieldMetadata,
boolean includeAccessor, boolean includeMutator) {
if (governorTypeDetails.getField(fieldMetadata.getFieldName()) == null) {
builder.addField(fieldMetadata);
}
if (includeAccessor) {
MethodMetadata accessorMethod = getAccessorMethod(fieldMetadata.build());
if (accessorMethod != null
&& governorTypeDetails.getMethod(accessorMethod.getMethodName()) == null) {
builder.addMethod(accessorMethod);
}
}
if (includeMutator) {
MethodMetadata mutatorMethod = getMutatorMethod(fieldMetadata.build());
if (mutatorMethod != null
&& governorTypeDetails.getMethod(mutatorMethod.getMethodName()) == null) {
builder.addMethod(mutatorMethod);
}
}
}
/**
* Ensures that the governor has provided method
*
* @param MethodMetadataBuilder the method to include(required)
* @since 2.0
*/
protected final void ensureGovernorHasMethod(final MethodMetadataBuilder methodMetadata) {
if (governorTypeDetails.getMethod(methodMetadata.getMethodName(),
AnnotatedJavaType.convertFromAnnotatedJavaTypes(methodMetadata.getParameterTypes())) == null
&& methodMetadata.getDeclaredByMetadataId().equals(getId())
&& MemberFindingUtils.getDeclaredMethod(builder.build(), methodMetadata.getMethodName(),
AnnotatedJavaType.convertFromAnnotatedJavaTypes(methodMetadata.getParameterTypes())) == null) {
builder.addMethod(methodMetadata);
}
}
/**
* Ensures that the governor has provided constructor
*
* @param ConstructorMetadataBuilder the constructor to include(required)
* @since 2.0
*/
protected final void ensureGovernorHasConstructor(
final ConstructorMetadataBuilder constructorMetadata) {
if (governorTypeDetails.getDeclaredConstructor(AnnotatedJavaType
.convertFromAnnotatedJavaTypes(constructorMetadata.getParameterTypes())) == null) {
builder.addConstructor(constructorMetadata);
}
}
protected MethodMetadata getAccessorMethod(final FieldMetadata field) {
// Check if this method has been cached
MethodMetadataBuilder accessor = accessorMethods.get(field);
if (accessor == null) {
accessor =
getAccessorMethod(
field,
InvocableMemberBodyBuilder.getInstance().appendFormalLine(
"return " + field.getFieldName().getSymbolName() + ";"));
accessorMethods.put(field, accessor);
}
// Return governor method if exists
if (accessor == null) {
return getGovernorMethod(BeanInfoUtils.getAccessorMethodName(field));
}
return accessor.build();
}
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 modifier the method modifier
* @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 MethodMetadata getMutatorMethod(final FieldMetadata field) {
// Check if the mutator has been cached
MethodMetadataBuilder mutator = mutatorMethods.get(field);
if (mutator == null) {
mutator = getMutatorMethod(field.getFieldName(), field.getFieldType());
mutatorMethods.put(field, mutator);
}
// Return governor method
if (mutator == null) {
return getGovernorMethod(BeanInfoUtils.getMutatorMethodName(field.getFieldName()),
field.getFieldType());
}
return mutator.build();
}
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() {
if (itdTypeDetails != null) {
return itdTypeDetails.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;
}
public JavaType getDestination() {
return destination;
}
/**
* Returns String which represents type inside ITD
*
* Delegates on {@link JavaType#getNameIncludingTypeParameters(boolean, org.springframework.roo.model.ImportRegistrationResolver)}
* (asStatic = false)
*
* @param type
* @return
*/
protected String getNameOfJavaType(JavaType type) {
return getNameOfJavaType(type, false);
}
/**
* Returns String which represents type inside ITD
*
* Delegates on {@link JavaType#getNameIncludingTypeParameters(boolean, org.springframework.roo.model.ImportRegistrationResolver)}
*
* @param type
* @param asStatic
* @return
*/
protected String getNameOfJavaType(JavaType type, boolean asStatic) {
return type.getNameIncludingTypeParameters(asStatic, builder.getImportRegistrationResolver());
}
}