Home | History | Annotate | Download | only in internal
      1 /**
      2  * Copyright (C) 2009 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  */
     16 
     17 package com.google.inject.internal;
     18 
     19 import static com.google.inject.internal.BytecodeGen.newFastClass;
     20 
     21 import com.google.common.collect.ImmutableList;
     22 import com.google.common.collect.ImmutableMap;
     23 import com.google.common.collect.ImmutableSet;
     24 import com.google.common.collect.Lists;
     25 import com.google.common.collect.Maps;
     26 import com.google.inject.spi.InjectionPoint;
     27 
     28 import net.sf.cglib.core.MethodWrapper;
     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.reflect.FastClass;
     33 import net.sf.cglib.reflect.FastConstructor;
     34 
     35 import org.aopalliance.intercept.MethodInterceptor;
     36 
     37 import java.lang.reflect.Constructor;
     38 import java.lang.reflect.InvocationTargetException;
     39 import java.lang.reflect.Method;
     40 import java.util.List;
     41 import java.util.Map;
     42 import java.util.logging.Level;
     43 import java.util.logging.Logger;
     44 
     45 /**
     46  * Builds a construction proxy that can participate in AOP. This class manages applying type and
     47  * method matchers to come up with the set of intercepted methods.
     48  *
     49  * @author jessewilson (at) google.com (Jesse Wilson)
     50  */
     51 final class ProxyFactory<T> implements ConstructionProxyFactory<T> {
     52 
     53   private static final Logger logger = Logger.getLogger(ProxyFactory.class.getName());
     54 
     55   private final InjectionPoint injectionPoint;
     56   private final ImmutableMap<Method, List<MethodInterceptor>> interceptors;
     57   private final Class<T> declaringClass;
     58   private final List<Method> methods;
     59   private final Callback[] callbacks;
     60 
     61   /**
     62    * PUBLIC is default; it's used if all the methods we're intercepting are public. This impacts
     63    * which classloader we should use for loading the enhanced class
     64    */
     65   private BytecodeGen.Visibility visibility = BytecodeGen.Visibility.PUBLIC;
     66 
     67   ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects) {
     68     this.injectionPoint = injectionPoint;
     69 
     70     @SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T>
     71         Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
     72     declaringClass = constructor.getDeclaringClass();
     73 
     74     // Find applicable aspects. Bow out if none are applicable to this class.
     75     List<MethodAspect> applicableAspects = Lists.newArrayList();
     76     for (MethodAspect methodAspect : methodAspects) {
     77       if (methodAspect.matches(declaringClass)) {
     78         applicableAspects.add(methodAspect);
     79       }
     80     }
     81 
     82     if (applicableAspects.isEmpty()) {
     83       interceptors = ImmutableMap.of();
     84       methods = ImmutableList.of();
     85       callbacks = null;
     86       return;
     87     }
     88 
     89     // Get list of methods from cglib.
     90     methods = Lists.newArrayList();
     91     Enhancer.getMethods(declaringClass, null, methods);
     92 
     93     // Create method/interceptor holders and record indices.
     94     List<MethodInterceptorsPair> methodInterceptorsPairs = Lists.newArrayList();
     95     for (Method method : methods) {
     96       methodInterceptorsPairs.add(new MethodInterceptorsPair(method));
     97     }
     98 
     99     // Iterate over aspects and add interceptors for the methods they apply to
    100     boolean anyMatched = false;
    101     for (MethodAspect methodAspect : applicableAspects) {
    102       for (MethodInterceptorsPair pair : methodInterceptorsPairs) {
    103         if (methodAspect.matches(pair.method)) {
    104           if(pair.method.isSynthetic()) {
    105             logger.log(Level.WARNING,
    106                 "Method [{0}] is synthetic and is being intercepted by {1}."
    107               + " This could indicate a bug.  The method may be intercepted twice,"
    108               + " or may not be intercepted at all.",
    109                 new Object[] { pair.method, methodAspect.interceptors() });
    110           }
    111           visibility = visibility.and(BytecodeGen.Visibility.forMember(pair.method));
    112           pair.addAll(methodAspect.interceptors());
    113           anyMatched = true;
    114         }
    115       }
    116     }
    117 
    118     if (!anyMatched) {
    119       interceptors = ImmutableMap.of();
    120       callbacks = null;
    121       return;
    122     }
    123 
    124     ImmutableMap.Builder<Method, List<MethodInterceptor>> interceptorsMapBuilder = null; // lazy
    125 
    126     callbacks = new Callback[methods.size()];
    127     for (int i = 0; i < methods.size(); i++) {
    128       MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
    129 
    130       if (!pair.hasInterceptors()) {
    131         callbacks[i] = net.sf.cglib.proxy.NoOp.INSTANCE;
    132         continue;
    133       }
    134 
    135       if (interceptorsMapBuilder == null) {
    136         interceptorsMapBuilder = ImmutableMap.builder();
    137       }
    138 
    139       ImmutableList<MethodInterceptor> deDuplicated =
    140           ImmutableSet.copyOf(pair.interceptors).asList();
    141       interceptorsMapBuilder.put(pair.method, deDuplicated);
    142       callbacks[i] = new InterceptorStackCallback(pair.method, deDuplicated);
    143     }
    144 
    145     interceptors = interceptorsMapBuilder != null
    146         ? interceptorsMapBuilder.build()
    147         : ImmutableMap.<Method, List<MethodInterceptor>>of();
    148   }
    149 
    150   /**
    151    * Returns the interceptors that apply to the constructed type.
    152    */
    153   public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() {
    154     return interceptors;
    155   }
    156 
    157   public ConstructionProxy<T> create() throws ErrorsException {
    158     if (interceptors.isEmpty()) {
    159       return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
    160     }
    161 
    162     @SuppressWarnings("unchecked")
    163     Class<? extends Callback>[] callbackTypes = new Class[callbacks.length];
    164     for (int i = 0; i < callbacks.length; i++) {
    165       if (callbacks[i] == net.sf.cglib.proxy.NoOp.INSTANCE) {
    166         callbackTypes[i] = net.sf.cglib.proxy.NoOp.class;
    167       } else {
    168         callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
    169       }
    170     }
    171 
    172     // Create the proxied class. We're careful to ensure that all enhancer state is not-specific
    173     // to this injector. Otherwise, the proxies for each injector will waste PermGen memory
    174     try {
    175     Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
    176     enhancer.setCallbackFilter(new IndicesCallbackFilter(methods));
    177     enhancer.setCallbackTypes(callbackTypes);
    178     return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
    179     } catch (Throwable e) {
    180       throw new Errors().errorEnhancingClass(declaringClass, e).toException();
    181     }
    182   }
    183 
    184   private static class MethodInterceptorsPair {
    185     final Method method;
    186     List<MethodInterceptor> interceptors; // lazy
    187 
    188     MethodInterceptorsPair(Method method) {
    189       this.method = method;
    190     }
    191 
    192     void addAll(List<MethodInterceptor> interceptors) {
    193       if (this.interceptors == null) {
    194         this.interceptors = Lists.newArrayList();
    195       }
    196       this.interceptors.addAll(interceptors);
    197     }
    198 
    199     boolean hasInterceptors() {
    200       return interceptors != null;
    201     }
    202   }
    203 
    204   /**
    205    * A callback filter that maps methods to unique IDs. We define equals and
    206    * hashCode without using any state related to the injector so that enhanced
    207    * classes intercepting the same methods can be shared between injectors (and
    208    * child injectors, etc).
    209    */
    210   private static class IndicesCallbackFilter implements CallbackFilter {
    211     final Map<Object, Integer> indices;
    212     final int hashCode;
    213 
    214     IndicesCallbackFilter(List<Method> methods) {
    215       final Map<Object, Integer> indices = Maps.newHashMap();
    216       for (int i = 0; i < methods.size(); i++) {
    217         indices.put(MethodWrapper.create(methods.get(i)), i);
    218       }
    219       this.indices = indices;
    220       this.hashCode = indices.hashCode();
    221     }
    222 
    223     public int accept(Method method) {
    224       return indices.get(MethodWrapper.create(method));
    225     }
    226 
    227     @Override public boolean equals(Object o) {
    228       return o instanceof IndicesCallbackFilter &&
    229           ((IndicesCallbackFilter) o).indices.equals(indices);
    230     }
    231 
    232     @Override public int hashCode() {
    233       return hashCode;
    234     }
    235   }
    236 
    237   /**
    238    * Constructs instances that participate in AOP.
    239    */
    240   private static class ProxyConstructor<T> implements ConstructionProxy<T> {
    241     final Class<?> enhanced;
    242     final InjectionPoint injectionPoint;
    243     final Constructor<T> constructor;
    244     final Callback[] callbacks;
    245 
    246     final FastConstructor fastConstructor;
    247     final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors;
    248 
    249     @SuppressWarnings("unchecked") // the constructor promises to construct 'T's
    250     ProxyConstructor(Enhancer enhancer, InjectionPoint injectionPoint, Callback[] callbacks,
    251         ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) {
    252       this.enhanced = enhancer.createClass(); // this returns a cached class if possible
    253       this.injectionPoint = injectionPoint;
    254       this.constructor = (Constructor<T>) injectionPoint.getMember();
    255       this.callbacks = callbacks;
    256       this.methodInterceptors = methodInterceptors;
    257 
    258       FastClass fastClass = newFastClass(enhanced, BytecodeGen.Visibility.forMember(constructor));
    259       this.fastConstructor = fastClass.getConstructor(constructor.getParameterTypes());
    260     }
    261 
    262     @SuppressWarnings("unchecked") // the constructor promises to produce 'T's
    263     public T newInstance(Object... arguments) throws InvocationTargetException {
    264       Enhancer.registerCallbacks(enhanced, callbacks);
    265       try {
    266         return (T) fastConstructor.newInstance(arguments);
    267       } finally {
    268         Enhancer.registerCallbacks(enhanced, null);
    269       }
    270     }
    271 
    272     public InjectionPoint getInjectionPoint() {
    273       return injectionPoint;
    274     }
    275 
    276     public Constructor<T> getConstructor() {
    277       return constructor;
    278     }
    279 
    280     public ImmutableMap<Method, List<MethodInterceptor>> getMethodInterceptors() {
    281       return methodInterceptors;
    282     }
    283   }
    284 }
    285