blob: bac53827f9f9bb792dc3a8cb69408712b6e1cc94 [file] [log] [blame]
crazybobleeb8cf1e52007-02-02 21:48:16 +00001/**
limpbizkit03b81a62009-03-18 05:34:39 +00002 * Copyright (C) 2009 Google Inc.
crazybobleeb8cf1e52007-02-02 21:48:16 +00003 *
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 */
crazybobleee3adfd62007-02-02 21:30:08 +000016
crazyboblee10a3b022007-02-10 01:49:38 +000017package com.google.inject;
crazybobleee3adfd62007-02-02 21:30:08 +000018
limpbizkit0de5e3e2008-12-07 08:28:31 +000019import com.google.inject.internal.BytecodeGen;
limpbizkit03b81a62009-03-18 05:34:39 +000020import static com.google.inject.internal.BytecodeGen.newFastClass;
limpbizkit53664a72009-02-21 00:25:27 +000021import com.google.inject.internal.ImmutableList;
22import com.google.inject.internal.ImmutableMap;
23import com.google.inject.internal.Lists;
limpbizkit53664a72009-02-21 00:25:27 +000024import com.google.inject.internal.Maps;
limpbizkita98bc7a2008-08-29 16:52:44 +000025import com.google.inject.spi.InjectionPoint;
limpbizkit9dc32d42008-06-15 11:29:10 +000026import java.lang.reflect.Constructor;
27import java.lang.reflect.InvocationTargetException;
limpbizkit9dc32d42008-06-15 11:29:10 +000028import java.lang.reflect.Method;
limpbizkit0de5e3e2008-12-07 08:28:31 +000029import java.util.Arrays;
limpbizkit9dc32d42008-06-15 11:29:10 +000030import java.util.List;
31import java.util.Map;
kevinb9na99dca72007-02-11 04:48:57 +000032import net.sf.cglib.proxy.Callback;
33import net.sf.cglib.proxy.CallbackFilter;
34import net.sf.cglib.proxy.Enhancer;
limpbizkit0de5e3e2008-12-07 08:28:31 +000035import net.sf.cglib.proxy.MethodProxy;
kevinb9na99dca72007-02-11 04:48:57 +000036import net.sf.cglib.reflect.FastClass;
37import net.sf.cglib.reflect.FastConstructor;
38import org.aopalliance.intercept.MethodInterceptor;
crazyboblee1b82a8f2007-02-02 23:30:42 +000039
crazybobleee3adfd62007-02-02 21:30:08 +000040/**
limpbizkit03b81a62009-03-18 05:34:39 +000041 * Builds a construction proxy that can participate in AOP. This class manages applying type and
42 * method matchers to come up with the set of intercepted methods.
crazyboblee1b82a8f2007-02-02 23:30:42 +000043 *
limpbizkit03b81a62009-03-18 05:34:39 +000044 * @author jessewilson@google.com (Jesse Wilson)
crazybobleee3adfd62007-02-02 21:30:08 +000045 */
limpbizkit03b81a62009-03-18 05:34:39 +000046class ProxyFactory<T> implements ConstructionProxyFactory<T> {
limpbizkit0de5e3e2008-12-07 08:28:31 +000047
48 private static final net.sf.cglib.proxy.MethodInterceptor NO_OP_METHOD_INTERCEPTOR
49 = new net.sf.cglib.proxy.MethodInterceptor() {
50 public Object intercept(
51 Object proxy, Method method, Object[] arguments, MethodProxy methodProxy)
52 throws Throwable {
53 return methodProxy.invokeSuper(proxy, arguments);
54 }
55 };
56
limpbizkit03b81a62009-03-18 05:34:39 +000057 private final InjectionPoint injectionPoint;
58 private final ImmutableMap<Method, List<MethodInterceptor>> interceptors;
59 private final Class<T> declaringClass;
60 private final List<Method> methods;
61 private final Callback[] callbacks;
62
limpbizkit0de5e3e2008-12-07 08:28:31 +000063 /**
limpbizkit03b81a62009-03-18 05:34:39 +000064 * PUBLIC is default; it's used if all the methods we're intercepting are public. This impacts
65 * which classloader we should use for loading the enhanced class
limpbizkit0de5e3e2008-12-07 08:28:31 +000066 */
limpbizkit03b81a62009-03-18 05:34:39 +000067 private BytecodeGen.Visibility visibility = BytecodeGen.Visibility.PUBLIC;
limpbizkit0de5e3e2008-12-07 08:28:31 +000068
limpbizkit03b81a62009-03-18 05:34:39 +000069 ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects) {
70 this.injectionPoint = injectionPoint;
limpbizkit0de5e3e2008-12-07 08:28:31 +000071
limpbizkit03b81a62009-03-18 05:34:39 +000072 @SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T>
73 Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
74 declaringClass = constructor.getDeclaringClass();
limpbizkit0de5e3e2008-12-07 08:28:31 +000075
limpbizkit03b81a62009-03-18 05:34:39 +000076 // Find applicable aspects. Bow out if none are applicable to this class.
77 List<MethodAspect> applicableAspects = Lists.newArrayList();
78 for (MethodAspect methodAspect : methodAspects) {
79 if (methodAspect.matches(declaringClass)) {
80 applicableAspects.add(methodAspect);
crazyboblee1b82a8f2007-02-02 23:30:42 +000081 }
82 }
83
limpbizkit03b81a62009-03-18 05:34:39 +000084 if (applicableAspects.isEmpty()) {
85 interceptors = ImmutableMap.of();
86 methods = ImmutableList.of();
87 callbacks = null;
88 return;
limpbizkit0de5e3e2008-12-07 08:28:31 +000089 }
crazyboblee1b82a8f2007-02-02 23:30:42 +000090
limpbizkit03b81a62009-03-18 05:34:39 +000091 // Get list of methods from cglib.
92 methods = Lists.newArrayList();
93 Enhancer.getMethods(declaringClass, null, methods);
94
95 // Create method/interceptor holders and record indices.
96 List<MethodInterceptorsPair> methodInterceptorsPairs = Lists.newArrayList();
97 for (Method method : methods) {
98 methodInterceptorsPairs.add(new MethodInterceptorsPair(method));
limpbizkit0de5e3e2008-12-07 08:28:31 +000099 }
limpbizkit696c5cd2008-12-30 21:48:17 +0000100
limpbizkit03b81a62009-03-18 05:34:39 +0000101 // Iterate over aspects and add interceptors for the methods they apply to
102 boolean anyMatched = false;
103 for (MethodAspect methodAspect : applicableAspects) {
104 for (MethodInterceptorsPair pair : methodInterceptorsPairs) {
105 if (methodAspect.matches(pair.method)) {
106 visibility = visibility.and(BytecodeGen.Visibility.forMember(pair.method));
107 pair.addAll(methodAspect.interceptors());
108 anyMatched = true;
109 }
110 }
limpbizkit696c5cd2008-12-30 21:48:17 +0000111 }
limpbizkit03b81a62009-03-18 05:34:39 +0000112
113 if (!anyMatched) {
114 interceptors = ImmutableMap.of();
115 callbacks = null;
116 return;
117 }
118
119 ImmutableMap.Builder<Method, List<MethodInterceptor>> interceptorsMapBuilder = null; // lazy
120
121 callbacks = new Callback[methods.size()];
122 for (int i = 0; i < methods.size(); i++) {
123 MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
124
125 if (!pair.hasInterceptors()) {
126 callbacks[i] = NO_OP_METHOD_INTERCEPTOR;
127 continue;
128 }
129
130 if (interceptorsMapBuilder == null) {
131 interceptorsMapBuilder = ImmutableMap.builder();
132 }
133
134 interceptorsMapBuilder.put(pair.method, ImmutableList.copyOf(pair.interceptors));
135 callbacks[i] = new InterceptorStackCallback(pair.method, pair.interceptors);
136 }
137
138 interceptors = interceptorsMapBuilder != null
139 ? interceptorsMapBuilder.build()
140 : ImmutableMap.<Method, List<MethodInterceptor>>of();
141 }
142
143 /**
144 * Returns the interceptors that apply to the constructed type. When InjectableType.Listeners
145 * add additional interceptors, this builder will be thrown out and another created.n
146 */
147 public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() {
148 return interceptors;
149 }
150
151 public ConstructionProxy<T> create() {
152 if (interceptors.isEmpty()) {
153 return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
154 }
155
156 @SuppressWarnings("unchecked")
157 Class<? extends Callback>[] callbackTypes = new Class[methods.size()];
158 Arrays.fill(callbackTypes, net.sf.cglib.proxy.MethodInterceptor.class);
159
160 // Create the proxied class. We're careful to ensure that all enhancer state is not-specific
161 // to this injector. Otherwise, the proxies for each injector will waste PermGen memory
162 Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
163 enhancer.setCallbackFilter(new IndicesCallbackFilter(declaringClass, methods));
164 enhancer.setCallbackTypes(callbackTypes);
165 return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000166 }
167
limpbizkit0de5e3e2008-12-07 08:28:31 +0000168 private static class MethodInterceptorsPair {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000169 final Method method;
limpbizkit03b81a62009-03-18 05:34:39 +0000170 List<MethodInterceptor> interceptors; // lazy
crazyboblee1b82a8f2007-02-02 23:30:42 +0000171
limpbizkit03b81a62009-03-18 05:34:39 +0000172 MethodInterceptorsPair(Method method) {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000173 this.method = method;
174 }
175
crazyboblee62fcdde2007-02-03 02:10:13 +0000176 void addAll(List<MethodInterceptor> interceptors) {
177 if (this.interceptors == null) {
limpbizkit696c5cd2008-12-30 21:48:17 +0000178 this.interceptors = Lists.newArrayList();
crazyboblee1b82a8f2007-02-02 23:30:42 +0000179 }
crazyboblee62fcdde2007-02-03 02:10:13 +0000180 this.interceptors.addAll(interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000181 }
182
183 boolean hasInterceptors() {
184 return interceptors != null;
185 }
186 }
limpbizkit0de5e3e2008-12-07 08:28:31 +0000187
188 /**
189 * A callback filter that maps methods to unique IDs. We define equals and hashCode using the
190 * declaring class so that enhanced classes can be shared between injectors.
191 */
192 private static class IndicesCallbackFilter implements CallbackFilter {
limpbizkit2ac83692008-12-08 01:30:42 +0000193 final Class<?> declaringClass;
194 final Map<Method, Integer> indices;
limpbizkit0de5e3e2008-12-07 08:28:31 +0000195
limpbizkit03b81a62009-03-18 05:34:39 +0000196 IndicesCallbackFilter(Class<?> declaringClass, List<Method> methods) {
limpbizkit0de5e3e2008-12-07 08:28:31 +0000197 this.declaringClass = declaringClass;
198 final Map<Method, Integer> indices = Maps.newHashMap();
199 for (int i = 0; i < methods.size(); i++) {
200 Method method = methods.get(i);
201 indices.put(method, i);
202 }
203
204 this.indices = indices;
205 }
206
207 public int accept(Method method) {
208 return indices.get(method);
209 }
210
211 @Override public boolean equals(Object o) {
212 return o instanceof IndicesCallbackFilter &&
213 ((IndicesCallbackFilter) o).declaringClass == declaringClass;
214 }
215
216 @Override public int hashCode() {
217 return declaringClass.hashCode();
218 }
219 }
limpbizkit03b81a62009-03-18 05:34:39 +0000220
221 /**
222 * Constructs instances that participate in AOP.
223 */
224 private static class ProxyConstructor<T> implements ConstructionProxy<T> {
225 final Class<?> enhanced;
226 final InjectionPoint injectionPoint;
227 final Constructor<T> constructor;
228 final Callback[] callbacks;
229
230 final FastConstructor fastConstructor;
231 final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors;
232
233 @SuppressWarnings("unchecked") // the constructor promises to construct 'T's
234 ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks,
235 ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) {
236 this.enhanced = enhancer.createClass(); // this returns a cached class if possible
237 this.injectionPoint = injectionPoint;
238 this.constructor = (Constructor<T>) injectionPoint.getMember();
239 this.callbacks = callbacks;
240 this.methodInterceptors = methodInterceptors;
241
242 FastClass fastClass = newFastClass(enhanced, BytecodeGen.Visibility.forMember(constructor));
243 this.fastConstructor = fastClass.getConstructor(constructor.getParameterTypes());
244 }
245
246 @SuppressWarnings("unchecked") // the constructor promises to produce 'T's
247 public T newInstance(Object[] arguments) throws InvocationTargetException {
248 Enhancer.registerCallbacks(enhanced, callbacks);
249 try {
250 return (T) fastConstructor.newInstance(arguments);
251 } finally {
252 Enhancer.registerCallbacks(enhanced, null);
253 }
254 }
255
256 public InjectionPoint getInjectionPoint() {
257 return injectionPoint;
258 }
259
260 public Constructor<T> getConstructor() {
261 return constructor;
262 }
263
264 public Map<Method, List<MethodInterceptor>> getMethodInterceptors() {
265 return methodInterceptors;
266 }
267 }
crazybobleee3adfd62007-02-02 21:30:08 +0000268}