Java tutorial
/* * This file is part of the Spider Web Framework. * * The Spider Web Framework 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. * * The Spider Web Framework 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 the Spider Web Framework. If not, see <http://www.gnu.org/licenses/>. */ package com.medallia.tiny; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.google.common.collect.Maps; /** * Has the ability to register objects which can later be retrieved based on class or annotation. * * Each class can be mapped to a single object. If multiple objects are registered for the same class, then the * new values will overwrite old ones for lookups by class ({@link #get(Class)}. Lookups by annotation ({@link #getByAnnotation(Class, Class)}) * will give you the last object stored for that annotation. * * {@link #get(Class)} will try to give you an object matching the type directly. If no such object exist, then an unspecified * contained object will be returned. I.e. <tt>op.get(Object.class)</tt> can give you any object in the provider, provided that * none have been registered for the class <tt>Object.class</tt> directly. * * Usually this class will be used for the {@link #makeArgsFor(Method)} method, which will find valid parameters for all * arguments to the function, provided such objects are registered. The parameters may have annotations, in which case the lookup * will be based on the annotation. If not, we will use lookup by class. */ public class ObjectProvider { private static final Object NO_ARG = new Object(); // So we can still set the last argument to null private static final Log LOG = LogFactory.getLog(ObjectProvider.class); private static final Object[] NO_ARUMENTS = new Object[0]; protected final Map<Class<?>, Object> map; private final Map<Class<?>, Object> annotationMap; private boolean errorOnUnknownType; private Object lastArg = NO_ARG; /** * Creates a new ObjectProvider with no registered objects */ public ObjectProvider() { map = Maps.newLinkedHashMap(); annotationMap = Maps.newLinkedHashMap(); } /** make a new object provider by copying the given provider */ protected ObjectProvider(ObjectProvider from) { map = Maps.newLinkedHashMap(from.map); annotationMap = Maps.newLinkedHashMap(from.annotationMap); errorOnUnknownType = from.errorOnUnknownType; lastArg = from.lastArg; } /** @return a copy of this object */ protected ObjectProvider copyObjectProvider() { return new ObjectProvider(this); } /** if called an exception will be thrown if an unknown object is requested instead of passing in null */ public ObjectProvider errorOnUnknownType() { errorOnUnknownType = true; return this; } /** * Ensures that o is of type X and registers it. * Casts a {@link ClassCastException} if o is not of type X * @return this ObjectProvider */ public <X> ObjectProvider castAndRegister(Class<X> c, Object o) { return register(c, c.cast(o)); } /** * Registers o for o.getClass(). Does not register the object if it is null. * @return this ObjectProvider */ public ObjectProvider register(Object o) { if (o == null) return this; if (o instanceof Proxy) { for (Class<?> c : o.getClass().getInterfaces()) castAndRegister(c, o); return this; } return castAndRegister(o.getClass(), o); } /** * Registers o for o.getClass() and for the provided annotation */ public ObjectProvider registerWithAnnotation(Class<? extends Annotation> annotation, Object o) { annotationMap.put(annotation, o); return register(o); } /** * Get an object by annotation */ @SuppressWarnings("unchecked") public <X> X getByAnnotation(Class<? extends Annotation> annotation, Class<X> c) { Object o = annotationMap.get(annotation); if (o == null) LOG.warn("No object for annotation " + annotation + " in " + this); // We do a safe cast if possible here, since we do not know what type of object was used when registering return c.isPrimitive() ? (X) o : c.cast(o); } /** * Registers o for c. */ public <X> ObjectProvider register(Class<X> c, X o) { map.put(c, o); return this; } /** Object that can produce another object */ public interface ObjectFactory<X> { /** @return the produced object */ X make(); } /** * Register a factory object; if this ObjectProvider is asked to * provide the type of object the factory produces it will * call {@link ObjectFactory#make()} to obtain the object. */ public <X> ObjectProvider registerFactory(ObjectFactory<X> of) { for (Type t : of.getClass().getGenericInterfaces()) { if (t instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) t; if (pt.getRawType().equals(ObjectFactory.class)) { @SuppressWarnings("unchecked") Class<X> x = (Class<X>) pt.getActualTypeArguments()[0]; registerFactory(x, of); break; } } } return this; } /** Same as {@link #registerFactory(ObjectFactory)}, but give the type of the produced * objects explicitly. */ public <X> ObjectProvider registerFactory(Class<X> c, ObjectFactory<X> of) { map.put(c, of); return this; } /** * @return an object of type c, or throw {@link IllegalArgumentException} if none are registered with this ObjectProvider */ @SuppressWarnings("unchecked") public <X> X get(Class<X> c) { if (map.containsKey(c)) return (X) toValue(map.get(c)); //c.cast(e.value()) will fail if c.isPrimitive() for (Map.Entry<Class<?>, Object> e : map.entrySet()) { if (c.isAssignableFrom(e.getKey())) { Object v = toValue(e.getValue()); return (X) v; } } if (errorOnUnknownType) throw new IllegalArgumentException("No object registred for " + c + " in " + this); else return null; } private Object toValue(Object obj) { if (obj instanceof ObjectFactory) { obj = ((ObjectFactory) obj).make(); } return obj; } /** * @return True if an object of type c is registered with this ObjectProvider, false otherwise */ public boolean has(Class<?> c) { if (map.containsKey(c)) return true; //c.cast(e.value()) will fail if c.isPrimitive() for (Class<?> mappedclass : map.keySet()) { if (c.isAssignableFrom(mappedclass)) return true; } return false; } /** * @return a copy of this, with the extra object o registered */ public ObjectProvider copyWith(Object o) { return copyObjectProvider().register(o); } /** * @return a copy of this, with the extra object o registered */ public <X> ObjectProvider copyWith(Class<X> c, X o) { return copyObjectProvider().register(c, o); } @Override public String toString() { return "ObjectProvider: " + Arrays.toString(new Object[] { map, annotationMap, lastArg }); } /** * Returns an ObjectProvivder that will use the provided object as the last object when calling {@link #makeArgsFor(Method)} */ public ObjectProvider copyWithLast(Object o) { ObjectProvider op = copyObjectProvider(); op.lastArg = o; return op; } /** Storage for the argument types and argument annotations for a {@link Method} or {@link Constructor} */ private static class Parameters { final Class<?>[] pt; final Annotation[][] a; private Parameters(Method m) { this.pt = m.getParameterTypes(); this.a = m.getParameterAnnotations(); } private Parameters(Constructor cons) { this.pt = cons.getParameterTypes(); this.a = cons.getParameterAnnotations(); } } /** Cache for {@link Parameters} since these are relatively expensive to obtain */ private static final ConcurrentMap<Object, Parameters> parametersMap = Maps.newConcurrentMap(); /** * Creates a parameter list for invoking the method using object from this ObjectProvider. * Parameters are obtained by calling {@link #get(Class)} on the classes in m.getParameterTypes(). * If a lastArg is specified for this ObjectProvider, the last argument will be that object. */ public Object[] makeArgsFor(Method m) { Parameters p = parametersMap.get(m); if (p == null) parametersMap.put(m, p = new Parameters(m)); return makeArgsFor(p.pt, p.a); } /** Same as {@link #makeArgsFor(Method)}, but for a {@link Constructor} */ public Object[] makeArgsFor(Constructor cons) { Parameters p = parametersMap.get(cons); if (p == null) parametersMap.put(cons, p = new Parameters(cons)); return makeArgsFor(p.pt, p.a); } private Object[] makeArgsFor(Class<?>[] pt, Annotation[][] a) { if (pt.length == 0) return NO_ARUMENTS; Object[] params = new Object[pt.length]; for (int i = 0; i < pt.length; i++) { if (i == pt.length - 1 && lastArg != NO_ARG) params[i] = lastArg; else { if (a[i].length == 1) params[i] = getByAnnotation(a[i][0].annotationType(), pt[i]); else if (a[i].length == 0) params[i] = get(pt[i]); else throw new IllegalArgumentException("Parameter " + i + " has multiple annotations"); } } return params; } }