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