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