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 }