blob: ac4e4546f9bada27d072cc04e1a5b8810e09c7d5 [file] [log] [blame]
limpbizkite451ef72007-12-19 01:15:59 +00001/**
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
limpbizkit49f67c02008-06-10 20:56:17 +000019import com.google.inject.Binder;
limpbizkit49f67c02008-06-10 20:56:17 +000020import com.google.inject.Key;
sberlinee446ca2010-10-24 21:36:39 +000021import com.google.inject.Module;
limpbizkit49f67c02008-06-10 20:56:17 +000022import com.google.inject.Provider;
23import com.google.inject.TypeLiteral;
limpbizkite451ef72007-12-19 01:15:59 +000024import com.google.inject.binder.ScopedBindingBuilder;
sberlind9c913a2011-06-26 21:02:54 +000025import static com.google.common.base.Preconditions.checkNotNull;
limpbizkite39d8d82008-03-26 07:19:25 +000026import com.google.inject.internal.UniqueAnnotations;
sberlind9c913a2011-06-26 21:02:54 +000027import com.google.common.collect.ImmutableList;
28import com.google.common.collect.ImmutableSet;
29import com.google.common.collect.Lists;
sberlin5ac45a82010-10-16 21:38:22 +000030import com.google.inject.spi.Dependency;
31import com.google.inject.spi.ProviderWithDependencies;
limpbizkit49f67c02008-06-10 20:56:17 +000032import com.google.inject.util.Types;
sberlin488284d2010-11-17 14:27:41 +000033
34import java.io.Serializable;
limpbizkite451ef72007-12-19 01:15:59 +000035import java.lang.annotation.Annotation;
limpbizkit49f67c02008-06-10 20:56:17 +000036import java.lang.reflect.InvocationHandler;
37import java.lang.reflect.Method;
38import java.lang.reflect.ParameterizedType;
39import java.lang.reflect.Proxy;
40import java.lang.reflect.Type;
41import java.lang.reflect.TypeVariable;
sberlinee446ca2010-10-24 21:36:39 +000042import java.util.Arrays;
sberlin488284d2010-11-17 14:27:41 +000043import java.util.List;
sberlin5ac45a82010-10-16 21:38:22 +000044import java.util.Set;
limpbizkite451ef72007-12-19 01:15:59 +000045
46/**
sberlin488284d2010-11-17 14:27:41 +000047 * <p>Builds a binding for a {@link CheckedProvider}.
sberlinee446ca2010-10-24 21:36:39 +000048 *
49 * <p>You can use a fluent API and custom providers:
limpbizkit0c43f222007-12-19 01:38:16 +000050 * <pre><code>ThrowingProviderBinder.create(binder())
limpbizkite451ef72007-12-19 01:15:59 +000051 * .bind(RemoteProvider.class, Customer.class)
52 * .to(RemoteCustomerProvider.class)
53 * .in(RequestScope.class);
limpbizkit0c43f222007-12-19 01:38:16 +000054 * </code></pre>
sberlinee446ca2010-10-24 21:36:39 +000055 * or, you can use throwing provider methods:
56 * <pre><code>class MyModule extends AbstractModule {
57 * configure() {
58 * ThrowingProviderBinder.install(this, binder());
59 * }
60 *
sberlin488284d2010-11-17 14:27:41 +000061 * {@literal @}CheckedProvides(RemoteProvider.class)
sberlinee446ca2010-10-24 21:36:39 +000062 * {@literal @}RequestScope
63 * Customer provideCustomer(FlakyCustomerCreator creator) throws RemoteException {
64 * return creator.getCustomerOrThrow();
65 * }
66 * }
67 * </code></pre>
limpbizkite451ef72007-12-19 01:15:59 +000068 *
69 * @author jmourits@google.com (Jerome Mourits)
70 * @author jessewilson@google.com (Jesse Wilson)
71 */
72public class ThrowingProviderBinder {
73
74 private final Binder binder;
75
76 private ThrowingProviderBinder(Binder binder) {
77 this.binder = binder;
78 }
79
sberlinee446ca2010-10-24 21:36:39 +000080 public static ThrowingProviderBinder create(Binder binder) {
81 return new ThrowingProviderBinder(binder.skipSources(
82 ThrowingProviderBinder.class,
83 ThrowingProviderBinder.SecondaryBinder.class));
84 }
85
sberlinc13b5452010-10-31 18:38:24 +000086 /**
sberlin488284d2010-11-17 14:27:41 +000087 * Returns a module that installs {@literal @}{@link CheckedProvides} methods.
sberlinc13b5452010-10-31 18:38:24 +000088 *
89 * @since 3.0
90 */
sberlin488284d2010-11-17 14:27:41 +000091 public static Module forModule(Module module) {
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +000092 return CheckedProviderMethodsModule.forModule(module);
limpbizkite451ef72007-12-19 01:15:59 +000093 }
94
sberlin488284d2010-11-17 14:27:41 +000095 public <P extends CheckedProvider> SecondaryBinder<P>
limpbizkite451ef72007-12-19 01:15:59 +000096 bind(final Class<P> interfaceType, final Type valueType) {
97 return new SecondaryBinder<P>(interfaceType, valueType);
98 }
99
sberlin488284d2010-11-17 14:27:41 +0000100 public class SecondaryBinder<P extends CheckedProvider> {
limpbizkite451ef72007-12-19 01:15:59 +0000101 private final Class<P> interfaceType;
102 private final Type valueType;
sberlin488284d2010-11-17 14:27:41 +0000103 private final List<Class<? extends Throwable>> exceptionTypes;
sberlinee446ca2010-10-24 21:36:39 +0000104 private final boolean valid;
limpbizkite451ef72007-12-19 01:15:59 +0000105
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +0000106 private Class<? extends Annotation> annotationType;
107 private Annotation annotation;
108 private Key<P> interfaceKey;
109
limpbizkite451ef72007-12-19 01:15:59 +0000110 public SecondaryBinder(Class<P> interfaceType, Type valueType) {
kevinb9n1601ae52008-06-03 22:21:04 +0000111 this.interfaceType = checkNotNull(interfaceType, "interfaceType");
112 this.valueType = checkNotNull(valueType, "valueType");
sberlinee446ca2010-10-24 21:36:39 +0000113 if(checkInterface()) {
sberlin488284d2010-11-17 14:27:41 +0000114 this.exceptionTypes = getExceptionType(interfaceType);
sberlinee446ca2010-10-24 21:36:39 +0000115 valid = true;
116 } else {
117 valid = false;
sberlin488284d2010-11-17 14:27:41 +0000118 this.exceptionTypes = ImmutableList.of();
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +0000119 }
sberlinee446ca2010-10-24 21:36:39 +0000120 }
121
sberlin488284d2010-11-17 14:27:41 +0000122 List<Class<? extends Throwable>> getExceptionTypes() {
123 return exceptionTypes;
limpbizkite451ef72007-12-19 01:15:59 +0000124 }
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +0000125
126 Key<P> getKey() {
127 return interfaceKey;
128 }
limpbizkite451ef72007-12-19 01:15:59 +0000129
130 public SecondaryBinder<P> annotatedWith(Class<? extends Annotation> annotationType) {
131 if (!(this.annotationType == null && this.annotation == null)) {
132 throw new IllegalStateException();
133 }
134 this.annotationType = annotationType;
135 return this;
136 }
137
138 public SecondaryBinder<P> annotatedWith(Annotation annotation) {
139 if (!(this.annotationType == null && this.annotation == null)) {
140 throw new IllegalStateException();
141 }
142 this.annotation = annotation;
143 return this;
144 }
145
146 public ScopedBindingBuilder to(P target) {
limpbizkite39d8d82008-03-26 07:19:25 +0000147 Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create());
limpbizkite451ef72007-12-19 01:15:59 +0000148 binder.bind(targetKey).toInstance(target);
149 return to(targetKey);
150 }
151
152 public ScopedBindingBuilder to(Class<? extends P> targetType) {
153 return to(Key.get(targetType));
154 }
sberlinee446ca2010-10-24 21:36:39 +0000155
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +0000156 ScopedBindingBuilder toProviderMethod(CheckedProviderMethod<?> target) {
157 Key<CheckedProviderMethod> targetKey =
158 Key.get(CheckedProviderMethod.class, UniqueAnnotations.create());
sberlinee446ca2010-10-24 21:36:39 +0000159 binder.bind(targetKey).toInstance(target);
160
161 return toInternal(targetKey);
162 }
limpbizkite451ef72007-12-19 01:15:59 +0000163
sberlinee446ca2010-10-24 21:36:39 +0000164 public ScopedBindingBuilder to(Key<? extends P> targetKey) {
kevinb9n1601ae52008-06-03 22:21:04 +0000165 checkNotNull(targetKey, "targetKey");
sberlinee446ca2010-10-24 21:36:39 +0000166 return toInternal(targetKey);
167 }
168
sberlin488284d2010-11-17 14:27:41 +0000169 private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider> targetKey) {
limpbizkite39d8d82008-03-26 07:19:25 +0000170 final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create());
sberlin5ac45a82010-10-16 21:38:22 +0000171 final Provider<Result> resultProvider = binder.getProvider(resultKey);
sberlin488284d2010-11-17 14:27:41 +0000172 final Provider<? extends CheckedProvider> targetProvider = binder.getProvider(targetKey);
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +0000173 interfaceKey = createKey();
limpbizkite451ef72007-12-19 01:15:59 +0000174
sberlinee446ca2010-10-24 21:36:39 +0000175 // don't bother binding the proxy type if this is in an invalid state.
176 if(valid) {
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +0000177 binder.bind(interfaceKey).toProvider(new ProviderWithDependencies<P>() {
sberlinee446ca2010-10-24 21:36:39 +0000178 private final P instance = interfaceType.cast(Proxy.newProxyInstance(
179 interfaceType.getClassLoader(), new Class<?>[] { interfaceType },
180 new InvocationHandler() {
181 public Object invoke(Object proxy, Method method, Object[] args)
182 throws Throwable {
183 return resultProvider.get().getOrThrow();
184 }
185 }));
186
187 public P get() {
188 return instance;
189 }
190
191 public Set<Dependency<?>> getDependencies() {
192 return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey));
193 }
194 });
195 }
limpbizkite451ef72007-12-19 01:15:59 +0000196
sberlin5ac45a82010-10-16 21:38:22 +0000197 return binder.bind(resultKey).toProvider(new ProviderWithDependencies<Result>() {
limpbizkite451ef72007-12-19 01:15:59 +0000198 public Result get() {
199 try {
sberlin5ac45a82010-10-16 21:38:22 +0000200 return Result.forValue(targetProvider.get().get());
limpbizkite451ef72007-12-19 01:15:59 +0000201 } catch (Exception e) {
sberlin488284d2010-11-17 14:27:41 +0000202 for(Class<? extends Throwable> exceptionType : exceptionTypes) {
203 if (exceptionType.isInstance(e)) {
204 return Result.forException(e);
205 }
206 }
207
208 if (e instanceof RuntimeException) {
limpbizkite451ef72007-12-19 01:15:59 +0000209 throw (RuntimeException) e;
210 } else {
211 // this should never happen
212 throw new RuntimeException(e);
213 }
214 }
215 }
sberlin5ac45a82010-10-16 21:38:22 +0000216
217 public Set<Dependency<?>> getDependencies() {
218 return ImmutableSet.<Dependency<?>>of(Dependency.get(targetKey));
219 }
limpbizkite451ef72007-12-19 01:15:59 +0000220 });
221 }
222
223 /**
224 * Returns the exception type declared to be thrown by the get method of
225 * {@code interfaceType}.
226 */
sberlin488284d2010-11-17 14:27:41 +0000227 private List<Class<? extends Throwable>> getExceptionType(Class<P> interfaceType) {
228 try {
229 Method getMethod = interfaceType.getMethod("get");
230 List<TypeLiteral<?>> exceptionLiterals =
231 TypeLiteral.get(interfaceType).getExceptionTypes(getMethod);
232 List<Class<? extends Throwable>> results = Lists.newArrayList();
233 for (TypeLiteral<?> exLiteral : exceptionLiterals) {
234 results.add(exLiteral.getRawType().asSubclass(Throwable.class));
235 }
236 return results;
237 } catch (SecurityException e) {
238 throw new IllegalStateException("Not allowed to inspect exception types", e);
239 } catch (NoSuchMethodException e) {
240 throw new IllegalStateException("No 'get'method available", e);
241 }
limpbizkite451ef72007-12-19 01:15:59 +0000242 }
243
sberlinee446ca2010-10-24 21:36:39 +0000244 private boolean checkInterface() {
245 if(!checkArgument(interfaceType.isInterface(),
246 "%s must be an interface", interfaceType.getName())) {
247 return false;
248 }
249 if(!checkArgument(interfaceType.getGenericInterfaces().length == 1,
sberlin488284d2010-11-17 14:27:41 +0000250 "%s must extend CheckedProvider (and only CheckedProvider)",
sberlinee446ca2010-10-24 21:36:39 +0000251 interfaceType)) {
252 return false;
253 }
sberlin488284d2010-11-17 14:27:41 +0000254
255 boolean tpMode = interfaceType.getInterfaces()[0] == ThrowingProvider.class;
256 if(!tpMode) {
257 if(!checkArgument(interfaceType.getInterfaces()[0] == CheckedProvider.class,
258 "%s must extend CheckedProvider (and only CheckedProvider)",
259 interfaceType)) {
260 return false;
261 }
sberlinee446ca2010-10-24 21:36:39 +0000262 }
limpbizkite451ef72007-12-19 01:15:59 +0000263
264 // Ensure that T is parameterized and unconstrained.
265 ParameterizedType genericThrowingProvider
266 = (ParameterizedType) interfaceType.getGenericInterfaces()[0];
267 if (interfaceType.getTypeParameters().length == 1) {
limpbizkite451ef72007-12-19 01:15:59 +0000268 String returnTypeName = interfaceType.getTypeParameters()[0].getName();
269 Type returnType = genericThrowingProvider.getActualTypeArguments()[0];
sberlinee446ca2010-10-24 21:36:39 +0000270 if(!checkArgument(returnType instanceof TypeVariable,
sberlin488284d2010-11-17 14:27:41 +0000271 "%s does not properly extend CheckedProvider, the first type parameter of CheckedProvider (%s) is not a generic type",
sberlinee446ca2010-10-24 21:36:39 +0000272 interfaceType, returnType)) {
273 return false;
274 }
275 if(!checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()),
sberlin488284d2010-11-17 14:27:41 +0000276 "The generic type (%s) of %s does not match the generic type of CheckedProvider (%s)",
sberlinee446ca2010-10-24 21:36:39 +0000277 returnTypeName, interfaceType, ((TypeVariable)returnType).getName())) {
278 return false;
279 }
limpbizkite451ef72007-12-19 01:15:59 +0000280 } else {
sberlinee446ca2010-10-24 21:36:39 +0000281 if(!checkArgument(interfaceType.getTypeParameters().length == 0,
282 "%s has more than one generic type parameter: %s",
283 interfaceType, Arrays.asList(interfaceType.getTypeParameters()))) {
284 return false;
285 }
286 if(!checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType),
287 "%s expects the value type to be %s, but it was %s",
288 interfaceType, genericThrowingProvider.getActualTypeArguments()[0], valueType)) {
289 return false;
290 }
limpbizkite451ef72007-12-19 01:15:59 +0000291 }
292
sberlin488284d2010-11-17 14:27:41 +0000293 if(tpMode) { // only validate exception in ThrowingProvider mode.
294 Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1];
295 if(!checkArgument(exceptionType instanceof Class,
296 "%s has the wrong Exception generic type (%s) when extending CheckedProvider",
297 interfaceType, exceptionType)) {
298 return false;
299 }
sberlinee446ca2010-10-24 21:36:39 +0000300 }
limpbizkite451ef72007-12-19 01:15:59 +0000301
302 if (interfaceType.getDeclaredMethods().length == 1) {
303 Method method = interfaceType.getDeclaredMethods()[0];
sberlinee446ca2010-10-24 21:36:39 +0000304 if(!checkArgument(method.getName().equals("get"),
305 "%s may not declare any new methods, but declared %s",
306 interfaceType, method)) {
307 return false;
308 }
309 if(!checkArgument(method.getParameterTypes().length == 0,
310 "%s may not declare any new methods, but declared %s",
311 interfaceType, method.toGenericString())) {
312 return false;
313 }
limpbizkite451ef72007-12-19 01:15:59 +0000314 } else {
sberlinee446ca2010-10-24 21:36:39 +0000315 if(!checkArgument(interfaceType.getDeclaredMethods().length == 0,
316 "%s may not declare any new methods, but declared %s",
317 interfaceType, Arrays.asList(interfaceType.getDeclaredMethods()))) {
318 return false;
319 }
limpbizkite451ef72007-12-19 01:15:59 +0000320 }
sberlinee446ca2010-10-24 21:36:39 +0000321
322 return true;
limpbizkite451ef72007-12-19 01:15:59 +0000323 }
324
sberlinee446ca2010-10-24 21:36:39 +0000325 private boolean checkArgument(boolean condition,
limpbizkite451ef72007-12-19 01:15:59 +0000326 String messageFormat, Object... args) {
327 if (!condition) {
sberlinee446ca2010-10-24 21:36:39 +0000328 binder.addError(messageFormat, args);
329 return false;
330 } else {
331 return true;
limpbizkite451ef72007-12-19 01:15:59 +0000332 }
333 }
334
335 @SuppressWarnings({"unchecked"})
336 private Key<P> createKey() {
337 TypeLiteral<P> typeLiteral;
338 if (interfaceType.getTypeParameters().length == 1) {
limpbizkit49f67c02008-06-10 20:56:17 +0000339 ParameterizedType type = Types.newParameterizedTypeWithOwner(
340 interfaceType.getEnclosingClass(), interfaceType, valueType);
341 typeLiteral = (TypeLiteral<P>) TypeLiteral.get(type);
limpbizkite451ef72007-12-19 01:15:59 +0000342 } else {
343 typeLiteral = TypeLiteral.get(interfaceType);
344 }
345
346 if (annotation != null) {
347 return Key.get(typeLiteral, annotation);
348
349 } else if (annotationType != null) {
350 return Key.get(typeLiteral, annotationType);
351
352 } else {
353 return Key.get(typeLiteral);
354 }
355 }
356 }
357
358 /**
limpbizkite451ef72007-12-19 01:15:59 +0000359 * Represents the returned value from a call to {@link
sberlin488284d2010-11-17 14:27:41 +0000360 * CheckedProvider#get()}. This is the value that will be scoped by Guice.
limpbizkite451ef72007-12-19 01:15:59 +0000361 */
sberlin488284d2010-11-17 14:27:41 +0000362 static class Result implements Serializable {
limpbizkite451ef72007-12-19 01:15:59 +0000363 private final Object value;
364 private final Exception exception;
365
366 private Result(Object value, Exception exception) {
367 this.value = value;
368 this.exception = exception;
369 }
370
371 public static Result forValue(Object value) {
372 return new Result(value, null);
373 }
374
375 public static Result forException(Exception e) {
376 return new Result(null, e);
377 }
378
379 public Object getOrThrow() throws Exception {
380 if (exception != null) {
381 throw exception;
382 } else {
383 return value;
384 }
385 }
sberlin488284d2010-11-17 14:27:41 +0000386
sberlin@gmail.comba8a4cd2010-11-27 01:56:53 +0000387 private static final long serialVersionUID = 0L;
limpbizkite451ef72007-12-19 01:15:59 +0000388 }
389}