blob: 7dfea560d078f7f035fbaf6e939be75c4d12f349 [file] [log] [blame]
crazybobleeb8cf1e52007-02-02 21:48:16 +00001/**
2 * Copyright (C) 2006 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 */
crazybobleee3adfd62007-02-02 21:30:08 +000016
crazyboblee10a3b022007-02-10 01:49:38 +000017package com.google.inject;
crazybobleee3adfd62007-02-02 21:30:08 +000018
crazyboblee6b5db112007-02-07 23:14:55 +000019import com.google.inject.util.GuiceFastClass;
20import com.google.inject.util.GuiceNamingPolicy;
crazyboblee10a3b022007-02-10 01:49:38 +000021import com.google.inject.util.ReferenceCache;
crazyboblee1b82a8f2007-02-02 23:30:42 +000022import java.lang.reflect.Constructor;
crazyboblee1b82a8f2007-02-02 23:30:42 +000023import java.lang.reflect.InvocationTargetException;
crazyboblee10a3b022007-02-10 01:49:38 +000024import java.lang.reflect.Method;
25import java.util.ArrayList;
26import java.util.HashMap;
27import java.util.List;
28import java.util.Map;
kevinb9na99dca72007-02-11 04:48:57 +000029import net.sf.cglib.proxy.Callback;
30import net.sf.cglib.proxy.CallbackFilter;
31import net.sf.cglib.proxy.Enhancer;
32import net.sf.cglib.proxy.NoOp;
33import net.sf.cglib.reflect.FastClass;
34import net.sf.cglib.reflect.FastConstructor;
35import org.aopalliance.intercept.MethodInterceptor;
crazyboblee1b82a8f2007-02-02 23:30:42 +000036
crazybobleee3adfd62007-02-02 21:30:08 +000037/**
crazyboblee1b82a8f2007-02-02 23:30:42 +000038 * Proxies classes applying interceptors to methods as specified in
39 * {@link ProxyFactoryBuilder}.
40 *
crazybobleee3adfd62007-02-02 21:30:08 +000041 * @author crazybob@google.com (Bob Lee)
42 */
crazyboblee10a3b022007-02-10 01:49:38 +000043class ProxyFactory implements ConstructionProxyFactory {
crazybobleee3adfd62007-02-02 21:30:08 +000044
crazyboblee1b82a8f2007-02-02 23:30:42 +000045 final List<MethodAspect> methodAspects;
kevinb9na99dca72007-02-11 04:48:57 +000046 final ConstructionProxyFactory defaultFactory
47 = new DefaultConstructionProxyFactory();
crazyboblee1b82a8f2007-02-02 23:30:42 +000048
49 ProxyFactory(List<MethodAspect> methodAspects) {
50 this.methodAspects = methodAspects;
51 }
52
kevinb9na99dca72007-02-11 04:48:57 +000053 Map<Constructor<?>, ConstructionProxy<?>> constructionProxies
54 = new ReferenceCache<Constructor<?>, ConstructionProxy<?>>() {
crazyboblee1b82a8f2007-02-02 23:30:42 +000055 protected ConstructionProxy<?> create(Constructor<?> constructor) {
56 return createConstructionProxy(constructor);
57 }
58 };
59
crazyboblee2a1ea892007-02-03 02:17:19 +000060 /**
crazyboblee420d4622007-02-03 02:47:12 +000061 * Gets a factory for the given type. Uses the zero-arg constructor. Wraps
kevinb9na99dca72007-02-11 04:48:57 +000062 * exceptions in {@link RuntimeException} including
63 * {@link InvocationTargetException}.
crazyboblee2a1ea892007-02-03 02:17:19 +000064 */
65 public <T> Factory<T> getFactory(Class<T> type) throws NoSuchMethodException {
kevinb9na99dca72007-02-11 04:48:57 +000066 final ConstructionProxy<T> constructionProxy
67 = createConstructionProxy(type.getDeclaredConstructor());
crazyboblee2a1ea892007-02-03 02:17:19 +000068 return new Factory<T>() {
69 public T get() {
70 try {
71 return constructionProxy.newInstance();
kevinb9na99dca72007-02-11 04:48:57 +000072 }
73 catch (InvocationTargetException e) {
crazyboblee2a1ea892007-02-03 02:17:19 +000074 throw new RuntimeException(e);
75 }
76 }
77 };
78 }
79
crazyboblee1b82a8f2007-02-02 23:30:42 +000080 <T> ConstructionProxy<T> createConstructionProxy(Constructor<T> constructor) {
81 Class<T> declaringClass = constructor.getDeclaringClass();
82
83 // Find applicable aspects. Bow out if none are applicable to this class.
84 List<MethodAspect> applicableAspects = new ArrayList<MethodAspect>();
85 for (MethodAspect methodAspect : methodAspects) {
86 if (methodAspect.matches(declaringClass)) {
87 applicableAspects.add(methodAspect);
88 }
89 }
90 if (applicableAspects.isEmpty()) {
91 return defaultFactory.get(constructor);
92 }
93
94 // Get list of methods from cglib.
95 List<Method> methods = new ArrayList<Method>();
96 Enhancer.getMethods(declaringClass, null, methods);
97 final Map<Method, Integer> indices = new HashMap<Method, Integer>();
98
99 // Create method/interceptor holders and record indices.
kevinb9na99dca72007-02-11 04:48:57 +0000100 List<MethodInterceptorsPair> methodInterceptorsPairs
101 = new ArrayList<MethodInterceptorsPair>();
crazyboblee1b82a8f2007-02-02 23:30:42 +0000102 for (int i = 0; i < methods.size(); i++) {
103 Method method = methods.get(i);
104 methodInterceptorsPairs.add(new MethodInterceptorsPair(method));
105 indices.put(method, i);
106 }
107
kevinb9na99dca72007-02-11 04:48:57 +0000108 // Iterate over aspects and add interceptors for the methods they apply to
crazyboblee1b82a8f2007-02-02 23:30:42 +0000109 boolean anyMatched = false;
110 for (MethodAspect methodAspect : applicableAspects) {
kevinb9na99dca72007-02-11 04:48:57 +0000111 for (MethodInterceptorsPair pair : methodInterceptorsPairs) {
112 if (methodAspect.matches(pair.method)) {
113 pair.addAll(methodAspect.interceptors());
crazyboblee1b82a8f2007-02-02 23:30:42 +0000114 anyMatched = true;
115 }
116 }
117 }
118 if (!anyMatched) {
119 return defaultFactory.get(constructor);
120 }
121
122 // Create callbacks.
123 Callback[] callbacks = new Callback[methods.size()];
kevinb9n48d13072007-02-12 18:21:26 +0000124
125 @SuppressWarnings("unchecked")
crazyboblee1b82a8f2007-02-02 23:30:42 +0000126 Class<? extends Callback>[] callbackTypes = new Class[methods.size()];
127 for (int i = 0; i < methods.size(); i++) {
kevinb9na99dca72007-02-11 04:48:57 +0000128 MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
129 if (!pair.hasInterceptors()) {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000130 callbacks[i] = NoOp.INSTANCE;
131 callbackTypes[i] = NoOp.class;
kevinb9na99dca72007-02-11 04:48:57 +0000132 }
133 else {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000134 callbacks[i] = new InterceptorStackCallback(
kevinb9na99dca72007-02-11 04:48:57 +0000135 pair.method, pair.interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000136 callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
137 }
138 }
139
140 // Create the proxied class.
141 Enhancer enhancer = new Enhancer();
142 enhancer.setSuperclass(declaringClass);
143 enhancer.setUseCache(false); // We do enough caching.
144 enhancer.setCallbackFilter(new CallbackFilter() {
145 public int accept(Method method) {
146 return indices.get(method);
147 }
148 });
149 enhancer.setCallbackTypes(callbackTypes);
150 enhancer.setUseFactory(false);
crazyboblee6b5db112007-02-07 23:14:55 +0000151 enhancer.setNamingPolicy(new GuiceNamingPolicy());
crazyboblee1b82a8f2007-02-02 23:30:42 +0000152
153 Class<?> proxied = enhancer.createClass();
154
155 // Store callbacks.
156 Enhancer.registerStaticCallbacks(proxied, callbacks);
157
158 return createConstructionProxy(proxied, constructor.getParameterTypes());
159 }
160
161 /**
162 * Creates a construction proxy given a class and parameter types.
163 */
164 <T> ConstructionProxy<T> createConstructionProxy(Class<?> clazz,
165 Class[] parameterTypes) {
crazyboblee6b5db112007-02-07 23:14:55 +0000166 FastClass fastClass = GuiceFastClass.create(clazz);
kevinb9na99dca72007-02-11 04:48:57 +0000167 final FastConstructor fastConstructor
168 = fastClass.getConstructor(parameterTypes);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000169 return new ConstructionProxy<T>() {
kevinb9na99dca72007-02-11 04:48:57 +0000170 @SuppressWarnings("unchecked")
crazyboblee1b82a8f2007-02-02 23:30:42 +0000171 public T newInstance(Object... arguments)
172 throws InvocationTargetException {
173 return (T) fastConstructor.newInstance(arguments);
174 }
175 };
176 }
177
178 static class MethodInterceptorsPair {
179
180 final Method method;
181 List<MethodInterceptor> interceptors;
182
183 public MethodInterceptorsPair(Method method) {
184 this.method = method;
185 }
186
crazyboblee62fcdde2007-02-03 02:10:13 +0000187 void addAll(List<MethodInterceptor> interceptors) {
188 if (this.interceptors == null) {
189 this.interceptors = new ArrayList<MethodInterceptor>();
crazyboblee1b82a8f2007-02-02 23:30:42 +0000190 }
crazyboblee62fcdde2007-02-03 02:10:13 +0000191 this.interceptors.addAll(interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000192 }
193
194 boolean hasInterceptors() {
195 return interceptors != null;
196 }
197 }
198
kevinb9na99dca72007-02-11 04:48:57 +0000199 @SuppressWarnings("unchecked")
crazyboblee1b82a8f2007-02-02 23:30:42 +0000200 public <T> ConstructionProxy<T> get(Constructor<T> constructor) {
201 return (ConstructionProxy<T>) constructionProxies.get(constructor);
202 }
crazybobleee3adfd62007-02-02 21:30:08 +0000203}