package com.google.inject.internal;
import static com.google.inject.internal.BytecodeGen.newFastClassForMember;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.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.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.cglib.core.MethodWrapper;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.reflect.FastClass;
import org.aopalliance.intercept.MethodInterceptor;
final class ProxyFactory<T> implements ConstructionProxyFactory<T> {
private static final Logger logger = Logger.getLogger(ProxyFactory.class.getName());
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;
private BytecodeGen.Visibility visibility = BytecodeGen.Visibility.PUBLIC;
ProxyFactory(InjectionPoint injectionPoint, Iterable<MethodAspect> methodAspects) {
this.injectionPoint = injectionPoint;
@SuppressWarnings("unchecked")
Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
declaringClass = constructor.getDeclaringClass();
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;
}
methods = Lists.newArrayList();
Enhancer.getMethods(declaringClass, null, methods);
List<MethodInterceptorsPair> methodInterceptorsPairs = Lists.newArrayList();
for (Method method : methods) {
methodInterceptorsPairs.add(new MethodInterceptorsPair(method));
}
boolean anyMatched = false;
for (MethodAspect methodAspect : applicableAspects) {
for (MethodInterceptorsPair pair : methodInterceptorsPairs) {
if (methodAspect.matches(pair.method)) {
if (pair.method.isSynthetic()) {
logger.log(
Level.WARNING,
"Method [{0}] is synthetic and is being intercepted by {1}."
+ " This could indicate a bug. The method may be intercepted twice,"
+ " or may not be intercepted at all.",
new Object[] {pair.method, methodAspect.interceptors()});
}
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;
callbacks = new Callback[methods.size()];
for (int i = 0; i < methods.size(); i++) {
MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
if (!pair.hasInterceptors()) {
callbacks[i] = net.sf.cglib.proxy.NoOp.INSTANCE;
continue;
}
if (interceptorsMapBuilder == null) {
interceptorsMapBuilder = ImmutableMap.builder();
}
ImmutableList<MethodInterceptor> deDuplicated =
ImmutableSet.copyOf(pair.interceptors).asList();
interceptorsMapBuilder.put(pair.method, deDuplicated);
callbacks[i] = new InterceptorStackCallback(pair.method, deDuplicated);
}
interceptors =
interceptorsMapBuilder != null
? interceptorsMapBuilder.build()
: ImmutableMap.<Method, List<MethodInterceptor>>of();
}
public ImmutableMap<Method, List<MethodInterceptor>> getInterceptors() {
return interceptors;
}
@Override
public ConstructionProxy<T> create() throws ErrorsException {
if (interceptors.isEmpty()) {
return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
}
@SuppressWarnings("unchecked")
Class<? extends Callback>[] callbackTypes = new Class[callbacks.length];
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] == net.sf.cglib.proxy.NoOp.INSTANCE) {
callbackTypes[i] = net.sf.cglib.proxy.NoOp.class;
} else {
callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
}
}
try {
Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
enhancer.setCallbackFilter(new IndicesCallbackFilter(methods));
enhancer.setCallbackTypes(callbackTypes);
return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
} catch (Throwable e) {
throw new Errors().errorEnhancingClass(declaringClass, e).toException();
}
}
private static class MethodInterceptorsPair {
final Method method;
List<MethodInterceptor> interceptors;
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;
}
}
private static class IndicesCallbackFilter implements CallbackFilter {
final Map<Object, Integer> indices;
final int hashCode;
IndicesCallbackFilter(List<Method> methods) {
final Map<Object, Integer> indices = Maps.newHashMap();
for (int i = 0; i < methods.size(); i++) {
indices.put(MethodWrapper.create(methods.get(i)), i);
}
this.indices = indices;
this.hashCode = indices.hashCode();
}
@Override
public int accept(Method method) {
return indices.get(MethodWrapper.create(method));
}
@Override
public boolean equals(Object o) {
return o instanceof IndicesCallbackFilter
&& ((IndicesCallbackFilter) o).indices.equals(indices);
}
@Override
public int hashCode() {
return hashCode;
}
}
private static class ProxyConstructor<T> implements ConstructionProxy<T> {
final Class<?> enhanced;
final InjectionPoint injectionPoint;
final Constructor<T> constructor;
final Callback[] callbacks;
final int constructorIndex;
final ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors;
final FastClass fastClass;
@SuppressWarnings("unchecked")
ProxyConstructor(
Enhancer enhancer,
InjectionPoint injectionPoint,
Callback[] callbacks,
ImmutableMap<Method, List<MethodInterceptor>> methodInterceptors) {
this.enhanced = enhancer.createClass();
this.injectionPoint = injectionPoint;
this.constructor = (Constructor<T>) injectionPoint.getMember();
this.callbacks = callbacks;
this.methodInterceptors = methodInterceptors;
this.fastClass = newFastClassForMember(enhanced, constructor);
this.constructorIndex = fastClass.getIndex(constructor.getParameterTypes());
}
@Override
@SuppressWarnings("unchecked")
public T newInstance(Object... arguments) throws InvocationTargetException {
Enhancer.registerCallbacks(enhanced, callbacks);
try {
return (T) fastClass.newInstance(constructorIndex, arguments);
} finally {
Enhancer.registerCallbacks(enhanced, null);
}
}
@Override
public InjectionPoint getInjectionPoint() {
return injectionPoint;
}
@Override
public Constructor<T> getConstructor() {
return constructor;
}
@Override
public ImmutableMap<Method, List<MethodInterceptor>> getMethodInterceptors() {
return methodInterceptors;
}
}
}