zkbind/src/main/java/org/zkoss/bind/proxy/BeanProxyHandler.java

Summary

Maintainability
F
4 days
Test Coverage
/** BeanProxyHandler.java.

    Purpose:
        
    Description:
        
    History:
        12:16:01 PM Dec 25, 2014, Created by jumperchen

Copyright (C) 2014 Potix Corporation. All Rights Reserved.
 */
package org.zkoss.bind.proxy;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

import javassist.util.proxy.MethodFilter;
import javassist.util.proxy.MethodHandler;

import org.zkoss.bind.BindContext;
import org.zkoss.bind.annotation.ImmutableFields;
import org.zkoss.bind.annotation.Transient;
import org.zkoss.bind.impl.AllocUtil;
import org.zkoss.bind.sys.SavePropertyBinding;
import org.zkoss.bind.xel.zel.BindELContext;
import org.zkoss.lang.Classes;
import org.zkoss.zk.ui.UiException;

/**
 * A bean proxy handler
 * 
 * @author jumperchen
 * @since 8.0.0
 */
public class BeanProxyHandler<T> implements MethodHandler, Serializable {
    protected static MethodFilter BEAN_METHOD_FILTER = new MethodFilter() {
        public boolean isHandled(Method m) {
            if (m.isAnnotationPresent(Transient.class))
                return false;
            final String name = m.getName();
            if ("hashCode".equals(name) || "equals".equals(name))
                return true;
            if (ProxyHelper.isAttribute(m)) {
                if (name.startsWith("set"))
                    return isSetMethodHandled(m);
                if (name.startsWith("get") || name.startsWith("is"))
                    return true;
            }
            try {
                FormProxyObject.class.getMethod(name, m.getParameterTypes());
                return true;
            } catch (NoSuchMethodException e) {
                return false;
            }
        }

    };
    protected T _origin;

    protected Map<String, Object> _cache;
    protected Set<String> _dirtyFieldNames; // field name that is dirty

    //ZK-3185: Enable form validation with reference and collection binding
    protected ProxyNode _node;

    public BeanProxyHandler(T origin) {
        _origin = origin;
    }

    protected static boolean isSetMethodHandled(Method m) {
        try {
            final String getter = ProxyHelper.toGetter(ProxyHelper.toAttrName(m));
            final Method getMethod = Classes.getMethodByObject(m.getDeclaringClass(), getter, null);
            if (getMethod.isAnnotationPresent(Transient.class))
                return false;
        } catch (NoSuchMethodException e) {
            // ignore if no getter available
        }
        return true;
    }

    private void addCache(String key, Object value) {
        _cache = AllocUtil.inst.putMap(_cache, key, value);
    }

    private void addDirtyField(String field) {
        _dirtyFieldNames = AllocUtil.inst.addSet(_dirtyFieldNames, field);
    }

    //ZK-3185: Enable form validation with reference and collection binding
    private void setPath(String property, ProxyNode parent) {
        if (property == null && _node != null) // means update
            _node.setParent(parent);
        else
            _node = new ProxyNodeImpl(property, parent);
    }

