| /** |
| * Copyright (C) 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.inject.internal; |
| |
| import com.google.inject.ProvisionException; |
| import static com.google.inject.internal.BytecodeGen.newFastClass; |
| import com.google.inject.internal.util.ImmutableList; |
| import com.google.inject.internal.util.ImmutableMap; |
| import com.google.inject.internal.util.Lists; |
| import com.google.inject.internal.util.Maps; |
| import com.google.inject.spi.InjectionPoint; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import net.sf.cglib.proxy.Callback; |
| import net.sf.cglib.proxy.CallbackFilter; |
| import net.sf.cglib.proxy.Enhancer; |
| import net.sf.cglib.proxy.MethodProxy; |
| import net.sf.cglib.reflect.FastClass; |
| import net.sf.cglib.reflect.FastConstructor; |
| import org.aopalliance.intercept.MethodInterceptor; |
| |
| /** |
| * Builds a construction proxy that can participate in AOP. This class manages applying type and |
| * method matchers to come up with the set of intercepted methods. |
| * |
| * @author jessewilson@google.com (Jesse Wilson) |
| */ |
| final class ProxyFactory<T> implements ConstructionProxyFactory<T> { |
| |
| private static final net.sf.cglib.proxy.MethodInterceptor NO_OP_METHOD_INTERCEPTOR |
| = new net.sf.cglib.proxy.MethodInterceptor() { |
| public Object intercept( |
| Object proxy, Method method, Object[] arguments, MethodProxy methodProxy) |
| throws Throwable { |
| return methodProxy.invokeSuper(proxy, arguments); |
| } |
| }; |
| |
| private final InjectionPoint injectionPoint; |
| private final ImmutableMap<Method, List<MethodInterceptor>> interceptors; |
| private final Class<T> declaringClass; |
| private final List<Method> methods; |
| private final Callback[] callbacks; |
| |
| /** |
| * PUBLIC is default; it's used if all the methods we're intercepting are public. This impacts |
| * which classloader we should use for loading the enhanced class |
| */ |
| private BytecodeGen.Visibility visibility = BytecodeGen.Visibility.PUBLIC; |
| |
| ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects) { |
| this.injectionPoint = injectionPoint; |
| |
| @SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T> |
| Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember(); |
| declaringClass = constructor.getDeclaringClass(); |
| |
| // Find applicable aspects. Bow out if none are applicable to this class. |
| List<MethodAspect> applicableAspects = Lists.newArrayList(); |
| for (MethodAspect methodAspect : methodAspects) { |
| if (methodAspect.matches(declaringClass)) { |
| applicableAspects.add(methodAspect); |
| } |
| } |
| |
| if (applicableAspects.isEmpty()) { |
| interceptors = ImmutableMap.of(); |
| methods = ImmutableList.of(); |
| callbacks = null; |
| return; |
| } |
| |
| // Get list of methods from cglib. |
| methods = Lists.newArrayList(); |
| Enhancer.getMethods(declaringClass, null, methods); |
| |
| // Create method/interceptor holders and record indices. |
| List<MethodInterceptorsPair> methodInterceptorsPairs = Lists.newArrayList(); |
| for (Method method : methods) { |
| methodInterceptorsPairs.add(new MethodInterceptorsPair(method)); |
| } |
| |
| // Iterate over aspects and add interceptors for the methods they apply to |
| boolean anyMatched = false; |
| for (MethodAspect methodAspect : applicableAspects) { |
| for (MethodInterceptorsPair pair : methodInterceptorsPairs) { |
| if (methodAspect.matches(pair.method)) { |
| visibility = visibility.and(BytecodeGen.Visibility.forMember(pair.method)); |
| pair.addAll(methodAspect.interceptors()); |
| anyMatched = true; |
| } |
| } |
| } |
| |
| if (!anyMatched) { |
| interceptors = ImmutableMap.of(); |
| callbacks = null; |
| return; |
| } |
| |
| ImmutableMap.Builder<Method, List<MethodInterceptor>> interceptorsMapBuilder = null; // lazy |
| |
| callbacks = new Callback[methods.size()]; |
| for (int i = 0; i < methods.size(); i++) { |
| MethodInterceptorsPair pair = methodInterceptorsPairs.get(i); |
| |
| if (!pair.hasInterceptors()) { |
| callbacks[i] = NO_OP_METHOD_INTERCEPTOR; |
| continue; |
| } |
| |
| if (interceptorsMapBuilder == null) { |
| interceptorsMapBuilder = ImmutableMap.builder(); |
| } |
| |
| interceptorsMapBuilder.put(pair.method, ImmutableList.copyOf(pair.interceptors)); |
| callbacks[i] = new InterceptorStackCallback(pair.method, pair.interceptors); |
| } |
| |
| interceptors = interceptorsMapBuilder != null |
| ? interceptorsMapBuilder.build() |
| : ImmutableMap.<Method, List<MethodInterceptor>>of(); |
| } |
| |
| /** |
| * Returns the interceptors that apply to the constructed type. |
| */ |
| public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() { |
| return interceptors; |
| } |
| |
| public ConstructionProxy<T> create() { |
| if (interceptors.isEmpty()) { |
| return new DefaultConstructionProxyFactory<T>(injectionPoint).create(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| Class<? extends Callback>[] callbackTypes = new Class[methods.size()]; |
| Arrays.fill(callbackTypes, net.sf.cglib.proxy.MethodInterceptor.class); |
| |
| // Create the proxied class. We're careful to ensure that all enhancer state is not-specific |
| // to this injector. Otherwise, the proxies for each injector will waste PermGen memory |
| try { |
| Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility); |
| enhancer.setCallbackFilter(new IndicesCallbackFilter(declaringClass, methods)); |
| enhancer.setCallbackTypes(callbackTypes); |
| return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors); |
| } catch (Throwable e) { |
| throw new ProvisionException("Unable to method intercept: " + declaringClass, e); |
| } |
| } |
| |
| private static class MethodInterceptorsPair { |
| final Method method; |
| List<MethodInterceptor> interceptors; // lazy |
| |
| MethodInterceptorsPair(Method method) { |
| this.method = method; |
| } |
| |
| void addAll(List<MethodInterceptor> interceptors) { |
| if (this.interceptors == null) { |
| this.interceptors = Lists.newArrayList(); |
| } |
| this.interceptors.addAll(interceptors); |
| } |
| |
| boolean hasInterceptors() { |
| return interceptors != null; |
| } |
| } |
| |
| /** |
| * A callback filter that maps methods to unique IDs. We define equals and hashCode using the |
| * declaring class so that enhanced classes can be shared between injectors. |
| */ |
| private static class IndicesCallbackFilter implements CallbackFilter { |
| final Class<?> declaringClass; |
| final Map<Method, Integer> indices; |
| |
| IndicesCallbackFilter(Class<?> declaringClass, List<Method> methods) { |
| this.declaringClass = declaringClass; |
| final Map<Method, Integer> indices = Maps.newHashMap(); |
| for (int i = 0; i < methods.size(); i++) { |
| Method method = methods.get(i); |
| indices.put(method, i); |
| } |
| |
| this.indices = indices; |
| } |
| |
| public int accept(Method method) { |
| return indices.get(method); |
| } |
| |
| @Override public boolean equals(Object o) { |
| return o instanceof IndicesCallbackFilter && |
| ((IndicesCallbackFilter) o).declaringClass == declaringClass; |
| } |
| |
| @Override public int hashCode() { |
| return declaringClass.hashCode(); |
| } |
| } |
| |
| /** |
| * Constructs instances that participate in AOP. |
| */ |
| private static class ProxyConstructor<T> implements ConstructionProxy<T> { |
| final Class<?> enhanced; |
| final InjectionPoint injectionPoint; |
| final Constructor<T> constructor; |
| final Callback[] callbacks; |
| |
| final FastConstructor fastConstructor; |
| final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors; |
| |
| @SuppressWarnings("unchecked") // the constructor promises to construct 'T's |
| ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks, |
| ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) { |
| this.enhanced = enhancer.createClass(); // this returns a cached class if possible |
| this.injectionPoint = injectionPoint; |
| this.constructor = (Constructor<T>) injectionPoint.getMember(); |
| this.callbacks = callbacks; |
| this.methodInterceptors = methodInterceptors; |
| |
| FastClass fastClass = newFastClass(enhanced, BytecodeGen.Visibility.forMember(constructor)); |
| this.fastConstructor = fastClass.getConstructor(constructor.getParameterTypes()); |
| } |
| |
| @SuppressWarnings("unchecked") // the constructor promises to produce 'T's |
| public T newInstance(Object[] arguments) throws InvocationTargetException { |
| Enhancer.registerCallbacks(enhanced, callbacks); |
| try { |
| return (T) fastConstructor.newInstance(arguments); |
| } finally { |
| Enhancer.registerCallbacks(enhanced, null); |
| } |
| } |
| |
| public InjectionPoint getInjectionPoint() { |
| return injectionPoint; |
| } |
| |
| public Constructor<T> getConstructor() { |
| return constructor; |
| } |
| |
| public ImmutableMap<Method, List<MethodInterceptor>> getMethodInterceptors() { |
| return methodInterceptors; |
| } |
| } |
| } |