blob: ccd4b7cfb49e13904918065803b2050fe7c4d1ac [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
limpbizkit5ae41eb2009-06-06 17:51:27 +000017package com.google.inject.internal;
crazybobleee3adfd62007-02-02 21:30:08 +000018
limpbizkit03b81a62009-03-18 05:34:39 +000019import static com.google.inject.internal.BytecodeGen.newFastClass;
limpbizkita98bc7a2008-08-29 16:52:44 +000020import com.google.inject.spi.InjectionPoint;
limpbizkit9dc32d42008-06-15 11:29:10 +000021import java.lang.reflect.Constructor;
22import java.lang.reflect.InvocationTargetException;
limpbizkit9dc32d42008-06-15 11:29:10 +000023import java.lang.reflect.Method;
limpbizkit0de5e3e2008-12-07 08:28:31 +000024import java.util.Arrays;
limpbizkit9dc32d42008-06-15 11:29:10 +000025import java.util.List;
26import java.util.Map;
kevinb9na99dca72007-02-11 04:48:57 +000027import net.sf.cglib.proxy.Callback;
28import net.sf.cglib.proxy.CallbackFilter;
29import net.sf.cglib.proxy.Enhancer;
limpbizkit0de5e3e2008-12-07 08:28:31 +000030import net.sf.cglib.proxy.MethodProxy;
kevinb9na99dca72007-02-11 04:48:57 +000031import net.sf.cglib.reflect.FastClass;
32import net.sf.cglib.reflect.FastConstructor;
33import org.aopalliance.intercept.MethodInterceptor;
crazyboblee1b82a8f2007-02-02 23:30:42 +000034
crazybobleee3adfd62007-02-02 21:30:08 +000035/**
limpbizkit03b81a62009-03-18 05:34:39 +000036 * Builds a construction proxy that can participate in AOP. This class manages applying type and
37 * method matchers to come up with the set of intercepted methods.
crazyboblee1b82a8f2007-02-02 23:30:42 +000038 *
limpbizkit03b81a62009-03-18 05:34:39 +000039 * @author jessewilson@google.com (Jesse Wilson)
crazybobleee3adfd62007-02-02 21:30:08 +000040 */
limpbizkit5ae41eb2009-06-06 17:51:27 +000041final class ProxyFactory<T> implements ConstructionProxyFactory<T> {
limpbizkit0de5e3e2008-12-07 08:28:31 +000042
43 private static final net.sf.cglib.proxy.MethodInterceptor NO_OP_METHOD_INTERCEPTOR
44 = new net.sf.cglib.proxy.MethodInterceptor() {
45 public Object intercept(
46 Object proxy, Method method, Object[] arguments, MethodProxy methodProxy)
47 throws Throwable {
48 return methodProxy.invokeSuper(proxy, arguments);
49 }
50 };
51
limpbizkit03b81a62009-03-18 05:34:39 +000052 private final InjectionPoint injectionPoint;
53 private final ImmutableMap<Method, List<MethodInterceptor>> interceptors;
54 private final Class<T> declaringClass;
55 private final List<Method> methods;
56 private final Callback[] callbacks;
57
limpbizkit0de5e3e2008-12-07 08:28:31 +000058 /**
limpbizkit03b81a62009-03-18 05:34:39 +000059 * PUBLIC is default; it's used if all the methods we're intercepting are public. This impacts
60 * which classloader we should use for loading the enhanced class
limpbizkit0de5e3e2008-12-07 08:28:31 +000061 */
limpbizkit03b81a62009-03-18 05:34:39 +000062 private BytecodeGen.Visibility visibility = BytecodeGen.Visibility.PUBLIC;
limpbizkit0de5e3e2008-12-07 08:28:31 +000063
limpbizkit03b81a62009-03-18 05:34:39 +000064 ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects) {
65 this.injectionPoint = injectionPoint;
limpbizkit0de5e3e2008-12-07 08:28:31 +000066
limpbizkit03b81a62009-03-18 05:34:39 +000067 @SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T>
68 Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
69 declaringClass = constructor.getDeclaringClass();
limpbizkit0de5e3e2008-12-07 08:28:31 +000070
limpbizkit03b81a62009-03-18 05:34:39 +000071 // Find applicable aspects. Bow out if none are applicable to this class.
72 List<MethodAspect> applicableAspects = Lists.newArrayList();
73 for (MethodAspect methodAspect : methodAspects) {
74 if (methodAspect.matches(declaringClass)) {
75 applicableAspects.add(methodAspect);
crazyboblee1b82a8f2007-02-02 23:30:42 +000076 }
77 }
78
limpbizkit03b81a62009-03-18 05:34:39 +000079 if (applicableAspects.isEmpty()) {
80 interceptors = ImmutableMap.of();
81 methods = ImmutableList.of();
82 callbacks = null;
83 return;
limpbizkit0de5e3e2008-12-07 08:28:31 +000084 }
crazyboblee1b82a8f2007-02-02 23:30:42 +000085
limpbizkit03b81a62009-03-18 05:34:39 +000086 // Get list of methods from cglib.
87 methods = Lists.newArrayList();
88 Enhancer.getMethods(declaringClass, null, methods);
89
90 // Create method/interceptor holders and record indices.
91 List<MethodInterceptorsPair> methodInterceptorsPairs = Lists.newArrayList();
92 for (Method method : methods) {
93 methodInterceptorsPairs.add(new MethodInterceptorsPair(method));
limpbizkit0de5e3e2008-12-07 08:28:31 +000094 }
limpbizkit696c5cd2008-12-30 21:48:17 +000095
limpbizkit03b81a62009-03-18 05:34:39 +000096 // Iterate over aspects and add interceptors for the methods they apply to
97 boolean anyMatched = false;
98 for (MethodAspect methodAspect : applicableAspects) {
99 for (MethodInterceptorsPair pair : methodInterceptorsPairs) {
100 if (methodAspect.matches(pair.method)) {
101 visibility = visibility.and(BytecodeGen.Visibility.forMember(pair.method));
102 pair.addAll(methodAspect.interceptors());
103 anyMatched = true;
104 }
105 }
limpbizkit696c5cd2008-12-30 21:48:17 +0000106 }
limpbizkit03b81a62009-03-18 05:34:39 +0000107
108 if (!anyMatched) {
109 interceptors = ImmutableMap.of();
110 callbacks = null;
111 return;
112 }
113
114 ImmutableMap.Builder<Method, List<MethodInterceptor>> interceptorsMapBuilder = null; // lazy
115
116 callbacks = new Callback[methods.size()];
117 for (int i = 0; i < methods.size(); i++) {
118 MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
119
120 if (!pair.hasInterceptors()) {
121 callbacks[i] = NO_OP_METHOD_INTERCEPTOR;
122 continue;
123 }
124
125 if (interceptorsMapBuilder == null) {
126 interceptorsMapBuilder = ImmutableMap.builder();
127 }
128
129 interceptorsMapBuilder.put(pair.method, ImmutableList.copyOf(pair.interceptors));
130 callbacks[i] = new InterceptorStackCallback(pair.method, pair.interceptors);
131 }
132
133 interceptors = interceptorsMapBuilder != null
134 ? interceptorsMapBuilder.build()
135 : ImmutableMap.<Method, List<MethodInterceptor>>of();
136 }
137
138 /**
limpbizkitee792462009-04-08 23:48:49 +0000139 * Returns the interceptors that apply to the constructed type.
limpbizkit03b81a62009-03-18 05:34:39 +0000140 */
141 public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() {
142 return interceptors;
143 }
144
145 public ConstructionProxy<T> create() {
146 if (interceptors.isEmpty()) {
147 return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
148 }
149
150 @SuppressWarnings("unchecked")
151 Class<? extends Callback>[] callbackTypes = new Class[methods.size()];
152 Arrays.fill(callbackTypes, net.sf.cglib.proxy.MethodInterceptor.class);
153
154 // Create the proxied class. We're careful to ensure that all enhancer state is not-specific
155 // to this injector. Otherwise, the proxies for each injector will waste PermGen memory
156 Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
157 enhancer.setCallbackFilter(new IndicesCallbackFilter(declaringClass, methods));
158 enhancer.setCallbackTypes(callbackTypes);
159 return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000160 }
161
limpbizkit0de5e3e2008-12-07 08:28:31 +0000162 private static class MethodInterceptorsPair {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000163 final Method method;
limpbizkit03b81a62009-03-18 05:34:39 +0000164 List<MethodInterceptor> interceptors; // lazy
crazyboblee1b82a8f2007-02-02 23:30:42 +0000165
limpbizkit03b81a62009-03-18 05:34:39 +0000166 MethodInterceptorsPair(Method method) {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000167 this.method = method;
168 }
169
crazyboblee62fcdde2007-02-03 02:10:13 +0000170 void addAll(List<MethodInterceptor> interceptors) {
171 if (this.interceptors == null) {
limpbizkit696c5cd2008-12-30 21:48:17 +0000172 this.interceptors = Lists.newArrayList();
crazyboblee1b82a8f2007-02-02 23:30:42 +0000173 }
crazyboblee62fcdde2007-02-03 02:10:13 +0000174 this.interceptors.addAll(interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000175 }
176
177 boolean hasInterceptors() {
178 return interceptors != null;
179 }
180 }
limpbizkit0de5e3e2008-12-07 08:28:31 +0000181
182 /**
183 * A callback filter that maps methods to unique IDs. We define equals and hashCode using the
184 * declaring class so that enhanced classes can be shared between injectors.
185 */
186 private static class IndicesCallbackFilter implements CallbackFilter {
limpbizkit2ac83692008-12-08 01:30:42 +0000187 final Class<?> declaringClass;
188 final Map<Method, Integer> indices;
limpbizkit0de5e3e2008-12-07 08:28:31 +0000189
limpbizkit03b81a62009-03-18 05:34:39 +0000190 IndicesCallbackFilter(Class<?> declaringClass, List<Method> methods) {
limpbizkit0de5e3e2008-12-07 08:28:31 +0000191 this.declaringClass = declaringClass;
192 final Map<Method, Integer> indices = Maps.newHashMap();
193 for (int i = 0; i < methods.size(); i++) {
194 Method method = methods.get(i);
195 indices.put(method, i);
196 }
197
198 this.indices = indices;
199 }
200
201 public int accept(Method method) {
202 return indices.get(method);
203 }
204
205 @Override public boolean equals(Object o) {
206 return o instanceof IndicesCallbackFilter &&
207 ((IndicesCallbackFilter) o).declaringClass == declaringClass;
208 }
209
210 @Override public int hashCode() {
211 return declaringClass.hashCode();
212 }
213 }
limpbizkit03b81a62009-03-18 05:34:39 +0000214
215 /**
216 * Constructs instances that participate in AOP.
217 */
218 private static class ProxyConstructor<T> implements ConstructionProxy<T> {
219 final Class<?> enhanced;
220 final InjectionPoint injectionPoint;
221 final Constructor<T> constructor;
222 final Callback[] callbacks;
223
224 final FastConstructor fastConstructor;
225 final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors;
226
227 @SuppressWarnings("unchecked") // the constructor promises to construct 'T's
228 ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks,
229 ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) {
230 this.enhanced = enhancer.createClass(); // this returns a cached class if possible
231 this.injectionPoint = injectionPoint;
232 this.constructor = (Constructor<T>) injectionPoint.getMember();
233 this.callbacks = callbacks;
234 this.methodInterceptors = methodInterceptors;
235
236 FastClass fastClass = newFastClass(enhanced, BytecodeGen.Visibility.forMember(constructor));
237 this.fastConstructor = fastClass.getConstructor(constructor.getParameterTypes());
238 }
239
240 @SuppressWarnings("unchecked") // the constructor promises to produce 'T's
241 public T newInstance(Object[] arguments) throws InvocationTargetException {
242 Enhancer.registerCallbacks(enhanced, callbacks);
243 try {
244 return (T) fastConstructor.newInstance(arguments);
245 } finally {
246 Enhancer.registerCallbacks(enhanced, null);
247 }
248 }
249
250 public InjectionPoint getInjectionPoint() {
251 return injectionPoint;
252 }
253
254 public Constructor<T> getConstructor() {
255 return constructor;
256 }
257
limpbizkita843a952009-04-08 22:24:55 +0000258 public ImmutableMap<Method, List<MethodInterceptor>> getMethodInterceptors() {
limpbizkit03b81a62009-03-18 05:34:39 +0000259 return methodInterceptors;
260 }
261 }
crazybobleee3adfd62007-02-02 21:30:08 +0000262}