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