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