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    }