All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zkoss.bind.proxy.ProxyHelper Maven / Gradle / Ivy

There is a newer version: 10.0.0-jakarta
Show newest version
/** ProxyHelper.java.

	Purpose:

	Description:

	History:
		12:03:06 PM Dec 25, 2014, Created by jumperchen

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

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;

import org.zkoss.bind.Form;
import org.zkoss.bind.annotation.Immutable;
import org.zkoss.bind.annotation.ImmutableElements;
import org.zkoss.bind.annotation.ImmutableFields;
import org.zkoss.bind.sys.SavePropertyBinding;
import org.zkoss.bind.xel.zel.BindELContext;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Library;
import org.zkoss.lang.SystemException;
import org.zkoss.util.Pair;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zul.ListModelArray;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.ListModelMap;
import org.zkoss.zul.ListModelSet;

/**
 * A proxy helper class to create a proxy cache mechanism for Set, List, Collection,
 * Map, and POJO.
 * @author jumperchen
 * @since 8.0.0
 */
public class ProxyHelper {
	private static final Map, Boolean> _ignoredClasses = new ConcurrentHashMap<>();
	private static final Map, Boolean> _ignoredSuperClasses = new ConcurrentHashMap<>();
	private static final List _proxyTargetHandlers;
	private static ProxyDecorator _proxyDecorator;

	static {
		List classes = Library.getProperties("org.zkoss.bind.proxy.IgnoredProxyClasses");
		if (classes != null && !classes.isEmpty()) {
			for (String className : classes) {
				try {
					addIgnoredProxyClass(Classes.forNameByThread(className.trim()));
				} catch (ClassNotFoundException ex) {
					throw new SystemException("Failed to load class " + className);
				}
			}
		}
		List superClasses = Library.getProperties("org.zkoss.bind.proxy.IgnoredSuperProxyClasses");
		if (superClasses != null && !superClasses.isEmpty()) {
			for (String className : superClasses) {
				try {
					addIgnoredSuperProxyClass(Classes.forNameByThread(className.trim()));
				} catch (ClassNotFoundException ex) {
					throw new SystemException("Failed to load class " + className);
				}
			}
		}
		_proxyTargetHandlers = new LinkedList<>(ZKProxyTargetHandlers.getSystemProxyTargetHandlers());

		String cls = Library.getProperty("org.zkoss.bind.proxy.ProxyDecoratorClass");
		if (cls != null) {
			try {
				_proxyDecorator = (ProxyDecorator) Classes.newInstanceByThread(cls.trim());
			} catch (Exception e) {
				throw new SystemException("Failed to load class " + cls);
			}
		}
	}
	/**
	 * Creates a proxy object from the given origin object, if any.
	 */
	@SuppressWarnings({"rawtypes" })
	public static  T createProxyIfAny(T origin) {
		return createProxyIfAny(origin, null);
	}

