ThrowingProviderBinder.java revision 9a227bef3b82a045323ef2cf38ec60b2e42cf0fe
1/**
2 * Copyright (C) 2007 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.inject.throwingproviders;
18
19import com.google.inject.Binder;
20import com.google.inject.Inject;
21import com.google.inject.Injector;
22import com.google.inject.Key;
23import com.google.inject.Provider;
24import com.google.inject.TypeLiteral;
25import com.google.inject.binder.ScopedBindingBuilder;
26import static com.google.inject.internal.util.Preconditions.checkNotNull;
27import com.google.inject.internal.UniqueAnnotations;
28import com.google.inject.util.Types;
29import java.lang.annotation.Annotation;
30import java.lang.reflect.InvocationHandler;
31import java.lang.reflect.Method;
32import java.lang.reflect.ParameterizedType;
33import java.lang.reflect.Proxy;
34import java.lang.reflect.Type;
35import java.lang.reflect.TypeVariable;
36
37/**
38 * <p>Builds a binding for a {@link ThrowingProvider} using a fluent API:
39 * <pre><code>ThrowingProviderBinder.create(binder())
40 *    .bind(RemoteProvider.class, Customer.class)
41 *    .to(RemoteCustomerProvider.class)
42 *    .in(RequestScope.class);
43 * </code></pre>
44 *
45 * @author jmourits@google.com (Jerome Mourits)
46 * @author jessewilson@google.com (Jesse Wilson)
47 */
48public class ThrowingProviderBinder {
49
50  private final Binder binder;
51
52  private ThrowingProviderBinder(Binder binder) {
53    this.binder = binder;
54  }
55
56  public static ThrowingProviderBinder create(Binder binder) {
57    return new ThrowingProviderBinder(binder);
58  }
59
60  public <P extends ThrowingProvider> SecondaryBinder<P>
61      bind(final Class<P> interfaceType, final Type valueType) {
62    return new SecondaryBinder<P>(interfaceType, valueType);
63  }
64
65  public class SecondaryBinder<P extends ThrowingProvider> {
66    private final Class<P> interfaceType;
67    private final Type valueType;
68    private Class<? extends Annotation> annotationType;
69    private Annotation annotation;
70    private final Class<?> exceptionType;
71
72    public SecondaryBinder(Class<P> interfaceType, Type valueType) {
73      this.interfaceType = checkNotNull(interfaceType, "interfaceType");
74      this.valueType = checkNotNull(valueType, "valueType");
75      checkInterface();
76      this.exceptionType = getExceptionType(interfaceType);
77    }
78
79    public SecondaryBinder<P> annotatedWith(Class<? extends Annotation> annotationType) {
80      if (!(this.annotationType == null && this.annotation == null)) {
81        throw new IllegalStateException();
82      }
83      this.annotationType = annotationType;
84      return this;
85    }
86
87    public SecondaryBinder<P> annotatedWith(Annotation annotation) {
88      if (!(this.annotationType == null && this.annotation == null)) {
89        throw new IllegalStateException();
90      }
91      this.annotation = annotation;
92      return this;
93    }
94
95    public ScopedBindingBuilder to(P target) {
96      Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create());
97      binder.bind(targetKey).toInstance(target);
98      return to(targetKey);
99    }
100
101    public ScopedBindingBuilder to(Class<? extends P> targetType) {
102      return to(Key.get(targetType));
103    }
104
105    public ScopedBindingBuilder to(final Key<? extends P> targetKey) {
106      checkNotNull(targetKey, "targetKey");
107      final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create());
108      final Key<P> key = createKey();
109
110      binder.bind(key).toProvider(new Provider<P>() {
111        private P instance;
112
113        @Inject void initialize(final Injector injector) {
114          instance = interfaceType.cast(Proxy.newProxyInstance(
115              interfaceType.getClassLoader(), new Class<?>[] { interfaceType },
116              new InvocationHandler() {
117                public Object invoke(Object proxy, Method method, Object[] args)
118                    throws Throwable {
119                  return injector.getInstance(resultKey).getOrThrow();
120                }
121              }));
122          }
123
124          public P get() {
125            return instance;
126          }
127        });
128
129      return binder.bind(resultKey).toProvider(new Provider<Result>() {
130        private Injector injector;
131
132        @Inject void initialize(Injector injector) {
133          this.injector = injector;
134        }
135
136        public Result get() {
137          try {
138            return Result.forValue(injector.getInstance(targetKey).get());
139          } catch (Exception e) {
140            if (exceptionType.isInstance(e)) {
141              return Result.forException(e);
142            } else if (e instanceof RuntimeException) {
143              throw (RuntimeException) e;
144            } else {
145              // this should never happen
146              throw new RuntimeException(e);
147            }
148          }
149        }
150      });
151    }
152
153    /**
154     * Returns the exception type declared to be thrown by the get method of
155     * {@code interfaceType}.
156     */
157    @SuppressWarnings({"unchecked"})
158    private <P extends ThrowingProvider> Class<?> getExceptionType(Class<P> interfaceType) {
159      ParameterizedType genericUnreliableProvider
160          = (ParameterizedType) interfaceType.getGenericInterfaces()[0];
161      return (Class<? extends Exception>) genericUnreliableProvider.getActualTypeArguments()[1];
162    }
163
164    private void checkInterface() {
165      String errorMessage = "%s is not a compliant interface "
166          + "- see the Javadoc for ThrowingProvider";
167
168      checkArgument(interfaceType.isInterface(), errorMessage, interfaceType.getName());
169      checkArgument(interfaceType.getGenericInterfaces().length == 1, errorMessage,
170          interfaceType.getName());
171      checkArgument(interfaceType.getInterfaces()[0] == ThrowingProvider.class,
172          errorMessage, interfaceType.getName());
173
174      // Ensure that T is parameterized and unconstrained.
175      ParameterizedType genericThrowingProvider
176          = (ParameterizedType) interfaceType.getGenericInterfaces()[0];
177      if (interfaceType.getTypeParameters().length == 1) {
178        checkArgument(interfaceType.getTypeParameters().length == 1, errorMessage,
179            interfaceType.getName());
180        String returnTypeName = interfaceType.getTypeParameters()[0].getName();
181        Type returnType = genericThrowingProvider.getActualTypeArguments()[0];
182        checkArgument(returnType instanceof TypeVariable, errorMessage, interfaceType.getName());
183        checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()),
184            errorMessage, interfaceType.getName());
185      } else {
186        checkArgument(interfaceType.getTypeParameters().length == 0,
187            errorMessage, interfaceType.getName());
188        checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType),
189            errorMessage, interfaceType.getName());
190      }
191
192      Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1];
193      checkArgument(exceptionType instanceof Class, errorMessage, interfaceType.getName());
194
195      if (interfaceType.getDeclaredMethods().length == 1) {
196        Method method = interfaceType.getDeclaredMethods()[0];
197        checkArgument(method.getName().equals("get"), errorMessage, interfaceType.getName());
198        checkArgument(method.getParameterTypes().length == 0,
199            errorMessage, interfaceType.getName());
200      } else {
201        checkArgument(interfaceType.getDeclaredMethods().length == 0,
202            errorMessage, interfaceType.getName());
203      }
204    }
205
206    private void checkArgument(boolean condition,
207        String messageFormat, Object... args) {
208      if (!condition) {
209        throw new IllegalArgumentException(String.format(messageFormat, args));
210      }
211    }
212
213    @SuppressWarnings({"unchecked"})
214    private Key<P> createKey() {
215      TypeLiteral<P> typeLiteral;
216      if (interfaceType.getTypeParameters().length == 1) {
217        ParameterizedType type = Types.newParameterizedTypeWithOwner(
218            interfaceType.getEnclosingClass(), interfaceType, valueType);
219        typeLiteral = (TypeLiteral<P>) TypeLiteral.get(type);
220      } else {
221        typeLiteral = TypeLiteral.get(interfaceType);
222      }
223
224      if (annotation != null) {
225        return Key.get(typeLiteral, annotation);
226
227      } else if (annotationType != null) {
228        return Key.get(typeLiteral, annotationType);
229
230      } else {
231        return Key.get(typeLiteral);
232      }
233    }
234  }
235
236  /**
237   * Represents the returned value from a call to {@link
238   * ThrowingProvider#get()}. This is the value that will be scoped by Guice.
239   */
240  private static class Result {
241    private final Object value;
242    private final Exception exception;
243
244    private Result(Object value, Exception exception) {
245      this.value = value;
246      this.exception = exception;
247    }
248
249    public static Result forValue(Object value) {
250      return new Result(value, null);
251    }
252
253    public static Result forException(Exception e) {
254      return new Result(null, e);
255    }
256
257    public Object getOrThrow() throws Exception {
258      if (exception != null) {
259        throw exception;
260      } else {
261        return value;
262      }
263    }
264  }
265}
266