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.commons.services.PlasticProxyFactory;
016import org.apache.tapestry5.commons.util.CollectionFactory;
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("Method {} of filter interface {} does not have a matching method in {}.", 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}