	/**
	 * Creates a proxy object from the given origin object, if any.
	 * @param annotations the annotations of the caller method to indicate whether
	 * the elements of the collection or Map type can proxy deeply, if any. (Optional)
	 * Like {@link ImmutableElements}
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static  T createProxyIfAny(T origin, Annotation[] annotations) {
		if (origin == null)
			return null;
		if (origin instanceof FormProxyObject) {
			return origin;
		}

		origin = getOriginObject(origin);
		boolean hasImmutableFields = false;
		if (annotations != null) {
			for (Annotation annot : annotations) {
				if (annot.annotationType().isAssignableFrom(Immutable.class))
					return origin;
				if (annot.annotationType().isAssignableFrom(ImmutableFields.class))
					hasImmutableFields = true;
			}
		}
		if (isImmutable(origin))
			return origin;

		ProxyFactory factory = new ProxyFactory();
		factory.setUseWriteReplace(false);
		if (origin instanceof ListModelList) // detect ZK ListModel First
			return (T) new ListModelListProxy((ListModelList) origin, annotations);
		else if (origin instanceof ListModelSet)
			return (T) new ListModelSetProxy((ListModelSet) origin, annotations);
		else if (origin instanceof ListModelMap)
			return (T) new ListModelMapProxy((ListModelMap) origin, annotations);
		else if (origin instanceof ListModelArray)
			return (T) new ListModelArrayProxy((ListModelArray) origin, annotations);
		else if (origin instanceof List) {
			return (T) new ListProxy((List) origin, annotations);
		} else if (origin instanceof Set) {
			return (T) new SetProxy((Set) origin, annotations);
		} else if (origin instanceof Map) {
			return (T) new MapProxy((Map) origin, annotations);
		} else if (origin instanceof Collection) {
			return (T) new ListProxy((Collection) origin, annotations);
		} else if (origin.getClass().isArray()) {
			throw new UnsupportedOperationException("Array cannot be a proxy object!");
		} else {
			factory.setFilter(BeanProxyHandler.BEAN_METHOD_FILTER);
			factory.setSuperclass(getTargetClassIfProxied(origin.getClass()));
			if (hasImmutableFields) {
				factory.setInterfaces(new Class[] { FormProxyObject.class, ImmutableFields.class });
			} else {
				factory.setInterfaces(new Class[] { FormProxyObject.class });
			}
			Class proxyClass = factory.createClass();
			Object p1;
			try {
				p1 = proxyClass.getDeclaredConstructor().newInstance();
			} catch (Exception e) {
				throw UiException.Aide.wrap(e,
						"Cannot create a proxy object:[" + origin.getClass() + "], an empty constructor is needed.");
			}

			((Proxy) p1).setHandler(new BeanProxyHandler<>(origin));
			return _proxyDecorator != null ? (T) _proxyDecorator.decorate((ProxyObject) p1) : (T) p1;
		}
	}

	/**
	 * Adds an ignored proxy class type. Once the data binder try to create a proxy
	 * object for the form binding, it will check whether the origin class type
	 * should be ignored.
	 * 

Default it will apply these classes from the library property * org.zkoss.bind.proxy.IgnoredProxyClasses *

*/ public static void addIgnoredProxyClass(Class type) { _ignoredClasses.put(type, Boolean.TRUE); } /** * Adds an ignored super proxy class type. Once the data binder try to create a proxy * object for the form binding, it will check the super classes of the origin class type. If one of the super * classes is in the ignored super classes, the origin class type should be ignored. *

Default it will apply these classes from the library property * org.zkoss.bind.proxy.IgnoredSuperProxyClasses *

* @since 8.6.1 */ public static void addIgnoredSuperProxyClass(Class type) { _ignoredSuperClasses.put(type, Boolean.TRUE); } /** * Returns whether the given origin object is immutable. */ public static boolean isImmutable(Object origin) { if (BindELContext.isImmutable(origin)) return true; return checkImmutable(origin.getClass()); } /** * Checks if the target class is already proxied */ private static Class getTargetClassIfProxied(Class clazz) { if (ProxyFactory.isProxyClass(clazz)) clazz = (Class) clazz.getSuperclass(); return clazz; } /** * Internal use only. */ public static T getOriginObject(T origin) { for (ProxyTargetHandler handlers : _proxyTargetHandlers) { if (handlers != null) { origin = handlers.getOriginObject(origin); } } return origin; } private static boolean checkImmutable(Class type) { if (_ignoredClasses.containsKey(type)) return true; if (Modifier.isFinal(type.getModifiers())) { _ignoredClasses.put(type, Boolean.TRUE); return true; } for (Map.Entry, Boolean> clzInfo : _ignoredSuperClasses.entrySet()) { if (clzInfo.getKey().isAssignableFrom(type)) { _ignoredClasses.put(type, Boolean.TRUE); //cache return true; } } return false; } /** * Creates a proxy form object from the given origin object, if any. * @param origin the origin data object * @param type the class type of the data object */ @SuppressWarnings({"rawtypes" }) public static T createFormProxy(T origin, Class type) { return createFormProxy(origin, type, null); } /** * Creates a proxy form object from the given origin object, if any. * @param origin the origin data object * @param type the class type of the data object * @param interfaces the interface type of the data object, if any. * @since 8.0.1 */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static T createFormProxy(T origin, Class type, Class[] interfaces) { if (origin instanceof Form) return origin; origin = getOriginObject(origin); ProxyFactory factory = new ProxyFactory(); factory.setUseWriteReplace(false); factory.setFilter(FormProxyHandler.FORM_METHOD_FILTER); if (origin instanceof FormProxyObject) type = ((FormProxyObject) origin).getOriginObject().getClass(); else if (origin != null) type = getTargetClassIfProxied(origin.getClass()); // set super class boolean isTypeInterface = type.isInterface(); if (!isTypeInterface) factory.setSuperclass(type); // set interface int i = 0; int len0 = interfaces != null ? interfaces.length : 0; Class[] newArray = new Class[len0 + 3 + (isTypeInterface ? 1 : 0)]; if (interfaces != null) { i += len0; System.arraycopy(interfaces, 0, newArray, 0, len0); } newArray[i++] = FormProxyObject.class; newArray[i++] = Form.class; newArray[i++] = FormFieldCleaner.class; if (isTypeInterface) { newArray[i] = type; } factory.setInterfaces(newArray); Class proxyClass = factory.createClass(); Object p1; try { p1 = proxyClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw UiException.Aide.wrap(e, "Cannot create a proxy object:[" + (origin != null ? origin.getClass() : null) + "], an empty constructor is needed."); } ((Proxy) p1).setHandler(new FormProxyHandler<>(origin)); return _proxyDecorator != null ? (T) _proxyDecorator.decorate((ProxyObject) p1) : (T) p1; } /** * Internal use only. */ /* package */static void cacheSavePropertyBinding(ProxyNode node, String property, SavePropertyBinding savePropertyBinding) { while (node != null) { ProxyNode parent = node.getParent(); if (parent == null) { node.getCachedSavePropertyBinding().add(new Pair<>(property, savePropertyBinding)); break; } else { String parentProperty = parent.getProperty(); if (!property.startsWith("[") && parentProperty != null && !parentProperty.isEmpty()) parentProperty += "."; property = parentProperty + property; node = parent; } } } /** * Internal use only. */ /* package */static void callOnDataChange(ProxyNode node, Object value) { while (node != null) { ProxyNode parent = node.getParent(); if (parent == null && node.getOnDataChangeCallback() != null) { node.getOnDataChangeCallback().call(value); break; } else { node = parent; } } } /** * Internal use only. */ /* package */static void callOnDirtyChange(ProxyNode node) { while (node != null) { ProxyNode parent = node.getParent(); if (parent == null && node.getOnDirtyChangeCallback() != null) { node.getOnDirtyChangeCallback().call(true); break; } else { node = parent; } } } /** * An interface to decorate the {@link ProxyObject} for some purposes, like * providing custom validation logic or adding extra handler on it. * *

To specify the custom proxy decorator class, you can specify the library * property org.zkoss.bind.proxy.ProxyDecoratorClass in zk.xml *

* since 8.0.3 */ public interface ProxyDecorator { ProxyObject decorate(ProxyObject proxyObject); } /** * Internal use only. */ public static String toSetter(String attr) { return capitalize("set", attr); } /** * Internal use only. */ public static String toGetter(String attr) { return capitalize("get", attr); } /** * Internal use only. */ public static String capitalize(String prefix, String attr) { return prefix + Character.toUpperCase(attr.charAt(0)) + attr.substring( 1); } /** * Internal use only. */ public static boolean isAttribute(Method method) { if (!Modifier.isPublic(method.getModifiers())) return false; final String nm = method.getName(); final int len = nm.length(); switch (method.getParameterTypes().length) { case 0: if (len >= 3 && nm.startsWith("is")) return true; return len >= 4 && nm.startsWith("get"); case 1: return len >= 4 && nm.startsWith("set"); default: return false; } } /** * Internal use only. */ public static String toAttrName(Method method, int prefix) { final String name = method.getName(); final String attrName = name.substring(prefix); return Character.toLowerCase(attrName.charAt(0)) + attrName.substring( 1); } /** * Internal use only. */ public static String toAttrName(Method method) { return toAttrName(method, 3); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy