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