    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                public String createObject()
    121                {
    122                    return InternalUtils.asString(method);
    123                }
    124            };
    125
    126            return getMemberLocation(method, method.getName(), Type.getMethodDescriptor(method),
    127                    descriptionCreator);
    128        }
    129
    130        public Location getConstructorLocation(final Constructor constructor)
    131        {
    132            ObjectCreator<String> descriptionCreator = new ObjectCreator<String>()
    133            {
    134                public String createObject()
    135                {
    136                    StringBuilder builder = new StringBuilder(constructor.getDeclaringClass().getName()).append("(");
    137                    String sep = "";
    138
    139                    for (Class parameterType : constructor.getParameterTypes())
    140                    {
    141                        builder.append(sep);
    142                        builder.append(parameterType.getSimpleName());
    143
    144                        sep = ", ";
    145                    }
    146
    147                    builder.append(")");
    148
    149                    return builder.toString();
    150                }
    151            };
    152
    153            return getMemberLocation(constructor, "<init>", Type.getConstructorDescriptor(constructor),
    154                    descriptionCreator);
    155        }
    156
    157        public void clearCache()
    158        {
    159            memberToLocation.clear();
    160        }
    161
    162
    163        public Location getMemberLocation(Member member, String methodName, String memberTypeDesc, ObjectCreator<String> textDescriptionCreator)
    164        {
    165            String className = member.getDeclaringClass().getName();
    166
    167            String key = className + ":" + methodName + ":" + memberTypeDesc;
    168
    169            Location location = memberToLocation.get(key);
    170
    171            if (location == null)
    172            {
    173                location = constructMemberLocation(member, methodName, memberTypeDesc, textDescriptionCreator.createObject());
    174
    175                memberToLocation.put(key, location);
    176            }
    177
    178            return location;
    179
    180        }
    181
    182        private Location constructMemberLocation(Member member, String methodName, String memberTypeDesc, String textDescription)
    183        {
    184
    185            ClassNode classNode = readClassNode(member.getDeclaringClass());
    186
    187            if (classNode == null)
    188            {
    189                throw new RuntimeException(String.format("Unable to read class file for %s (to gather line number information).",
    190                        textDescription));
    191            }
    192
    193            for (MethodNode mn : (List<MethodNode>) classNode.methods)
    194            {
    195                if (mn.name.equals(methodName) && mn.desc.equals(memberTypeDesc))
    196                {
    197                    int lineNumber = findFirstLineNumber(mn.instructions);
    198
    199                    // If debugging info is not available, we may lose the line number data, in which case,
    200                    // just generate the Location from the textDescription.
    201
    202                    if (lineNumber < 1)
    203                    {
    204                        break;
    205                    }
    206
    207                    String description = String.format("%s (at %s:%d)", textDescription, classNode.sourceFile, lineNumber);
    208
    209                    return new StringLocation(description, lineNumber);
    210                }
    211            }
    212
    213            // Didn't find it. Odd.
    214
    215            return new StringLocation(textDescription, 0);
    216        }
    217
    218        private int findFirstLineNumber(InsnList instructions)
    219        {
    220            for (AbstractInsnNode node = instructions.getFirst(); node != null; node = node.getNext())
    221            {
    222                if (node instanceof LineNumberNode)
    223                {
    224                    return ((LineNumberNode) node).line;
    225                }
    226            }
    227
    228            return -1;
    229        }
    230
    231        public void addPlasticClassListener(PlasticClassListener listener)
    232        {
    233            manager.addPlasticClassListener(listener);
    234        }
    235
    236        public void removePlasticClassListener(PlasticClassListener listener)
    237        {
    238            manager.removePlasticClassListener(listener);
    239        }
    240
    241    }