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