1 /** 2 * Copyright (C) 2010 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 17 package com.google.inject.throwingproviders; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.collect.ImmutableSet; 22 import com.google.common.collect.Lists; 23 import com.google.inject.Binder; 24 import com.google.inject.Key; 25 import com.google.inject.Module; 26 import com.google.inject.Provider; 27 import com.google.inject.TypeLiteral; 28 import com.google.inject.internal.Annotations; 29 import com.google.inject.internal.Errors; 30 import com.google.inject.internal.UniqueAnnotations; 31 import com.google.inject.spi.Dependency; 32 import com.google.inject.spi.Message; 33 import com.google.inject.util.Modules; 34 35 import java.lang.annotation.Annotation; 36 import java.lang.reflect.Member; 37 import java.lang.reflect.Method; 38 import java.util.List; 39 import java.util.logging.Logger; 40 41 /** 42 * Creates bindings to methods annotated with {@literal @}{@link CheckedProvides}. Use the scope 43 * and binding annotations on the provider method to configure the binding. 44 * 45 * @author sameb (at) google.com (Sam Berlin) 46 */ 47 final class CheckedProviderMethodsModule implements Module { 48 private static final Key<Logger> LOGGER_KEY = Key.get(Logger.class); 49 50 private final Object delegate; 51 private final TypeLiteral<?> typeLiteral; 52 53 private CheckedProviderMethodsModule(Object delegate) { 54 this.delegate = checkNotNull(delegate, "delegate"); 55 this.typeLiteral = TypeLiteral.get(this.delegate.getClass()); 56 } 57 58 /** 59 * Returns a module which creates bindings for provider methods from the given module. 60 */ 61 static Module forModule(Module module) { 62 // avoid infinite recursion, since installing a module always installs itself 63 if (module instanceof CheckedProviderMethodsModule) { 64 return Modules.EMPTY_MODULE; 65 } 66 67 return new CheckedProviderMethodsModule(module); 68 } 69 70 public synchronized void configure(Binder binder) { 71 for (CheckedProviderMethod<?> throwingProviderMethod : getProviderMethods(binder)) { 72 throwingProviderMethod.configure(binder); 73 } 74 } 75 76 List<CheckedProviderMethod<?>> getProviderMethods(Binder binder) { 77 List<CheckedProviderMethod<?>> result = Lists.newArrayList(); 78 for (Class<?> c = delegate.getClass(); c != Object.class; c = c.getSuperclass()) { 79 for (Method method : c.getDeclaredMethods()) { 80 CheckedProvides checkedProvides = method.getAnnotation(CheckedProvides.class); 81 if(checkedProvides != null) { 82 result.add(createProviderMethod(binder, method, checkedProvides)); 83 } 84 } 85 } 86 return result; 87 } 88 89 <T> CheckedProviderMethod<T> createProviderMethod(Binder binder, final Method method, 90 CheckedProvides checkedProvides) { 91 @SuppressWarnings("rawtypes") 92 Class<? extends CheckedProvider> throwingProvider = checkedProvides.value(); 93 binder = binder.withSource(method); 94 Errors errors = new Errors(method); 95 96 // prepare the parameter providers 97 List<Dependency<?>> dependencies = Lists.newArrayList(); 98 List<Provider<?>> parameterProviders = Lists.newArrayList(); 99 List<TypeLiteral<?>> parameterTypes = typeLiteral.getParameterTypes(method); 100 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 101 for (int i = 0; i < parameterTypes.size(); i++) { 102 Key<?> key = getKey(errors, parameterTypes.get(i), method, parameterAnnotations[i]); 103 if (key.equals(LOGGER_KEY)) { 104 // If it was a Logger, change the key to be unique & bind it to a 105 // provider that provides a logger with a proper name. 106 // This solves issue 482 (returning a new anonymous logger on every call exhausts memory) 107 Key<Logger> loggerKey = Key.get(Logger.class, UniqueAnnotations.create()); 108 binder.bind(loggerKey).toProvider(new LogProvider(method)); 109 key = loggerKey; 110 } 111 dependencies.add(Dependency.get(key)); 112 parameterProviders.add(binder.getProvider(key)); 113 } 114 115 @SuppressWarnings("unchecked") // Define T as the method's return type. 116 TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method); 117 List<TypeLiteral<?>> exceptionTypes = typeLiteral.getExceptionTypes(method); 118 119 Key<T> key = getKey(errors, returnType, method, method.getAnnotations()); 120 Class<? extends Annotation> scopeAnnotation 121 = Annotations.findScopeAnnotation(errors, method.getAnnotations()); 122 123 for (Message message : errors.getMessages()) { 124 binder.addError(message); 125 } 126 127 return new CheckedProviderMethod<T>(key, method, delegate, ImmutableSet.copyOf(dependencies), 128 parameterProviders, scopeAnnotation, throwingProvider, exceptionTypes, 129 checkedProvides.scopeExceptions()); 130 } 131 132 <T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) { 133 Annotation bindingAnnotation = Annotations.findBindingAnnotation(errors, member, annotations); 134 return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation); 135 } 136 137 @Override public boolean equals(Object o) { 138 return o instanceof CheckedProviderMethodsModule 139 && ((CheckedProviderMethodsModule) o).delegate == delegate; 140 } 141 142 @Override public int hashCode() { 143 return delegate.hashCode(); 144 } 145 146 /** A provider that returns a logger based on the method name. */ 147 private static final class LogProvider implements Provider<Logger> { 148 private final String name; 149 150 public LogProvider(Method method) { 151 this.name = method.getDeclaringClass().getName() + "." + method.getName(); 152 } 153 154 public Logger get() { 155 return Logger.getLogger(name); 156 } 157 } 158 } 159