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 }