/* * Copyright 2008 Tim Peierls * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.directwebremoting.guice.util; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import com.google.inject.Binder; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Provider; import static java.lang.reflect.Modifier.*; /** * Utilities for creating ad hoc providers. These come in two flavors, * constructor and factory method. In both cases, you can specify additional * methods to inject after creation by chaining calls to * {@link BindingProvider#injecting injecting(methodName, paramKeys)}. * Each method has two variants, one with a {@code Binder} parameter and one * without. The former reports construction errors via the binder (so more than * one error can be reported), the latter throws a runtime exception immediately, * preventing further error reporting. * * <p> Some examples: * <pre> * bind(SomeInterface.class) * .toProvider( * .fromConstructor(LegacyImpl.class, * Key.get(String.class, MyAnnotation.class)) * .injecting("configure", Key.get(Configuration.class))); * </pre> * This binds {@code SomeInterface} to a provider that uses the * {@code LegacyImpl} constructor with a single String parameter. * That parameter is injected as if it had been marked with {@code @MyAnnotation}. * It also calls the {@code LegacyImpl.configure} method with an injected * instance of {@code Configuration}. * <pre> * bind(SomeInterface.class) * .toProvider( * .fromFactoryMethod(SomeInterface.class, * LegacyFactory.class, "newSomeInterface", * Key.get(String.class, MyAnnotation.class))); * </pre> * This binds {@code SomeInterface} to a provider that calls a factory method * {@code newSomeInterface} of {@code LegacyFactory} with a single string parameter, * which is injected as if it were marked with {@code @MyAnnotation}. If the * method is not static, an instance of {@code LegacyFactory} is created by injection * and used to call the method. * @author Tim Peierls [tim at peierls dot net] */ public class Providers { /** * For fluent-style decoration with one or more method bindings when * using {@link #fromConstructor(Class, Key...)}. */ public interface BindingProvider<T> extends Provider<T> { /** * Adds injection of a method defined by the given name and parameter * types (specified as Guice keys) to this provider. */ BindingProvider<T> injecting(String methodName, Key<?>... paramKeys); } /** * Creates a chainable provider that constructs an instance of the * given type given a list of constructor parameter types, specified * as Guice keys. Construction errors are thrown immediately. */ public static <T> BindingProvider<T> fromConstructor(Class<T> type, final Key<?>... keys) { return new ConstructorBindingProvider<T>(null, type, keys); } /** * Creates a chainable provider that constructs an instance of the * given type given a list of constructor parameter types, specified * as Guice keys. Construction errors are passed to {@code binder} * to be thrown at the end of all binding. */ public static <T> BindingProvider<T> fromConstructor(Binder binder, Class<T> type, final Key<?>... keys) { return new ConstructorBindingProvider<T>(binder, type, keys); } /** * Creates a chainable provider that constructs an instance of {@code providedType} * using a factory method defined by {@code factoryType}, {@code methodName}, * and a list of method parameter types specified as Guice keys. If the * method is not static an instance of the factory type is created and injected. * Construction errors are thrown immediately. */ public static <T> BindingProvider<T> fromFactoryMethod( Class<T> providedType, Class<?> factoryType, String methodName, final Key<?>... keys) { return new FactoryMethodBindingProvider<T>( null, providedType, Key.get(factoryType), methodName, keys); } /** * Creates a chainable provider that constructs an instance of {@code providedType} * using a factory method defined by {@code factoryType}, {@code methodName}, * and a list of method parameter types specified as Guice keys. If the * method is not static an instance of the factory type is created and injected. * Construction errors are passed to {@code binder} to be thrown at the end * of all binding. */ public static <T> BindingProvider<T> fromFactoryMethod( Binder binder, Class<T> providedType, Class<?> factoryType, String methodName, final Key<?>... keys) { return new FactoryMethodBindingProvider<T>( binder, providedType, Key.get(factoryType), methodName, keys); } /** * Creates a chainable provider that constructs an instance of {@code providedType} by * calling method {@code methodName} of the type in {@code factoryKey} with * method parameter types specified as Guice keys. If the method is not static * an instance is created and injected using the factory key. * Construction errors are thrown immediately. */ public static <T> BindingProvider<T> fromFactoryMethod( Class<T> providedType, Key<?> factoryKey, String methodName, final Key<?>... keys) { return new FactoryMethodBindingProvider<T>( null, providedType, factoryKey, methodName, keys); } /** * Creates a chainable provider that constructs an instance of {@code providedType} by * calling method {@code methodName} of the type in {@code factoryKey} with * method parameter types specified as Guice keys. If the method is not static * an instance is created and injected using the factory key. * Construction errors are passed to {@code binder} to be thrown at the end * of all binding. */ public static <T> BindingProvider<T> fromFactoryMethod( Binder binder, Class<T> providedType, Key<?> factoryKey, String methodName, final Key<?>... keys) { return new FactoryMethodBindingProvider<T>( binder, providedType, factoryKey, methodName, keys); } // // Implementation classes // private abstract static class AbstractBindingProvider<T> implements BindingProvider<T> { protected AbstractBindingProvider(Binder binder, Class<T> type, Key<?>... keys) { this.binder = binder; this.type = type; this.keys = keys; } public final T get() { return get(theInjector); } protected abstract T get(Injector injector); public final BindingProvider<T> injecting(String methodName, Key<?>... paramKeys) { return new MethodBindingProvider<T>(this, type, methodName, paramKeys); } protected final Class<?>[] getTypes() { Class<?>[] types = new Class[keys.length]; int i = 0; for (Key<?> key : keys) { types[i] = (Class<?>) key.getTypeLiteral().getType(); i++; } return types; } protected final Object[] getValues(Injector injector) { Object[] values = new Object[keys.length]; int i = 0; for (Key<?> key : keys) { Object param = injector.getInstance(key); values[i] = param; i++; } return values; } protected final Binder binder; protected final Class<T> type; protected final Key<?>[] keys; /** * Effectively immutable: Injected at end of bind-time, * read-only thereafter, and there is (or should be) a * happens-before edge between bind-time and subsequent reads. */ @Inject private Injector theInjector; } private static class ConstructorBindingProvider<T> extends AbstractBindingProvider<T> { ConstructorBindingProvider(Binder binder, Class<T> type, Key<?>... keys) { super(binder, type, keys); Constructor<T> newConstructor = null; try { newConstructor = type.getConstructor(getTypes()); } catch (NoSuchMethodException e) { if (binder == null) { throw new IllegalArgumentException("no such constructor", e); } else { binder.addError(e); } } this.constructor = newConstructor; } @Override public T get(Injector injector) { try { return constructor.newInstance(getValues(injector)); } catch (InstantiationException e) { throw new IllegalStateException(e); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } } private final Constructor<T> constructor; } private static class MethodBindingProvider<T> extends AbstractBindingProvider<T> { MethodBindingProvider(AbstractBindingProvider<T> prev, Class<T> type, String methodName, Key<?>... keys) { super(prev.binder, type, keys); Method newMethod = null; try { newMethod = type.getMethod(methodName, getTypes()); } catch (NoSuchMethodException e) { if (binder == null) { throw new IllegalArgumentException("no such method", e); } else { binder.addError(e); } } this.prev = prev; this.method = newMethod; } @Override public T get(Injector injector) { T target = prev.get(injector); try { method.invoke(target, getValues(injector)); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } return target; } private final AbstractBindingProvider<T> prev; private final Method method; } private static class FactoryMethodBindingProvider<T> extends AbstractBindingProvider<T> { FactoryMethodBindingProvider(Binder binder, Class<T> providedType, Key<?> factoryKey, String methodName, Key<?>... keys) { super(binder, providedType, keys); Method newMethod = null; try { Class<?> factoryType = (Class<?>) factoryKey.getTypeLiteral().getType(); newMethod = factoryType.getMethod(methodName, getTypes()); newMethod.getReturnType().asSubclass(providedType); } catch (NoSuchMethodException e) { if (binder == null) { throw new IllegalArgumentException("no such method", e); } else { binder.addError(e); } } catch (ClassCastException e) { if (binder == null) { throw new IllegalArgumentException("bad return type", e); } else { binder.addError(e); } } this.method = newMethod; this.factoryKey = factoryKey; } @Override public T get(Injector injector) { try { Object target = null; if (!isStatic(method.getModifiers())) { target = injector.getInstance(factoryKey); } @SuppressWarnings("unchecked") T result = (T) method.invoke(target, getValues(injector)); return result; } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } } private final Method method; private final Key<?> factoryKey; } private Providers() { throw new AssertionError("uninstantiable"); } }