blob: 39e08a0e9d9615cb276938737127fbe52bcbdbc1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.proxy2.javassist;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.proxy2.Invocation;
import org.apache.commons.proxy2.ProxyUtils;
/**
* A <a href="http://www.jboss.org/products/javassist">Javassist</a>-based {@link Invocation} implementation. This class
* actually serves as the superclass for all <a href="http://www.jboss.org/products/javassist">Javassist</a>-based
* method invocations. Subclasses are dynamically created to deal with specific interface methods (they're hard-wired).
*
* @author James Carman
* @since 1.0
*/
public abstract class JavassistInvocation implements Invocation
{
//******************************************************************************************************************
// Fields
//******************************************************************************************************************
private static WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>> loaderToClassCache
= new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>();
/** The proxy object */
private final Object proxy;
/** The target object */
private final Object target;
/** The invoked method */
private final Method method;
/** The method arguments */
private final Object[] arguments;
//******************************************************************************************************************
// Static Methods
//******************************************************************************************************************
private static String createCastExpression(Class<?> type, String objectToCast)
{
if (!type.isPrimitive())
{
return "( " + ProxyUtils.getJavaClassName(type) + " )" + objectToCast;
}
else
{
return "( ( " + ProxyUtils.getWrapperClass(type).getName() + " )" + objectToCast + " )." + type.getName()
+ "Value()";
}
}
private static Class<?> createInvocationClass(ClassLoader classLoader, Method interfaceMethod)
throws CannotCompileException
{
final CtClass ctClass = JavassistUtils.createClass(getSimpleName(interfaceMethod.getDeclaringClass()) + "_"
+ interfaceMethod.getName() + "_invocation", JavassistInvocation.class);
final CtConstructor constructor = new CtConstructor(JavassistUtils.resolve(new Class[] { Object.class,
Object.class, Method.class, Object[].class }), ctClass);
constructor.setBody("{\n\tsuper($$);\n}");
ctClass.addConstructor(constructor);
final CtMethod proceedMethod = new CtMethod(JavassistUtils.resolve(Object.class), "proceed",
JavassistUtils.resolve(new Class[0]), ctClass);
final Class<?>[] argumentTypes = interfaceMethod.getParameterTypes();
final StringBuilder proceedBody = new StringBuilder("{\n");
if (!Void.TYPE.equals(interfaceMethod.getReturnType()))
{
proceedBody.append("\treturn ");
if (interfaceMethod.getReturnType().isPrimitive())
{
proceedBody.append("new ");
proceedBody.append(ProxyUtils.getWrapperClass(interfaceMethod.getReturnType()).getName());
proceedBody.append("( ");
}
}
else
{
proceedBody.append("\t");
}
proceedBody.append("( (");
proceedBody.append(ProxyUtils.getJavaClassName(interfaceMethod.getDeclaringClass()));
proceedBody.append(" )getTarget() ).");
proceedBody.append(interfaceMethod.getName());
proceedBody.append("(");
for (int i = 0; i < argumentTypes.length; ++i)
{
final Class<?> argumentType = argumentTypes[i];
proceedBody.append(createCastExpression(argumentType, "getArguments()[" + i + "]"));
if (i != argumentTypes.length - 1)
{
proceedBody.append(", ");
}
}
if (!Void.TYPE.equals(interfaceMethod.getReturnType()) && interfaceMethod.getReturnType().isPrimitive())
{
proceedBody.append(") );\n");
}
else
{
proceedBody.append(");\n");
}
if (Void.TYPE.equals(interfaceMethod.getReturnType()))
{
proceedBody.append("\treturn null;\n");
}
proceedBody.append("}");
final String body = proceedBody.toString();
proceedMethod.setBody(body);
ctClass.addMethod(proceedMethod);
@SuppressWarnings("deprecation")
final Class<?> invocationClass = ctClass.toClass(classLoader);
return invocationClass;
}
private static Map<String, WeakReference<Class<?>>> getClassCache(ClassLoader classLoader)
{
Map<String, WeakReference<Class<?>>> cache = loaderToClassCache.get(classLoader);
if (cache == null)
{
cache = new HashMap<String, WeakReference<Class<?>>>();
loaderToClassCache.put(classLoader, cache);
}
return cache;
}
/**
* Returns a method invocation class specifically coded to invoke the supplied interface method.
*
* @param classLoader
* the classloader to use
* @param interfaceMethod
* the interface method
* @return a method invocation class specifically coded to invoke the supplied interface method
* @throws CannotCompileException
* if a compilation error occurs
*/
static synchronized Class<?> getMethodInvocationClass(ClassLoader classLoader, Method interfaceMethod)
throws CannotCompileException
{
final Map<String, WeakReference<Class<?>>> classCache = getClassCache(classLoader);
final String key = toClassCacheKey(interfaceMethod);
final WeakReference<Class<?>> invocationClassRef = classCache.get(key);
Class<?> invocationClass;
if (invocationClassRef == null)
{
invocationClass = createInvocationClass(classLoader, interfaceMethod);
classCache.put(key, new WeakReference<Class<?>>(invocationClass));
}
else
{
synchronized (invocationClassRef)
{
invocationClass = invocationClassRef.get();
if (invocationClass == null)
{
invocationClass = createInvocationClass(classLoader, interfaceMethod);
classCache.put(key, new WeakReference<Class<?>>(invocationClass));
}
}
}
return invocationClass;
}
private static String getSimpleName(Class<?> c)
{
final String name = c.getName();
final int ndx = name.lastIndexOf('.');
return ndx == -1 ? name : name.substring(ndx + 1);
}
private static String toClassCacheKey(Method method)
{
return String.valueOf(method);
}
//******************************************************************************************************************
// Constructors
//******************************************************************************************************************
protected JavassistInvocation(Object proxy, Object target, Method method, Object[] arguments)
{
this.proxy = proxy;
this.target = target;
this.method = method;
this.arguments = ObjectUtils.defaultIfNull(ArrayUtils.clone(arguments), ProxyUtils.EMPTY_ARGUMENTS);
}
//******************************************************************************************************************
// Invocation Implementation
//******************************************************************************************************************
protected final Object getTarget()
{
return target;
}
public Object[] getArguments()
{
return arguments;
}
public Method getMethod()
{
return method;
}
public Object getProxy()
{
return proxy;
}
}