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