001    // Copyright 2006, 2007, 2008, 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.services.Builtin;
021    import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
022    import org.apache.tapestry5.ioc.services.StrategyBuilder;
023    import org.apache.tapestry5.ioc.util.StrategyRegistry;
024    import org.apache.tapestry5.plastic.ClassInstantiator;
025    import org.apache.tapestry5.plastic.InstructionBuilder;
026    import org.apache.tapestry5.plastic.InstructionBuilderCallback;
027    import org.apache.tapestry5.plastic.MethodDescription;
028    import org.apache.tapestry5.plastic.PlasticClass;
029    import org.apache.tapestry5.plastic.PlasticClassTransformer;
030    import org.apache.tapestry5.plastic.PlasticField;
031    
032    public class StrategyBuilderImpl implements StrategyBuilder
033    {
034        private final PlasticProxyFactory proxyFactory;
035    
036        public StrategyBuilderImpl(@Builtin
037        PlasticProxyFactory proxyFactory)
038        {
039            this.proxyFactory = proxyFactory;
040        }
041    
042        public <S> S build(StrategyRegistry<S> registry)
043        {
044            return createProxy(registry.getAdapterType(), registry);
045        }
046    
047        public <S> S build(Class<S> adapterType, Map<Class, S> registrations)
048        {
049            StrategyRegistry<S> registry = StrategyRegistry.newInstance(adapterType, registrations);
050    
051            return build(registry);
052        }
053    
054        private <S> S createProxy(final Class<S> interfaceType, final StrategyRegistry<S> registry)
055        {
056            ClassInstantiator instantiator = proxyFactory.createProxy(interfaceType, new PlasticClassTransformer()
057            {
058                public void transform(PlasticClass plasticClass)
059                {
060                    final PlasticField registryField = plasticClass.introduceField(StrategyRegistry.class, "registry")
061                            .inject(registry);
062    
063                    for (final Method method : interfaceType.getMethods())
064                    {
065                        plasticClass.introduceMethod(new MethodDescription(method), new InstructionBuilderCallback()
066                        {
067                            public void doBuild(InstructionBuilder builder)
068                            {
069                                Class returnType = method.getReturnType();
070    
071                                builder.loadThis().getField(registryField);
072    
073                                // Argument 0 is the selector used to find the adapter and should be an object reference,
074                                // not a primitive.
075    
076                                builder.loadArgument(0);
077    
078                                // Use the StrategyRegistry to get the adapter to re-invoke the method on
079                                builder.invoke(StrategyRegistry.class, Object.class, "getByInstance", Object.class)
080                                        .checkcast(interfaceType);
081    
082                                // That leaves the correct adapter on top of the stack. Get the
083                                // selector and the rest of the arguments in place and invoke the method.
084    
085                                builder.loadArguments().invoke(interfaceType, returnType, method.getName(),
086                                        method.getParameterTypes());
087    
088                                builder.returnResult();
089                            }
090                        });
091                    }
092    
093                    plasticClass.addToString(String.format("<Strategy for %s>", interfaceType.getName()));
094                }
095            });
096    
097            return interfaceType.cast(instantiator.newInstance());
098        }
099    }