| /** |
| * Copyright (C) 2007 Google Inc. |
| * |
| * 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 com.google.inject.throwingproviders; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| 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 com.google.inject.TypeLiteral; |
| import com.google.inject.binder.ScopedBindingBuilder; |
| import com.google.inject.internal.UniqueAnnotations; |
| import com.google.inject.util.Types; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Proxy; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| |
| /** |
| * <p>Builds a binding for an {@link ThrowingProvider} using a fluent API: |
| * <pre><code>ThrowingProviderBinder.create(binder()) |
| * .bind(RemoteProvider.class, Customer.class) |
| * .to(RemoteCustomerProvider.class) |
| * .in(RequestScope.class); |
| * </code></pre> |
| * |
| * @author jmourits@google.com (Jerome Mourits) |
| * @author jessewilson@google.com (Jesse Wilson) |
| */ |
| public class ThrowingProviderBinder { |
| |
| private final Binder binder; |
| |
| private ThrowingProviderBinder(Binder binder) { |
| this.binder = binder; |
| } |
| |
| public static ThrowingProviderBinder create(Binder binder) { |
| return new ThrowingProviderBinder(binder); |
| } |
| |
| public <P extends ThrowingProvider> SecondaryBinder<P> |
| bind(final Class<P> interfaceType, final Type valueType) { |
| return new SecondaryBinder<P>(interfaceType, valueType); |
| } |
| |
| public class SecondaryBinder<P extends ThrowingProvider> { |
| private final Class<P> interfaceType; |
| private final Type valueType; |
| private Class<? extends Annotation> annotationType; |
| private Annotation annotation; |
| private final Class<?> exceptionType; |
| |
| public SecondaryBinder(Class<P> interfaceType, Type valueType) { |
| this.interfaceType = checkNotNull(interfaceType, "interfaceType"); |
| this.valueType = checkNotNull(valueType, "valueType"); |
| checkInterface(); |
| this.exceptionType = getExceptionType(interfaceType); |
| } |
| |
| public SecondaryBinder<P> annotatedWith(Class<? extends Annotation> annotationType) { |
| if (!(this.annotationType == null && this.annotation == null)) { |
| throw new IllegalStateException(); |
| } |
| this.annotationType = annotationType; |
| return this; |
| } |
| |
| public SecondaryBinder<P> annotatedWith(Annotation annotation) { |
| if (!(this.annotationType == null && this.annotation == null)) { |
| throw new IllegalStateException(); |
| } |
| this.annotation = annotation; |
| return this; |
| } |
| |
| public ScopedBindingBuilder to(P target) { |
| Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create()); |
| binder.bind(targetKey).toInstance(target); |
| return to(targetKey); |
| } |
| |
| public ScopedBindingBuilder to(Class<? extends P> targetType) { |
| return to(Key.get(targetType)); |
| } |
| |
| public ScopedBindingBuilder to(final Key<? extends P> targetKey) { |
| checkNotNull(targetKey, "targetKey"); |
| final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create()); |
| final Key<P> key = createKey(); |
| |
| binder.bind(key).toProvider(new Provider<P>() { |
| private P instance; |
| |
| @Inject void initialize(final Injector injector) { |
| instance = interfaceType.cast(Proxy.newProxyInstance( |
| interfaceType.getClassLoader(), new Class<?>[] { interfaceType }, |
| new InvocationHandler() { |
| public Object invoke(Object proxy, Method method, Object[] args) |
| throws Throwable { |
| return injector.getInstance(resultKey).getOrThrow(); |
| } |
| })); |
| } |
| |
| public P get() { |
| return instance; |
| } |
| }); |
| |
| return binder.bind(resultKey).toProvider(new Provider<Result>() { |
| private Injector injector; |
| |
| @Inject void initialize(Injector injector) { |
| this.injector = injector; |
| } |
| |
| public Result get() { |
| try { |
| return Result.forValue(injector.getInstance(targetKey).get()); |
| } catch (Exception e) { |
| if (exceptionType.isInstance(e)) { |
| return Result.forException(e); |
| } else if (e instanceof RuntimeException) { |
| throw (RuntimeException) e; |
| } else { |
| // this should never happen |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Returns the exception type declared to be thrown by the get method of |
| * {@code interfaceType}. |
| */ |
| @SuppressWarnings({"unchecked"}) |
| private <P extends ThrowingProvider> Class<?> getExceptionType(Class<P> interfaceType) { |
| ParameterizedType genericUnreliableProvider |
| = (ParameterizedType) interfaceType.getGenericInterfaces()[0]; |
| return (Class<? extends Exception>) genericUnreliableProvider.getActualTypeArguments()[1]; |
| } |
| |
| private void checkInterface() { |
| String errorMessage = "%s is not a compliant interface " |
| + "- see the Javadoc for ThrowingProvider"; |
| |
| checkArgument(interfaceType.isInterface(), errorMessage, interfaceType.getName()); |
| checkArgument(interfaceType.getGenericInterfaces().length == 1, errorMessage, |
| interfaceType.getName()); |
| checkArgument(interfaceType.getInterfaces()[0] == ThrowingProvider.class, |
| errorMessage, interfaceType.getName()); |
| |
| // Ensure that T is parameterized and unconstrained. |
| ParameterizedType genericThrowingProvider |
| = (ParameterizedType) interfaceType.getGenericInterfaces()[0]; |
| if (interfaceType.getTypeParameters().length == 1) { |
| checkArgument(interfaceType.getTypeParameters().length == 1, errorMessage, |
| interfaceType.getName()); |
| String returnTypeName = interfaceType.getTypeParameters()[0].getName(); |
| Type returnType = genericThrowingProvider.getActualTypeArguments()[0]; |
| checkArgument(returnType instanceof TypeVariable, errorMessage, interfaceType.getName()); |
| checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()), |
| errorMessage, interfaceType.getName()); |
| } else { |
| checkArgument(interfaceType.getTypeParameters().length == 0, |
| errorMessage, interfaceType.getName()); |
| checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType), |
| errorMessage, interfaceType.getName()); |
| } |
| |
| Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1]; |
| checkArgument(exceptionType instanceof Class, errorMessage, interfaceType.getName()); |
| |
| if (interfaceType.getDeclaredMethods().length == 1) { |
| Method method = interfaceType.getDeclaredMethods()[0]; |
| checkArgument(method.getName().equals("get"), errorMessage, interfaceType.getName()); |
| checkArgument(method.getParameterTypes().length == 0, |
| errorMessage, interfaceType.getName()); |
| } else { |
| checkArgument(interfaceType.getDeclaredMethods().length == 0, |
| errorMessage, interfaceType.getName()); |
| } |
| } |
| |
| private void checkArgument(boolean condition, |
| String messageFormat, Object... args) { |
| if (!condition) { |
| throw new IllegalArgumentException(String.format(messageFormat, args)); |
| } |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| private Key<P> createKey() { |
| TypeLiteral<P> typeLiteral; |
| if (interfaceType.getTypeParameters().length == 1) { |
| ParameterizedType type = Types.newParameterizedTypeWithOwner( |
| interfaceType.getEnclosingClass(), interfaceType, valueType); |
| typeLiteral = (TypeLiteral<P>) TypeLiteral.get(type); |
| } else { |
| typeLiteral = TypeLiteral.get(interfaceType); |
| } |
| |
| if (annotation != null) { |
| return Key.get(typeLiteral, annotation); |
| |
| } else if (annotationType != null) { |
| return Key.get(typeLiteral, annotationType); |
| |
| } else { |
| return Key.get(typeLiteral); |
| } |
| } |
| } |
| |
| /** |
| * Represents the returned value from a call to {@link |
| * ThrowingProvider#get()}. This is the value that will be scoped by Guice. |
| */ |
| private static class Result { |
| private final Object value; |
| private final Exception exception; |
| |
| private Result(Object value, Exception exception) { |
| this.value = value; |
| this.exception = exception; |
| } |
| |
| public static Result forValue(Object value) { |
| return new Result(value, null); |
| } |
| |
| public static Result forException(Exception e) { |
| return new Result(null, e); |
| } |
| |
| public Object getOrThrow() throws Exception { |
| if (exception != null) { |
| throw exception; |
| } else { |
| return value; |
| } |
| } |
| } |
| } |