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