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                                            Integer affinity;
127                                            InvocationHandler handler;
128                                            
129                                            CompatibleEntry(Integer 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                                                    if (obj==this) {
140                                                            return 0;
141                                                    }
142                                                    return obj.hashCode() - hashCode();
143                                            }
144                                            
145                                            public int hashCode() {
146                                                    return affinity==null ? super.hashCode() : affinity.intValue();
147                                            }                                                                               
148                                    }
149                                    
150                                    List compatible=new ArrayList();
151                                    Iterator it=handlers.iterator();
152                                    while (it.hasNext()) {
153                                            InvocationHandler handler=(InvocationHandler) it.next();
154                                            if (handler.getParameterType()==null || handler.getParameterType().isAssignableFrom(clazz)) {
155                                                    Integer affinity = handler.getParameterType()==null ? new Integer(Integer.MAX_VALUE) : DuckConverterFactory.classAffinity(clazz, handler.getParameterType());
156                                                    compatible.add(new CompatibleEntry(affinity, handler));
157                                            }
158                                    }
159                                    
160                                    Collections.sort(compatible);
161                                    
162                                    final InvocationHandler[] bucketHandlers=new InvocationHandler[compatible.size()];
163                                    Iterator cit = compatible.iterator();
164                                    for (int i=0; cit.hasNext(); ++i) {
165                                            bucketHandlers[i] = ((CompatibleEntry) cit.next()).handler;
166                                    }
167                                    
168                                    ret = new Invocation() {
169    
170                                            public void invoke(Object arg, ResultConsumer resultConsumer) {
171                                                    for (int i=0, l=bucketHandlers.length; i<l; i++) {
172                                                            try {
173                                                                    bucketHandlers[i].invoke(arg, resultConsumer);
174                                                            } catch (Exception e) {
175                                                                    dispatch(new DispatchException(bucketHandlers[i], e));
176                                                            } catch (Error e) {
177                                                                    throw e;
178                                                            } catch (Throwable e) {
179                                                                    throw new RuntimeException("This should never happen", e);
180                                                            }
181                                                    }
182                                            }
183                                            
184                                            public String toString() {
185                                                    return "[class bucket] "+clazz.getName()+", "+bucketHandlers.length+" handlers";
186                                            }
187                                            
188                                    };
189                                    
190                                    buckets.put(clazz, ret);
191                            }
192                            return ret;                     
193                    }
194            }
195    
196            /**
197             * Consumes invocation results. This implementation
198             * simply dispatches results. 
199             * Override this method to provide more advanced results handling.
200             * @param result
201             */
202            protected void consumeResult(Object result) {
203                    dispatch(result);
204            }
205            
206            /**
207             * @return Number of handlers
208             */
209            public int size() {
210                    return handlers.size();
211            }
212    }