    001    // Copyright 2006, 2007, 2008, 2010 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.ioc.internal.services;
    016
    017    import static java.lang.String.format;
    018
    019    import java.lang.annotation.Annotation;
    020    import java.lang.reflect.Constructor;
    021    import java.lang.reflect.Method;
    022    import java.lang.reflect.Modifier;
    023
    024    import javassist.CtClass;
    025    import javassist.CtConstructor;
    026    import javassist.CtMethod;
    027
    028    import org.apache.tapestry5.ioc.Location;
    029    import org.apache.tapestry5.ioc.ObjectCreator;
    030    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    031    import org.apache.tapestry5.ioc.services.ClassFab;
    032    import org.apache.tapestry5.ioc.services.ClassFabUtils;
    033    import org.apache.tapestry5.ioc.services.ClassFactory;
    034    import org.apache.tapestry5.ioc.services.MethodSignature;
    035    import org.slf4j.Logger;
    036    import org.slf4j.LoggerFactory;
    037
    038    /**
    039     * Implementation of {@link org.apache.tapestry5.ioc.services.ClassFactory}.
    040     */
    041    @SuppressWarnings("all")
    042    public class ClassFactoryImpl implements ClassFactory
    043    {
    044        private final Logger logger;
    045
    046        /**
    047         * ClassPool shared by all modules (all CtClassSource instances).
    048         */
    049        private final ClassFactoryClassPool pool;
    050
    051        private final CtClassSource classSource;
    052
    053        private final ClassLoader loader;
    054
    055        public ClassFactoryImpl(ClassLoader classLoader)
    056        {
    057            this(classLoader, LoggerFactory.getLogger(ClassFactoryImpl.class));
    058        }
    059
    060        public ClassFactoryImpl()
    061        {
    062            this(Thread.currentThread().getContextClassLoader());
    063        }
    064
    065        /**
    066         * Main constructor where a specific class loader and log is provided.
    067         */
    068        public ClassFactoryImpl(ClassLoader classLoader, Logger log)
    069        {
    070            this(classLoader, new ClassFactoryClassPool(classLoader), log);
    071        }
    072
    073        /**
    074         * Special constructor used when the class pool is provided externally.
    075         */
    076        public ClassFactoryImpl(ClassLoader classLoader, ClassFactoryClassPool pool, Logger logger)
    077        {
    078            this(classLoader, pool, new CtClassSourceImpl(pool, classLoader), logger);
    079        }
    080
    081        public ClassFactoryImpl(ClassLoader classLoader, ClassFactoryClassPool pool, CtClassSource classSource,
    082                Logger logger)
    083        {
    084            loader = classLoader;
    085
    086            this.pool = pool;
    087
    088            this.classSource = classSource;
    089
    090            this.logger = logger;
    091        }
    092
    093        public ClassFab newClass(Class serviceInterface)
    094        {
    095            String name = ClassFabUtils.generateClassName(serviceInterface);
    096
    097            ClassFab cf = newClass(name, Object.class);
    098
    099            cf.addInterface(serviceInterface);
    100
    101            return cf;
    102        }
    103
    104        public ClassFab newClass(String name, Class superClass)
    105        {
    106            if (logger.isDebugEnabled())
    107                logger.debug(String.format("Create ClassFab for %s (extends %s)", name, superClass.getName()));
    108
    109            try
    110            {
    111                CtClass ctNewClass = classSource.newClass(name, superClass);
    112
    113                return new ClassFabImpl(classSource, ctNewClass, logger);
    114            }
    115            catch (Exception ex)
    116            {
    117                throw new RuntimeException(ServiceMessages.unableToCreateClass(name, superClass, ex), ex);
    118            }
    119        }
    120
    121        public Class importClass(Class clazz)
    122        {
    123            return pool.importClass(clazz);
    124        }
    125
    126        public int getCreatedClassCount()
    127        {
    128            return classSource.getCreatedClassCount();
    129        }
    130
    131        public ClassLoader getClassLoader()
    132        {
    133            return loader;
    134        }
    135
    136        public Location getMethodLocation(Method method)
    137        {
    138            assert method != null;
    139
    140            // TODO: Is it worth caching this? Probably not as it usually is only
    141            // invoked perhaps at startup and in the event of errors.
    142
    143            Class declaringClass = method.getDeclaringClass();
    144            Class effectiveClass = importClass(declaringClass);
    145
    146            CtClass ctClass = classSource.toCtClass(effectiveClass);
    147
    148            StringBuilder builder = new StringBuilder("(");
    149
    150            for (Class parameterType : method.getParameterTypes())
    151            {
    152                builder.append(ClassFabUtils.getTypeCode(parameterType));
    153            }
    154
    155            builder.append(")");
    156            builder.append(ClassFabUtils.getTypeCode(method.getReturnType()));
    157
    158            try
    159            {
    160                CtMethod ctMethod = ctClass.getMethod(method.getName(), builder.toString());
    161
    162                int lineNumber = ctMethod.getMethodInfo().getLineNumber(0);
    163
    164                String sourceFile = ctMethod.getDeclaringClass().getClassFile2().getSourceFile();
    165
    166                String description = String.format("%s (at %s:%d)", InternalUtils.asString(method), sourceFile, lineNumber);
    167
    168                return new StringLocation(description, lineNumber);
    169            }
    170            catch (Exception ex)
    171            {
    172                return new StringLocation(InternalUtils.asString(method), 0);
    173            }
    174        }
    175
    176        public Location getConstructorLocation(Constructor constructor)
    177        {
    178            assert constructor != null;
    179
    180            StringBuilder builder = new StringBuilder();
    181
    182            Class declaringClass = constructor.getDeclaringClass();
    183
    184            builder.append(declaringClass.getName());
    185            builder.append("(");
    186
    187            CtClass ctClass = classSource.toCtClass(declaringClass);
    188
    189            StringBuilder descripton = new StringBuilder("(");
    190
    191            Class[] parameterTypes = constructor.getParameterTypes();
    192            for (int i = 0; i < parameterTypes.length; i++)
    193            {
    194                Class parameterType = parameterTypes[i];
    195
    196                if (i > 0)
    197                    builder.append(", ");
    198
    199                builder.append(parameterType.getSimpleName());
    200
    201                descripton.append(ClassFabUtils.getTypeCode(parameterType));
    202            }
    203
    204            builder.append(")");
    205
    206            // A constructor resembles a method of type void
    207            descripton.append(")V");
    208
    209            int lineNumber = 0;
    210
    211            try
    212            {
    213                CtConstructor ctConstructor = ctClass.getConstructor(descripton.toString());
    214
    215                lineNumber = ctConstructor.getMethodInfo().getLineNumber(0);
    216
    217                String sourceFile = ctConstructor.getDeclaringClass().getClassFile2().getSourceFile();
    218
    219                builder.append(String.format(" (at %s:%d)", sourceFile, lineNumber));
    220            }
    221            catch (Exception ex)
    222            {
    223                // Leave the line number as 0 (aka "unknown").
    224            }
    225
    226            return new StringLocation(builder.toString(), lineNumber);
    227        }
    228
    229        public <T> T createProxy(Class<T> proxyInterface, ObjectCreator delegateCreator, String description)
    230        {
    231            return createProxy(proxyInterface, null, delegateCreator, description);
    232        }
    233
    234        public <T> T createProxy(Class<T> proxyInterface, Class<? extends T> delegateClass, ObjectCreator delegateCreator, String description)
    235        {
    236            ClassFab classFab = newClass(proxyInterface);
    237
    238            classFab.addField("_creator", Modifier.PRIVATE | Modifier.FINAL, ObjectCreator.class);
    239
    240            classFab.addConstructor(new Class[]
    241            { ObjectCreator.class }, null, "_creator = $1;");
    242
    243            String body = format("return (%s) _creator.createObject();", proxyInterface.getName());
    244
    245            MethodSignature sig = new MethodSignature(proxyInterface, "_delegate", null, null);
    246
    247            classFab.addMethod(Modifier.PRIVATE, sig, body);
    248
    249            classFab.proxyMethodsToDelegate(proxyInterface, "_delegate()", description);
    250
    251            if(delegateClass != null)
    252            {
    253                classFab.copyClassAnnotationsFromDelegate(delegateClass);
    254
    255                classFab.copyMethodAnnotationsFromDelegate(proxyInterface, (Class)delegateClass);
    256            }
    257
    258            Class proxyClass = classFab.createClass();
    259
    260            try
    261            {
    262                Object proxy = proxyClass.getConstructors()[0].newInstance(delegateCreator);
    263
    264                return proxyInterface.cast(proxy);
    265            }
    266            catch (Exception ex)
    267            {
    268                // This should never happen, so we won't go to a lot of trouble
    269                // reporting it.
    270                throw new RuntimeException(ex.getMessage(), ex);
    271            }
    272        }
    273
    274    }