    public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Exception {
        try {
            final String mname = method.getName();
            if (mname.equals("hashCode")) {
                int a = (_origin != null) ? (Integer) method.invoke(_origin, args) : 0;
                return 37 * 31 + a;
            }
            if (mname.equals("equals")) {
                if (_origin != null) {
                    if (_origin instanceof FormProxyObject) {
                        return method.invoke(((FormProxyObject) _origin).getOriginObject(), args);
                    } else if (args.length == 1 && args[0] instanceof FormProxyObject) {
                        return method.invoke(_origin, new Object[] { ((FormProxyObject) args[0]).getOriginObject() });
                    } else {
                        return method.invoke(_origin, args);
                    }
                }
                // check null value
                return args.length == 1 && args[0] == null;
            }
            if (method.getDeclaringClass().isAssignableFrom(FormProxyObject.class)) {
                if ("submitToOrigin".equals(mname)) {
                    if (_origin != null && _cache != null) {
                        for (Map.Entry<String, Object> me : _cache.entrySet()) {
                            final Object value = me.getValue();
                            if (value instanceof FormProxyObject) {
                                ((FormProxyObject) value).submitToOrigin((BindContext) args[0]);
                            }

                            if (_dirtyFieldNames != null && _dirtyFieldNames.contains(me.getKey())) {
                                final String setter = ProxyHelper.toSetter(me.getKey());
                                try {
                                    final Method m = Classes.getMethodByObject(_origin.getClass(), setter,
                                            new Object[] { value });
                                    m.invoke(_origin, Classes.coerce(m.getParameterTypes()[0], value));
                                    BindELContext.addNotifys(m, _origin, me.getKey(), value, (BindContext) args[0]);
                                } catch (NoSuchMethodException e) {
                                    throw UiException.Aide.wrap(e);
                                }
                            }
                        }
                        if (_dirtyFieldNames != null)
                            _dirtyFieldNames.clear();
                    }
                } else if ("getOriginObject".equals(mname)) {
                    return _origin;
                } else if ("resetFromOrigin".equals(mname)) {
                    if (_dirtyFieldNames != null)
                        _dirtyFieldNames.clear();
                    if (_cache != null)
                        _cache.clear();
                } else if ("isFormDirty".equals(mname)) {
                    boolean dirty = false;

                    if (_cache != null) {
                        // If the dirty field is a form proxy object it may not be dirty.
                        // But once it contains a non-form proxy object, it must be dirty.
                        for (Map.Entry<String, Object> me : _cache.entrySet()) {
                            final Object value = me.getValue();
                            if (_dirtyFieldNames != null && _dirtyFieldNames.contains(me.getKey())) {
                                dirty = true;
                                break;
                            }

                            if (value instanceof FormProxyObject) {
                                if (((FormProxyObject) value).isFormDirty()) {
                                    dirty = true;
                                    break;
                                }
                            }
                        }
                    }
                    return dirty;
                } else if ("setPath".equals(mname)) {
                    //ZK-3185: Enable form validation with reference and collection binding
                    setPath((String) args[0], (ProxyNode) args[1]);
                } else if ("cacheSavePropertyBinding".equals(mname)) {
                    //ZK-3185: Enable form validation with reference and collection binding
                    ProxyHelper.cacheSavePropertyBinding(_node, _node.getProperty() + "." + (String) args[0], (SavePropertyBinding) args[1]);
                    return null;
                } else {
                    throw new IllegalAccessError("Not implemented yet for FormProxyObject interface: [" + mname + "]");
                }
            } else {
                if (mname.startsWith("get")) {
                    if (_origin == null)
                        return null;

                    final String attr = ProxyHelper.toAttrName(method);
                    if (_cache != null) {
                        if (_cache.containsKey(attr)) {
                            Object cacheData = _cache.get(attr);
                            if (_origin.getClass().getAnnotation(ImmutableFields.class) == null
                                    && !(self instanceof ImmutableFields)) {
                                // ZK-2736 Form proxy with Immutable values
                                Object proxyIfAny = ProxyHelper.createProxyIfAny(
                                        cacheData, method.getAnnotations());

                                addCache(attr, proxyIfAny);

                                return proxyIfAny;
                            }
                            return cacheData;
                        }
                    }

                    Object value = method.invoke(_origin, args);
                    if (value != null) {
                        if (_origin.getClass().getAnnotation(ImmutableFields.class) == null
                                && !(self instanceof ImmutableFields)) {
                            // ZK-2736 Form proxy with Immutable values
                            value = ProxyHelper.createProxyIfAny(value, method.getAnnotations());
                        }

                        addCache(attr, value);
                        if (value instanceof FormProxyObject) //ZK-3185: Enable form validation with reference and collection binding
                            ((FormProxyObject) value).setPath(attr, _node);
                    }
                    return value;
                } else if (mname.startsWith("is")) {
                    if (_origin == null)
                        return false;

                    final String attr = ProxyHelper.toAttrName(method, 2);
                    if (_cache != null) {
                        if (_cache.containsKey(attr)) {
                            return _cache.get(attr);
                        }
                    }
                    return method.invoke(_origin, args);
                } else {
                    final String attrName = ProxyHelper.toAttrName(method);

                    addCache(attrName, args[0]);
                    addDirtyField(attrName);
                    ProxyHelper.callOnDataChange(_node, new Object[]{self, attrName});
                    ProxyHelper.callOnDirtyChange(_node);
                }
            }
        } catch (Exception e) {
            throw UiException.Aide.wrap(e);
        }
        return null;
    }
}