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