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