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