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