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    }