    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
    015    package org.apache.tapestry5.ioc.internal.services;
    016
    017    import java.lang.reflect.Method;
    018    import java.util.Map;
    019
    020    import org.apache.tapestry5.ioc.ObjectCreator;
    021    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    022    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    023    import org.apache.tapestry5.ioc.services.Builtin;
    024    import org.apache.tapestry5.ioc.services.ClassFabUtils;
    025    import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
    026    import org.apache.tapestry5.ioc.services.ThunkCreator;
    027    import org.apache.tapestry5.plastic.ClassInstantiator;
    028    import org.apache.tapestry5.plastic.InstructionBuilder;
    029    import org.apache.tapestry5.plastic.InstructionBuilderCallback;
    030    import org.apache.tapestry5.plastic.PlasticClass;
    031    import org.apache.tapestry5.plastic.PlasticClassTransformer;
    032    import org.apache.tapestry5.plastic.PlasticField;
    033    import org.apache.tapestry5.plastic.PlasticMethod;
    034    import org.apache.tapestry5.plastic.PlasticUtils;
    035
    036    @SuppressWarnings("all")
    037    public class ThunkCreatorImpl implements ThunkCreator
    038    {
    039        private final Map<Class, ClassInstantiator> interfaceToInstantiator = CollectionFactory.newConcurrentMap();
    040
    041        private final PlasticProxyFactory proxyFactory;
    042
    043        private static final Method CREATE_OBJECT = PlasticUtils.getMethod(ObjectCreator.class, "createObject");
    044
    045        public ThunkCreatorImpl(@Builtin
    046        PlasticProxyFactory proxyFactory)
    047        {
    048            this.proxyFactory = proxyFactory;
    049        }
    050
    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                        ClassFabUtils.toJavaClassName(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                public void transform(PlasticClass plasticClass)
    085                {
    086                    final PlasticField objectCreatorField = plasticClass.introduceField(ObjectCreator.class, "creator")
    087                            .injectFromInstanceContext();
    088
    089                    PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(interfaceType.getName(), "delegate",
    090                            null, null);
    091
    092                    delegateMethod.changeImplementation(new InstructionBuilderCallback()
    093                    {
    094                        public void doBuild(InstructionBuilder builder)
    095                        {
    096                            builder.loadThis().getField(objectCreatorField);
    097                            builder.invoke(CREATE_OBJECT);
    098                            builder.checkcast(interfaceType).returnResult();
    099                        }
    100                    });
    101
    102                    for (Method method : interfaceType.getMethods())
    103                    {
    104                        plasticClass.introduceMethod(method).delegateTo(delegateMethod);
    105                    }
    106
    107                    if (!plasticClass.isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
    108                    {
    109                        final PlasticField descriptionField = plasticClass.introduceField(String.class, "description")
    110                                .injectFromInstanceContext();
    111
    112                        plasticClass.introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
    113                        {
    114                            public void doBuild(InstructionBuilder builder)
    115                            {
    116                                builder.loadThis().getField(descriptionField).returnResult();
    117                            }
    118                        });
    119                    }
    120                }
    121            });
    122        }
    123    }