/************************************************
 * Copyright (c) Shen Li. All rights reserved.  *
 * http://joyaop.sourceforge.net                *
 * -------------------------------------------- *
 * Distributable under LGPL license.            *
 * See terms of license at gnu.org.             *
 ************************************************/
package net.sf.joyaop.impl;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.joyaop.AspectRuntimeException;
import net.sf.joyaop.util.ClassUtils;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author Shen Li
 */
class SerializableProxy implements Serializable {
    private static final long serialVersionUID = 0;
    private Object proxy;
    private Class[] interfaceClasses;
    private CallbackFilter callbackFilter;

    public SerializableProxy(Object proxy, Class[] interfaceClasses, CallbackFilter callbackFilter) {
        this.proxy = proxy;
        this.interfaceClasses = interfaceClasses;
        this.callbackFilter = callbackFilter;
    }

    public Object readResolve() {
        return proxy;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(((Factory) proxy).getCallbacks());
        out.writeObject(proxy.getClass().getSuperclass());
        out.writeObject(getSuperClassFields());
        out.writeObject(interfaceClasses);
        out.writeObject(callbackFilter);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        Callback[] callbacks = (Callback[]) in.readObject();
        Class superClass = (Class) in.readObject();
        List fieldValues = (List) in.readObject();
        Factory factory = ClassUtils.getProxyFactory(superClass);
        if (factory == null) {
            Class[] interfaceClasses = (Class[]) in.readObject();
            CallbackFilter callbackFilter = (CallbackFilter) in.readObject();
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(superClass);
            enhancer.setInterfaces(interfaceClasses);
            enhancer.setCallbacks(callbacks);
            enhancer.setCallbackFilter(callbackFilter);
            enhancer.setClassLoader(ClassUtils.getClassLoader());
            proxy = enhancer.create();
            ClassUtils.putProxyFactory(superClass, (Factory) proxy);
        }
        proxy = factory.newInstance(callbacks);
        setSuperClassFields(fieldValues);
    }

    private List getSuperClassFields() {
        final List fields = new ArrayList();
        visitSuperClassFields(new FieldVisitor() {
            public void visit(Field field) {
                try {
                    fields.add(field.get(proxy));
                } catch (IllegalAccessException e) {
                    throw new AspectRuntimeException(e);
                }
            }
        });
        return fields;
    }

    private void setSuperClassFields(List fields) {
        final Iterator i = fields.iterator();
        visitSuperClassFields(new FieldVisitor() {
            public void visit(Field field) {
                try {
                    field.set(proxy, (Object) i.next());
                } catch (IllegalAccessException e) {
                    throw new AspectRuntimeException(e);
                }
            }
        });
    }

    private void visitSuperClassFields(FieldVisitor visitor) {
        Class clazz = proxy.getClass().getSuperclass();
        while (!clazz.equals(Object.class)) {
            Field[] fields = clazz.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                fields[i].setAccessible(true);
                int modifiers = fields[i].getModifiers();
                if (!Modifier.isFinal(modifiers) && !Modifier.isStatic(modifiers)) {
                    visitor.visit(fields[i]);
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    private static interface FieldVisitor {
        void visit(Field field);
    }
}