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