001 /* 002 @license.text@ 003 */ 004 package biz.hammurapi.util; 005 006 import java.lang.reflect.Array; 007 import java.lang.reflect.InvocationTargetException; 008 import java.lang.reflect.Method; 009 import java.util.ArrayList; 010 import java.util.Arrays; 011 import java.util.Collection; 012 import java.util.Collections; 013 import java.util.HashMap; 014 import java.util.IdentityHashMap; 015 import java.util.Iterator; 016 import java.util.LinkedList; 017 import java.util.List; 018 import java.util.Map; 019 020 import biz.hammurapi.config.Command; 021 022 /** 023 * Dispatching visitor navigates through Visitables hierarchy and 024 * invokes visit(<I>Type</I>) method of targets with compatible <I>Type</I>. 025 * @author Pavel Vlasov 026 * @version $Revision: 1.9 $ 027 */ 028 public class DispatchingVisitor implements PoliteVisitor, VisitorStackSource, Command { 029 private Collection targets; 030 private VisitorExceptionSink exceptionSink; 031 private Map handlers=new HashMap(); 032 private Map leaveHandlers=new HashMap(); 033 private Map verifyHandlers=new HashMap(); 034 private Map filterHandlers=new HashMap(); 035 private Collection listeners=new ArrayList(); 036 private Collection classesOfInterest=new ArrayList(); 037 private Collection unmodifiableClassesOfInterest=Collections.unmodifiableCollection(classesOfInterest); 038 039 private Collection handlerList=new ArrayList(); 040 private Collection leaveHandlerList=new ArrayList(); 041 private Collection verifyHandlerList=new ArrayList(); 042 private Collection filterHandlerList=new ArrayList(); 043 044 /** 045 * @param clazz 046 * @return handlers for class. 047 */ 048 private Collection getHandlers(Class clazz) { 049 synchronized (handlers) { 050 List ret=(List) handlers.get(clazz); 051 if (ret==null) { 052 ret=new ArrayList(); 053 handlers.put(clazz, ret); 054 Iterator it=handlerList.iterator(); 055 while (it.hasNext()) { 056 InvocationHandler handler=(InvocationHandler) it.next(); 057 if (handler.method.getParameterTypes()[0].isAssignableFrom(clazz)) { 058 ret.add(handler); 059 } 060 } 061 Collections.sort(ret); 062 } 063 return ret; 064 } 065 } 066 067 /** 068 * @param clazz 069 * @return leave handlers for class 070 */ 071 private Collection getLeaveHandlers(Class clazz) { 072 synchronized (leaveHandlers) { 073 List ret=(List) leaveHandlers.get(clazz); 074 if (ret==null) { 075 ret=new ArrayList(); 076 leaveHandlers.put(clazz, ret); 077 Iterator it=leaveHandlerList.iterator(); 078 while (it.hasNext()) { 079 InvocationHandler handler=(InvocationHandler) it.next(); 080 if (handler.method.getParameterTypes()[0].isAssignableFrom(clazz)) { 081 ret.add(handler); 082 } 083 } 084 Collections.sort(ret); 085 Collections.reverse(ret); 086 } 087 return ret; 088 } 089 } 090 091 /** 092 * @param clazz 093 * @return leave handlers for class 094 */ 095 private Collection getVerifyHandlers(Class clazz) { 096 synchronized (verifyHandlers) { 097 List ret=(List) verifyHandlers.get(clazz); 098 if (ret==null) { 099 ret=new ArrayList(); 100 verifyHandlers.put(clazz, ret); 101 Iterator it=verifyHandlerList.iterator(); 102 while (it.hasNext()) { 103 InvocationHandler handler=(InvocationHandler) it.next(); 104 if (handler.method.getParameterTypes()[0].isAssignableFrom(clazz)) { 105 ret.add(handler); 106 } 107 } 108 Collections.sort(ret); 109 Collections.reverse(ret); 110 } 111 return ret; 112 } 113 } 114 115 /** 116 * @param clazz 117 * @return filter handlers for class. 118 */ 119 private Collection getFilterHandlers(Class clazz) { 120 synchronized (filterHandlers) { 121 List ret=(List) filterHandlers.get(clazz); 122 if (ret==null) { 123 ret=new ArrayList(); 124 filterHandlers.put(clazz, ret); 125 Iterator it=filterHandlerList.iterator(); 126 while (it.hasNext()) { 127 InvocationHandler handler=(InvocationHandler) it.next(); 128 if (handler.method.getParameterTypes()[0].isAssignableFrom(clazz)) { 129 ret.add(handler); 130 } 131 } 132 } 133 return ret; 134 } 135 } 136 137 /** 138 * Targets which want to listen to invocations of self should implement 139 * this interface. 140 * @author Pavel Vlasov 141 * @revision $Revision: 1.9 $ 142 */ 143 public interface SelfListener { 144 void onInvocation(Method method, Object visitable); 145 } 146 147 public interface Listener { 148 void onInvocationRegistration(Object target, Method method); 149 void onTargetRegistration(Object target); 150 void onFilterRegistration(Method filter, Method target); 151 void noInvocationsWarning(Object target); 152 void onInvocation(Object target, Method method, Object visitable); 153 void onVisit(Object target); 154 void onLeave(Object target); 155 } 156 157 /** 158 * If target implements this insterface then it is used to 159 * filter invocations to other targets. approve() method(s) of 160 * this target, if any, will be invoked before invocation of 161 * visit()/leave() methods of filtered target. 162 * 163 * @author Pavel Vlasov 164 * @version $Revision: 1.9 $ 165 */ 166 public interface Filter { 167 Collection getTargets(); 168 } 169 170 public interface Stats { 171 long getVisits(); 172 long getInvocations(); 173 void reset(); 174 } 175 176 private static class StatsImpl implements Stats { 177 private long visits; 178 private long invocations; 179 180 public long getVisits() { 181 return visits; 182 } 183 184 public long getInvocations() { 185 return invocations; 186 } 187 188 public void reset() { 189 visits=0; 190 invocations=0; 191 } 192 193 public String toString() { 194 return "["+Thread.currentThread()+"] "+visits+" visits, "+invocations+" invocations"; 195 } 196 197 synchronized void addVisit() { 198 visits++; 199 } 200 201 synchronized void addInvocation() { 202 invocations++; 203 } 204 205 } 206 207 private int handlerCounter; 208 private StatsImpl stats=new StatsImpl(); 209 private ThreadLocal threadStats=new ThreadLocal() { 210 protected Object initialValue() { 211 return new StatsImpl(); 212 } 213 }; 214 215 private class InvocationHandler implements Comparable { 216 boolean active=true; 217 Method method; 218 Object object; 219 int position=handlerCounter++; 220 int order; 221 List filtersList=new LinkedList(); 222 ApproveInvocationHandler[] filters; 223 boolean returnsValue; 224 225 public String toString() { 226 return getClass().getName()+"["+order+" "+method+"]"; 227 } 228 229 void addFilter(ApproveInvocationHandler filter) { 230 filtersList.add(filter); 231 Iterator it=listeners.iterator(); 232 while (it.hasNext()) { 233 ((Listener) it.next()).onFilterRegistration(filter.method, method); 234 } 235 } 236 237 void commitFilters() { 238 filters=null; 239 if (!filtersList.isEmpty()) { 240 filters=(ApproveInvocationHandler[]) filtersList.toArray(new ApproveInvocationHandler[filtersList.size()]); 241 } 242 } 243 244 InvocationHandler(Method method, Object object) { 245 Iterator it=listeners.iterator(); 246 while (it.hasNext()) { 247 ((Listener) it.next()).onInvocationRegistration(object, method); 248 } 249 250 this.method=method; 251 this.object=object; 252 this.returnsValue=!(void.class.equals(method.getReturnType()) || boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType())); 253 254 addClassOfInterest(method.getParameterTypes()[0]); 255 256 if (object instanceof OrderedTarget) { 257 Integer ret=((OrderedTarget) object).getOrder(); 258 if (ret!=null) { 259 order=ret.intValue(); 260 } 261 } 262 263 } 264 265 Object invoke(Object arg) { 266 if (active) { 267 for (int i=0; filters!=null && i<filters.length; i++) { 268 if (Boolean.FALSE.equals(filters[i].invoke(arg))) { 269 return null; 270 } 271 } 272 try { 273 Iterator it=listeners.iterator(); 274 while (it.hasNext()) { 275 ((Listener) it.next()).onInvocation(object, method, arg); 276 } 277 stats.addInvocation(); 278 ((StatsImpl) threadStats.get()).addInvocation(); 279 if (object instanceof SelfListener) { 280 ((SelfListener) object).onInvocation(method, arg); 281 } 282 283 if (!method.getParameterTypes()[0].isInstance(arg)) { 284 System.err.println(method+", "+arg.getClass()); 285 } 286 287 return method.invoke(object, new Object[] {arg}); 288 } catch (IllegalArgumentException e) { 289 if (exceptionSink==null) { 290 e.printStackTrace(); 291 } else { 292 exceptionSink.consume(DispatchingVisitor.this, object, method, arg, e); 293 } 294 } catch (IllegalAccessException e) { 295 if (exceptionSink==null) { 296 e.printStackTrace(); 297 } else { 298 exceptionSink.consume(DispatchingVisitor.this, object, method, arg, e); 299 } 300 } catch (InvocationTargetException e) { 301 Throwable targetException = e.getTargetException(); 302 if (targetException instanceof Error) { 303 throw (Error) targetException; 304 } else if (exceptionSink==null) { 305 targetException.printStackTrace(); 306 } else { 307 exceptionSink.consume(DispatchingVisitor.this, object, method, arg, targetException instanceof Exception ? (Exception) targetException : e); 308 } 309 } 310 } 311 return null; 312 } 313 314 public int compareTo(Object o) { 315 if (this==o) { 316 return 0; 317 } 318 InvocationHandler ih=(InvocationHandler) o; 319 if (order==ih.order) { 320 if (position==ih.position) { 321 Class c=method.getParameterTypes()[0]; 322 Class ihc=ih.method.getParameterTypes()[0]; 323 if (c.equals(ihc)) { 324 return method.getDeclaringClass().getName().compareTo(ih.method.getDeclaringClass().getName()); 325 } 326 if (c.isAssignableFrom(ihc)) { 327 return -1; 328 } else if (ihc.isAssignableFrom(c)) { 329 return 1; 330 } else { 331 int idc=inheritanceDepth(c); 332 int ihidc=inheritanceDepth(ihc); 333 if (idc==ihidc) { 334 return method.getDeclaringClass().getName().compareTo(ih.method.getDeclaringClass().getName()); 335 } 336 return idc-ihidc; 337 } 338 } 339 return position-ih.position; 340 } 341 return order==ih.order ? 0 : order>ih.order ? 1 : -1; 342 } 343 } 344 345 private int inheritanceDepth(Class clazz) { 346 if (clazz==null || Object.class.equals(clazz)) { 347 return 0; 348 } 349 int ret=0; 350 ret=Math.max(ret, inheritanceDepth(clazz.getSuperclass())); 351 for (int i=0, j=clazz.getInterfaces().length; i<j; i++) { 352 ret=Math.max(ret, inheritanceDepth(clazz.getInterfaces()[i])); 353 } 354 return ret+1; 355 } 356 357 private class ApproveInvocationHandler extends InvocationHandler { 358 private Class parameterType; 359 360 ApproveInvocationHandler(Method method, Object object) { 361 super(method, object); 362 parameterType=method.getParameterTypes()[0]; 363 } 364 365 Map results=new IdentityHashMap(); 366 367 Object invoke(Object arg) { 368 if (!parameterType.isInstance(arg)) { 369 return null; // Incompatible parameter. 370 } 371 372 if (results.containsKey(arg)) { 373 return results.get(arg); 374 } 375 Object ret=super.invoke(arg); 376 results.put(arg, ret); 377 return ret; 378 } 379 380 void remove(Object key) { 381 results.remove(key); 382 } 383 } 384 385 private ThreadLocal visitorStackTL=new ThreadLocal() { 386 387 protected Object initialValue() { 388 return new VisitorStack(); 389 } 390 }; 391 392 private int size; 393 394 public VisitorStack getVisitorStack() { 395 return (VisitorStack) visitorStackTL.get(); 396 } 397 398 public boolean visit(Object target) { 399 getVisitorStack().push(target); 400 Iterator lit=listeners.iterator(); 401 while (lit.hasNext()) { 402 ((Listener) lit.next()).onVisit(target); 403 } 404 405 if (handlerList.isEmpty()) { 406 return false; 407 } 408 409 stats.addVisit(); 410 ((StatsImpl) threadStats.get()).addVisit(); 411 412 if (target!=null) { 413 // Verify target first. 414 Iterator it=getVerifyHandlers(target.getClass()).iterator(); 415 while (it.hasNext()) { 416 if (Boolean.FALSE.equals(((InvocationHandler) it.next()).invoke(target))) { 417 return false; 418 } 419 } 420 421 // Invoke visit methods. 422 it=getHandlers(target.getClass()).iterator(); 423 while (it.hasNext()) { 424 InvocationHandler ih=(InvocationHandler) it.next(); 425 Object ret=ih.invoke(target); 426 if (Boolean.FALSE.equals(ret)) { 427 return false; 428 } 429 430 if (ret!=null && ih.returnsValue) { 431 processReturnValue(ih.object, ih.method, target, ret); 432 } 433 } 434 } 435 436 return true; 437 } 438 439 public Collection getTargets() { 440 return targets; 441 } 442 443 /** 444 * Passes return values back to visitor. 445 * Arrays and collections are iterated and individual elements are passed to the visitor. 446 * Override this method to process return values in a different way. 447 * @param target - Object which method was invoked 448 * @param method - Method which was invoked 449 * @param argument - Method argument 450 * @param returnValue - Return value 451 */ 452 protected void processReturnValue(Object target, Method method, Object argument, Object returnValue) { 453 if (returnValue instanceof Collection) { 454 Iterator it=((Collection) returnValue).iterator(); 455 while (it.hasNext()) { 456 VisitableBase.object2visitor(it.next(), this); 457 } 458 } else if (returnValue.getClass().isArray()) { 459 for (int i=0,l=Array.getLength(returnValue);i<l;i++) { 460 VisitableBase.object2visitor(Array.get(returnValue, i), this); 461 } 462 } else { 463 VisitableBase.object2visitor(returnValue, this); 464 } 465 } 466 467 /** 468 * 469 * @return total number of handlers 470 */ 471 public int size() { 472 return size; 473 } 474 475 public Collection getClassesOfInterest() { 476 return unmodifiableClassesOfInterest; 477 } 478 479 private void addClassOfInterest(Class clazz) { 480 Iterator it=classesOfInterest.iterator(); 481 while (it.hasNext()) { 482 Class cls=(Class) it.next(); 483 if (cls.isAssignableFrom(clazz)) { 484 return; 485 } 486 487 if (clazz.isAssignableFrom(cls)) { 488 it.remove(); 489 } 490 } 491 classesOfInterest.add(clazz); 492 } 493 494 /** 495 * @param targets 496 * @param exceptionSink 497 */ 498 public DispatchingVisitor(Collection targets, VisitorExceptionSink exceptionSink, Listener listener) { 499 if (listener!=null) { 500 listeners.add(listener); 501 } 502 this.targets = Collections.unmodifiableList(new LinkedList(targets)); 503 this.exceptionSink = exceptionSink; 504 Iterator it=targets.iterator(); 505 while (it.hasNext()) { 506 Object target=it.next(); 507 if (target instanceof DispatcherAware) { 508 ((DispatcherAware) target).setDispatcher(this); 509 } 510 511 Iterator lit=listeners.iterator(); 512 while (lit.hasNext()) { 513 ((Listener) lit.next()).onTargetRegistration(target); 514 } 515 516 boolean hasInvocations=false; 517 Method[] methods=target.getClass().getMethods(); 518 for (int i=0; i<methods.length; i++) { 519 if (methods[i].getParameterTypes().length == 1) { 520 Class returnType = methods[i].getReturnType(); 521 if ("visit".equals(methods[i].getName()) && (void.class.equals(returnType) || boolean.class.equals(returnType))) { 522 handlerList.add(new InvocationHandler(methods[i], target)); 523 hasInvocations=true; 524 } else if ("leave".equals(methods[i].getName()) && void.class.equals(returnType)) { 525 leaveHandlerList.add(new InvocationHandler(methods[i], target)); 526 hasInvocations=true; 527 } else if (boolean.class.equals(returnType) && "verify".equals(methods[i].getName())) { 528 verifyHandlerList.add(new InvocationHandler(methods[i], target)); 529 hasInvocations=true; 530 } else if (target instanceof Filter && !((Filter) target).getTargets().isEmpty() && "approve".equals(methods[i].getName()) && boolean.class.equals(returnType)) { 531 filterHandlerList.add(new ApproveInvocationHandler(methods[i], target)); 532 hasInvocations=true; 533 } 534 } 535 } 536 537 if (!hasInvocations) { 538 lit=listeners.iterator(); 539 while (lit.hasNext()) { 540 ((Listener) lit.next()).noInvocationsWarning(target); 541 } 542 } 543 544 if (target instanceof Listener) { 545 listeners.add(target); 546 } 547 } 548 549 assignFilters(handlerList); 550 assignFilters(leaveHandlerList); 551 552 size = handlerList.size()+leaveHandlerList.size(); 553 } 554 555 /** 556 * 557 */ 558 private void assignFilters(Collection handlers) { 559 Iterator it=handlers.iterator(); 560 while (it.hasNext()) { 561 InvocationHandler handler=(InvocationHandler) it.next(); 562 Iterator fit=filterHandlerList.iterator(); 563 while (fit.hasNext()) { 564 ApproveInvocationHandler approveHandler=(ApproveInvocationHandler) fit.next(); 565 if (((Filter) approveHandler.object).getTargets().contains(handler.object)) { 566 handler.addFilter(approveHandler); 567 } 568 } 569 handler.commitFilters(); 570 } 571 } 572 573 public DispatchingVisitor(Collection targets, VisitorExceptionSink exceptionSink) { 574 this(targets, exceptionSink, null); 575 } 576 577 /** 578 * @param target 579 * @param exceptionSink 580 */ 581 public DispatchingVisitor(Object target, VisitorExceptionSink exceptionSink) { 582 this(Arrays.asList(new Object[] {target}), exceptionSink); 583 } 584 585 public DispatchingVisitor(Object target, VisitorExceptionSink exceptionSink, Listener listener) { 586 this(Arrays.asList(new Object[] {target}), exceptionSink, listener); 587 } 588 589 /** 590 * @return Returns visitor statistics. 591 */ 592 public Stats getStats() { 593 return stats; 594 } 595 596 public Stats getThreadStats() { 597 Stats ret = (Stats) threadStats.get(); 598 return ret; 599 } 600 601 public void leave(Object target) { 602 Iterator lit=listeners.iterator(); 603 while (lit.hasNext()) { 604 ((Listener) lit.next()).onLeave(target); 605 } 606 607 if (target!=null) { 608 Iterator it=getLeaveHandlers(target.getClass()).iterator(); 609 while (it.hasNext()) { 610 InvocationHandler ih=(InvocationHandler) it.next(); 611 Object ret=ih.invoke(target); 612 613 if (ret!=null && ih.returnsValue) { 614 processReturnValue(ih.object, ih.method, target, ret); 615 } 616 } 617 618 it=getFilterHandlers(target.getClass()).iterator(); 619 while (it.hasNext()) { 620 ((ApproveInvocationHandler) it.next()).remove(target); 621 } 622 } 623 getVisitorStack().pop(target); 624 } 625 626 /** 627 * Removes object from targets collection. 628 * @param target 629 */ 630 public void remove(Object target) { 631 Iterator it=handlerList.iterator(); 632 while (it.hasNext()) { 633 InvocationHandler h=(InvocationHandler) it.next(); 634 if (h.object==target) { 635 h.active=false; 636 } 637 } 638 639 it=leaveHandlerList.iterator(); 640 while (it.hasNext()) { 641 InvocationHandler h=(InvocationHandler) it.next(); 642 if (h.object==target) { 643 h.active=false; 644 } 645 } 646 647 it=verifyHandlerList.iterator(); 648 while (it.hasNext()) { 649 InvocationHandler h=(InvocationHandler) it.next(); 650 if (h.object==target) { 651 h.active=false; 652 } 653 } 654 } 655 656 /** 657 * Passes executionContext for visiting. 658 * Subclasses can override this method and 659 * queue object for execution and invoke 660 * accept/visit in a different thread. 661 */ 662 public void execute(Object executionContext) { 663 if (executionContext instanceof Visitable) { 664 ((Visitable) executionContext).accept(this); 665 } else { 666 visit(executionContext); 667 } 668 } 669 }