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}