/**
*    
*/

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;
}