crazyboblee | b8cf1e5 | 2007-02-02 21:48:16 +0000 | [diff] [blame] | 1 | /** |
| 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 | */ |
crazyboblee | e3adfd6 | 2007-02-02 21:30:08 +0000 | [diff] [blame] | 16 | |
crazyboblee | 10a3b02 | 2007-02-10 01:49:38 +0000 | [diff] [blame] | 17 | package com.google.inject; |
crazyboblee | e3adfd6 | 2007-02-02 21:30:08 +0000 | [diff] [blame] | 18 | |
crazyboblee | 6b5db11 | 2007-02-07 23:14:55 +0000 | [diff] [blame] | 19 | import com.google.inject.util.GuiceFastClass; |
| 20 | import com.google.inject.util.GuiceNamingPolicy; |
crazyboblee | 10a3b02 | 2007-02-10 01:49:38 +0000 | [diff] [blame] | 21 | import com.google.inject.util.ReferenceCache; |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 22 | import java.lang.reflect.Constructor; |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 23 | import java.lang.reflect.InvocationTargetException; |
crazyboblee | 10a3b02 | 2007-02-10 01:49:38 +0000 | [diff] [blame] | 24 | import java.lang.reflect.Method; |
| 25 | import java.util.ArrayList; |
| 26 | import java.util.HashMap; |
| 27 | import java.util.List; |
| 28 | import java.util.Map; |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 29 | import net.sf.cglib.proxy.Callback; |
| 30 | import net.sf.cglib.proxy.CallbackFilter; |
| 31 | import net.sf.cglib.proxy.Enhancer; |
| 32 | import net.sf.cglib.proxy.NoOp; |
| 33 | import net.sf.cglib.reflect.FastClass; |
| 34 | import net.sf.cglib.reflect.FastConstructor; |
| 35 | import org.aopalliance.intercept.MethodInterceptor; |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 36 | |
crazyboblee | e3adfd6 | 2007-02-02 21:30:08 +0000 | [diff] [blame] | 37 | /** |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 38 | * Proxies classes applying interceptors to methods as specified in |
| 39 | * {@link ProxyFactoryBuilder}. |
| 40 | * |
crazyboblee | e3adfd6 | 2007-02-02 21:30:08 +0000 | [diff] [blame] | 41 | * @author crazybob@google.com (Bob Lee) |
| 42 | */ |
crazyboblee | 10a3b02 | 2007-02-10 01:49:38 +0000 | [diff] [blame] | 43 | class ProxyFactory implements ConstructionProxyFactory { |
crazyboblee | e3adfd6 | 2007-02-02 21:30:08 +0000 | [diff] [blame] | 44 | |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 45 | final List<MethodAspect> methodAspects; |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 46 | final ConstructionProxyFactory defaultFactory |
| 47 | = new DefaultConstructionProxyFactory(); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 48 | |
| 49 | ProxyFactory(List<MethodAspect> methodAspects) { |
| 50 | this.methodAspects = methodAspects; |
| 51 | } |
| 52 | |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 53 | Map<Constructor<?>, ConstructionProxy<?>> constructionProxies |
| 54 | = new ReferenceCache<Constructor<?>, ConstructionProxy<?>>() { |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 55 | protected ConstructionProxy<?> create(Constructor<?> constructor) { |
| 56 | return createConstructionProxy(constructor); |
| 57 | } |
| 58 | }; |
| 59 | |
crazyboblee | 2a1ea89 | 2007-02-03 02:17:19 +0000 | [diff] [blame] | 60 | /** |
crazyboblee | 420d462 | 2007-02-03 02:47:12 +0000 | [diff] [blame] | 61 | * Gets a factory for the given type. Uses the zero-arg constructor. Wraps |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 62 | * exceptions in {@link RuntimeException} including |
| 63 | * {@link InvocationTargetException}. |
crazyboblee | 2a1ea89 | 2007-02-03 02:17:19 +0000 | [diff] [blame] | 64 | */ |
| 65 | public <T> Factory<T> getFactory(Class<T> type) throws NoSuchMethodException { |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 66 | final ConstructionProxy<T> constructionProxy |
| 67 | = createConstructionProxy(type.getDeclaredConstructor()); |
crazyboblee | 2a1ea89 | 2007-02-03 02:17:19 +0000 | [diff] [blame] | 68 | return new Factory<T>() { |
| 69 | public T get() { |
| 70 | try { |
| 71 | return constructionProxy.newInstance(); |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 72 | } |
| 73 | catch (InvocationTargetException e) { |
crazyboblee | 2a1ea89 | 2007-02-03 02:17:19 +0000 | [diff] [blame] | 74 | throw new RuntimeException(e); |
| 75 | } |
| 76 | } |
| 77 | }; |
| 78 | } |
| 79 | |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 80 | <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. |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 100 | List<MethodInterceptorsPair> methodInterceptorsPairs |
| 101 | = new ArrayList<MethodInterceptorsPair>(); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 102 | 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 | |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 108 | // Iterate over aspects and add interceptors for the methods they apply to |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 109 | boolean anyMatched = false; |
| 110 | for (MethodAspect methodAspect : applicableAspects) { |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 111 | for (MethodInterceptorsPair pair : methodInterceptorsPairs) { |
| 112 | if (methodAspect.matches(pair.method)) { |
| 113 | pair.addAll(methodAspect.interceptors()); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 114 | 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()]; |
crazyboblee | 62fcdde | 2007-02-03 02:10:13 +0000 | [diff] [blame] | 124 | // noinspection unchecked |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 125 | Class<? extends Callback>[] callbackTypes = new Class[methods.size()]; |
| 126 | for (int i = 0; i < methods.size(); i++) { |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 127 | MethodInterceptorsPair pair = methodInterceptorsPairs.get(i); |
| 128 | if (!pair.hasInterceptors()) { |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 129 | callbacks[i] = NoOp.INSTANCE; |
| 130 | callbackTypes[i] = NoOp.class; |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 131 | } |
| 132 | else { |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 133 | callbacks[i] = new InterceptorStackCallback( |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 134 | pair.method, pair.interceptors); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 135 | 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); |
crazyboblee | 6b5db11 | 2007-02-07 23:14:55 +0000 | [diff] [blame] | 150 | enhancer.setNamingPolicy(new GuiceNamingPolicy()); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 151 | |
| 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) { |
crazyboblee | 6b5db11 | 2007-02-07 23:14:55 +0000 | [diff] [blame] | 165 | FastClass fastClass = GuiceFastClass.create(clazz); |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 166 | final FastConstructor fastConstructor |
| 167 | = fastClass.getConstructor(parameterTypes); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 168 | return new ConstructionProxy<T>() { |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 169 | @SuppressWarnings("unchecked") |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 170 | 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 | |
crazyboblee | 62fcdde | 2007-02-03 02:10:13 +0000 | [diff] [blame] | 186 | void addAll(List<MethodInterceptor> interceptors) { |
| 187 | if (this.interceptors == null) { |
| 188 | this.interceptors = new ArrayList<MethodInterceptor>(); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 189 | } |
crazyboblee | 62fcdde | 2007-02-03 02:10:13 +0000 | [diff] [blame] | 190 | this.interceptors.addAll(interceptors); |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | boolean hasInterceptors() { |
| 194 | return interceptors != null; |
| 195 | } |
| 196 | } |
| 197 | |
kevinb9n | a99dca7 | 2007-02-11 04:48:57 +0000 | [diff] [blame^] | 198 | @SuppressWarnings("unchecked") |
crazyboblee | 1b82a8f | 2007-02-02 23:30:42 +0000 | [diff] [blame] | 199 | public <T> ConstructionProxy<T> get(Constructor<T> constructor) { |
| 200 | return (ConstructionProxy<T>) constructionProxies.get(constructor); |
| 201 | } |
crazyboblee | e3adfd6 | 2007-02-02 21:30:08 +0000 | [diff] [blame] | 202 | } |