1/**
2 * Copyright (C) 2009 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.internal;
18
19import static com.google.inject.internal.BytecodeGen.newFastClass;
20
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.ImmutableMap;
23import com.google.common.collect.ImmutableSet;
24import com.google.common.collect.Lists;
25import com.google.common.collect.Maps;
26import com.google.inject.spi.InjectionPoint;
27
28import net.sf.cglib.core.MethodWrapper;
29import net.sf.cglib.proxy.Callback;
30import net.sf.cglib.proxy.CallbackFilter;
31import net.sf.cglib.proxy.Enhancer;
32import net.sf.cglib.reflect.FastClass;
33import net.sf.cglib.reflect.FastConstructor;
34
35import org.aopalliance.intercept.MethodInterceptor;
36
37import java.lang.reflect.Constructor;
38import java.lang.reflect.InvocationTargetException;
39import java.lang.reflect.Method;
40import java.util.List;
41import java.util.Map;
42import java.util.logging.Level;
43import java.util.logging.Logger;
44
45/**
46 * Builds a construction proxy that can participate in AOP. This class manages applying type and
47 * method matchers to come up with the set of intercepted methods.
48 *
49 * @author jessewilson@google.com (Jesse Wilson)
50 */
51final class ProxyFactory<T> implements ConstructionProxyFactory<T> {
52
53  private static final Logger logger = Logger.getLogger(ProxyFactory.class.getName());
54
55  private final InjectionPoint injectionPoint;
56  private final ImmutableMap<Method, List<MethodInterceptor>> interceptors;
57  private final Class<T> declaringClass;
58  private final List<Method> methods;
59  private final Callback[] callbacks;
60
61  /**
62   * PUBLIC is default; it's used if all the methods we're intercepting are public. This impacts
63   * which classloader we should use for loading the enhanced class
64   */
65  private BytecodeGen.Visibility visibility = BytecodeGen.Visibility.PUBLIC;
66
67  ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects) {
68    this.injectionPoint = injectionPoint;
69
70    @SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T>
71        Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
72    declaringClass = constructor.getDeclaringClass();
73
74    // Find applicable aspects. Bow out if none are applicable to this class.
75    List<MethodAspect> applicableAspects = Lists.newArrayList();
76    for (MethodAspect methodAspect : methodAspects) {
77      if (methodAspect.matches(declaringClass)) {
78        applicableAspects.add(methodAspect);
79      }
80    }
81
82    if (applicableAspects.isEmpty()) {
83      interceptors = ImmutableMap.of();
84      methods = ImmutableList.of();
85      callbacks = null;
86      return;
87    }
88
89    // Get list of methods from cglib.
90    methods = Lists.newArrayList();
91    Enhancer.getMethods(declaringClass, null, methods);
92
93    // Create method/interceptor holders and record indices.
94    List<MethodInterceptorsPair> methodInterceptorsPairs = Lists.newArrayList();
95    for (Method method : methods) {
96      methodInterceptorsPairs.add(new MethodInterceptorsPair(method));
97    }
98
99    // Iterate over aspects and add interceptors for the methods they apply to
100    boolean anyMatched = false;
101    for (MethodAspect methodAspect : applicableAspects) {
102      for (MethodInterceptorsPair pair : methodInterceptorsPairs) {
103        if (methodAspect.matches(pair.method)) {
104          if(pair.method.isSynthetic()) {
105            logger.log(Level.WARNING,
106                "Method [{0}] is synthetic and is being intercepted by {1}."
107              + " This could indicate a bug.  The method may be intercepted twice,"
108              + " or may not be intercepted at all.",
109                new Object[] { pair.method, methodAspect.interceptors() });
110          }
111          visibility = visibility.and(BytecodeGen.Visibility.forMember(pair.method));
112          pair.addAll(methodAspect.interceptors());
113          anyMatched = true;
114        }
115      }
116    }
117
118    if (!anyMatched) {
119      interceptors = ImmutableMap.of();
120      callbacks = null;
121      return;
122    }
123
124    ImmutableMap.Builder<Method, List<MethodInterceptor>> interceptorsMapBuilder = null; // lazy
125
126    callbacks = new Callback[methods.size()];
127    for (int i = 0; i < methods.size(); i++) {
128      MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
129
130      if (!pair.hasInterceptors()) {
131        callbacks[i] = net.sf.cglib.proxy.NoOp.INSTANCE;
132        continue;
133      }
134
135      if (interceptorsMapBuilder == null) {
136        interceptorsMapBuilder = ImmutableMap.builder();
137      }
138
139      ImmutableList<MethodInterceptor> deDuplicated =
140          ImmutableSet.copyOf(pair.interceptors).asList();
141      interceptorsMapBuilder.put(pair.method, deDuplicated);
142      callbacks[i] = new InterceptorStackCallback(pair.method, deDuplicated);
143    }
144
145    interceptors = interceptorsMapBuilder != null
146        ? interceptorsMapBuilder.build()
147        : ImmutableMap.<Method, List<MethodInterceptor>>of();
148  }
149
150  /**
151   * Returns the interceptors that apply to the constructed type.
152   */
153  public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() {
154    return interceptors;
155  }
156
157  public ConstructionProxy<T> create() throws ErrorsException {
158    if (interceptors.isEmpty()) {
159      return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
160    }
161
162    @SuppressWarnings("unchecked")
163    Class<? extends Callback>[] callbackTypes = new Class[callbacks.length];
164    for (int i = 0; i < callbacks.length; i++) {
165      if (callbacks[i] == net.sf.cglib.proxy.NoOp.INSTANCE) {
166        callbackTypes[i] = net.sf.cglib.proxy.NoOp.class;
167      } else {
168        callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
169      }
170    }
171
172    // Create the proxied class. We're careful to ensure that all enhancer state is not-specific
173    // to this injector. Otherwise, the proxies for each injector will waste PermGen memory
174    try {
175    Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
176    enhancer.setCallbackFilter(new IndicesCallbackFilter(methods));
177    enhancer.setCallbackTypes(callbackTypes);
178    return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
179    } catch (Throwable e) {
180      throw new Errors().errorEnhancingClass(declaringClass, e).toException();
181    }
182  }
183
184  private static class MethodInterceptorsPair {
185    final Method method;
186    List<MethodInterceptor> interceptors; // lazy
187
188    MethodInterceptorsPair(Method method) {
189      this.method = method;
190    }
191
192    void addAll(List<MethodInterceptor> interceptors) {
193      if (this.interceptors == null) {
194        this.interceptors = Lists.newArrayList();
195      }
196      this.interceptors.addAll(interceptors);
197    }
198
199    boolean hasInterceptors() {
200      return interceptors != null;
201    }
202  }
203
204  /**
205   * A callback filter that maps methods to unique IDs. We define equals and
206   * hashCode without using any state related to the injector so that enhanced
207   * classes intercepting the same methods can be shared between injectors (and
208   * child injectors, etc).
209   */
210  private static class IndicesCallbackFilter implements CallbackFilter {
211    final Map<Object, Integer> indices;
212    final int hashCode;
213
214    IndicesCallbackFilter(List<Method> methods) {
215      final Map<Object, Integer> indices = Maps.newHashMap();
216      for (int i = 0; i < methods.size(); i++) {
217        indices.put(MethodWrapper.create(methods.get(i)), i);
218      }
219      this.indices = indices;
220      this.hashCode = indices.hashCode();
221    }
222
223    public int accept(Method method) {
224      return indices.get(MethodWrapper.create(method));
225    }
226
227    @Override public boolean equals(Object o) {
228      return o instanceof IndicesCallbackFilter &&
229          ((IndicesCallbackFilter) o).indices.equals(indices);
230    }
231
232    @Override public int hashCode() {
233      return hashCode;
234    }
235  }
236
237  /**
238   * Constructs instances that participate in AOP.
239   */
240  private static class ProxyConstructor<T> implements ConstructionProxy<T> {
241    final Class<?> enhanced;
242    final InjectionPoint injectionPoint;
243    final Constructor<T> constructor;
244    final Callback[] callbacks;
245
246    final FastConstructor fastConstructor;
247    final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors;
248
249    @SuppressWarnings("unchecked") // the constructor promises to construct 'T's
250    ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks,
251        ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) {
252      this.enhanced = enhancer.createClass(); // this returns a cached class if possible
253      this.injectionPoint = injectionPoint;
254      this.constructor = (Constructor<T>) injectionPoint.getMember();
255      this.callbacks = callbacks;
256      this.methodInterceptors = methodInterceptors;
257
258      FastClass fastClass = newFastClass(enhanced, BytecodeGen.Visibility.forMember(constructor));
259      this.fastConstructor = fastClass.getConstructor(constructor.getParameterTypes());
260    }
261
262    @SuppressWarnings("unchecked") // the constructor promises to produce 'T's
263    public T newInstance(Object... arguments) throws InvocationTargetException {
264      Enhancer.registerCallbacks(enhanced, callbacks);
265      try {
266        return (T) fastConstructor.newInstance(arguments);
267      } finally {
268        Enhancer.registerCallbacks(enhanced, null);
269      }
270    }
271
272    public InjectionPoint getInjectionPoint() {
273      return injectionPoint;
274    }
275
276    public Constructor<T> getConstructor() {
277      return constructor;
278    }
279
280    public ImmutableMap<Method, List<MethodInterceptor>> getMethodInterceptors() {
281      return methodInterceptors;
282    }
283  }
284}
285