Elements.java revision bf0d876bb767f45c5cfbed3929e2cf6acd7d061a
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.spi;
18
19import static com.google.common.base.Preconditions.checkArgument;
20import static com.google.common.base.Preconditions.checkState;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.Lists;
23import com.google.common.collect.Sets;
24import com.google.inject.AbstractModule;
25import com.google.inject.Binder;
26import com.google.inject.Binding;
27import com.google.inject.Key;
28import com.google.inject.Module;
29import com.google.inject.PrivateBinder;
30import com.google.inject.PrivateModule;
31import com.google.inject.Provider;
32import com.google.inject.Scope;
33import com.google.inject.Stage;
34import com.google.inject.TypeLiteral;
35import com.google.inject.binder.AnnotatedBindingBuilder;
36import com.google.inject.binder.AnnotatedConstantBindingBuilder;
37import com.google.inject.binder.AnnotatedElementBuilder;
38import com.google.inject.internal.AbstractBindingBuilder;
39import com.google.inject.internal.BindingBuilder;
40import com.google.inject.internal.ConstantBindingBuilderImpl;
41import com.google.inject.internal.Errors;
42import com.google.inject.internal.PrivateElementsImpl;
43import com.google.inject.internal.ProviderMethodsModule;
44import com.google.inject.internal.SourceProvider;
45import com.google.inject.matcher.Matcher;
46import java.lang.annotation.Annotation;
47import java.lang.reflect.Method;
48import java.util.Arrays;
49import java.util.Collection;
50import java.util.Collections;
51import java.util.List;
52import java.util.Set;
53import org.aopalliance.intercept.MethodInterceptor;
54
55/**
56 * Exposes elements of a module so they can be inspected, validated or {@link ModuleWriter
57 * rewritten}.
58 *
59 * @author jessewilson@google.com (Jesse Wilson)
60 * @since 2.0
61 */
62public final class Elements {
63  private static final BindingTargetVisitor<Object, Object> GET_INSTANCE_VISITOR
64      = new DefaultBindingTargetVisitor<Object, Object>() {
65    @Override public Object visitInstance(InstanceBinding<?> binding) {
66      return binding.getInstance();
67    }
68
69    @Override protected Object visitOther(Binding<?> binding) {
70      throw new IllegalArgumentException();
71    }
72  };
73
74  /**
75   * Records the elements executed by {@code modules}.
76   */
77  public static List<Element> getElements(Module... modules) {
78    return getElements(Stage.DEVELOPMENT, Arrays.asList(modules));
79  }
80
81  /**
82   * Records the elements executed by {@code modules}.
83   */
84  public static List<Element> getElements(Stage stage, Module... modules) {
85    return getElements(stage, Arrays.asList(modules));
86  }
87
88  /**
89   * Records the elements executed by {@code modules}.
90   */
91  public static List<Element> getElements(Iterable<? extends Module> modules) {
92    return getElements(Stage.DEVELOPMENT, modules);
93  }
94
95  /**
96   * Records the elements executed by {@code modules}.
97   */
98  public static List<Element> getElements(Stage stage, Iterable<? extends Module> modules) {
99    RecordingBinder binder = new RecordingBinder(stage);
100    for (Module module : modules) {
101      binder.install(module);
102    }
103    return Collections.unmodifiableList(binder.elements);
104  }
105
106  @SuppressWarnings("unchecked")
107  static <T> BindingTargetVisitor<T, T> getInstanceVisitor() {
108    return (BindingTargetVisitor<T, T>) GET_INSTANCE_VISITOR;
109  }
110
111  private static class RecordingBinder implements Binder, PrivateBinder {
112    private final Stage stage;
113    private final Set<Module> modules;
114    private final List<Element> elements;
115    private final Object source;
116    private final SourceProvider sourceProvider;
117
118    /** The binder where exposed bindings will be created */
119    private final RecordingBinder parent;
120    private final PrivateElementsImpl privateElements;
121
122    private RecordingBinder(Stage stage) {
123      this.stage = stage;
124      this.modules = Sets.newHashSet();
125      this.elements = Lists.newArrayList();
126      this.source = null;
127      this.sourceProvider = new SourceProvider().plusSkippedClasses(
128          Elements.class, RecordingBinder.class, AbstractModule.class,
129          ConstantBindingBuilderImpl.class, AbstractBindingBuilder.class, BindingBuilder.class);
130      this.parent = null;
131      this.privateElements = null;
132    }
133
134    /** Creates a recording binder that's backed by {@code prototype}. */
135    private RecordingBinder(
136        RecordingBinder prototype, Object source, SourceProvider sourceProvider) {
137      checkArgument(source == null ^ sourceProvider == null);
138
139      this.stage = prototype.stage;
140      this.modules = prototype.modules;
141      this.elements = prototype.elements;
142      this.source = source;
143      this.sourceProvider = sourceProvider;
144      this.parent = prototype.parent;
145      this.privateElements = prototype.privateElements;
146    }
147
148    /** Creates a private recording binder. */
149    private RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements) {
150      this.stage = parent.stage;
151      this.modules = Sets.newHashSet();
152      this.elements = privateElements.getElementsMutable();
153      this.source = parent.source;
154      this.sourceProvider = parent.sourceProvider;
155      this.parent = parent;
156      this.privateElements = privateElements;
157    }
158
159    /*if[AOP]*/
160    public void bindInterceptor(
161        Matcher<? super Class<?>> classMatcher,
162        Matcher<? super Method> methodMatcher,
163        MethodInterceptor... interceptors) {
164      elements.add(new InterceptorBinding(getSource(), classMatcher, methodMatcher, interceptors));
165    }
166    /*end[AOP]*/
167
168    public void bindScope(Class<? extends Annotation> annotationType, Scope scope) {
169      elements.add(new ScopeBinding(getSource(), annotationType, scope));
170    }
171
172    public void requestInjection(Object... instances) {
173      for (Object instance : instances) {
174        elements.add(new InjectionRequest(getSource(), instance));
175      }
176    }
177
178    public void requestStaticInjection(Class<?>... types) {
179      for (Class<?> type : types) {
180        elements.add(new StaticInjectionRequest(getSource(), type));
181      }
182    }
183
184    public void install(Module module) {
185      if (modules.add(module)) {
186        Binder binder = this;
187        if (module instanceof PrivateModule) {
188          binder = binder.newPrivateBinder();
189        }
190
191        try {
192          module.configure(binder);
193        } catch (RuntimeException e) {
194          Collection<Message> messages = Errors.getMessagesFromThrowable(e);
195          if (!messages.isEmpty()) {
196            elements.addAll(messages);
197          } else {
198            addError(e);
199          }
200        }
201        binder.install(ProviderMethodsModule.forModule(module));
202      }
203    }
204
205    public Stage currentStage() {
206      return stage;
207    }
208
209    public void addError(String message, Object... arguments) {
210      elements.add(new Message(getSource(), Errors.format(message, arguments)));
211    }
212
213    public void addError(Throwable t) {
214      String message = "An exception was caught and reported. Message: " + t.getMessage();
215      elements.add(new Message(ImmutableList.of(getSource()), message, t));
216    }
217
218    public void addError(Message message) {
219      elements.add(message);
220    }
221
222    public <T> AnnotatedBindingBuilder<T> bind(Key<T> key) {
223      return new BindingBuilder<T>(this, elements, getSource(), key);
224    }
225
226    public <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
227      return bind(Key.get(typeLiteral));
228    }
229
230    public <T> AnnotatedBindingBuilder<T> bind(Class<T> type) {
231      return bind(Key.get(type));
232    }
233
234    public AnnotatedConstantBindingBuilder bindConstant() {
235      return new ConstantBindingBuilderImpl<Void>(this, elements, getSource());
236    }
237
238    public <T> Provider<T> getProvider(final Key<T> key) {
239      final ProviderLookup<T> command = new ProviderLookup<T>(getSource(), key);
240      elements.add(command);
241      return new Provider<T>() {
242        public T get() {
243          Provider<T> delegate = command.getDelegate();
244          checkState(delegate != null,
245              "This provider cannot be used until the Injector has been created.");
246          return delegate.get();
247        }
248
249        @Override public String toString() {
250          return "Provider<" + key.getTypeLiteral() + ">";
251        }
252      };
253    }
254
255    public <T> Provider<T> getProvider(Class<T> type) {
256      return getProvider(Key.get(type));
257    }
258
259    public void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
260        TypeConverter converter) {
261      elements.add(new TypeConverterBinding(getSource(), typeMatcher, converter));
262    }
263
264    public RecordingBinder withSource(final Object source) {
265      return new RecordingBinder(this, source, null);
266    }
267
268    public RecordingBinder skipSources(Class... classesToSkip) {
269      // if a source is specified explicitly, we don't need to skip sources
270      if (source != null) {
271        return this;
272      }
273
274      SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip);
275      return new RecordingBinder(this, null, newSourceProvider);
276    }
277
278    public PrivateBinder newPrivateBinder() {
279      PrivateElementsImpl privateElements = new PrivateElementsImpl(getSource());
280      elements.add(privateElements);
281      return new RecordingBinder(this, privateElements);
282    }
283
284    public void expose(Key<?> key) {
285      exposeInternal(key);
286    }
287
288    public AnnotatedElementBuilder expose(Class<?> type) {
289      return exposeInternal(Key.get(type));
290    }
291
292    public AnnotatedElementBuilder expose(TypeLiteral<?> type) {
293      return exposeInternal(Key.get(type));
294    }
295
296    private <T> AnnotatedElementBuilder exposeInternal(Key<T> key) {
297      if (privateElements == null) {
298        addError("Cannot expose %s on a standard binder. "
299            + "Exposed bindings are only applicable to private binders.", key);
300        return new AnnotatedElementBuilder() {
301          public void annotatedWith(Class<? extends Annotation> annotationType) {}
302          public void annotatedWith(Annotation annotation) {}
303        };
304      }
305
306      BindingBuilder<T> exposeBinding = new BindingBuilder<T>(
307          this, parent.elements, getSource(), key);
308
309      BindingBuilder.ExposureBuilder<T> builder = exposeBinding.usingKeyFrom(privateElements);
310      privateElements.addExposureBuilder(builder);
311      return builder;
312    }
313
314    protected Object getSource() {
315      return sourceProvider != null
316          ? sourceProvider.get()
317          : source;
318    }
319
320    @Override public String toString() {
321      return "Binder";
322    }
323  }
324}
325