package wicket.contrib.spring.injection;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Method;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class SerializableCGLIBProxyCreator
{

	public SerializableCGLIBProxyCreator()
	{
		super();
	}
	
    public static Object makeProxy(Class targetClass, SerializableMethodInterceptor interceptor){
        
            //make the calls backs
            Callback[] callbacks = {new ExternalizeCallback(interceptor,targetClass), interceptor};
            
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(targetClass);
            
            if(!hasExternalizeMethod(targetClass))
                enhancer.setInterfaces(new Class[]{IWriteReplace.class});
            
            enhancer.setCallbacks(callbacks);
            enhancer.setCallbackFilter(SINGLETON_CF);
            
            Object proxy = enhancer.create();
            return proxy;
        }
       
        private static ProxyCallbackFilter SINGLETON_CF = new ProxyCallbackFilter();
        private static class ProxyCallbackFilter implements CallbackFilter{

    		public int accept(Method m)
    		{
    			if(isExternalizeMethod(m))
    			    return 0;
    		    return 1;
    		}
        }
        
        private static boolean hasExternalizeMethod(Class targetClass){
            Class cl = targetClass;
            while(cl != null){
                Method[] ms = cl.getDeclaredMethods();
                for (int i = 0; i < ms.length; i++) {
                	Method m = ms[i];
                	if(isExternalizeMethod(m)){
            			return true;
                	}
    			}
                cl = cl.getSuperclass();
            }
            return false;
        }
        
        private static boolean isExternalizeMethod(Method m){
            if("writeReplace".equals(m.getName())
                     && m.getParameterTypes().length == 0){
                Class[] exs = m.getExceptionTypes();
                if(exs.length == 1 && exs[0].equals(ObjectStreamException.class))
                    return true;
                else
                    return false;
            }
            return false;
        }
        
        private static class ExternalizeCallback implements MethodInterceptor{

            private final SerializableMethodInterceptor _mi;
            private final Class _targetClass;
            
            ExternalizeCallback(SerializableMethodInterceptor mi, Class targetClass){
                _mi = mi;
                _targetClass = targetClass;
            }
            
    		public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
    		{
    		    
    			return new ProxyReplacement(_mi,_targetClass);
    		}
            
        }
        
        public static class ProxyReplacement implements Serializable {
            private final SerializableMethodInterceptor _mi;
            private final Class _targetClass;
            ProxyReplacement(SerializableMethodInterceptor mi,Class target){
               _mi = mi; 
               _targetClass = target;
            }
            
            public Object readResolve() throws ObjectStreamException{
                return makeProxy(_targetClass,_mi);
            }
        }
        
        public interface SerializableMethodInterceptor extends MethodInterceptor, Serializable{
        }
        
        public interface IWriteReplace{
            public void writeReplace() throws ObjectStreamException;
        }

}
