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