package org.springframework.roo.classpath.details; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.Validate; import org.springframework.roo.classpath.PhysicalTypeIdentifier; 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.model.ImportRegistrationResolver; import org.springframework.roo.model.ImportRegistrationResolverImpl; import org.springframework.roo.model.JavaType; import org.springframework.roo.support.util.CollectionUtils; /** * Assists in the building of an {@link ItdTypeDetails} instance. * <p> * All methods on this class (which does NOT include the constructor) accept * null arguments, and will automatically ignore any attempt to add an * {@link IdentifiableJavaStructure} that is not use the same * declaredByMetadataId as when the instance was constructed. * <p> * In addition, any method on this class which accepts an * {@link InvocableMemberMetadata} will verify a * {@link InvocableMemberMetadata#getBody()} is provided. This therefore detects * programming errors which result from requesting a member to be included in an * ITD but without providing the actual executable body for that member. * * @author Ben Alex * @author Stefan Schmidt * @author Juan Carlos GarcĂ­a * @since 1.0 */ public class ItdTypeDetailsBuilder extends AbstractMemberHoldingTypeDetailsBuilder<ItdTypeDetails> { private final JavaType aspect; private final List<DeclaredFieldAnnotationDetails> fieldAnnotations = new ArrayList<DeclaredFieldAnnotationDetails>(); private final ClassOrInterfaceTypeDetails governor; private final ImportRegistrationResolver importRegistrationResolver; private final List<DeclaredMethodAnnotationDetails> methodAnnotations = new ArrayList<DeclaredMethodAnnotationDetails>(); private final boolean privilegedAspect; private final Set<JavaType> declarePrecedence; /** * Constructor based on an existing ITD * * @param existing (required) */ public ItdTypeDetailsBuilder(final ItdTypeDetails existing) { super(existing.getDeclaredByMetadataId(), existing); aspect = existing.getAspect(); governor = existing.getGovernor(); importRegistrationResolver = new ImportRegistrationResolverImpl(aspect.getPackage()); privilegedAspect = existing.isPrivilegedAspect(); declarePrecedence = existing.getDeclarePrecedence(); } /** * Constructor * * @param declaredByMetadataId * @param governor (required) * @param aspect (required) * @param privilegedAspect */ public ItdTypeDetailsBuilder(final String declaredByMetadataId, final ClassOrInterfaceTypeDetails governor, final JavaType aspect, final boolean privilegedAspect) { super(declaredByMetadataId); Validate.notNull(governor, "Name (to receive the introductions) required"); Validate.notNull(aspect, "Aspect required"); this.aspect = aspect; this.governor = governor; importRegistrationResolver = new ImportRegistrationResolverImpl(aspect.getPackage()); this.privilegedAspect = privilegedAspect; this.declarePrecedence = new LinkedHashSet<JavaType>(); } public void addFieldAnnotation(final DeclaredFieldAnnotationDetails declaredFieldAnnotationDetails) { if (declaredFieldAnnotationDetails == null) { return; } final JavaType declaredBy = PhysicalTypeIdentifier.getJavaType(declaredFieldAnnotationDetails.getField() .getDeclaredByMetadataId()); final boolean hasAnnotation = MemberFindingUtils.getAnnotationOfType(declaredFieldAnnotationDetails.getField() .getAnnotations(), declaredFieldAnnotationDetails.getFieldAnnotation() .getAnnotationType()) != null; if (!declaredFieldAnnotationDetails.isRemoveAnnotation()) { Validate .isTrue( !hasAnnotation, "Field annotation '@%s' is already present on the target field '%s.%s' (ITD target '%s')", declaredFieldAnnotationDetails.getFieldAnnotation().getAnnotationType() .getSimpleTypeName(), declaredBy.getFullyQualifiedTypeName(), declaredFieldAnnotationDetails.getField().getFieldName().getSymbolName(), aspect.getFullyQualifiedTypeName()); } else { Validate .isTrue( hasAnnotation, "Field annotation '@%s' cannot be removed as it is not present on the target field '%s.%s' (ITD target '%s')", declaredFieldAnnotationDetails.getFieldAnnotation().getAnnotationType() .getSimpleTypeName(), declaredBy.getFullyQualifiedTypeName(), declaredFieldAnnotationDetails.getField().getFieldName().getSymbolName(), aspect.getFullyQualifiedTypeName()); } fieldAnnotations.add(declaredFieldAnnotationDetails); } @Override public void addImports(final Collection<ImportMetadata> imports) { if (imports != null) { for (final ImportMetadata anImport : imports) { importRegistrationResolver.addImport(anImport.getImportType()); } } } public void addMethodAnnotation( final DeclaredMethodAnnotationDetails declaredMethodAnnotationDetails) { if (declaredMethodAnnotationDetails == null) { return; } final boolean hasAnnotation = MemberFindingUtils.getAnnotationOfType(declaredMethodAnnotationDetails.getMethodMetadata() .getAnnotations(), declaredMethodAnnotationDetails.getMethodAnnotation() .getAnnotationType()) != null; Validate.isTrue(!hasAnnotation, "Method annotation '@%s' is already present on the target field '%s' (ITD target '%s')", declaredMethodAnnotationDetails.getMethodAnnotation().getAnnotationType() .getSimpleTypeName(), declaredMethodAnnotationDetails.getMethodMetadata() .getMethodName().getSymbolName(), aspect.getFullyQualifiedTypeName()); methodAnnotations.add(declaredMethodAnnotationDetails); } @Deprecated // Should use addAnnotation() instead public void addTypeAnnotation(final AnnotationMetadata annotationMetadata) { addAnnotation(annotationMetadata); } /** * Set the aspects to use on {@code declare precedence} * AspectJ declaration. * * @param aspects */ public void setDeclarePrecedence(JavaType... aspects) { if (aspects != null && aspects.length > 0) { Validate.isTrue(aspects.length >= 1, "precedence must contain, at least, 1 aspect"); } CollectionUtils.populate(declarePrecedence, Arrays.asList(aspects)); } public ItdTypeDetails build() { return new DefaultItdTypeDetails(getCustomData().build(), getDeclaredByMetadataId(), getModifier(), governor, aspect, privilegedAspect, importRegistrationResolver.getRegisteredImports(), buildConstructors(), buildFields(), buildMethods(), getExtendsTypes(), getImplementsTypes(), buildAnnotations(), fieldAnnotations, methodAnnotations, buildInnerTypes(), declarePrecedence); } public ImportRegistrationResolver getImportRegistrationResolver() { return importRegistrationResolver; } @Override protected void onAddAnnotation(final AnnotationMetadataBuilder md) { Validate.isTrue(governor.getAnnotation(md.getAnnotationType()) == null, "Type annotation '%s' already defined in target type '%s' (ITD target '%s')", md.getAnnotationType(), governor.getName().getFullyQualifiedTypeName(), aspect.getFullyQualifiedTypeName()); Validate.isTrue(build().getAnnotation(md.getAnnotationType()) == null, "Type annotation '%s' already defined in ITD (ITD target '%s')", md.getAnnotationType(), aspect.getFullyQualifiedTypeName()); } @Override protected void onAddConstructor(final ConstructorMetadataBuilder md) { Validate.isTrue(governor.getDeclaredConstructor(AnnotatedJavaType .convertFromAnnotatedJavaTypes(md.getParameterTypes())) == null, "Constructor with %d parameters already defined in target type '%s' (ITD target '%s')", md .getParameterTypes().size(), governor.getName().getFullyQualifiedTypeName(), aspect .getFullyQualifiedTypeName()); Validate.isTrue( build().getDeclaredConstructor( AnnotatedJavaType.convertFromAnnotatedJavaTypes(md.getParameterTypes())) == null, "Constructor with %d parameters already defined in ITD (ITD target '%s')", md .getParameterTypes().size(), aspect.getFullyQualifiedTypeName()); Validate .notBlank( md.getBody(), "Constructor '%s' failed to provide a body, despite being identified for ITD inclusion", md); } @Override protected void onAddExtendsTypes(final JavaType type) { Validate.isTrue(!governor.getExtendsTypes().contains(type), "Type '%s' already declared in extends types list in target type '%s' (ITD target '%s')", type, governor.getName().getFullyQualifiedTypeName(), aspect.getFullyQualifiedTypeName()); Validate.isTrue(!getExtendsTypes().contains(type), "Type '%s' already declared in extends types list in ITD (ITD target '%s')", type, aspect.getFullyQualifiedTypeName()); } @Override protected void onAddField(final FieldMetadataBuilder md) { Validate.isTrue(governor.getDeclaredField(md.getFieldName()) == null, "Field '%s' already defined in target type '%s' (ITD target '%s')", md.getFieldName(), governor.getName().getFullyQualifiedTypeName(), aspect.getFullyQualifiedTypeName()); Validate.isTrue(build().getDeclaredField(md.getFieldName()) == null, "Field '%s' already defined in ITD (ITD target '%s')", md.getFieldName(), aspect.getFullyQualifiedTypeName()); } @Override protected void onAddImplementType(final JavaType type) { Validate .isTrue( !governor.getImplementsTypes().contains(type), "Type '%s' already declared in implements types list in target type '%s' (ITD target '%s')", type, governor.getName().getFullyQualifiedTypeName(), aspect.getFullyQualifiedTypeName()); Validate.isTrue(!getImplementsTypes().contains(type), "Type '%s' already declared in implements types list in ITD (ITD target '%s')", type, aspect.getFullyQualifiedTypeName()); } @Override public void onAddInnerType(final ClassOrInterfaceTypeDetailsBuilder cid) { if (cid == null) { return; } Validate.isTrue(Modifier.isStatic(cid.getModifier()), "Currently only static inner types are supported by AspectJ"); } @Override protected void onAddMethod(final MethodMetadataBuilder md) { Validate.isTrue( MemberFindingUtils.getDeclaredMethod(governor, md.getMethodName(), AnnotatedJavaType.convertFromAnnotatedJavaTypes(md.getParameterTypes())) == null, "Method '%s' already defined in target type '%s' (ITD target '%s')", md.getMethodName(), governor.getName().getFullyQualifiedTypeName(), aspect.getFullyQualifiedTypeName()); Validate.isTrue( MemberFindingUtils.getDeclaredMethod(build(), md.getMethodName(), AnnotatedJavaType.convertFromAnnotatedJavaTypes(md.getParameterTypes())) == null, "Method '%s' already defined in ITD (ITD target '%s')", md.getMethodName(), aspect.getFullyQualifiedTypeName()); if (!Modifier.isAbstract(md.getModifier()) && md.getReturnType() != JavaType.VOID_PRIMITIVE) { Validate.notBlank(md.getBody(), "Method '%s' failed to provide a body, despite being identified for ITD inclusion", md); } } }