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}