/**
*    
*/

package poxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

import callback.PrimitiveUtil;
/**
* More industrial-level dynamic proxy , which main detach the Invocation handler and the real instance of a proxy class,
* which can store many proxy handler with them listener, this is very useful.
* Mean while, it also can work in multi thread env, so you can use it with Thread pool and Scheduler!
*    
* @author daniel
*
*/

public class IndustrialDynamicProxy {
   /*
    * container of iface & his InvocationHandler
    * Note: work in multithread env
    */

   private Map _iface2Handler = new HashMap();
    
   /*
    * Generates the 'caller' side of the caller/listener relationship.
    */

   public Object generateCaller(Class iFace, CallBackType type) {
    CallBackHanlder newHandler;
    Object proxy= null;
     //check iface
     if(!iFace.isInterface()){
         throw new IllegalArgumentException( "Class [" + iFace.getName() + "] is not an interface");
    }
     //generate a new handler    
    newHandler = new CallBackHanlder(type);
     //generate the face's proxy
    proxy = Proxy.newProxyInstance(iFace.getClassLoader(),
                     new Class[] { iFace },
                    newHandler);
     //register iface & proxy
     synchronized(_iface2Handler){
       if(_iface2Handler.containsKey(iFace)){
         throw new IllegalArgumentException( "Caller already generated " + " for interface [" +    
                                                iFace.getName() + "]");
      }
      _iface2Handler.put(iFace, newHandler);
    }
     return proxy;
  }

   /*
    * Register a listener for a given interface.    If the caller has not
         * been registered, then an error will be thrown.    
    */

     public void registerListener(Class iFace, Object o) {
     CallBackHanlder handler;
        
     if(!iFace.isAssignableFrom(o.getClass())){
         throw new IllegalArgumentException( "Object [" + o + "] does not " +
                                         "implement [" + iFace.getName() +
                                         "]");
     }
        
     synchronized (_iface2Handler) {
        handler = (CallBackHanlder) _iface2Handler.get(iFace);
     }
        
     if(handler == null){
         throw new IllegalArgumentException( "No callback for interface [" +
                                         iFace.getName() + "] exists");
     }
        
     handler.addListener(o);
    }
   /**
    * @param args
    */

   public static void main(String[] args) {
    
     //Base testhandler
    IndustrialDynamicProxy    x= new IndustrialDynamicProxy();
    
     //register iface, get the proxy
    MyInterface caller = (MyInterface)x.generateCaller(MyInterface. class, CallBackType.RETURN_LAST);
    
     //register the listener(real instance)
    x.registerListener(MyInterface. class, new MyInterface(){
      @Override
       public int addTwo( int a, int b) {
        System.out.println( "Target method called");
                                 return a + b;
      }        
    });
    
     //call the listener
     int result=caller.addTwo(2, 4);
     //take a look
    System.out.println( "addTwo(2,4):"+result);
  }

   /**
    * An inner class to operate listeners in private
    */

   static class CallBackHanlder implements InvocationHandler{
     /*
     * container of listeners (real instance)
     * Note: work in multithread env
     */

     private Set _listeners = new HashSet();
     //tool of get right Type
     private CallBackType _type = null;
    
    CallBackHanlder(CallBackType type){
       this._type=type;
    }
    
     private void addListener(Object o){
       synchronized(_listeners){
        _listeners.add(o);
      }
        
    }
    @Override
     public Object invoke(Object proxy, Method method, Object[] args)
         throws Throwable {
      Set listeners;
       synchronized (_listeners) {
                                listeners = new HashSet(_listeners);
                        }
       return _type.callListeners(method, args, listeners);
    }
    
  }
}



//Interface
interface MyInterface {
         int addTwo( int a, int b);
}

/*
* CallBackType,    
*/

abstract class CallBackType {
        
         public static final CallBackType RETURN_LAST = new CallBackType() {
            
                 public Object callListeners(Method meth, Object[] methArgs,Set listeners) throws Throwable
                {
                        Object last = null;
                         for (Iterator i=listeners.iterator(); i.hasNext(); ) {
                                Object listener = (Object)i.next();
                        
                                 try {
                                        last = meth.invoke(listener, methArgs);
                                } catch(InvocationTargetException e) {
                                         throw e.getTargetException();
                                }
                        }
                        
                         if (last == null) {
                                 // Nobody listening ...
                                 return PrimitiveUtil.getBasicValue(meth.getReturnType());
                        }

                         return last;
                }
        };        
        
         public abstract Object callListeners(Method meth, Object[] methArgs,
                                                                                 Set listeners)
                 throws Throwable;
}