/*
* gvNIX is an open source tool for rapid application development (RAD).
* Copyright (C) 2010 Generalitat Valenciana
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gvnix.support;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
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.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.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.EnumAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.details.comments.CommentStructure;
import org.springframework.roo.classpath.details.comments.CommentStructure.CommentLocation;
import org.springframework.roo.classpath.details.comments.JavadocComment;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.model.EnumDetails;
import org.springframework.roo.model.ImportRegistrationResolver;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
/**
* Helper which provides utilities for a ITD generator (Metadata)
*
* @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
* href="http://www.dgti.gva.es">General Directorate for Information
* Technologies (DGTI)</a>
*/
public class ItdBuilderHelper {
private final AbstractItdTypeDetailsProvidingMetadataItem metadata;
private final ImportRegistrationResolver importResolver;
private final ClassOrInterfaceTypeDetails governorTypeDetails;
private final MemberDetails memberDetails;
public ItdBuilderHelper(
AbstractItdTypeDetailsProvidingMetadataItem metadata,
PhysicalTypeMetadata governorPhysicalTypeMetadata,
ImportRegistrationResolver importResolver) {
this(metadata, governorPhysicalTypeMetadata, importResolver, null);
}
/**
* Constructor
*
* @param metadata
* @param governorPhysicalTypeMetadata
* @param importResolver (usually to get use
* <code>builder.getImportRegistrationResolver()</code>)
* @param memberDetails (optional)
*/
public ItdBuilderHelper(
AbstractItdTypeDetailsProvidingMetadataItem metadata,
PhysicalTypeMetadata governorPhysicalTypeMetadata,
ImportRegistrationResolver importResolver,
MemberDetails memberDetails) {
this.metadata = metadata;
final Object physicalTypeDetails = governorPhysicalTypeMetadata
.getMemberHoldingTypeDetails();
if (physicalTypeDetails instanceof ClassOrInterfaceTypeDetails) {
// We have reliable physical type details
this.governorTypeDetails = (ClassOrInterfaceTypeDetails) physicalTypeDetails;
}
else {
// This metadata is invalid
this.governorTypeDetails = null;
}
this.importResolver = importResolver;
this.memberDetails = memberDetails;
}
/**
* Gets final names to use of a type in method body after import resolver.
*
* @param type
* @return name to use in method body
*/
public String getFinalTypeName(JavaType type) {
return type.getNameIncludingTypeParameters(false, importResolver);
}
/**
* Create an annotation value from string array
*
* @param name
* @param stringValues
* @return
*/
public ArrayAttributeValue<StringAttributeValue> toAttributeValue(
String name, Iterable<String> stringValues) {
List<StringAttributeValue> stringAttributeValues = new ArrayList<StringAttributeValue>();
JavaSymbolName ignored = new JavaSymbolName("ignored");
for (String str : stringValues) {
stringAttributeValues.add(new StringAttributeValue(ignored, str));
}
return new ArrayAttributeValue<StringAttributeValue>(
new JavaSymbolName(name), stringAttributeValues);
}
/**
* Create an annotation value from Enum array
*
* @param name
* @param Sy
* @return
*/
public ArrayAttributeValue<EnumAttributeValue> toEnumAttributeValue(
String name, JavaType enumType, Iterable<String> enumValueNames) {
List<EnumAttributeValue> enumAttributeValues = new ArrayList<EnumAttributeValue>();
JavaSymbolName ignored = new JavaSymbolName("ignored");
for (String enumName : enumValueNames) {
enumAttributeValues.add(new EnumAttributeValue(ignored,
new EnumDetails(enumType, new JavaSymbolName(enumName))));
}
return new ArrayAttributeValue<EnumAttributeValue>(new JavaSymbolName(
name), enumAttributeValues);
}
/**
* Try to locate a method in governor
*
* @param methodName
* @param paramTypes
* @return
*/
public MethodMetadata methodExists(JavaSymbolName methodName,
List<AnnotatedJavaType> paramTypes) {
return MemberFindingUtils.getDeclaredMethod(governorTypeDetails,
methodName,
AnnotatedJavaType.convertFromAnnotatedJavaTypes(paramTypes));
}
/**
* Builds body method for a field getter method.
*
* @param bodyBuilder
* @param fieldName
*/
public void buildGetterMethodBody(InvocableMemberBodyBuilder bodyBuilder,
JavaSymbolName fieldName) {
bodyBuilder.appendFormalLine(String.format("return this.%s;",
fieldName.getSymbolName()));
}
/**
* Builds body method for a field setter method.
*
* @param bodyBuilder
* @param fieldName
*/
public void buildSetterMethodBody(InvocableMemberBodyBuilder bodyBuilder,
JavaSymbolName fieldName) {
bodyBuilder.appendFormalLine(String.format("this.%s = %s;",
fieldName.getSymbolName(), fieldName.getSymbolName()));
}
/**
* Prepares a field-getter method.
* <p/>
* First, try to locate it on governor. Otherwise create it.
*
* @param filedMetadata
* @param aAnnotations (optional) annotation list
* @return
*/
public MethodMetadata getGetterMethod(FieldMetadata fieldMetadata,
List<AnnotationMetadataBuilder> aAnnotations) {
JavaSymbolName methodName = getGetterMethodNameForField(fieldMetadata
.getFieldName());
return getGetterMethod(fieldMetadata.getFieldName(), methodName,
fieldMetadata.getFieldType(), aAnnotations);
}
/**
* Prepares a field-getter method.
* <p/>
* First, try to locate it on governor. Otherwise create it.
*
* @param filedName
* @param fieldType
* @param aAnnotations (optional) annotation list
* @return
*/
public MethodMetadata getGetterMethod(JavaSymbolName filedName,
JavaType fieldType, List<AnnotationMetadataBuilder> aAnnotations) {
JavaSymbolName methodName = getGetterMethodNameForField(filedName);
return getGetterMethod(filedName, methodName, fieldType, aAnnotations);
}
/**
* @param filedName
* @return default getter name for received fieldName
*/
public JavaSymbolName getGetterMethodNameForField(JavaSymbolName filedName) {
JavaSymbolName methodName = new JavaSymbolName("get".concat(filedName
.getSymbolNameCapitalisedFirstLetter()));
return methodName;
}
/**
* Prepares a field-getter method.
* <p/>
* First, try to locate it on governor. Otherwise create it.
*
* @param filedName
* @param methodName
* @param fieldType
* @param aAnnotations (optional) annotation list
* @return
*/
public MethodMetadata getGetterMethod(JavaSymbolName filedName,
JavaSymbolName methodName, JavaType fieldType,
List<AnnotationMetadataBuilder> aAnnotations) {
// Define method parameter types
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
// Check if a method with the same signature already exists in the
// target type
final MethodMetadata method = methodExists(methodName, parameterTypes);
if (method != null) {
// If it already exists, just return the method and omit its
// generation via the ITD
return method;
}
// Define method annotations
List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
if (aAnnotations != null) {
annotations.addAll(aAnnotations);
}
// Define method throws types (none in this case)
List<JavaType> throwsTypes = new ArrayList<JavaType>();
// Define method parameter names
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
// Create the method body
InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
buildGetterMethodBody(bodyBuilder, filedName);
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
metadata.getId(), Modifier.PUBLIC, methodName, fieldType,
parameterTypes, parameterNames, bodyBuilder);
methodBuilder.setAnnotations(annotations);
methodBuilder.setThrowsTypes(throwsTypes);
return methodBuilder.build(); // Build and return a MethodMetadata
// instance
}
/**
* Prepares a field-setter method.
* <p/>
* First, try to locate it on governor. Otherwise create it.
*
* @param filedMetadata
* @param aAnnotations (optional) annotation list
* @return
*/
public MethodMetadata getSetterMethod(FieldMetadata fieldMetadata,
List<AnnotationMetadataBuilder> aAnnotations) {
JavaSymbolName methodName = getSetterMethodNameForField(fieldMetadata
.getFieldName());
return getSetterMethod(fieldMetadata.getFieldName(), methodName,
fieldMetadata.getFieldType(), aAnnotations);
}
/**
* Prepares a field-setter method.
* <p/>
* First, try to locate it on governor. Otherwise create it.
*
* @param filedName
* @param fieldType
* @param aAnnotations (optional) annotation list
* @return
*/
public MethodMetadata getSetterMethod(JavaSymbolName filedName,
JavaType fieldType, List<AnnotationMetadataBuilder> aAnnotations) {
JavaSymbolName methodName = getSetterMethodNameForField(filedName);
return getSetterMethod(filedName, methodName, fieldType, aAnnotations);
}
/**
* @param filedName
* @return default setter name for received fieldName
*/
public JavaSymbolName getSetterMethodNameForField(JavaSymbolName filedName) {
JavaSymbolName methodName = new JavaSymbolName("set".concat(filedName
.getSymbolNameCapitalisedFirstLetter()));
return methodName;
}
/**
* Prepares a field-setter method.
* <p/>
* First, try to locate it on governor. Otherwise create it.
*
* @param filedName
* @param methodName
* @param fieldType
* @param aAnnotations (optional) annotation list
* @return
*/
public MethodMetadata getSetterMethod(JavaSymbolName filedName,
JavaSymbolName methodName, JavaType fieldType,
List<AnnotationMetadataBuilder> aAnnotations) {
// Define method parameter types
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
parameterTypes.add(new AnnotatedJavaType(fieldType));
// Check if a method with the same signature already exists in the
// target type
final MethodMetadata method = methodExists(methodName, parameterTypes);
if (method != null) {
// If it already exists, just return the method and omit its
// generation via the ITD
return method;
}
// Define method annotations
List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
if (aAnnotations != null) {
annotations.addAll(aAnnotations);
}
// Define method throws types (none in this case)
List<JavaType> throwsTypes = new ArrayList<JavaType>();
// Define method parameter names
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
parameterNames.add(filedName);
// Create the method body
InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
buildSetterMethodBody(bodyBuilder, filedName);
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
metadata.getId(), Modifier.PUBLIC, methodName,
JavaType.VOID_PRIMITIVE, parameterTypes, parameterNames,
bodyBuilder);
methodBuilder.setAnnotations(annotations);
methodBuilder.setThrowsTypes(throwsTypes);
return methodBuilder.build(); // Build and return a MethodMetadata
// instance
}
/**
* Action to do in
* {@link ItdBuilderHelper#getField(JavaSymbolName, int, JavaType, List, GET_FIELD_EXISTS_ACTION)}
* when required field is already defined in target class
*
* @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made
* for <a href="http://www.dgti.gva.es">General Directorate for
* Information Technologies (DGTI)</a>
*/
public static enum GET_FIELD_EXISTS_ACTION {
RETURN_EXISTING, RETURN_EXISTING_IF_ANNOTATION_MATCH, CREATE_NEW_ALWAYS, THROW_ERROR
}
/**
* Create metadata for auto-wired convertionService field.
*
* @return a FieldMetadata object
*/
public FieldMetadata getField(JavaSymbolName fieldName, int modifiers,
JavaType fieldType,
List<AnnotationMetadataBuilder> annotationsRequired,
GET_FIELD_EXISTS_ACTION whenExists) {
JavaSymbolName curName = fieldName;
// Check if field exist
FieldMetadata currentField = getDeclaredField(curName);
if (currentField != null) {
if (whenExists == GET_FIELD_EXISTS_ACTION.RETURN_EXISTING) {
return currentField;
}
else if (whenExists == GET_FIELD_EXISTS_ACTION.RETURN_EXISTING_IF_ANNOTATION_MATCH) {
if (annotationsRequired == null
|| annotationsRequired.isEmpty()) {
return currentField;
}
if (hasFieldAllAnnotation(currentField, annotationsRequired)) {
return currentField;
}
}
else if (whenExists == GET_FIELD_EXISTS_ACTION.THROW_ERROR) {
throw new IllegalStateException(String.format(
"Field %s already exist", fieldName));
}
}
if (currentField != null) {
// No compatible field: look for new name
JavaSymbolName newName = new JavaSymbolName(fieldName
.getSymbolName().concat("_"));
return getField(newName, modifiers, fieldType, annotationsRequired,
whenExists);
}
// create field
List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(
1);
if (annotationsRequired != null && !annotationsRequired.isEmpty()) {
annotations.addAll(annotationsRequired);
}
// Using the FieldMetadataBuilder to create the field
// definition.
final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder(
metadata.getId(), modifiers, annotations, fieldName, // Field
fieldType); // Field type
return fieldBuilder.build(); // Build and return a
// FieldMetadata
// instance
}
/**
* Look in current entity definitions for a field
* <p/>
* If {@link MemberDetails} is initialized look on it.
*
* @param curName
* @return
*/
private FieldMetadata getDeclaredField(JavaSymbolName name) {
if (memberDetails == null) {
return governorTypeDetails.getDeclaredField(name);
}
for (FieldMetadata field : memberDetails.getFields()) {
if (field.getFieldName().equals(name)) {
return field;
}
}
return null;
}
/**
* Check if a currentField has all annotations
* <p/>
* TODO check annotation values
*
* @param currentField
* @param annotationsRequired
* @return
*/
private boolean hasFieldAllAnnotation(FieldMetadata currentField,
List<AnnotationMetadataBuilder> annotationsRequired) {
for (AnnotationMetadataBuilder annotation : annotationsRequired) {
if (currentField.getAnnotation(annotation.getAnnotationType()) == null) {
return false;
}
}
return true;
}
/**
* Adds JavaDoc metadata to a method builder
*
* @param methodBuilder
* @param description (optional) main method javaDoc
* @param returnInfo (optional) return description
* @param parametersInfo (optional) description of every parameters (if it's
* set must match with method parameters )
*/
public void addJavaDocToMethod(MethodMetadataBuilder methodBuilder,
String description, String returnInfo, String... parametersInfo) {
if (parametersInfo != null
&& parametersInfo.length > 0
&& parametersInfo.length != methodBuilder.getParameterNames()
.size()) {
throw new IllegalArgumentException(
"Parameter names size doesn't match with parameters info.");
}
CommentStructure comments = methodBuilder.getCommentStructure();
if (comments == null) {
comments = new CommentStructure();
methodBuilder.setCommentStructure(comments);
}
if (StringUtils.isNotBlank(description)) {
comments.addComment(new JavadocComment(), CommentLocation.BEGINNING);
comments.addComment(
new JavadocComment(StringUtils.replace(description, "\n",
"\n<p/>\n")), CommentLocation.BEGINNING);
}
addJavaDocParams(methodBuilder, comments, parametersInfo);
if (StringUtils.isNotBlank(returnInfo)) {
comments.addComment(new JavadocComment(), CommentLocation.BEGINNING);
comments.addComment(
new JavadocComment("@returns ".concat(returnInfo)),
CommentLocation.BEGINNING);
}
addJavaDocThrows(methodBuilder, comments);
}
/**
* Adds method throws to JavaDoc
*
* @param methodBuilder
* @param comments
*/
private void addJavaDocThrows(MethodMetadataBuilder methodBuilder,
CommentStructure comments) {
if (!methodBuilder.getThrowsTypes().isEmpty()) {
comments.addComment(new JavadocComment(), CommentLocation.BEGINNING);
for (JavaType throwType : methodBuilder.getThrowsTypes()) {
comments.addComment(
new JavadocComment("@throws ".concat(throwType
.getSimpleTypeName())),
CommentLocation.BEGINNING);
}
}
}
/**
* Adds method params to JavaDoc
*
* @param methodBuilder
* @param comments
* @param parametersInfo
*/
private void addJavaDocParams(MethodMetadataBuilder methodBuilder,
CommentStructure comments, String... parametersInfo) {
StringBuilder sBuilder;
if (!methodBuilder.getParameterNames().isEmpty()) {
comments.addComment(new JavadocComment(), CommentLocation.BEGINNING);
int paramIndex = 0;
for (JavaSymbolName paramName : methodBuilder.getParameterNames()) {
sBuilder = new StringBuilder("@param ");
sBuilder.append(paramName.getSymbolName());
if (parametersInfo != null
&& paramIndex < parametersInfo.length) {
sBuilder.append(parametersInfo[paramIndex]);
}
comments.addComment(new JavadocComment(sBuilder.toString()),
CommentLocation.BEGINNING);
paramIndex++;
}
}
}
/**
* Transform a list of {@link JavaType} to a list of
* {@link AnnotationMetadataBuilder}
*
* @param annotations
* @return
*/
public List<AnnotationMetadataBuilder> toAnnotationMetadata(
JavaType... annotations) {
return toAnnotationMetadata(Arrays.asList(annotations));
}
/**
* Transform a list of {@link JavaType} to a list of
* {@link AnnotationMetadataBuilder}
*
* @param annotations
* @return
*/
public List<AnnotationMetadataBuilder> toAnnotationMetadata(
List<JavaType> annotations) {
List<AnnotationMetadataBuilder> result = new ArrayList<AnnotationMetadataBuilder>();
if (annotations != null && !annotations.isEmpty()) {
for (JavaType annotation : annotations) {
result.add(new AnnotationMetadataBuilder(annotation));
}
}
return result;
}
/**
* Transform a list of {@link JavaType} to a list of
* {@link AnnotatatedJavaType}
*
* @param types
* @return
*/
public List<AnnotatedJavaType> toAnnotedJavaType(JavaType... types) {
return toAnnotedJavaType(Arrays.asList(types));
}
/**
* Transform a list of {@link JavaType} to a list of
* {@link AnnotationMetadataBuilder}
*
* @param types
* @return
*/
public List<AnnotatedJavaType> toAnnotedJavaType(List<JavaType> types) {
List<AnnotatedJavaType> result = new ArrayList<AnnotatedJavaType>();
if (types != null && !types.isEmpty()) {
for (JavaType type : types) {
result.add(new AnnotatedJavaType(type));
}
}
return result;
}
/**
* Transform a list of Strings to a list of {@link JavaSymbolName}
*
* @param annotations
* @return
*/
public List<JavaSymbolName> toSymbolName(String... names) {
return toSymbolName(Arrays.asList(names));
}
/**
* Transform a list of Strings to a list of {@link JavaSymbolName}
*
* @param annotations
* @return
*/
public List<JavaSymbolName> toSymbolName(List<String> names) {
List<JavaSymbolName> result = new ArrayList<JavaSymbolName>();
if (names != null && !names.isEmpty()) {
for (String name : names) {
result.add(new JavaSymbolName(name));
}
}
return result;
}
}