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