    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.ioc.internal.services;
    014
    015import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    016import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
    017import org.apache.tapestry5.plastic.*;
    018import org.slf4j.Logger;
    019
    020import java.util.Iterator;
    021import java.util.List;
    022
    023/**
    024 * Used by the {@link org.apache.tapestry5.ioc.internal.services.PipelineBuilderImpl} to create bridge classes and to
    025 * create instances of bridge classes. A bridge class implements the <em>service</em> interface. Within the chain,
    026 * bridge 1 is passed to filter 1. Invoking methods on bridge 1 will invoke methods on filter 2.
    027 */
    028public class BridgeBuilder<S, F>
    029{
    030    private final Logger logger;
    031
    032    private final Class<S> serviceInterface;
    033
    034    private final Class<F> filterInterface;
    035
    036    private final FilterMethodAnalyzer filterMethodAnalyzer;
    037
    038    private final PlasticProxyFactory proxyFactory;
    039
    040    private ClassInstantiator<S> instantiator;
    041
    042    public BridgeBuilder(Logger logger, Class<S> serviceInterface, Class<F> filterInterface, PlasticProxyFactory proxyFactory)
    043    {
    044        this.logger = logger;
    045        this.serviceInterface = serviceInterface;
    046        this.filterInterface = filterInterface;
    047
    048        this.proxyFactory = proxyFactory;
    049
    050        filterMethodAnalyzer = new FilterMethodAnalyzer(serviceInterface);
    051    }
    052
    053    /**
    054     * Instantiates a bridge object.
    055     *
    056     * @param nextBridge
    057     *         the next Bridge object in the pipeline, or the terminator service
    058     * @param filter
    059     *         the filter object for this step of the pipeline
    060     */
    061    public S instantiateBridge(S nextBridge, F filter)
    062    {
    063        if (instantiator == null)
    064            createInstantiator();
    065
    066        return instantiator.with(filterInterface, filter).with(serviceInterface, nextBridge).newInstance();
    067    }
    068
    069    private void createInstantiator()
    070    {
    071        instantiator = proxyFactory.createProxy(serviceInterface, new PlasticClassTransformer()
    072        {
    073            @Override
    074            public void transform(PlasticClass plasticClass)
    075            {
    076                PlasticField filterField = plasticClass.introduceField(filterInterface, "filter")
    077                        .injectFromInstanceContext();
    078                PlasticField nextField = plasticClass.introduceField(serviceInterface, "next")
    079                        .injectFromInstanceContext();
    080
    081                processMethods(plasticClass, filterField, nextField);
    082
    083                plasticClass.addToString(String.format("<PipelineBridge from %s to %s>", serviceInterface.getName(),
    084                        filterInterface.getName()));
    085            }
    086        });
    087    }
    088
    089    private void processMethods(PlasticClass plasticClass, PlasticField filterField, PlasticField nextField)
    090    {
    091        List<MethodSignature> serviceMethods = CollectionFactory.newList();
    092        List<MethodSignature> filterMethods = CollectionFactory.newList();
    093
    094        MethodIterator mi = new MethodIterator(serviceInterface);
    095
    096        while (mi.hasNext())
    097        {
    098            serviceMethods.add(mi.next());
    099        }
    100
    101        mi = new MethodIterator(filterInterface);
    102
    103        while (mi.hasNext())
    104        {
    105            filterMethods.add(mi.next());
    106        }
    107
    108        while (!serviceMethods.isEmpty())
    109        {
    110            MethodSignature ms = serviceMethods.remove(0);
    111
    112            addBridgeMethod(plasticClass, filterField, nextField, ms, filterMethods);
    113        }
    114
    115        reportExtraFilterMethods(filterMethods);
    116    }
    117
    118    private void reportExtraFilterMethods(List filterMethods)
    119    {
    120        Iterator i = filterMethods.iterator();
    121
    122        while (i.hasNext())
    123        {
    124            MethodSignature ms = (MethodSignature) i.next();
    125
    126            logger.error(String.format("Method %s of filter interface %s does not have a matching method in %s.", ms, filterInterface.getName(), serviceInterface.getName()));
    127        }
    128    }
    129
    130    /**
    131     * Finds a matching method in filterMethods for the given service method. A matching method has the same signature
    132     * as the service interface method, but with an additional parameter matching the service interface itself.
    133     *
    134     * The matching method signature from the list of filterMethods is removed and code generation strategies for making
    135     * the two methods call each other are added.
    136     */
    137    private void addBridgeMethod(PlasticClass plasticClass, PlasticField filterField, PlasticField nextField,
    138                                 final MethodSignature ms, List filterMethods)
    139    {
    140        PlasticMethod method = plasticClass.introduceMethod(ms.getMethod());
    141
    142        Iterator i = filterMethods.iterator();
    143
    144        while (i.hasNext())
    145        {
    146            MethodSignature fms = (MethodSignature) i.next();
    147
    148            int position = filterMethodAnalyzer.findServiceInterfacePosition(ms, fms);
    149
    150            if (position >= 0)
    151            {
    152                bridgeServiceMethodToFilterMethod(method, filterField, nextField, position, ms, fms);
    153                i.remove();
    154                return;
    155            }
    156        }
    157
    158        method.changeImplementation(new InstructionBuilderCallback()
    159        {
    160            @Override
    161            public void doBuild(InstructionBuilder builder)
    162            {
    163                String message = String.format("Method %s has no match in filter interface %s.", ms, filterInterface.getName());
    164
    165                logger.error(message);
    166
    167                builder.throwException(RuntimeException.class, message);
    168            }
    169        });
    170    }
    171
    172    private void bridgeServiceMethodToFilterMethod(PlasticMethod method, final PlasticField filterField,
    173                                                   final PlasticField nextField, final int position, MethodSignature ms, final MethodSignature fms)
    174    {
    175        method.changeImplementation(new InstructionBuilderCallback()
    176        {
    177            @Override
    178            public void doBuild(InstructionBuilder builder)
    179            {
    180                builder.loadThis().getField(filterField);
    181
    182                int argumentIndex = 0;
    183
    184                for (int i = 0; i < fms.getParameterTypes().length; i++)
    185                {
    186                    if (i == position)
    187                    {
    188                        builder.loadThis().getField(nextField);
    189                    } else
    190                    {
    191                        builder.loadArgument(argumentIndex++);
    192                    }
    193                }
    194
    195                builder.invoke(fms.getMethod()).returnResult();
    196            }
    197        });
    198    }
    199
    200}