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.reflect.Method;
021
022import javassist.CannotCompileException;
023import javassist.CtClass;
024import javassist.CtConstructor;
025import javassist.CtMethod;
026
027import org.apache.commons.proxy2.Interceptor;
028import org.apache.commons.proxy2.Invoker;
029import org.apache.commons.proxy2.ObjectProvider;
030import org.apache.commons.proxy2.ProxyUtils;
031import org.apache.commons.proxy2.exception.ProxyFactoryException;
032import org.apache.commons.proxy2.impl.AbstractProxyClassGenerator;
033import org.apache.commons.proxy2.impl.AbstractSubclassingProxyFactory;
034import org.apache.commons.proxy2.impl.ProxyClassCache;
035
036public class JavassistProxyFactory extends AbstractSubclassingProxyFactory
037{
038    //******************************************************************************************************************
039    // Fields
040    //******************************************************************************************************************
041
042    private static final String GET_METHOD_METHOD_NAME = "_javassistGetMethod";
043
044    private static final ProxyClassCache DELEGATING_PROXY_CACHE = new ProxyClassCache(
045            new DelegatingProxyClassGenerator());
046    private static final ProxyClassCache INTERCEPTOR_PROXY_CACHE = new ProxyClassCache(
047            new InterceptorProxyClassGenerator());
048    private static final ProxyClassCache INVOKER_PROXY_CACHE = new ProxyClassCache(new InvokerProxyClassGenerator());
049
050    //******************************************************************************************************************
051    // Static Methods
052    //******************************************************************************************************************
053
054    private static void addGetMethodMethod(CtClass proxyClass) throws CannotCompileException
055    {
056        final CtMethod method = new CtMethod(JavassistUtils.resolve(Method.class), GET_METHOD_METHOD_NAME,
057                JavassistUtils.resolve(new Class[] { String.class, String.class, Class[].class }), proxyClass);
058        final String body = "try { return Class.forName($1).getMethod($2, $3); } catch( Exception e ) "
059                + "{ throw new RuntimeException(\"Unable to look up method.\", e); }";
060        method.setBody(body);
061        proxyClass.addMethod(method);
062    }
063
064    //******************************************************************************************************************
065    // ProxyFactory Implementation
066    //******************************************************************************************************************
067
068    /**
069     * {@inheritDoc}
070     */
071    @Override
072    public <T> T createDelegatorProxy(ClassLoader classLoader, ObjectProvider<?> targetProvider,
073            Class<?>... proxyClasses)
074    {
075        try
076        {
077            @SuppressWarnings("unchecked") // type inference
078            final Class<? extends T> clazz = (Class<? extends T>) DELEGATING_PROXY_CACHE.getProxyClass(classLoader,
079                    proxyClasses);
080            return clazz.getConstructor(ObjectProvider.class).newInstance(targetProvider);
081        }
082        catch (Exception e)
083        {
084            throw new ProxyFactoryException("Unable to instantiate proxy2 from generated proxy2 class.", e);
085        }
086    }
087
088    /**
089     * {@inheritDoc}
090     */
091    @Override
092    public <T> T createInterceptorProxy(ClassLoader classLoader, Object target, Interceptor interceptor,
093            Class<?>... proxyClasses)
094    {
095        try
096        {
097            @SuppressWarnings("unchecked") // type inference
098            final Class<? extends T> clazz = (Class<? extends T>) INTERCEPTOR_PROXY_CACHE.getProxyClass(classLoader,
099                    proxyClasses);
100            return clazz.getConstructor(Object.class, Interceptor.class).newInstance(target, interceptor);
101        }
102        catch (Exception e)
103        {
104            throw new ProxyFactoryException("Unable to instantiate proxy2 class instance.", e);
105        }
106    }
107
108    /**
109     * {@inheritDoc}
110     */
111    @Override
112    public <T> T createInvokerProxy(ClassLoader classLoader, Invoker invoker, Class<?>... proxyClasses)
113    {
114        try
115        {
116            @SuppressWarnings("unchecked") // type inference
117            final Class<? extends T> clazz = (Class<? extends T>) INVOKER_PROXY_CACHE.getProxyClass(classLoader,
118                    proxyClasses);
119            return clazz.getConstructor(Invoker.class).newInstance(invoker);
120        }
121        catch (Exception e)
122        {
123            throw new ProxyFactoryException("Unable to instantiate proxy2 from generated proxy2 class.", e);
124        }
125    }
126
127    //******************************************************************************************************************
128    // Inner Classes
129    //******************************************************************************************************************
130
131    private static class DelegatingProxyClassGenerator extends AbstractProxyClassGenerator
132    {
133        @Override
134        public Class<?> generateProxyClass(ClassLoader classLoader, Class<?>... proxyClasses)
135        {
136            try
137            {
138                final CtClass proxyClass = JavassistUtils.createClass(getSuperclass(proxyClasses));
139                JavassistUtils.addField(ObjectProvider.class, "provider", proxyClass);
140                final CtConstructor proxyConstructor = new CtConstructor(
141                        JavassistUtils.resolve(new Class[] { ObjectProvider.class }), proxyClass);
142                proxyConstructor.setBody("{ this.provider = $1; }");
143                proxyClass.addConstructor(proxyConstructor);
144                JavassistUtils.addInterfaces(proxyClass, toInterfaces(proxyClasses));
145                addHashCodeMethod(proxyClass);
146                addEqualsMethod(proxyClass);
147                final Method[] methods = getImplementationMethods(proxyClasses);
148                for (int i = 0; i < methods.length; ++i)
149                {
150                    if (!ProxyUtils.isEqualsMethod(methods[i]) && !ProxyUtils.isHashCode(methods[i]))
151                    {
152                        final Method method = methods[i];
153                        final CtMethod ctMethod = new CtMethod(JavassistUtils.resolve(method.getReturnType()),
154                                method.getName(), JavassistUtils.resolve(method.getParameterTypes()), proxyClass);
155                        final String body = "{ return ( $r ) ( ( " + method.getDeclaringClass().getName()
156                                + " )provider.getObject() )." + method.getName() + "($$); }";
157                        ctMethod.setBody(body);
158                        proxyClass.addMethod(ctMethod);
159                    }
160                }
161                return proxyClass.toClass(classLoader, null);
162            }
163            catch (CannotCompileException e)
164            {
165                throw new ProxyFactoryException("Could not compile class.", e);
166            }
167        }
168    }
169
170    private static class InterceptorProxyClassGenerator extends AbstractProxyClassGenerator
171    {
172        @Override
173        public Class<?> generateProxyClass(ClassLoader classLoader, Class<?>... proxyClasses)
174        {
175            try
176            {
177                final CtClass proxyClass = JavassistUtils.createClass(getSuperclass(proxyClasses));
178                final Method[] methods = getImplementationMethods(proxyClasses);
179                JavassistUtils.addInterfaces(proxyClass, toInterfaces(proxyClasses));
180                JavassistUtils.addField(Object.class, "target", proxyClass);
181                JavassistUtils.addField(Interceptor.class, "interceptor", proxyClass);
182                addGetMethodMethod(proxyClass);
183                addHashCodeMethod(proxyClass);
184                addEqualsMethod(proxyClass);
185                final CtConstructor proxyConstructor = new CtConstructor(JavassistUtils.resolve(new Class[] {
186                        Object.class, Interceptor.class }), proxyClass);
187                proxyConstructor.setBody("{\n\tthis.target = $1;\n\tthis.interceptor = $2; }");
188                proxyClass.addConstructor(proxyConstructor);
189                for (int i = 0; i < methods.length; ++i)
190                {
191                    if (!ProxyUtils.isEqualsMethod(methods[i]) && !ProxyUtils.isHashCode(methods[i]))
192                    {
193                        final CtMethod method = new CtMethod(JavassistUtils.resolve(methods[i].getReturnType()),
194                                methods[i].getName(), JavassistUtils.resolve(methods[i].getParameterTypes()),
195                                proxyClass);
196                        final Class<?> invocationClass = JavassistInvocation.getMethodInvocationClass(classLoader,
197                                methods[i]);
198
199                        final String body = "{\n\t return ( $r ) interceptor.intercept( new "
200                                + invocationClass.getName() + "( this, target, " + GET_METHOD_METHOD_NAME + "(\""
201                                + methods[i].getDeclaringClass().getName() + "\", \"" + methods[i].getName()
202                                + "\", $sig), $args ) );\n }";
203                        method.setBody(body);
204                        proxyClass.addMethod(method);
205                    }
206
207                }
208                return proxyClass.toClass(classLoader, null);
209            }
210            catch (CannotCompileException e)
211            {
212                throw new ProxyFactoryException("Could not compile class.", e);
213            }
214        }
215
216    }
217
218    private static void addEqualsMethod(CtClass proxyClass) throws CannotCompileException
219    {
220        final CtMethod equalsMethod = new CtMethod(JavassistUtils.resolve(Boolean.TYPE), "equals",
221                JavassistUtils.resolve(new Class[] { Object.class }), proxyClass);
222        final String body = "{\n\treturn this == $1;\n}";
223        equalsMethod.setBody(body);
224        proxyClass.addMethod(equalsMethod);
225    }
226
227    private static void addHashCodeMethod(CtClass proxyClass) throws CannotCompileException
228    {
229        final CtMethod hashCodeMethod = new CtMethod(JavassistUtils.resolve(Integer.TYPE), "hashCode", new CtClass[0],
230                proxyClass);
231        hashCodeMethod.setBody("{\n\treturn System.identityHashCode(this);\n}");
232        proxyClass.addMethod(hashCodeMethod);
233    }
234
235    private static class InvokerProxyClassGenerator extends AbstractProxyClassGenerator
236    {
237        @Override
238        public Class<?> generateProxyClass(ClassLoader classLoader, Class<?>... proxyClasses)
239        {
240            try
241            {
242                final CtClass proxyClass = JavassistUtils.createClass(getSuperclass(proxyClasses));
243                final Method[] methods = getImplementationMethods(proxyClasses);
244                JavassistUtils.addInterfaces(proxyClass, toInterfaces(proxyClasses));
245                JavassistUtils.addField(Invoker.class, "invoker", proxyClass);
246                final CtConstructor proxyConstructor = new CtConstructor(
247                        JavassistUtils.resolve(new Class[] { Invoker.class }), proxyClass);
248                proxyConstructor.setBody("{\n\tthis.invoker = $1; }");
249                proxyClass.addConstructor(proxyConstructor);
250                addGetMethodMethod(proxyClass);
251                addHashCodeMethod(proxyClass);
252                addEqualsMethod(proxyClass);
253                for (int i = 0; i < methods.length; ++i)
254                {
255                    if (!ProxyUtils.isEqualsMethod(methods[i]) && !ProxyUtils.isHashCode(methods[i]))
256                    {
257                        final CtMethod method = new CtMethod(JavassistUtils.resolve(methods[i].getReturnType()),
258                                methods[i].getName(), JavassistUtils.resolve(methods[i].getParameterTypes()),
259                                proxyClass);
260                        final String body = "{\n\t return ( $r ) invoker.invoke( this, " + GET_METHOD_METHOD_NAME
261                                + "(\"" + methods[i].getDeclaringClass().getName() + "\", \"" + methods[i].getName()
262                                + "\", $sig), $args );\n }";
263                        method.setBody(body);
264                        proxyClass.addMethod(method);
265                    }
266                }
267                return proxyClass.toClass(classLoader, null);
268            }
269            catch (CannotCompileException e)
270            {
271                throw new ProxyFactoryException("Could not compile class.", e);
272            }
273        }
274    }
275}