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 }