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
015package org.apache.tapestry5.ioc.internal.services;
016
017import static java.lang.String.format;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.Constructor;
021import java.lang.reflect.Method;
022import java.lang.reflect.Modifier;
023
024import javassist.CtClass;
025import javassist.CtConstructor;
026import javassist.CtMethod;
027
028import org.apache.tapestry5.ioc.Location;
029import org.apache.tapestry5.ioc.ObjectCreator;
030import org.apache.tapestry5.ioc.internal.util.InternalUtils;
031import org.apache.tapestry5.ioc.services.ClassFab;
032import org.apache.tapestry5.ioc.services.ClassFabUtils;
033import org.apache.tapestry5.ioc.services.ClassFactory;
034import org.apache.tapestry5.ioc.services.MethodSignature;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * Implementation of {@link org.apache.tapestry5.ioc.services.ClassFactory}.
040 */
041@SuppressWarnings("all")
042public 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}