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    
015    package org.apache.tapestry5.ioc.internal.services;
016    
017    import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
018    import org.apache.tapestry5.internal.plastic.asm.Type;
019    import org.apache.tapestry5.internal.plastic.asm.tree.*;
020    import org.apache.tapestry5.ioc.Location;
021    import org.apache.tapestry5.ioc.ObjectCreator;
022    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
023    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
024    import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
025    import org.apache.tapestry5.plastic.*;
026    import org.slf4j.Logger;
027    
028    import java.lang.reflect.Constructor;
029    import java.lang.reflect.Member;
030    import java.lang.reflect.Method;
031    import java.util.List;
032    import java.util.Map;
033    
034    public 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    }