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    }