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