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