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