    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
    015package org.apache.tapestry5.ioc.internal.services;
    016
    017import java.lang.reflect.Method;
    018import java.util.Map;
    019
    020import org.apache.tapestry5.commons.services.PlasticProxyFactory;
    021import org.apache.tapestry5.commons.util.StrategyRegistry;
    022import org.apache.tapestry5.ioc.services.Builtin;
    023import org.apache.tapestry5.ioc.services.StrategyBuilder;
    024import org.apache.tapestry5.plastic.ClassInstantiator;
    025import org.apache.tapestry5.plastic.InstructionBuilder;
    026import org.apache.tapestry5.plastic.InstructionBuilderCallback;
    027import org.apache.tapestry5.plastic.MethodDescription;
    028import org.apache.tapestry5.plastic.PlasticClass;
    029import org.apache.tapestry5.plastic.PlasticClassTransformer;
    030import org.apache.tapestry5.plastic.PlasticField;
    031
    032public 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    @Override
    043    public <S> S build(StrategyRegistry<S> registry)
    044    {
    045        return createProxy(registry.getAdapterType(), registry);
    046    }
    047
    048    @Override
    049    public <S> S build(Class<S> adapterType, Map<Class, S> registrations)
    050    {
    051        StrategyRegistry<S> registry = StrategyRegistry.newInstance(adapterType, registrations);
    052
    053        return build(registry);
    054    }
    055
    056    private <S> S createProxy(final Class<S> interfaceType, final StrategyRegistry<S> registry)
    057    {
    058        ClassInstantiator instantiator = proxyFactory.createProxy(interfaceType, new PlasticClassTransformer()
    059        {
    060            @Override
    061            public void transform(PlasticClass plasticClass)
    062            {
    063                final PlasticField registryField = plasticClass.introduceField(StrategyRegistry.class, "registry")
    064                        .inject(registry);
    065                Class<?> interfaceSelectorType = null;
    066
    067                for (final Method method : interfaceType.getMethods())
    068                {
    069                    Class<?>[] parameterTypes = method.getParameterTypes();
    070                    if (parameterTypes.length == 0)
    071                    {
    072                        throw new IllegalArgumentException("Invalid method "  + method
    073                            + ", when using the strategy pattern, every method must take at least the selector as its parameter");
    074                    }
    075                    Class<?> methodSelectorType = parameterTypes[0];
    076                    if (interfaceSelectorType == null)
    077                    {
    078                        interfaceSelectorType = methodSelectorType;
    079                    } else if (!interfaceSelectorType.equals(methodSelectorType))
    080                    {
    081                        throw new IllegalArgumentException("Conflicting method definitions,"
    082                            + " expecting the first argument of every method to have the same type");
    083
    084                    }
    085                    plasticClass.introduceMethod(new MethodDescription(method), new InstructionBuilderCallback()
    086                    {
    087                        @Override
    088                        public void doBuild(InstructionBuilder builder)
    089                        {
    090                            Class returnType = method.getReturnType();
    091
    092                            builder.loadThis().getField(registryField);
    093
    094                            // Argument 0 is the selector used to find the adapter and should be an object reference,
    095                            // not a primitive.
    096
    097                            builder.loadArgument(0);
    098
    099                            // Use the StrategyRegistry to get the adapter to re-invoke the method on
    100                            builder.invoke(StrategyRegistry.class, Object.class, "getByInstance", Object.class)
    101                                    .checkcast(interfaceType);
    102
    103                            // That leaves the correct adapter on top of the stack. Get the
    104                            // selector and the rest of the arguments in place and invoke the method.
    105
    106                            builder.loadArguments().invoke(interfaceType, returnType, method.getName(),
    107                                    method.getParameterTypes());
    108
    109                            builder.returnResult();
    110                        }
    111                    });
    112                }
    113
    114                plasticClass.addToString(String.format("<Strategy for %s>", interfaceType.getName()));
    115            }
    116        });
    117
    118        return interfaceType.cast(instantiator.newInstance());
    119    }
    120}