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}