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 }