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 public String createObject() 121 { 122 return InternalUtils.asString(method); 123 } 124 }; 125 126 return getMemberLocation(method, method.getName(), Type.getMethodDescriptor(method), 127 descriptionCreator); 128 } 129 130 public Location getConstructorLocation(final Constructor constructor) 131 { 132 ObjectCreator<String> descriptionCreator = new ObjectCreator<String>() 133 { 134 public String createObject() 135 { 136 StringBuilder builder = new StringBuilder(constructor.getDeclaringClass().getName()).append("("); 137 String sep = ""; 138 139 for (Class parameterType : constructor.getParameterTypes()) 140 { 141 builder.append(sep); 142 builder.append(parameterType.getSimpleName()); 143 144 sep = ", "; 145 } 146 147 builder.append(")"); 148 149 return builder.toString(); 150 } 151 }; 152 153 return getMemberLocation(constructor, "<init>", Type.getConstructorDescriptor(constructor), 154 descriptionCreator); 155 } 156 157 public void clearCache() 158 { 159 memberToLocation.clear(); 160 } 161 162 163 public Location getMemberLocation(Member member, String methodName, String memberTypeDesc, ObjectCreator<String> textDescriptionCreator) 164 { 165 String className = member.getDeclaringClass().getName(); 166 167 String key = className + ":" + methodName + ":" + memberTypeDesc; 168 169 Location location = memberToLocation.get(key); 170 171 if (location == null) 172 { 173 location = constructMemberLocation(member, methodName, memberTypeDesc, textDescriptionCreator.createObject()); 174 175 memberToLocation.put(key, location); 176 } 177 178 return location; 179 180 } 181 182 private Location constructMemberLocation(Member member, String methodName, String memberTypeDesc, String textDescription) 183 { 184 185 ClassNode classNode = readClassNode(member.getDeclaringClass()); 186 187 if (classNode == null) 188 { 189 throw new RuntimeException(String.format("Unable to read class file for %s (to gather line number information).", 190 textDescription)); 191 } 192 193 for (MethodNode mn : (List<MethodNode>) classNode.methods) 194 { 195 if (mn.name.equals(methodName) && mn.desc.equals(memberTypeDesc)) 196 { 197 int lineNumber = findFirstLineNumber(mn.instructions); 198 199 // If debugging info is not available, we may lose the line number data, in which case, 200 // just generate the Location from the textDescription. 201 202 if (lineNumber < 1) 203 { 204 break; 205 } 206 207 String description = String.format("%s (at %s:%d)", textDescription, classNode.sourceFile, lineNumber); 208 209 return new StringLocation(description, lineNumber); 210 } 211 } 212 213 // Didn't find it. Odd. 214 215 return new StringLocation(textDescription, 0); 216 } 217 218 private int findFirstLineNumber(InsnList instructions) 219 { 220 for (AbstractInsnNode node = instructions.getFirst(); node != null; node = node.getNext()) 221 { 222 if (node instanceof LineNumberNode) 223 { 224 return ((LineNumberNode) node).line; 225 } 226 } 227 228 return -1; 229 } 230 231 public void addPlasticClassListener(PlasticClassListener listener) 232 { 233 manager.addPlasticClassListener(listener); 234 } 235 236 public void removePlasticClassListener(PlasticClassListener listener) 237 { 238 manager.removePlasticClassListener(listener); 239 } 240 241 }