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