package org.infinispan.cdi.common.util.annotatedtypebuilder; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.AnnotatedConstructor; import javax.enterprise.inject.spi.AnnotatedField; import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedParameter; import javax.enterprise.inject.spi.AnnotatedType; import org.infinispan.cdi.common.util.Reflections; import org.infinispan.cdi.common.util.logging.Log; import org.infinispan.commons.logging.LogFactory; /** * <p> Class for constructing a new AnnotatedType. A new instance of builder * should be used for each annotated type. </p> <p/> <p> {@link * AnnotatedTypeBuilder} is not thread-safe. </p> * * @author Stuart Douglas * @author Pete Muir * @see AnnotatedType */ public class AnnotatedTypeBuilder<X> { private static final Log log = LogFactory.getLog(AnnotatedTypeBuilder.class, Log.class); private Class<X> javaClass; private final AnnotationBuilder typeAnnotations; private final Map<Constructor<?>, AnnotationBuilder> constructors; private final Map<Constructor<?>, Map<Integer, AnnotationBuilder>> constructorParameters; private final Map<Constructor<?>, Map<Integer, Type>> constructorParameterTypes; private final Map<Field, AnnotationBuilder> fields; private final Map<Field, Type> fieldTypes; private final Map<Method, AnnotationBuilder> methods; private final Map<Method, Map<Integer, AnnotationBuilder>> methodParameters; private final Map<Method, Map<Integer, Type>> methodParameterTypes; /** * Create a new builder. A new builder has no annotations and no members. * * @see #readFromType(AnnotatedType) * @see #readFromType(Class) * @see #readFromType(AnnotatedType, boolean) * @see #readFromType(Class, boolean) */ public AnnotatedTypeBuilder() { this.typeAnnotations = new AnnotationBuilder(); this.constructors = new HashMap<Constructor<?>, AnnotationBuilder>(); this.constructorParameters = new HashMap<Constructor<?>, Map<Integer, AnnotationBuilder>>(); this.constructorParameterTypes = new HashMap<Constructor<?>, Map<Integer, Type>>(); this.fields = new HashMap<Field, AnnotationBuilder>(); this.fieldTypes = new HashMap<Field, Type>(); this.methods = new HashMap<Method, AnnotationBuilder>(); this.methodParameters = new HashMap<Method, Map<Integer, AnnotationBuilder>>(); this.methodParameterTypes = new HashMap<Method, Map<Integer, Type>>(); } /** * Add an annotation to the type declaration. * * @param annotation the annotation instance to add * @throws IllegalArgumentException if the annotation is null */ public AnnotatedTypeBuilder<X> addToClass(Annotation annotation) { typeAnnotations.add(annotation); return this; } /** * Reads in from an existing AnnotatedType. Any elements not present are * added. The javaClass will be read in. If the annotation already exists on * that element in the builder the read annotation will be used. * * @param type the type to read from * @throws IllegalArgumentException if type is null */ public AnnotatedTypeBuilder<X> readFromType(AnnotatedType<X> type) { return readFromType(type, true); } /** * Reads in from an existing AnnotatedType. Any elements not present are * added. The javaClass will be read in if overwrite is true. If the * annotation already exists on that element in the builder, overwrite * determines whether the original or read annotation will be used. * * @param type the type to read from * @param overwrite if true, the read annotation will replace any existing * annotation * @throws IllegalArgumentException if type is null */ public AnnotatedTypeBuilder<X> readFromType(AnnotatedType<X> type, boolean overwrite) { if (type == null) { throw log.parameterMustNotBeNull("type"); } if (javaClass == null || overwrite) { this.javaClass = type.getJavaClass(); } mergeAnnotationsOnElement(type, overwrite, typeAnnotations); for (AnnotatedField<? super X> field : type.getFields()) { if (fields.get(field.getJavaMember()) == null) { fields.put(field.getJavaMember(), new AnnotationBuilder()); } mergeAnnotationsOnElement(field, overwrite, fields.get(field.getJavaMember())); } for (AnnotatedMethod<? super X> method : type.getMethods()) { if (methods.get(method.getJavaMember()) == null) { methods.put(method.getJavaMember(), new AnnotationBuilder()); } mergeAnnotationsOnElement(method, overwrite, methods.get(method.getJavaMember())); for (AnnotatedParameter<? super X> p : method.getParameters()) { if (methodParameters.get(method.getJavaMember()) == null) { methodParameters.put(method.getJavaMember(), new HashMap<Integer, AnnotationBuilder>()); } if (methodParameters.get(method.getJavaMember()).get(p.getPosition()) == null) { methodParameters.get(method.getJavaMember()).put(p.getPosition(), new AnnotationBuilder()); } mergeAnnotationsOnElement(p, overwrite, methodParameters.get(method.getJavaMember()).get(p.getPosition())); } } for (AnnotatedConstructor<? super X> constructor : type.getConstructors()) { if (constructors.get(constructor.getJavaMember()) == null) { constructors.put(constructor.getJavaMember(), new AnnotationBuilder()); } mergeAnnotationsOnElement(constructor, overwrite, constructors.get(constructor.getJavaMember())); for (AnnotatedParameter<? super X> p : constructor.getParameters()) { if (constructorParameters.get(constructor.getJavaMember()) == null) { constructorParameters.put(constructor.getJavaMember(), new HashMap<Integer, AnnotationBuilder>()); } if (constructorParameters.get(constructor.getJavaMember()).get(p.getPosition()) == null) { constructorParameters.get(constructor.getJavaMember()).put(p.getPosition(), new AnnotationBuilder()); } mergeAnnotationsOnElement(p, overwrite, constructorParameters.get(constructor.getJavaMember()).get(p.getPosition())); } } return this; } /** * Reads the annotations from an existing java type. Annotations already * present will be overwritten * * @param type the type to read from * @throws IllegalArgumentException if type is null */ public AnnotatedTypeBuilder<X> readFromType(Class<X> type) { return readFromType(type, true); } /** * Reads the annotations from an existing java type. If overwrite is true * then existing annotations will be overwritten * * @param type the type to read from * @param overwrite if true, the read annotation will replace any existing * annotation */ public AnnotatedTypeBuilder<X> readFromType(Class<X> type, boolean overwrite) { if (type == null) { throw log.parameterMustNotBeNull("type"); } if (javaClass == null || overwrite) { this.javaClass = type; } for (Annotation annotation : type.getAnnotations()) { if (overwrite || !typeAnnotations.isAnnotationPresent(annotation.annotationType())) { typeAnnotations.add(annotation); } } for (Field field : Reflections.getAllDeclaredFields(type)) { AnnotationBuilder annotationBuilder = fields.get(field); if (annotationBuilder == null) { annotationBuilder = new AnnotationBuilder(); fields.put(field, annotationBuilder); } field.setAccessible(true); for (Annotation annotation : field.getAnnotations()) { if (overwrite || !annotationBuilder.isAnnotationPresent(annotation.annotationType())) { annotationBuilder.add(annotation); } } } for (Method method : Reflections.getAllDeclaredMethods(type)) { AnnotationBuilder annotationBuilder = methods.get(method); if (annotationBuilder == null) { annotationBuilder = new AnnotationBuilder(); methods.put(method, annotationBuilder); } method.setAccessible(true); for (Annotation annotation : method.getAnnotations()) { if (overwrite || !annotationBuilder.isAnnotationPresent(annotation.annotationType())) { annotationBuilder.add(annotation); } } Map<Integer, AnnotationBuilder> parameters = methodParameters.get(method); if (parameters == null) { parameters = new HashMap<Integer, AnnotationBuilder>(); methodParameters.put(method, parameters); } for (int i = 0; i < method.getParameterTypes().length; ++i) { AnnotationBuilder parameterAnnotationBuilder = parameters.get(i); if (parameterAnnotationBuilder == null) { parameterAnnotationBuilder = new AnnotationBuilder(); parameters.put(i, parameterAnnotationBuilder); } for (Annotation annotation : method.getParameterAnnotations()[i]) { if (overwrite || !parameterAnnotationBuilder.isAnnotationPresent(annotation.annotationType())) { parameterAnnotationBuilder.add(annotation); } } } } for (Constructor<?> constructor : type.getDeclaredConstructors()) { AnnotationBuilder annotationBuilder = constructors.get(constructor); if (annotationBuilder == null) { annotationBuilder = new AnnotationBuilder(); constructors.put(constructor, annotationBuilder); } constructor.setAccessible(true); for (Annotation annotation : constructor.getAnnotations()) { if (overwrite || !annotationBuilder.isAnnotationPresent(annotation.annotationType())) { annotationBuilder.add(annotation); } } Map<Integer, AnnotationBuilder> mparams = constructorParameters.get(constructor); if (mparams == null) { mparams = new HashMap<Integer, AnnotationBuilder>(); constructorParameters.put(constructor, mparams); } for (int i = 0; i < constructor.getParameterTypes().length; ++i) { AnnotationBuilder parameterAnnotationBuilder = mparams.get(i); if (parameterAnnotationBuilder == null) { parameterAnnotationBuilder = new AnnotationBuilder(); mparams.put(i, parameterAnnotationBuilder); } for (Annotation annotation : constructor.getParameterAnnotations()[i]) { if (overwrite || !parameterAnnotationBuilder.isAnnotationPresent(annotation.annotationType())) { annotationBuilder.add(annotation); } } } } return this; } protected void mergeAnnotationsOnElement(Annotated annotated, boolean overwriteExisting, AnnotationBuilder typeAnnotations) { for (Annotation annotation : annotated.getAnnotations()) { if (typeAnnotations.getAnnotation(annotation.annotationType()) != null) { if (overwriteExisting) { typeAnnotations.remove(annotation.annotationType()); typeAnnotations.add(annotation); } } else { typeAnnotations.add(annotation); } } } /** * Create an {@link AnnotatedType}. Any public members present on the * underlying class and not overridden by the builder will be automatically * added. */ public AnnotatedType<X> create() { Map<Constructor<?>, Map<Integer, AnnotationStore>> constructorParameterAnnnotations = new HashMap<Constructor<?>, Map<Integer, AnnotationStore>>(); Map<Constructor<?>, AnnotationStore> constructorAnnotations = new HashMap<Constructor<?>, AnnotationStore>(); Map<Method, Map<Integer, AnnotationStore>> methodParameterAnnnotations = new HashMap<Method, Map<Integer, AnnotationStore>>(); Map<Method, AnnotationStore> methodAnnotations = new HashMap<Method, AnnotationStore>(); Map<Field, AnnotationStore> fieldAnnotations = new HashMap<Field, AnnotationStore>(); for (Entry<Field, AnnotationBuilder> field : fields.entrySet()) { fieldAnnotations.put(field.getKey(), field.getValue().create()); } for (Entry<Method, AnnotationBuilder> method : methods.entrySet()) { methodAnnotations.put(method.getKey(), method.getValue().create()); } for (Entry<Method, Map<Integer, AnnotationBuilder>> parameters : methodParameters.entrySet()) { Map<Integer, AnnotationStore> parameterAnnotations = new HashMap<Integer, AnnotationStore>(); methodParameterAnnnotations.put(parameters.getKey(), parameterAnnotations); for (Entry<Integer, AnnotationBuilder> parameter : parameters.getValue().entrySet()) { parameterAnnotations.put(parameter.getKey(), parameter.getValue().create()); } } for (Entry<Constructor<?>, AnnotationBuilder> constructor : constructors.entrySet()) { constructorAnnotations.put(constructor.getKey(), constructor.getValue().create()); } for (Entry<Constructor<?>, Map<Integer, AnnotationBuilder>> parameters : constructorParameters.entrySet()) { Map<Integer, AnnotationStore> parameterAnnotations = new HashMap<Integer, AnnotationStore>(); constructorParameterAnnnotations.put(parameters.getKey(), parameterAnnotations); for (Entry<Integer, AnnotationBuilder> parameter : parameters.getValue().entrySet()) { parameterAnnotations.put(parameter.getKey(), parameter.getValue().create()); } } return new AnnotatedTypeImpl<X>(javaClass, typeAnnotations.create(), fieldAnnotations, methodAnnotations, methodParameterAnnnotations, constructorAnnotations, constructorParameterAnnnotations, fieldTypes, methodParameterTypes, constructorParameterTypes); } /** * Remove an annotation from the type * * @param annotationType the annotation type to remove * @throws IllegalArgumentException if the annotationType */ public AnnotatedTypeBuilder<X> removeFromClass(Class<? extends Annotation> annotationType) { typeAnnotations.remove(annotationType); return this; } /** * Remove an annotation from the specified method. * * @param method the method to remove the annotation from * @param annotationType the annotation type to remove * @throws IllegalArgumentException if the annotationType is null or if the * method is not currently declared on the type */ public AnnotatedTypeBuilder<X> removeFromMethod(Method method, Class<? extends Annotation> annotationType) { if (methods.get(method) == null) { throw new IllegalArgumentException("Method not present " + method.toString() + " on " + javaClass); } else { methods.get(method).remove(annotationType); } return this; } /** * Remove an annotation from the specified method. * * @param method the method to remove the annotation from * @param annotationType the annotation type to remove * @throws IllegalArgumentException if the annotationType is null or if the * method is not currently declared on the type */ public AnnotatedTypeBuilder<X> removeFromMethod(AnnotatedMethod<? super X> method, Class<? extends Annotation> annotationType) { return removeFromMethod(method.getJavaMember(), annotationType); } /** * Add an annotation to the specified method. If the method is not already * present, it will be added. * * @param method the method to add the annotation to * @param annotation the annotation to add * @throws IllegalArgumentException if the annotation is null */ public AnnotatedTypeBuilder<X> addToMethod(Method method, Annotation annotation) { if (methods.get(method) == null) { methods.put(method, new AnnotationBuilder()); } methods.get(method).add(annotation); return this; } /** * Add an annotation to the specified method. If the method is not already * present, it will be added. * * @param method the method to add the annotation to * @param annotation the annotation to add * @throws IllegalArgumentException if the annotation is null */ public AnnotatedTypeBuilder<X> addToMethod(AnnotatedMethod<? super X> method, Annotation annotation) { return addToMethod(method.getJavaMember(), annotation); } /** * Add an annotation to the specified method parameter. If the method is not * already present, it will be added. If the method parameter is not already * present, it will be added. * * @param method the method to add the annotation to * @param position the position of the parameter to add * @param annotation the annotation to add * @throws IllegalArgumentException if the annotation is null */ public AnnotatedTypeBuilder<X> addToMethodParameter(Method method, int position, Annotation annotation) { if (!methods.containsKey(method)) { methods.put(method, new AnnotationBuilder()); } if (methodParameters.get(method) == null) { methodParameters.put(method, new HashMap<Integer, AnnotationBuilder>()); } if (methodParameters.get(method).get(position) == null) { methodParameters.get(method).put(position, new AnnotationBuilder()); } methodParameters.get(method).get(position).add(annotation); return this; } /** * Remove an annotation from the specified method parameter. * * @param method the method to remove the annotation from * @param position the position of the parameter to remove * @param annotationType the annotation type to remove * @throws IllegalArgumentException if the annotationType is null, if the * method is not currently declared on the type or if the * parameter is not declared on the method */ public AnnotatedTypeBuilder<X> removeFromMethodParameter(Method method, int position, Class<? extends Annotation> annotationType) { if (methods.get(method) == null) { throw new IllegalArgumentException("Method not present " + method + " on " + javaClass); } else { if (methodParameters.get(method).get(position) == null) { throw new IllegalArgumentException("Method parameter " + position + " not present on " + method + " on " + javaClass); } else { methodParameters.get(method).get(position).remove(annotationType); } } return this; } /** * Add an annotation to the specified field. If the field is not already * present, it will be added. * * @param field the field to add the annotation to * @param annotation the annotation to add * @throws IllegalArgumentException if the annotation is null */ public AnnotatedTypeBuilder<X> addToField(Field field, Annotation annotation) { if (fields.get(field) == null) { fields.put(field, new AnnotationBuilder()); } fields.get(field).add(annotation); return this; } /** * Add an annotation to the specified field. If the field is not already * present, it will be added. * * @param field the field to add the annotation to * @param annotation the annotation to add * @throws IllegalArgumentException if the annotation is null */ public AnnotatedTypeBuilder<X> addToField(AnnotatedField<? super X> field, Annotation annotation) { return addToField(field.getJavaMember(), annotation); } /** * Remove an annotation from the specified field. * * @param field the field to remove the annotation from * @param annotationType the annotation type to remove * @throws IllegalArgumentException if the annotationType is null or if the * field is not currently declared on the type */ public AnnotatedTypeBuilder<X> removeFromField(Field field, Class<? extends Annotation> annotationType) { if (fields.get(field) == null) { throw new IllegalArgumentException("Field not present " + field + " on " + javaClass); } else { fields.get(field).remove(annotationType); } return this; } /** * Remove an annotation from the specified field. * * @param field the field to remove the annotation from * @param annotationType the annotation type to remove * @throws IllegalArgumentException if the annotationType is null or if the * field is not currently declared on the type */ public AnnotatedTypeBuilder<X> removeFromField(AnnotatedField<? super X> field, Class<? extends Annotation> annotationType) { return removeFromField(field.getJavaMember(), annotationType); } }