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