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