blob: 55eb5e1517facdd75b18647dc1a5ecaaa2c4d32b [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()];
crazyboblee62fcdde2007-02-03 02:10:13 +0000124 // noinspection unchecked
crazyboblee1b82a8f2007-02-02 23:30:42 +0000125 Class<? extends Callback>[] callbackTypes = new Class[methods.size()];
126 for (int i = 0; i < methods.size(); i++) {
kevinb9na99dca72007-02-11 04:48:57 +0000127 MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
128 if (!pair.hasInterceptors()) {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000129 callbacks[i] = NoOp.INSTANCE;
130 callbackTypes[i] = NoOp.class;
kevinb9na99dca72007-02-11 04:48:57 +0000131 }
132 else {
crazyboblee1b82a8f2007-02-02 23:30:42 +0000133 callbacks[i] = new InterceptorStackCallback(
kevinb9na99dca72007-02-11 04:48:57 +0000134 pair.method, pair.interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000135 callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
136 }
137 }
138
139 // Create the proxied class.
140 Enhancer enhancer = new Enhancer();
141 enhancer.setSuperclass(declaringClass);
142 enhancer.setUseCache(false); // We do enough caching.
143 enhancer.setCallbackFilter(new CallbackFilter() {
144 public int accept(Method method) {
145 return indices.get(method);
146 }
147 });
148 enhancer.setCallbackTypes(callbackTypes);
149 enhancer.setUseFactory(false);
crazyboblee6b5db112007-02-07 23:14:55 +0000150 enhancer.setNamingPolicy(new GuiceNamingPolicy());
crazyboblee1b82a8f2007-02-02 23:30:42 +0000151
152 Class<?> proxied = enhancer.createClass();
153
154 // Store callbacks.
155 Enhancer.registerStaticCallbacks(proxied, callbacks);
156
157 return createConstructionProxy(proxied, constructor.getParameterTypes());
158 }
159
160 /**
161 * Creates a construction proxy given a class and parameter types.
162 */
163 <T> ConstructionProxy<T> createConstructionProxy(Class<?> clazz,
164 Class[] parameterTypes) {
crazyboblee6b5db112007-02-07 23:14:55 +0000165 FastClass fastClass = GuiceFastClass.create(clazz);
kevinb9na99dca72007-02-11 04:48:57 +0000166 final FastConstructor fastConstructor
167 = fastClass.getConstructor(parameterTypes);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000168 return new ConstructionProxy<T>() {
kevinb9na99dca72007-02-11 04:48:57 +0000169 @SuppressWarnings("unchecked")
crazyboblee1b82a8f2007-02-02 23:30:42 +0000170 public T newInstance(Object... arguments)
171 throws InvocationTargetException {
172 return (T) fastConstructor.newInstance(arguments);
173 }
174 };
175 }
176
177 static class MethodInterceptorsPair {
178
179 final Method method;
180 List<MethodInterceptor> interceptors;
181
182 public MethodInterceptorsPair(Method method) {
183 this.method = method;
184 }
185
crazyboblee62fcdde2007-02-03 02:10:13 +0000186 void addAll(List<MethodInterceptor> interceptors) {
187 if (this.interceptors == null) {
188 this.interceptors = new ArrayList<MethodInterceptor>();
crazyboblee1b82a8f2007-02-02 23:30:42 +0000189 }
crazyboblee62fcdde2007-02-03 02:10:13 +0000190 this.interceptors.addAll(interceptors);
crazyboblee1b82a8f2007-02-02 23:30:42 +0000191 }
192
193 boolean hasInterceptors() {
194 return interceptors != null;
195 }
196 }
197
kevinb9na99dca72007-02-11 04:48:57 +0000198 @SuppressWarnings("unchecked")
crazyboblee1b82a8f2007-02-02 23:30:42 +0000199 public <T> ConstructionProxy<T> get(Constructor<T> constructor) {
200 return (ConstructionProxy<T>) constructionProxies.get(constructor);
201 }
crazybobleee3adfd62007-02-02 21:30:08 +0000202}