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("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}