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 }