001: // Copyright 2006, 2007 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:
015: package org.apache.tapestry.ioc.internal.services;
016:
017: import static java.lang.String.format;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
019:
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.Modifier;
022: import java.util.Iterator;
023: import java.util.List;
024:
025: import org.apache.commons.logging.Log;
026: import org.apache.tapestry.ioc.services.ClassFab;
027: import org.apache.tapestry.ioc.services.ClassFactory;
028: import org.apache.tapestry.ioc.services.MethodIterator;
029: import org.apache.tapestry.ioc.services.MethodSignature;
030:
031: /**
032: * Used by the {@link org.apache.tapestry.ioc.internal.services.PipelineBuilderImpl} to create
033: * bridge classes and to create instances of bridge classes. A bridge class implements the
034: * <em>service</em> interface. Within the chain, bridge 1 is passed to filter 1. Invoking methods
035: * on bridge 1 will invoke methods on filter 2.
036: */
037: class BridgeBuilder<S, F> {
038: private final Log _log;
039:
040: private final Class<S> _serviceInterface;
041:
042: private final Class<F> _filterInterface;
043:
044: private final ClassFab _classFab;
045:
046: private final FilterMethodAnalyzer _filterMethodAnalyzer;
047:
048: private Constructor _constructor;
049:
050: BridgeBuilder(Log log, Class<S> serviceInterface,
051: Class<F> filterInterface, ClassFactory classFactory) {
052: _log = log;
053: _serviceInterface = serviceInterface;
054: _filterInterface = filterInterface;
055:
056: _classFab = classFactory.newClass(_serviceInterface);
057:
058: _filterMethodAnalyzer = new FilterMethodAnalyzer(
059: serviceInterface);
060: }
061:
062: private void createClass() {
063: List<MethodSignature> serviceMethods = newList();
064: List<MethodSignature> filterMethods = newList();
065:
066: createInfrastructure();
067:
068: MethodIterator mi = new MethodIterator(_serviceInterface);
069:
070: while (mi.hasNext()) {
071: serviceMethods.add(mi.next());
072: }
073:
074: boolean toStringMethodExists = mi.getToString();
075:
076: mi = new MethodIterator(_filterInterface);
077:
078: while (mi.hasNext()) {
079: filterMethods.add(mi.next());
080: }
081:
082: while (!serviceMethods.isEmpty()) {
083: MethodSignature ms = serviceMethods.remove(0);
084:
085: addBridgeMethod(ms, filterMethods);
086: }
087:
088: reportExtraFilterMethods(filterMethods);
089:
090: if (!toStringMethodExists) {
091: String toString = format("<PipelineBridge from %s to %s>",
092: _serviceInterface.getName(), _filterInterface
093: .getName());
094: _classFab.addToString(toString);
095: }
096:
097: Class bridgeClass = _classFab.createClass();
098:
099: _constructor = bridgeClass.getConstructors()[0];
100: }
101:
102: private void createInfrastructure() {
103: _classFab.addField("_next", Modifier.PRIVATE | Modifier.FINAL,
104: _serviceInterface);
105: _classFab.addField("_filter",
106: Modifier.PRIVATE | Modifier.FINAL, _filterInterface);
107:
108: _classFab.addConstructor(new Class[] { _serviceInterface,
109: _filterInterface }, null,
110: "{ _next = $1; _filter = $2; }");
111:
112: _classFab.addInterface(_serviceInterface);
113: }
114:
115: /**
116: * Instantiates a bridge object.
117: *
118: * @param nextBridge
119: * the next Bridge object in the pipeline, or the terminator service
120: * @param filter
121: * the filter object for this step of the pipeline
122: */
123: public S instantiateBridge(S nextBridge, F filter) {
124: if (_constructor == null)
125: createClass();
126:
127: try {
128: Object instance = _constructor.newInstance(nextBridge,
129: filter);
130:
131: return _serviceInterface.cast(instance);
132: } catch (Exception ex) {
133: throw new RuntimeException(ex);
134: }
135: }
136:
137: private void reportExtraFilterMethods(List filterMethods) {
138: Iterator i = filterMethods.iterator();
139:
140: while (i.hasNext()) {
141: MethodSignature ms = (MethodSignature) i.next();
142:
143: _log.error(ServiceMessages.extraFilterMethod(ms,
144: _filterInterface, _serviceInterface), null);
145: }
146: }
147:
148: /**
149: * Finds a matching method in filterMethods for the given service method. A matching method has
150: * the same signature as the service interface method, but with an additional parameter matching
151: * the service interface itself.
152: * <p>
153: * The matching method signature from the list of filterMethods is removed and code generation
154: * strategies for making the two methods call each other are added.
155: */
156: private void addBridgeMethod(MethodSignature ms, List filterMethods) {
157: Iterator i = filterMethods.iterator();
158:
159: while (i.hasNext()) {
160: MethodSignature fms = (MethodSignature) i.next();
161:
162: int position = _filterMethodAnalyzer
163: .findServiceInterfacePosition(ms, fms);
164:
165: if (position >= 0) {
166: addBridgeMethod(position, ms, fms);
167: i.remove();
168: return;
169: }
170: }
171:
172: String message = ServiceMessages.unmatchedServiceMethod(ms,
173: _filterInterface);
174:
175: _log.error(message, null);
176:
177: String code = format("throw new %s(\"%s\");",
178: RuntimeException.class.getName(), message);
179:
180: _classFab.addMethod(Modifier.PUBLIC, ms, code);
181: }
182:
183: /**
184: * Adds a method to the class which bridges from the service method to the corresponding method
185: * in the filter interface. The next service (either another Bridge, or the terminator at the
186: * end of the pipeline) is passed to the filter).
187: */
188: private void addBridgeMethod(int position, MethodSignature ms,
189: MethodSignature fms) {
190: StringBuilder buffer = new StringBuilder(100);
191:
192: buffer.append("return ($r) _filter.");
193: buffer.append(ms.getName());
194: buffer.append("(");
195:
196: boolean comma = false;
197: int filterParameterCount = fms.getParameterTypes().length;
198:
199: for (int i = 0; i < position; i++) {
200: if (comma)
201: buffer.append(", ");
202:
203: buffer.append("$");
204: // Add one to the index to get the parameter symbol ($0 is the implicit
205: // this parameter).
206: buffer.append(i + 1);
207:
208: comma = true;
209: }
210:
211: if (comma)
212: buffer.append(", ");
213:
214: // _next is the variable in -this- Bridge that points to the -next- Bridge
215: // or the terminator for the pipeline. The filter is expected to reinvoke the
216: // method on the _next that's passed to it.
217:
218: buffer.append("_next");
219:
220: for (int i = position + 1; i < filterParameterCount; i++) {
221: buffer.append(", $");
222: buffer.append(i);
223: }
224:
225: buffer.append(");");
226:
227: // This should work, unless the exception types turn out to not be compatble. We still
228: // don't do a check on that, and not sure that Javassist does either!
229:
230: _classFab.addMethod(Modifier.PUBLIC, ms, buffer.toString());
231: }
232:
233: }
|