001: /*
002: * hammurapi-rules @mesopotamia.version@
003: * Hammurapi rules engine.
004: * Copyright (C) 2005 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://http://www.hammurapi.biz
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.dispatch;
024:
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import biz.hammurapi.convert.DuckConverterFactory;
036:
037: /**
038: * This class dispatches objects to invocation handlers which can accept them.
039: * Target invocation handlers are organized in "buckets" keyed by the argument class. This makes dispatching
040: * very efficient at runtime because only compatible handlers are interated over for invocation.
041: * @author Pavel Vlasov
042: * @revision $Revision$
043: */
044: public class Dispatcher {
045: private List handlers = new ArrayList();
046: private Map buckets = new HashMap();
047:
048: /**
049: * Creates dispatcher.
050: * @param targets Collection which contains instances of InvocationTarget or InvocationHandler.
051: */
052: public Dispatcher(Collection targets) {
053: super ();
054: Set allClasses = new HashSet();
055: Iterator it = targets.iterator();
056: while (it.hasNext()) {
057: Object o = it.next();
058: if (o instanceof InvocationTarget) {
059: Iterator hit = ((InvocationTarget) o)
060: .getInvocationHandlers().iterator();
061: while (hit.hasNext()) {
062: InvocationHandler ih = (InvocationHandler) hit
063: .next();
064: this .handlers.add(ih);
065: if (ih.getParameterType() != null) {
066: allClasses.add(ih.getParameterType());
067: }
068: }
069: } else if (o instanceof InvocationHandler) {
070: InvocationHandler handler = (InvocationHandler) o;
071: this .handlers.add(handler);
072: if (handler.getParameterType() != null) {
073: allClasses.add(handler.getParameterType());
074: }
075: } else {
076: throw new IllegalArgumentException("Invalid target: "
077: + o);
078: }
079: }
080:
081: // Create buckets for known classes
082: it = allClasses.iterator();
083: while (it.hasNext()) {
084: getClassBucket((Class) it.next());
085: }
086: }
087:
088: private ResultConsumer resultConsumer = new ResultConsumer() {
089: public void consume(Object result) {
090: if (result != null) {
091: Dispatcher.this .consumeResult(result);
092: }
093: }
094: };
095:
096: /**
097: * Simple form of invocation used by dispatcher
098: * to chain invocations.
099: * @author Pavel Vlasov
100: * @revision $Revision$
101: */
102: protected interface Invocation {
103: void invoke(Object arg, ResultConsumer resultConsumer);
104: }
105:
106: /**
107: * Dispatches object to invocation handlers.
108: * Exceptions thrown by handlers are wrapped into DispatchException and that exception is dispatched as
109: * though it is a return value. It is recommended to have an invocation handler which would handle thrown
110: * exceptions, e.g. log them. Errors are propagated up.
111: * @param arg Instance to be dispatched.
112: */
113: public void dispatch(Object arg) {
114: if (arg != null) {
115: getClassBucket(arg.getClass()).invoke(arg, resultConsumer);
116: }
117: }
118:
119: /**
120: * @param clazz
121: * @return Invocation which contains handlers compatible with given class. Handlers are ordered by inverse affinity. I.e. handlers with less
122: * specific parameters are invoked before handlers with more specific parameters. Handlers with unknown parameter type are invoked first.
123: */
124: private Invocation getClassBucket(final Class clazz) {
125: synchronized (buckets) {
126: Invocation ret = (Invocation) buckets.get(clazz);
127: if (ret == null) {
128: class CompatibleEntry implements Comparable {
129: int affinity;
130: InvocationHandler handler;
131:
132: CompatibleEntry(int affinity,
133: InvocationHandler handler) {
134: super ();
135: this .affinity = affinity;
136: this .handler = handler;
137: }
138:
139: /**
140: * Higher hash code (affinity) comes first
141: */
142: public int compareTo(Object obj) {
143: return obj.hashCode() - hashCode();
144: }
145:
146: public int hashCode() {
147: return affinity;
148: }
149: }
150:
151: List compatible = new ArrayList();
152: Iterator it = handlers.iterator();
153: while (it.hasNext()) {
154: InvocationHandler handler = (InvocationHandler) it
155: .next();
156: if (handler.getParameterType() == null
157: || handler.getParameterType()
158: .isAssignableFrom(clazz)) {
159: compatible
160: .add(new CompatibleEntry(
161: handler.getParameterType() == null ? Integer.MAX_VALUE
162: : DuckConverterFactory
163: .classAffinity(
164: clazz,
165: handler
166: .getParameterType()),
167: handler));
168: }
169: }
170:
171: Collections.sort(compatible);
172:
173: final InvocationHandler[] bucketHandlers = new InvocationHandler[compatible
174: .size()];
175: Iterator cit = compatible.iterator();
176: for (int i = 0; cit.hasNext(); ++i) {
177: bucketHandlers[i] = ((CompatibleEntry) cit.next()).handler;
178: }
179:
180: ret = new Invocation() {
181:
182: public void invoke(Object arg,
183: ResultConsumer resultConsumer) {
184: for (int i = 0, l = bucketHandlers.length; i < l; i++) {
185: try {
186: bucketHandlers[i].invoke(arg,
187: resultConsumer);
188: } catch (Exception e) {
189: dispatch(new DispatchException(
190: bucketHandlers[i], e));
191: } catch (Error e) {
192: throw e;
193: } catch (Throwable e) {
194: throw new RuntimeException(
195: "This should never happen", e);
196: }
197: }
198: }
199:
200: public String toString() {
201: return "[class bucket] " + clazz.getName()
202: + ", " + bucketHandlers.length
203: + " handlers";
204: }
205:
206: };
207:
208: buckets.put(clazz, ret);
209: }
210: return ret;
211: }
212: }
213:
214: /**
215: * Consumes invocation results. This implementation
216: * simply dispatches results.
217: * Override this method to provide more advanced results handling.
218: * @param result
219: */
220: protected void consumeResult(Object result) {
221: dispatch(result);
222: }
223:
224: /**
225: * @return Number of handlers
226: */
227: public int size() {
228: return handlers.size();
229: }
230: }
|