/*
* Grapht, an open source dependency injector.
* Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
* Copyright 2010-2014 Regents of the University of Minnesota
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 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, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.grouplens.grapht.annotation;
import javax.inject.Named;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* AnnotationBuilder is a "builder" for creating proxy Annotation instances.
* This is useful when configuring dependency injection to match a qualifier
* annotation that defines attributes, such as the {@link Named} qualifier. As
* an example, AnnotationBuilder can be used to construct a Named instance that
* matches particular applications of that annotation:
*
* <pre>
* Named proxy = new AnnotationBuilder<Named>(Named.class)
* .set("value", "name")
* .build();
* </pre>
* <p>
* The above snippet creates an instance of Named that returns the String,
* "name", from its value() method. All other methods declared by
* {@link Annotation} are implemented correctly given the values configured by
* the builder, or the defaults declared in the annotation definition.
* <p>
* This lets developers define attribute-based qualifiers easily without being
* forced to provide an actual annotation implementation that can be used to
* create instances.
* <p>The proxies returned by this builder are immutable and serializable, like
* those returned by {@link java.lang.reflect.AnnotatedElement}.
*
* @author <a href="http://grouplens.org">GroupLens Research</a>
* @param <T> The annotation type created
*/
public final class AnnotationBuilder<T extends Annotation> {
private final Map<String, Object> attributes;
private final Class<T> type;
/**
* Create a new AnnotationBuilder without any assigned values, that will
* create annotations of the given class type.
*
* @param annotType The annotation class type
* @throws NullPointerException if annotType is null
* @throws IllegalArgumentException if annotType is not an Annotation class
*/
public AnnotationBuilder(Class<T> annotType) {
if (annotType == null) {
throw new NullPointerException("Annotation type cannot be null");
}
if (!annotType.isAnnotation()) {
throw new IllegalArgumentException("Class type is not an Annotation: " + annotType);
}
type = annotType;
attributes = new HashMap<String, Object>();
}
/**
* Constructor method to allow the builder type to be inferred.
* @param annotType The annotation type to build.
* @param <T> The type of annotation to build (type parameter).
* @return An annotation builder to create an implementation of the annotation.
*/
public static <T extends Annotation> AnnotationBuilder<T> of(Class<T> annotType) {
return new AnnotationBuilder<T>(annotType);
}
/**
* Set the annotation defined member given by <tt>name</tt> to the boolean
* <tt>value</tt>.
*
* @param name The name of the annotation attribute or member to assign
* @param value The value to assign to the specified member
* @return This builder
* @throws NullPointerException if name is null
* @throws IllegalArgumentException if name is not a defined member in the
* annotation type for this builder, or if the type is not
* boolean
*/
public AnnotationBuilder<T> set(String name, boolean value) {
return set(name, Boolean.valueOf(value), boolean.class);
}
/**
* As {@link #set(String, boolean)} but assigns a byte value.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, byte value) {
return set(name, Byte.valueOf(value), byte.class);
}
/**
* As {@link #set(String, boolean)} but assigns a short value.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, short value) {
return set(name, Short.valueOf(value), short.class);
}
/**
* As {@link #set(String, boolean)} but assigns an int value.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, int value) {
return set(name, Integer.valueOf(value), int.class);
}
/**
* As {@link #set(String, boolean)} but assigns a long value.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, long value) {
return set(name, Long.valueOf(value), long.class);
}
/**
* As {@link #set(String, boolean)} but assigns a char value.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, char value) {
return set(name, Character.valueOf(value), char.class);
}
/**
* As {@link #set(String, boolean)} but assigns a float value.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, float value) {
return set(name, Float.valueOf(value), float.class);
}
/**
* As {@link #set(String, boolean)} but assigns a double value.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, double value) {
return set(name, Double.valueOf(value), double.class);
}
/**
* As {@link #set(String, boolean)} but assigns a String value. Throws a
* NullPointerException if value is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, String value) {
return set(name, value, String.class);
}
/**
* As {@link #set(String, boolean)} but assigns an Annotation instance to
* the value. A NullPointerException is thrown if value is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, Annotation value) {
return set(name, value, value.getClass());
}
/**
* As {@link #set(String, boolean)} but assigns a boolean[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, boolean[] value) {
return set(name, value, boolean[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a byte[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, byte[] value) {
return set(name, value, byte[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a short[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, short[] value) {
return set(name, value, short[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a int[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, int[] value) {
return set(name, value, int[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a long[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, long[] value) {
return set(name, value, long[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a char[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, char[] value) {
return set(name, value, char[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a float[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, float[] value) {
return set(name, value, float[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a double[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, double[] value) {
return set(name, value, double[].class);
}
/**
* As {@link #set(String, boolean)} but assigns a String[] value. A
* NullPointerException is thrown if the array is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, String[] value) {
return set(name, value, String[].class);
}
/**
* As {@link #set(String, boolean)} but assigns an Annotation array to the
* value. The array must be a proper array over the parameterized type, and
* not a cast of Object[], etc. A NullPointerException is thrown if the
* array is null.
*
* @param name
* @param value
* @return This builder
*/
public <A extends Annotation> AnnotationBuilder<T> set(String name, A[] value) {
return set(name, value, value.getClass());
}
/**
* As {@link #set(String, boolean)} but 'assigns an enum value'
* .A NullPointerException is thrown if value is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, Enum<? extends Enum> value) {
return set(name, value, value.getClass());
}
/**
* As {@link #set(String, Class)} but 'assigns an class value'
* .A NullPointerException is thrown if value is null.
*
* @param name
* @param value
* @return This builder
*/
public AnnotationBuilder<T> set(String name, Class<? extends Class> value) {
return set(name, value, value.getClass());
}
/**
* Set the 'value' attribute to the given boolean value.
*
* @param value The boolean value
* @return This builder
*/
public AnnotationBuilder<T> setValue(boolean value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given byte value.
*
* @param value The byte value
* @return This builder
*/
public AnnotationBuilder<T> setValue(byte value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given short value.
*
* @param value The short value
* @return This builder
*/
public AnnotationBuilder<T> setValue(short value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given int value.
*
* @param value The int value
* @return This builder
*/
public AnnotationBuilder<T> setValue(int value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given long value.
*
* @param value The long value
* @return This builder
*/
public AnnotationBuilder<T> setValue(long value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given double value.
*
* @param value The double value
* @return This builder
*/
public AnnotationBuilder<T> setValue(double value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given float value.
*
* @param value The float value
* @return This builder
*/
public AnnotationBuilder<T> setValue(float value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given char value.
*
* @param value The char value
* @return This builder
*/
public AnnotationBuilder<T> setValue(char value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given String value.
*
* @param value The String value
* @return This builder
*/
public AnnotationBuilder<T> setValue(String value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given annotation.
*
* @param value The annotation value
* @return This builder
*/
public <A extends Annotation> AnnotationBuilder<T> setValue(A value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given boolean array.
*
* @param value The boolean array
* @return This builder
*/
public AnnotationBuilder<T> setValue(boolean[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given byte array.
*
* @param value The byte array
* @return This builder
*/
public AnnotationBuilder<T> setValue(byte[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given short array.
*
* @param value The short array
* @return This builder
*/
public AnnotationBuilder<T> setValue(short[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given int array.
*
* @param value The int array
* @return This builder
*/
public AnnotationBuilder<T> setValue(int[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given long array.
*
* @param value The long array
* @return This builder
*/
public AnnotationBuilder<T> setValue(long[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given double array.
*
* @param value The double array
* @return This builder
*/
public AnnotationBuilder<T> setValue(double[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given float array.
*
* @param value The float array
* @return This builder
*/
public AnnotationBuilder<T> setValue(float[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given char array.
*
* @param value The char array
* @return This builder
*/
public AnnotationBuilder<T> setValue(char[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given String array.
*
* @param value The String array
* @return This builder
*/
public AnnotationBuilder<T> setValue(String[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given annotation array.
*
* @param value The annotation array
* @return This builder
*/
public <A extends Annotation> AnnotationBuilder<T> setValue(A[] value) {
return set("value", value);
}
/**
* Set the 'value' attribute to the given enum.
*
* @param value The enum type annotation
* @return This builder
*/
public AnnotationBuilder<T> setValue(Enum<? extends Enum> value) { return set("value", value);}
/**
* Set the 'value' attribute to the given class
*
* @param value The class type annotation
* @return This builder
*/
public AnnotationBuilder<T> setValue(Class<? extends Class> value) { return set("value", value);}
private AnnotationBuilder<T> set(String name, Object value, Class<?> type) {
try {
Method attr = this.type.getMethod(name);
if (!attr.getReturnType().isAssignableFrom(type)) {
throw new IllegalArgumentException("Attribute named: " + name + " expects a type of " + attr.getReturnType() + ", but got " + type);
}
// if valid, save for later
attributes.put(name, AnnotationProxy.copyAnnotationValue(value));
return this;
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Annotation type " + this.type + " does not have an attribute named: " + name, e);
}
}
/**
* Build an Annotation instance of type T that is configured to return the
* values assigned by the various set() methods for its defined attributes.
* If attributes have a default value and the value was not overridden by
* the builder's configuration, then the default will be returned by the
* annotation instance.
*
* @return An instance of T with the attribute values specified on this
* builder
* @throws IllegalStateException if there are attributes with no default
* that have not been assigned explicit values
*/
public T build() {
for (Method attr: type.getDeclaredMethods()) {
if (attr.getDefaultValue() == null) {
// this is a required value, so we have to
// verify that its been assigned
if (!attributes.containsKey(attr.getName())) {
throw new IllegalStateException("No value assigned to required attribute: " + attr.getName());
}
}
}
return type.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { type },
new AnnotationProxy<T>(type, attributes)));
}
}