Home | History | Annotate | Download | only in throwingproviders
      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