FactoryProvider2.java revision 53664a7f17492bd0c3c4728df61679147907dd18
1/** 2 * Copyright (C) 2008 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.assistedinject; 18 19import com.google.inject.AbstractModule; 20import com.google.inject.Binder; 21import com.google.inject.Binding; 22import com.google.inject.ConfigurationException; 23import com.google.inject.Inject; 24import com.google.inject.Injector; 25import com.google.inject.Key; 26import com.google.inject.Module; 27import com.google.inject.Provider; 28import com.google.inject.ProvisionException; 29import com.google.inject.TypeLiteral; 30import static com.google.inject.internal.Annotations.getKey; 31import com.google.inject.internal.Errors; 32import com.google.inject.internal.ErrorsException; 33import com.google.inject.internal.ImmutableList; 34import com.google.inject.internal.ImmutableMap; 35import static com.google.inject.internal.Iterables.getOnlyElement; 36import com.google.inject.internal.Lists; 37import static com.google.inject.internal.Preconditions.checkState; 38import com.google.inject.spi.Message; 39import com.google.inject.util.Providers; 40import java.lang.annotation.Annotation; 41import java.lang.reflect.InvocationHandler; 42import java.lang.reflect.Method; 43import java.lang.reflect.Proxy; 44import java.lang.reflect.Type; 45import java.util.Arrays; 46import java.util.List; 47 48/** 49 * The newer implementation of factory provider. This implementation uses a child injector to 50 * create values. 51 * 52 * @author jessewilson@google.com (Jesse Wilson) 53 * @author dtm@google.com (Daniel Martin) 54 */ 55final class FactoryProvider2<F> implements InvocationHandler, Provider<F> { 56 57 /** if a factory method parameter isn't annotated, it gets this annotation. */ 58 static final Assisted DEFAULT_ANNOTATION = new Assisted() { 59 public String value() { 60 return ""; 61 } 62 63 public Class<? extends Annotation> annotationType() { 64 return Assisted.class; 65 } 66 67 @Override public boolean equals(Object o) { 68 return o instanceof Assisted 69 && ((Assisted) o).value().equals(""); 70 } 71 72 @Override public int hashCode() { 73 return 127 * "value".hashCode() ^ "".hashCode(); 74 } 75 76 @Override public String toString() { 77 return "@" + Assisted.class.getName() + "(value=)"; 78 } 79 }; 80 81 /** the produced type, or null if all methods return concrete types */ 82 private final Key<?> producedType; 83 private final ImmutableMap<Method, Key<?>> returnTypesByMethod; 84 private final ImmutableMap<Method, ImmutableList<Key<?>>> paramTypes; 85 86 /** the hosting injector, or null if we haven't been initialized yet */ 87 private Injector injector; 88 89 /** the factory interface, implemented and provided */ 90 private final F factory; 91 92 /** 93 * @param factoryType a Java interface that defines one or more create methods. 94 * @param producedType a concrete type that is assignable to the return types of all factory 95 * methods. 96 */ 97 FactoryProvider2(Class<F> factoryType, Key<?> producedType) { 98 this.producedType = producedType; 99 100 Errors errors = new Errors(); 101 try { 102 ImmutableMap.Builder<Method, Key<?>> returnTypesBuilder = ImmutableMap.builder(); 103 ImmutableMap.Builder<Method, ImmutableList<Key<?>>> paramTypesBuilder 104 = ImmutableMap.builder(); 105 // TODO: also grab methods from superinterfaces 106 for (Method method : factoryType.getMethods()) { 107 Key<?> returnType = getKey(TypeLiteral.get(method.getGenericReturnType()), 108 method, method.getAnnotations(), errors); 109 returnTypesBuilder.put(method, returnType); 110 Type[] params = method.getGenericParameterTypes(); 111 Annotation[][] paramAnnotations = method.getParameterAnnotations(); 112 int p = 0; 113 List<Key<?>> keys = Lists.newArrayList(); 114 for (Type param : params) { 115 Key<?> paramKey = getKey(TypeLiteral.get(param), method, paramAnnotations[p++], errors); 116 keys.add(assistKey(method, paramKey, errors)); 117 } 118 paramTypesBuilder.put(method, ImmutableList.copyOf(keys)); 119 } 120 returnTypesByMethod = returnTypesBuilder.build(); 121 paramTypes = paramTypesBuilder.build(); 122 } catch (ErrorsException e) { 123 throw new ConfigurationException(e.getErrors().getMessages()); 124 } 125 126 factory = factoryType.cast(Proxy.newProxyInstance(factoryType.getClassLoader(), 127 new Class[] { factoryType }, this)); 128 } 129 130 public F get() { 131 return factory; 132 } 133 134 /** 135 * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation. 136 * This fails if another binding annotation is clobbered in the process. If the key already has 137 * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value. 138 */ 139 private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException { 140 if (key.getAnnotationType() == null) { 141 return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION); 142 } else if (key.getAnnotationType() == Assisted.class) { 143 return key; 144 } else { 145 errors.withSource(method).addMessage( 146 "Only @Assisted is allowed for factory parameters, but found @%s", 147 key.getAnnotationType()); 148 throw errors.toException(); 149 } 150 } 151 152 /** 153 * At injector-creation time, we initialize the invocation handler. At this time we make sure 154 * all factory methods will be able to build the target types. 155 */ 156 @Inject 157 void initialize(Injector injector) { 158 if (this.injector != null) { 159 throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class, 160 "Factories.create() factories may only be used in one Injector!"))); 161 } 162 163 this.injector = injector; 164 165 for (Method method : returnTypesByMethod.keySet()) { 166 Object[] args = new Object[method.getParameterTypes().length]; 167 Arrays.fill(args, "dummy object for validating Factories"); 168 getBindingFromNewInjector(method, args); // throws if the binding isn't properly configured 169 } 170 } 171 172 /** 173 * Creates a child injector that binds the args, and returns the binding for the method's result. 174 */ 175 public Binding<?> getBindingFromNewInjector(final Method method, final Object[] args) { 176 checkState(injector != null, 177 "Factories.create() factories cannot be used until they're initialized by Guice."); 178 179 final Key<?> returnType = returnTypesByMethod.get(method); 180 181 Module assistedModule = new AbstractModule() { 182 @SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value 183 protected void configure() { 184 Binder binder = binder().withSource(method); 185 186 int p = 0; 187 for (Key<?> paramKey : paramTypes.get(method)) { 188 // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter 189 binder.bind((Key) paramKey).toProvider(Providers.of(args[p++])); 190 } 191 192 if (producedType != null && !returnType.equals(producedType)) { 193 binder.bind(returnType).to((Key) producedType); 194 } else { 195 binder.bind(returnType); 196 } 197 } 198 }; 199 200 Injector forCreate = injector.createChildInjector(assistedModule); 201 return forCreate.getBinding(returnType); 202 } 203 204 /** 205 * When a factory method is invoked, we create a child injector that binds all parameters, then 206 * use that to get an instance of the return type. 207 */ 208 public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { 209 if (method.getDeclaringClass() == Object.class) { 210 return method.invoke(this, args); 211 } 212 213 Provider<?> provider = getBindingFromNewInjector(method, args).getProvider(); 214 try { 215 return provider.get(); 216 } catch (ProvisionException e) { 217 // if this is an exception declared by the factory method, throw it as-is 218 if (e.getErrorMessages().size() == 1) { 219 Message onlyError = getOnlyElement(e.getErrorMessages()); 220 Throwable cause = onlyError.getCause(); 221 if (cause != null && canRethrow(method, cause)) { 222 throw cause; 223 } 224 } 225 throw e; 226 } 227 } 228 229 @Override public String toString() { 230 return factory.getClass().getInterfaces()[0].getName() 231 + " for " + producedType.getTypeLiteral(); 232 } 233 234 @Override public boolean equals(Object o) { 235 return o == this || o == factory; 236 } 237 238 /** Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping. */ 239 static boolean canRethrow(Method invoked, Throwable thrown) { 240 if (thrown instanceof Error || thrown instanceof RuntimeException) { 241 return true; 242 } 243 244 for (Class<?> declared : invoked.getExceptionTypes()) { 245 if (declared.isInstance(thrown)) { 246 return true; 247 } 248 } 249 250 return false; 251 } 252} 253