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