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    }