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).getInvocationHandlers().iterator();
060 while (hit.hasNext()) {
061 InvocationHandler ih=(InvocationHandler) hit.next();
062 this.handlers.add(ih);
063 if (ih.getParameterType()!=null) {
064 allClasses.add(ih.getParameterType());
065 }
066 }
067 } else if (o instanceof InvocationHandler) {
068 InvocationHandler handler=(InvocationHandler) o;
069 this.handlers.add(handler);
070 if (handler.getParameterType()!=null) {
071 allClasses.add(handler.getParameterType());
072 }
073 } else {
074 throw new IllegalArgumentException("Invalid target: "+o);
075 }
076 }
077
078 // Create buckets for known classes
079 it=allClasses.iterator();
080 while (it.hasNext()) {
081 getClassBucket((Class) it.next());
082 }
083 }
084
085 private ResultConsumer resultConsumer = new ResultConsumer() {
086 public void consume(Object result) {
087 if (result!=null) {
088 Dispatcher.this.consumeResult(result);
089 }
090 }
091 };
092
093 /**
094 * Simple form of invocation used by dispatcher
095 * to chain invocations.
096 * @author Pavel Vlasov
097 * @revision $Revision$
098 */
099 protected interface Invocation {
100 void invoke(Object arg, ResultConsumer resultConsumer);
101 }
102
103 /**
104 * Dispatches object to invocation handlers.
105 * Exceptions thrown by handlers are wrapped into DispatchException and that exception is dispatched as
106 * though it is a return value. It is recommended to have an invocation handler which would handle thrown
107 * exceptions, e.g. log them. Errors are propagated up.
108 * @param arg Instance to be dispatched.
109 */
110 public void dispatch(Object arg) {
111 if (arg!=null) {
112 getClassBucket(arg.getClass()).invoke(arg, resultConsumer);
113 }
114 }
115
116 /**
117 * @param clazz
118 * @return Invocation which contains handlers compatible with given class. Handlers are ordered by inverse affinity. I.e. handlers with less
119 * specific parameters are invoked before handlers with more specific parameters. Handlers with unknown parameter type are invoked first.
120 */
121 private Invocation getClassBucket(final Class clazz) {
122 synchronized (buckets) {
123 Invocation ret=(Invocation) buckets.get(clazz);
124 if (ret==null) {
125 class CompatibleEntry implements Comparable {
126 int affinity;
127 InvocationHandler handler;
128
129 CompatibleEntry(int affinity, InvocationHandler handler) {
130 super();
131 this.affinity = affinity;
132 this.handler = handler;
133 }
134
135 /**
136 * Higher hash code (affinity) comes first
137 */
138 public int compareTo(Object obj) {
139 return obj.hashCode() - hashCode();
140 }
141
142 public int hashCode() {
143 return affinity;
144 }
145 }
146
147 List compatible=new ArrayList();
148 Iterator it=handlers.iterator();
149 while (it.hasNext()) {
150 InvocationHandler handler=(InvocationHandler) it.next();
151 if (handler.getParameterType()==null || handler.getParameterType().isAssignableFrom(clazz)) {
152 compatible.add(new CompatibleEntry(handler.getParameterType()==null ? Integer.MAX_VALUE : DuckConverterFactory.classAffinity(clazz, handler.getParameterType()), handler));
153 }
154 }
155
156 Collections.sort(compatible);
157
158 final InvocationHandler[] bucketHandlers=new InvocationHandler[compatible.size()];
159 Iterator cit = compatible.iterator();
160 for (int i=0; cit.hasNext(); ++i) {
161 bucketHandlers[i] = ((CompatibleEntry) cit.next()).handler;
162 }
163
164 ret = new Invocation() {
165
166 public void invoke(Object arg, ResultConsumer resultConsumer) {
167 for (int i=0, l=bucketHandlers.length; i<l; i++) {
168 try {
169 bucketHandlers[i].invoke(arg, resultConsumer);
170 } catch (Exception e) {
171 dispatch(new DispatchException(bucketHandlers[i], e));
172 } catch (Error e) {
173 throw e;
174 } catch (Throwable e) {
175 throw new RuntimeException("This should never happen", e);
176 }
177 }
178 }
179
180 public String toString() {
181 return "[class bucket] "+clazz.getName()+", "+bucketHandlers.length+" handlers";
182 }
183
184 };
185
186 buckets.put(clazz, ret);
187 }
188 return ret;
189 }
190 }
191
192 /**
193 * Consumes invocation results. This implementation
194 * simply dispatches results.
195 * Override this method to provide more advanced results handling.
196 * @param result
197 */
198 protected void consumeResult(Object result) {
199 dispatch(result);
200 }
201
202 /**
203 * @return Number of handlers
204 */
205 public int size() {
206 return handlers.size();
207 }
208 }