001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.proxy2.javassist;
019
020import java.lang.ref.WeakReference;
021import java.lang.reflect.Method;
022import java.util.HashMap;
023import java.util.Map;
024import java.util.WeakHashMap;
025
026import javassist.CannotCompileException;
027import javassist.CtClass;
028import javassist.CtConstructor;
029import javassist.CtMethod;
030
031import org.apache.commons.lang3.ArrayUtils;
032import org.apache.commons.lang3.ObjectUtils;
033import org.apache.commons.proxy2.Invocation;
034import org.apache.commons.proxy2.ProxyUtils;
035
036/**
037 * A <a href="http://www.jboss.org/products/javassist">Javassist</a>-based {@link Invocation} implementation. This class
038 * actually serves as the superclass for all <a href="http://www.jboss.org/products/javassist">Javassist</a>-based
039 * method invocations. Subclasses are dynamically created to deal with specific interface methods (they're hard-wired).
040 * 
041 * @since 1.0
042 */
043public abstract class JavassistInvocation implements Invocation
044{
045    //******************************************************************************************************************
046    // Fields
047    //******************************************************************************************************************
048
049    private static WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>> loaderToClassCache
050        = new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>();
051
052    /** The proxy object */
053    private final Object proxy;
054
055    /** The target object */
056    private final Object target;
057
058    /** The invoked method */
059    private final Method method;
060
061    /** The method arguments */
062    private final Object[] arguments;
063
064    //******************************************************************************************************************
065    // Static Methods
066    //******************************************************************************************************************
067
068    private static String createCastExpression(Class<?> type, String objectToCast)
069    {
070        if (!type.isPrimitive())
071        {
072            return "( " + ProxyUtils.getJavaClassName(type) + " )" + objectToCast;
073        }
074        else
075        {
076            return "( ( " + ProxyUtils.getWrapperClass(type).getName() + " )" + objectToCast + " )." + type.getName()
077                    + "Value()";
078        }
079    }
080
081    private static Class<?> createInvocationClass(ClassLoader classLoader, Method interfaceMethod)
082            throws CannotCompileException
083    {
084        final CtClass ctClass = JavassistUtils.createClass(getSimpleName(interfaceMethod.getDeclaringClass()) + "_"
085                + interfaceMethod.getName() + "_invocation", JavassistInvocation.class);
086        final CtConstructor constructor = new CtConstructor(JavassistUtils.resolve(new Class[] { Object.class,
087                Object.class, Method.class, Object[].class }), ctClass);
088        constructor.setBody("{\n\tsuper($$);\n}");
089        ctClass.addConstructor(constructor);
090        final CtMethod proceedMethod = new CtMethod(JavassistUtils.resolve(Object.class), "proceed",
091                JavassistUtils.resolve(new Class[0]), ctClass);
092        final Class<?>[] argumentTypes = interfaceMethod.getParameterTypes();
093        final StringBuilder proceedBody = new StringBuilder("{\n");
094        if (!Void.TYPE.equals(interfaceMethod.getReturnType()))
095        {
096            proceedBody.append("\treturn ");
097            if (interfaceMethod.getReturnType().isPrimitive())
098            {
099                proceedBody.append("new ");
100                proceedBody.append(ProxyUtils.getWrapperClass(interfaceMethod.getReturnType()).getName());
101                proceedBody.append("( ");
102            }
103        }
104        else
105        {
106            proceedBody.append("\t");
107        }
108        proceedBody.append("( (");
109        proceedBody.append(ProxyUtils.getJavaClassName(interfaceMethod.getDeclaringClass()));
110        proceedBody.append(" )getTarget() ).");
111        proceedBody.append(interfaceMethod.getName());
112        proceedBody.append("(");
113        for (int i = 0; i < argumentTypes.length; ++i)
114        {
115            final Class<?> argumentType = argumentTypes[i];
116            proceedBody.append(createCastExpression(argumentType, "getArguments()[" + i + "]"));
117            if (i != argumentTypes.length - 1)
118            {
119                proceedBody.append(", ");
120            }
121        }
122        if (!Void.TYPE.equals(interfaceMethod.getReturnType()) && interfaceMethod.getReturnType().isPrimitive())
123        {
124            proceedBody.append(") );\n");
125        }
126        else
127        {
128            proceedBody.append(");\n");
129        }
130        if (Void.TYPE.equals(interfaceMethod.getReturnType()))
131        {
132            proceedBody.append("\treturn null;\n");
133        }
134        proceedBody.append("}");
135        final String body = proceedBody.toString();
136        proceedMethod.setBody(body);
137        ctClass.addMethod(proceedMethod);
138
139        @SuppressWarnings("deprecation")
140        final Class<?> invocationClass = ctClass.toClass(classLoader);
141        return invocationClass;
142    }
143
144    private static Map<String, WeakReference<Class<?>>> getClassCache(ClassLoader classLoader)
145    {
146        Map<String, WeakReference<Class<?>>> cache = loaderToClassCache.get(classLoader);
147        if (cache == null)
148        {
149            cache = new HashMap<String, WeakReference<Class<?>>>();
150            loaderToClassCache.put(classLoader, cache);
151        }
152        return cache;
153    }
154
155    /**
156     * Returns a method invocation class specifically coded to invoke the supplied interface method.
157     * 
158     * @param classLoader
159     *            the classloader to use
160     * @param interfaceMethod
161     *            the interface method
162     * @return a method invocation class specifically coded to invoke the supplied interface method
163     * @throws CannotCompileException
164     *             if a compilation error occurs
165     */
166    static synchronized Class<?> getMethodInvocationClass(ClassLoader classLoader, Method interfaceMethod)
167            throws CannotCompileException
168    {
169        final Map<String, WeakReference<Class<?>>> classCache = getClassCache(classLoader);
170        final String key = toClassCacheKey(interfaceMethod);
171        final WeakReference<Class<?>> invocationClassRef = classCache.get(key);
172        Class<?> invocationClass;
173        if (invocationClassRef == null)
174        {
175            invocationClass = createInvocationClass(classLoader, interfaceMethod);
176            classCache.put(key, new WeakReference<Class<?>>(invocationClass));
177        }
178        else
179        {
180            synchronized (invocationClassRef)
181            {
182                invocationClass = invocationClassRef.get();
183                if (invocationClass == null)
184                {
185                    invocationClass = createInvocationClass(classLoader, interfaceMethod);
186                    classCache.put(key, new WeakReference<Class<?>>(invocationClass));
187                }
188            }
189        }
190        return invocationClass;
191    }
192
193    private static String getSimpleName(Class<?> c)
194    {
195        final String name = c.getName();
196        final int ndx = name.lastIndexOf('.');
197        return ndx == -1 ? name : name.substring(ndx + 1);
198    }
199
200    private static String toClassCacheKey(Method method)
201    {
202        return String.valueOf(method);
203    }
204
205    //******************************************************************************************************************
206    // Constructors
207    //******************************************************************************************************************
208
209    protected JavassistInvocation(Object proxy, Object target, Method method, Object[] arguments)
210    {
211        this.proxy = proxy;
212        this.target = target;
213        this.method = method;
214        this.arguments = ObjectUtils.defaultIfNull(ArrayUtils.clone(arguments), ProxyUtils.EMPTY_ARGUMENTS);
215    }
216
217    //******************************************************************************************************************
218    // Invocation Implementation
219    //******************************************************************************************************************
220    protected final Object getTarget()
221    {
222        return target;
223    }
224
225    @Override
226    public Object[] getArguments()
227    {
228        return arguments;
229    }
230
231    @Override
232    public Method getMethod()
233    {
234        return method;
235    }
236
237    @Override
238    public Object getProxy()
239    {
240        return proxy;
241    }
242}