blob: c3c8a7a6bf2d27da8e95980c154f37410e0c561e [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 /**
limpbizkitee792462009-04-08 23:48:49 +0000144 * Returns the interceptors that apply to the constructed type.
limpbizkit03b81a62009-03-18 05:34:39 +0000145 */
146 public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() {
147 return interceptors;
148 }
149
150 public ConstructionProxy<T> create() {
151 if (interceptors.isEmpty()) {
152 return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
153 }
154
155 @SuppressWarnings("unchecked")
156 Class<? extends Callback>[] callbackTypes = new Class[methods.size()];
157 Arrays.fill(callbackTypes, net.sf.cglib.proxy.MethodInterceptor.class);
158
159 // Create the proxied class. We're careful to ensure that all enhancer state is not-specific
160 // to this injector. Otherwise, the proxies for each injector will waste PermGen memory
161 Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
162 enhancer.setCallbackFilter(new IndicesCallbackFilter(declaringClass, methods));
163 enhancer.setCallbackTypes(callbackTypes);
164 return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000165 }
166
limpbizkit0de5e3e2008-12-07 08:28:31 +0000167 private static class MethodInterceptorsPair {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000168 final Method method;
limpbizkit03b81a62009-03-18 05:34:39 +0000169 List<MethodInterceptor> interceptors; // lazy
crazyboblee1b82a8f2007-02-02 23:30:42 +0000170
limpbizkit03b81a62009-03-18 05:34:39 +0000171 MethodInterceptorsPair(Method method) {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000172 this.method = method;
173 }
174
crazyboblee62fcdde2007-02-03 02:10:13 +0000175 void addAll(List<MethodInterceptor> interceptors) {
176 if (this.interceptors == null) {
limpbizkit696c5cd2008-12-30 21:48:17 +0000177 this.interceptors = Lists.newArrayList();
crazyboblee1b82a8f2007-02-02 23:30:42 +0000178 }
crazyboblee62fcdde2007-02-03 02:10:13 +0000179 this.interceptors.addAll(interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000180 }
181
182 boolean hasInterceptors() {
183 return interceptors != null;
184 }
185 }
limpbizkit0de5e3e2008-12-07 08:28:31 +0000186
187 /**
188 * A callback filter that maps methods to unique IDs. We define equals and hashCode using the
189 * declaring class so that enhanced classes can be shared between injectors.
190 */
191 private static class IndicesCallbackFilter implements CallbackFilter {
limpbizkit2ac83692008-12-08 01:30:42 +0000192 final Class<?> declaringClass;
193 final Map<Method, Integer> indices;
limpbizkit0de5e3e2008-12-07 08:28:31 +0000194
limpbizkit03b81a62009-03-18 05:34:39 +0000195 IndicesCallbackFilter(Class<?> declaringClass, List<Method> methods) {
limpbizkit0de5e3e2008-12-07 08:28:31 +0000196 this.declaringClass = declaringClass;
197 final Map<Method, Integer> indices = Maps.newHashMap();
198 for (int i = 0; i < methods.size(); i++) {
199 Method method = methods.get(i);
200 indices.put(method, i);
201 }
202
203 this.indices = indices;
204 }
205
206 public int accept(Method method) {
207 return indices.get(method);
208 }
209
210 @Override public boolean equals(Object o) {
211 return o instanceof IndicesCallbackFilter &&
212 ((IndicesCallbackFilter) o).declaringClass == declaringClass;
213 }
214
215 @Override public int hashCode() {
216 return declaringClass.hashCode();
217 }
218 }
limpbizkit03b81a62009-03-18 05:34:39 +0000219
220 /**
221 * Constructs instances that participate in AOP.
222 */
223 private static class ProxyConstructor<T> implements ConstructionProxy<T> {
224 final Class<?> enhanced;
225 final InjectionPoint injectionPoint;
226 final Constructor<T> constructor;
227 final Callback[] callbacks;
228
229 final FastConstructor fastConstructor;
230 final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors;
231
232 @SuppressWarnings("unchecked") // the constructor promises to construct 'T's
233 ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks,
234 ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) {
235 this.enhanced = enhancer.createClass(); // this returns a cached class if possible
236 this.injectionPoint = injectionPoint;
237 this.constructor = (Constructor<T>) injectionPoint.getMember();
238 this.callbacks = callbacks;
239 this.methodInterceptors = methodInterceptors;
240
241 FastClass fastClass = newFastClass(enhanced, BytecodeGen.Visibility.forMember(constructor));
242 this.fastConstructor = fastClass.getConstructor(constructor.getParameterTypes());
243 }
244
245 @SuppressWarnings("unchecked") // the constructor promises to produce 'T's
246 public T newInstance(Object[] arguments) throws InvocationTargetException {
247 Enhancer.registerCallbacks(enhanced, callbacks);
248 try {
249 return (T) fastConstructor.newInstance(arguments);
250 } finally {
251 Enhancer.registerCallbacks(enhanced, null);
252 }
253 }
254
255 public InjectionPoint getInjectionPoint() {
256 return injectionPoint;
257 }
258
259 public Constructor<T> getConstructor() {
260 return constructor;
261 }
262
limpbizkita843a952009-04-08 22:24:55 +0000263 public ImmutableMap<Method, List<MethodInterceptor>> getMethodInterceptors() {
limpbizkit03b81a62009-03-18 05:34:39 +0000264 return methodInterceptors;
265 }
266 }
crazybobleee3adfd62007-02-02 21:30:08 +0000267}