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.ioc.ObjectCreator;
021import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
022import org.apache.tapestry5.ioc.internal.util.InternalUtils;
023import org.apache.tapestry5.ioc.services.Builtin;
024import org.apache.tapestry5.ioc.services.ClassFabUtils;
025import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
026import org.apache.tapestry5.ioc.services.ThunkCreator;
027import org.apache.tapestry5.plastic.ClassInstantiator;
028import org.apache.tapestry5.plastic.InstructionBuilder;
029import org.apache.tapestry5.plastic.InstructionBuilderCallback;
030import org.apache.tapestry5.plastic.PlasticClass;
031import org.apache.tapestry5.plastic.PlasticClassTransformer;
032import org.apache.tapestry5.plastic.PlasticField;
033import org.apache.tapestry5.plastic.PlasticMethod;
034import org.apache.tapestry5.plastic.PlasticUtils;
035
036@SuppressWarnings("all")
037public 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}