001// Copyright 2011, 2012 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 org.apache.tapestry5.internal.plastic.PlasticInternalUtils; 018import org.apache.tapestry5.internal.plastic.asm.Type; 019import org.apache.tapestry5.internal.plastic.asm.tree.*; 020import org.apache.tapestry5.ioc.Location; 021import org.apache.tapestry5.ioc.ObjectCreator; 022import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 023import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils; 024import org.apache.tapestry5.ioc.services.PlasticProxyFactory; 025import org.apache.tapestry5.plastic.*; 026import org.slf4j.Logger; 027 028import java.lang.reflect.Constructor; 029import java.lang.reflect.Member; 030import java.lang.reflect.Method; 031import java.util.List; 032import java.util.Map; 033 034public class PlasticProxyFactoryImpl implements PlasticProxyFactory 035{ 036 public static final String INTERNAL_GET_DELEGATE = "_____internalGetDelegate_DONT_CALL_THIS_METHOD_____"; 037 038 private final PlasticManager manager; 039 040 private final Map<String, Location> memberToLocation = CollectionFactory.newConcurrentMap(); 041 042 public PlasticProxyFactoryImpl(ClassLoader parentClassLoader, Logger logger) 043 { 044 this(PlasticManager.withClassLoader(parentClassLoader).create(), logger); 045 } 046 047 public PlasticProxyFactoryImpl(PlasticManager manager, Logger logger) 048 { 049 assert manager != null; 050 051 this.manager = manager; 052 053 if (logger != null) 054 { 055 manager.addPlasticClassListener(new PlasticClassListenerLogger(logger)); 056 } 057 } 058 059 @Override 060 public ClassLoader getClassLoader() 061 { 062 return manager.getClassLoader(); 063 } 064 065 @Override 066 public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, Class<? extends T> implementationType, PlasticClassTransformer callback) 067 { 068 return createProxy(interfaceType, implementationType, callback, true); 069 } 070 071 @Override 072 public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, 073 Class<? extends T> implementationType, 074 PlasticClassTransformer callback, 075 boolean introduceInterface) { 076 return manager.createProxy(interfaceType, implementationType, callback, introduceInterface); 077 } 078 079 080 @Override 081 public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, PlasticClassTransformer callback) 082 { 083 return manager.createProxy(interfaceType, callback); 084 } 085 086 @Override 087 public <T> PlasticClassTransformation<T> createProxyTransformation(Class<T> interfaceType, 088 Class<? extends T> implementationType) 089 { 090 return manager.createProxyTransformation(interfaceType, implementationType); 091 } 092 093 @Override 094 public <T> PlasticClassTransformation<T> createProxyTransformation(Class<T> interfaceType) 095 { 096 return createProxyTransformation(interfaceType, null); 097 } 098 099 @Override 100 public <T> T createProxy(final Class<T> interfaceType, final ObjectCreator<T> creator, final String description) 101 { return createProxy(interfaceType, null, creator, description); 102 } 103 104 @Override 105 public <T> T createProxy(final Class<T> interfaceType, final Class<? extends T> implementationType, 106 final ObjectCreator<T> creator, final String description) 107 { 108 assert creator != null; 109 assert InternalCommonsUtils.isNonBlank(description); 110 111 ClassInstantiator<T> instantiator = createProxy(interfaceType, implementationType, new PlasticClassTransformer() 112 { 113 @Override 114 public void transform(PlasticClass plasticClass) 115 { 116 final PlasticField objectCreatorField = plasticClass.introduceField(ObjectCreator.class, "creator") 117 .inject(creator); 118 119 final String interfaceTypeName = interfaceType.getName(); 120 PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(interfaceTypeName, "delegate", 121 null, null); 122 123 final InstructionBuilderCallback returnCreateObject = new InstructionBuilderCallback() 124 { 125 @Override 126 public void doBuild(InstructionBuilder builder) 127 { 128 builder.loadThis().getField(objectCreatorField); 129 builder.invoke(ObjectCreator.class, Object.class, "createObject"); 130 builder.checkcast(interfaceType).returnResult(); 131 } 132 }; 133 134 delegateMethod.changeImplementation(returnCreateObject); 135 136 for (Method method : interfaceType.getMethods()) 137 { 138 plasticClass.introduceMethod(method).delegateTo(delegateMethod); 139 } 140 141 // TA5-2235 142 MethodDescription getDelegateMethodDescription = 143 new MethodDescription(interfaceType.getName(), INTERNAL_GET_DELEGATE); 144 plasticClass.introduceMethod(getDelegateMethodDescription, returnCreateObject); 145 146 plasticClass.addToString(description); 147 148 } 149 }); 150 151 return interfaceType.cast(instantiator.newInstance()); 152 } 153 154 private ClassNode readClassNode(Class clazz) 155 { 156 byte[] bytecode = PlasticInternalUtils.readBytecodeForClass(manager.getClassLoader(), clazz.getName(), false); 157 158 return bytecode == null ? null : PlasticInternalUtils.convertBytecodeToClassNode(bytecode); 159 } 160 161 @Override 162 public Location getMethodLocation(final Method method) 163 { 164 ObjectCreator<String> descriptionCreator = new ObjectCreator<String>() 165 { 166 @Override 167 public String createObject() 168 { 169 return InternalCommonsUtils.asString(method); 170 } 171 }; 172 173 return getMemberLocation(method, method.getName(), Type.getMethodDescriptor(method), 174 descriptionCreator); 175 } 176 177 @Override 178 public Location getConstructorLocation(final Constructor constructor) 179 { 180 ObjectCreator<String> descriptionCreator = new ObjectCreator<String>() 181 { 182 @Override 183 public String createObject() 184 { 185 StringBuilder builder = new StringBuilder(constructor.getDeclaringClass().getName()).append('('); 186 String sep = ""; 187 188 for (Class parameterType : constructor.getParameterTypes()) 189 { 190 builder.append(sep); 191 builder.append(parameterType.getSimpleName()); 192 193 sep = ", "; 194 } 195 196 builder.append(')'); 197 198 return builder.toString(); 199 } 200 }; 201 202 return getMemberLocation(constructor, "<init>", Type.getConstructorDescriptor(constructor), 203 descriptionCreator); 204 } 205 206 @Override 207 public void clearCache() 208 { 209 memberToLocation.clear(); 210 } 211 212 213 public Location getMemberLocation(Member member, String methodName, String memberTypeDesc, ObjectCreator<String> textDescriptionCreator) 214 { 215 String className = member.getDeclaringClass().getName(); 216 217 String key = className + ":" + methodName + ":" + memberTypeDesc; 218 219 Location location = memberToLocation.get(key); 220 221 if (location == null) 222 { 223 location = constructMemberLocation(member, methodName, memberTypeDesc, textDescriptionCreator.createObject()); 224 225 memberToLocation.put(key, location); 226 } 227 228 return location; 229 230 } 231 232 private Location constructMemberLocation(Member member, String methodName, String memberTypeDesc, String textDescription) 233 { 234 235 ClassNode classNode = readClassNode(member.getDeclaringClass()); 236 237 if (classNode == null) 238 { 239 throw new RuntimeException(String.format("Unable to read class file for %s (to gather line number information).", 240 textDescription)); 241 } 242 243 for (MethodNode mn : (List<MethodNode>) classNode.methods) 244 { 245 if (mn.name.equals(methodName) && mn.desc.equals(memberTypeDesc)) 246 { 247 int lineNumber = findFirstLineNumber(mn.instructions); 248 249 // If debugging info is not available, we may lose the line number data, in which case, 250 // just generate the Location from the textDescription. 251 252 if (lineNumber < 1) 253 { 254 break; 255 } 256 257 String description = String.format("%s (at %s:%d)", textDescription, classNode.sourceFile, lineNumber); 258 259 return new StringLocation(description, lineNumber); 260 } 261 } 262 263 // Didn't find it. Odd. 264 265 return new StringLocation(textDescription, 0); 266 } 267 268 private int findFirstLineNumber(InsnList instructions) 269 { 270 for (AbstractInsnNode node = instructions.getFirst(); node != null; node = node.getNext()) 271 { 272 if (node instanceof LineNumberNode) 273 { 274 return ((LineNumberNode) node).line; 275 } 276 } 277 278 return -1; 279 } 280 281 @Override 282 public void addPlasticClassListener(PlasticClassListener listener) 283 { 284 manager.addPlasticClassListener(listener); 285 } 286 287 @Override 288 public void removePlasticClassListener(PlasticClassListener listener) 289 { 290 manager.removePlasticClassListener(listener); 291 } 292 293}