    001// Copyright 2009, 2010, 2011 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 java.lang.reflect.Method;
    018import java.util.Map;
    019
    020import org.apache.tapestry5.commons.ObjectCreator;
    021import org.apache.tapestry5.commons.services.PlasticProxyFactory;
    022import org.apache.tapestry5.commons.util.CollectionFactory;
    023import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    024import org.apache.tapestry5.ioc.services.Builtin;
    025import org.apache.tapestry5.ioc.services.ThunkCreator;
    026import org.apache.tapestry5.plastic.ClassInstantiator;
    027import org.apache.tapestry5.plastic.InstructionBuilder;
    028import org.apache.tapestry5.plastic.InstructionBuilderCallback;
    029import org.apache.tapestry5.plastic.PlasticClass;
    030import org.apache.tapestry5.plastic.PlasticClassTransformer;
    031import org.apache.tapestry5.plastic.PlasticField;
    032import org.apache.tapestry5.plastic.PlasticMethod;
    033import org.apache.tapestry5.plastic.PlasticUtils;
    034
    035@SuppressWarnings("all")
    036public class ThunkCreatorImpl implements ThunkCreator
    037{
    038    private final Map<Class, ClassInstantiator> interfaceToInstantiator = CollectionFactory.newConcurrentMap();
    039
    040    private final PlasticProxyFactory proxyFactory;
    041
    042    private static final Method CREATE_OBJECT = PlasticUtils.getMethod(ObjectCreator.class, "createObject");
    043
    044    public ThunkCreatorImpl(@Builtin
    045    PlasticProxyFactory proxyFactory)
    046    {
    047        this.proxyFactory = proxyFactory;
    048    }
    049
    050    @Override
    051    public <T> T createThunk(Class<T> proxyType, ObjectCreator objectCreator, String description)
    052    {
    053        assert proxyType != null;
    054        assert objectCreator != null;
    055        assert InternalUtils.isNonBlank(description);
    056
    057        if (!proxyType.isInterface())
    058            throw new IllegalArgumentException(String.format(
    059                    "Thunks may only be created for interfaces; %s is a class.",
    060                    PlasticUtils.toTypeName(proxyType)));
    061
    062        return getInstantiator(proxyType).with(ObjectCreator.class, objectCreator).with(String.class, description)
    063                .newInstance();
    064
    065    }
    066
    067    private <T> ClassInstantiator<T> getInstantiator(Class<T> interfaceType)
    068    {
    069        ClassInstantiator<T> result = interfaceToInstantiator.get(interfaceType);
    070
    071        if (result == null)
    072        {
    073            result = createInstantiator(interfaceType);
    074            interfaceToInstantiator.put(interfaceType, result);
    075        }
    076
    077        return result;
    078    }
    079
    080    private <T> ClassInstantiator<T> createInstantiator(final Class<T> interfaceType)
    081    {
    082        return proxyFactory.createProxy(interfaceType, new PlasticClassTransformer()
    083        {
    084            @Override
    085            public void transform(PlasticClass plasticClass)
    086            {
    087                final PlasticField objectCreatorField = plasticClass.introduceField(ObjectCreator.class, "creator")
    088                        .injectFromInstanceContext();
    089
    090                PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(interfaceType.getName(), "delegate",
    091                        null, null);
    092
    093                delegateMethod.changeImplementation(new InstructionBuilderCallback()
    094                {
    095                    @Override
    096                    public void doBuild(InstructionBuilder builder)
    097                    {
    098                        builder.loadThis().getField(objectCreatorField);
    099                        builder.invoke(CREATE_OBJECT);
    100                        builder.checkcast(interfaceType).returnResult();
    101                    }
    102                });
    103
    104                for (Method method : interfaceType.getMethods())
    105                {
    106                    plasticClass.introduceMethod(method).delegateTo(delegateMethod);
    107                }
    108
    109                if (!plasticClass.isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
    110                {
    111                    final PlasticField descriptionField = plasticClass.introduceField(String.class, "description")
    112                            .injectFromInstanceContext();
    113
    114                    plasticClass.introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
    115                    {
    116                        @Override
    117                        public void doBuild(InstructionBuilder builder)
    118                        {
    119                            builder.loadThis().getField(descriptionField).returnResult();
    120                        }
    121                    });
    122                }
    123            }
    124        });
    125    }
    126}