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

org.zkoss.bind.xel.zel.BindELContext Maven / Gradle / Ivy

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

	Purpose:
		
	Description:
		
	History:
		Aug 10, 2011 4:52:27 PM, Created by henrichen

Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/

package org.zkoss.bind.xel.zel;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.zkoss.bind.BindContext;
import org.zkoss.bind.Binder;
import org.zkoss.bind.Property;
import org.zkoss.bind.annotation.DependsOn;
import org.zkoss.bind.annotation.Immutable;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.bind.annotation.NotifyChangeDisabled;
import org.zkoss.bind.annotation.SmartNotifyChange;
import org.zkoss.bind.impl.AllocUtil;
import org.zkoss.bind.impl.BindContextUtil;
import org.zkoss.bind.impl.BinderImpl;
import org.zkoss.bind.impl.LoadChildrenBindingImpl;
import org.zkoss.bind.impl.LoadFormBindingImpl;
import org.zkoss.bind.impl.LoadPropertyBindingImpl;
import org.zkoss.bind.impl.PropertyImpl;
import org.zkoss.bind.init.ViewModelAnnotationResolvers;
import org.zkoss.bind.sys.BindEvaluatorX;
import org.zkoss.bind.sys.Binding;
import org.zkoss.lang.Primitives;
import org.zkoss.lang.reflect.Fields;
import org.zkoss.xel.ExpressionX;
import org.zkoss.xel.ValueReference;
import org.zkoss.xel.XelContext;
import org.zkoss.xel.zel.XelELContext;
import org.zkoss.zel.ELResolver;
import org.zkoss.zel.impl.parser.AstBracketSuffix;
import org.zkoss.zel.impl.parser.AstDotSuffix;
import org.zkoss.zel.impl.parser.AstMethodParameters;
import org.zkoss.zel.impl.parser.AstValue;
import org.zkoss.zel.impl.parser.Node;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;

/**
 * ELContext for Binding.
 * @author henrichen
 * @since 6.0.0
 */
public class BindELContext extends XelELContext {
	public BindELContext(XelContext xelc) {
		super(xelc);
	}

	protected ELResolver newELResolver(XelContext xelc) {
		return new BindELResolver(xelc);
	}

	public Binding getBinding() {
		return (Binding) getXelContext().getAttribute(BinderImpl.BINDING); //see BindEvaluatorXImpl#newXelContext()
	}

	public BindContext getBindContext() {
		return (BindContext) getXelContext().getAttribute(BinderImpl.BINDCTX); //see BindEvaluatorXImpl#newXelContext()
	}

	public boolean ignoreTracker() {
		return getBinding() == null || Boolean.TRUE.equals(getXelContext().getAttribute(BinderImpl.IGNORE_TRACKER)); //see BindEvaluatorXImpl#newXelContext()
	}

	public Object getAttribute(String name) {
		return getXelContext().getAttribute(name); //see BindEvaluatorXImpl#newXelContext()
	}

	public Object setAttribute(String name, Object value) {
		return getXelContext().setAttribute(name, value);
	}

	/**
	 * Removes the attribute, if any.
	 * @since 8.0.0
	 */
	public Object removeAttribute(String name) {
		return getXelContext().removeAttribute(name);
	}

	private static final String TMPBASE = "$TMPBASE$";

	public static Property prepareProperty(Object base, String prop, Object value, BindContext ctx) {
		if (ctx != null && prop.indexOf('[') >= 0) { //handle properties that containing [] indirect reference
			final Binder binder = ctx.getBinder();
			final Component comp = ctx.getComponent();
			Object old = null;
			try {
				old = comp.setAttribute(TMPBASE, base);
				final BindEvaluatorX eval = binder.getEvaluatorX();
				final BindContext bctx = BindContextUtil.newBindContext(binder, null, false, null, comp, null);
				final String expression = TMPBASE + (prop.startsWith("[") ? prop : ("." + prop));
				final ExpressionX exprX = eval.parseExpressionX(bctx, expression, Object.class);
				final String propTrim = prop.trim();
				final ValueReference valref = eval.getValueReference(bctx, comp, exprX);
				if (valref == null) {
					throw new UiException(
							"value reference not found by expression [" + exprX.getExpressionString() + "]");
				}
				base = valref.getBase();
				prop = propTrim.endsWith("]") ? "[" + valref.getProperty() + "]" : "" + valref.getProperty();
			} finally {
				comp.setAttribute(TMPBASE, old);
			}
		}
		return new PropertyImpl(base, prop, value);
	}

	//check method annotation and collect NotifyChange annotation
	public static Set getNotifys(Method m, Object base, String prop, Object value, BindContext ctx) {
		//TODO, Dennis, do we really need to pass value here?
		final Set notifys = new LinkedHashSet();
		if (m == null)
			return notifys;

		final NotifyChange annt = ViewModelAnnotationResolvers.getAnnotation(m, NotifyChange.class);
		//ZK-687 @NotifyChange should be doing automatically. 
		final NotifyChangeDisabled anntdis = ViewModelAnnotationResolvers.getAnnotation(m, NotifyChangeDisabled.class);

		final SmartNotifyChange sannt = ViewModelAnnotationResolvers.getAnnotation(m, SmartNotifyChange.class);

		if (annt != null && anntdis != null) {
			throw new UiException(
					"don't use " + NotifyChange.class + " with " + NotifyChangeDisabled.class + ", choose only one");
		}
		if (sannt != null && anntdis != null) {
			throw new UiException("don't use " + SmartNotifyChange.class + " with " + NotifyChangeDisabled.class
					+ ", choose only one");
		}

		if (annt != null) {
			//if has annotation, use annotated value or prop (if no value in annotation)
			String[] notifies = annt.value();
			if (notifies.length > 0) {
				for (String notify : notifies) {
					final Property propx = prepareProperty(base, notify, value, ctx);
					notifys.add(propx);
				}
			} else if (prop != null) { //property is null in doExecute case
				notifys.add(new PropertyImpl(base, prop, value));
			}
		} else if (anntdis == null && prop != null) {
			notifys.add(new PropertyImpl(base, prop, value));
		}

		if (sannt != null) {
			//if has annotation, use annotated value or prop (if no value in annotation)
			String[] notifies = sannt.value();
			if (notifies.length > 0) {
				for (String notify : notifies) {
					Property propx = null;
					try {
						if (!("*".equals(notify) || ".".equals(notify)))
							propx = prepareProperty(base, notify, Fields.get(base, notify), ctx);
						else
							propx = prepareProperty(base, notify, value, ctx);
					} catch (NoSuchMethodException e) {
						propx = prepareProperty(base, notify, value, ctx); // eat the exception
					}
					if (propx != null)
						notifys.add(propx);
				}
			} else if (prop != null) { //property is null in doExecute case
				notifys.add(new PropertyImpl(base, prop, value));
			}
		}

		return notifys;
	}
	
	@SuppressWarnings("unchecked")
	public static Set getNotifys(BindContext ctx) {
		return (Set) ctx.getAttribute(BinderImpl.NOTIFYS);
	}

	public static void addNotifys(Object base, String prop, Object value, BindContext ctx) {
		final Set properties = new HashSet(3);
		properties.add(new PropertyImpl(base, prop, value));
		addNotifys(properties, ctx);
	}

	public static void addNotifys(Method m, Object base, String prop, Object value, BindContext ctx) {
		final Set props = getNotifys(m, base, prop, value, ctx);
		addNotifys(props, ctx);
	}

	//utility method to add notifys to BindContext
	public static void addNotifys(Set props, BindContext ctx) {
		if (ctx == null) {
			return;
		}
		Set notifys = getNotifys(ctx);
		if (notifys == null) {
			notifys = new LinkedHashSet();
			ctx.setAttribute(BinderImpl.NOTIFYS, notifys);
		}
		notifys.addAll(props);
	}

	@SuppressWarnings("unchecked")
	private static Set getValidates(BindContext ctx) {
		return (Set) ctx.getAttribute(BinderImpl.VALIDATES);
	}

	//utility method to add validates to BindContext
	@SuppressWarnings("unused")
	private static void addValidates(Set props, BindContext ctx) {
		if (ctx == null) {
			return;
		}
		Set validates = getValidates(ctx);
		if (validates == null) {
			validates = new LinkedHashSet();
			ctx.setAttribute(BinderImpl.VALIDATES, validates);
		}
		validates.addAll(props);
	}

	public static String toNodeString(Node next, StringBuilder path) {
		if (next instanceof AstBracketSuffix) {
			final String bracketString = toNodeString(next.jjtGetChild(0), new StringBuilder()); //recursive
			path.append("[").append(bracketString).append("]");
		} else if (next instanceof AstValue) {
			for (int j = 0, len = next.jjtGetNumChildren(); j < len; ++j) {
				final Node kid = next.jjtGetChild(j);
				toNodeString(kid, path); //recursive
			}
		} else if (next instanceof AstDotSuffix) {
			path.append(".").append(next.getImage());
		} else if (next instanceof AstMethodParameters) {
			StringBuilder subPath = new StringBuilder();
			for (int j = 0, len = next.jjtGetNumChildren(); j < len; ++j) {
				if (j > 0)
					subPath.append(',');
				final Node kid = next.jjtGetChild(j);
				toNodeString(kid, subPath); //recursive
			}
			path.append("(").append(subPath).append(")");
		} else {
			path.append(next.getImage());
		}
		return path.toString();
	}

	public static String toNodeString(Node next, StringBuffer path) {
		if (next instanceof AstBracketSuffix) {
			final String bracketString = toNodeString(next.jjtGetChild(0), new StringBuffer()); //recursive
			path.append("[").append(bracketString).append("]");
		} else if (next instanceof AstValue) {
			for (int j = 0, len = next.jjtGetNumChildren(); j < len; ++j) {
				final Node kid = next.jjtGetChild(j);
				toNodeString(kid, path); //recursive
			}
		} else if (next instanceof AstDotSuffix) {
			path.append(".").append(next.getImage());
		} else if (next instanceof AstMethodParameters) {
			StringBuilder subPath = new StringBuilder();
			for (int j = 0, len = next.jjtGetNumChildren(); j < len; ++j) {
				if (j > 0)
					subPath.append(',');
				final Node kid = next.jjtGetChild(j);
				toNodeString(kid, subPath); //recursive
			}
			path.append("(").append(subPath).append(")");
		} else {
			path.append(next.getImage());
		}
		return path.toString();
	}

	public static boolean isBracket(String script) {
		return script.startsWith("[") && script.endsWith("]");
	}

	public static String appendFields(String prefix, String field) {
		return prefix + (isBracket(field) ? "" : '.') + field;
	}

	//check method annotation and collect NotifyChange annotation
	public static void addDependsOnTrackings(Method m, String basepath, List srcpath, Binding binding,
			BindContext ctx) {
		final DependsOn annt = ViewModelAnnotationResolvers.getAnnotation(m, DependsOn.class);
		if (annt != null) {
			String[] props = annt.value();
			if (props.length > 0) {
				if (binding instanceof LoadPropertyBindingImpl) {
					((LoadPropertyBindingImpl) binding).addDependsOnTrackings(srcpath, basepath, props);
				} else if (binding instanceof LoadFormBindingImpl) {
					((LoadFormBindingImpl) binding).addDependsOnTrackings(srcpath, basepath, props);
				} else if (binding instanceof LoadChildrenBindingImpl) {
					((LoadChildrenBindingImpl) binding).addDependsOnTrackings(srcpath, basepath, props);
				}
			}
		}
	}

	public static String pathToString(List path) {
		final StringBuffer sb = new StringBuffer();
		for (String prop : path) {
			sb.append(prop);
		}
		return sb.toString();
	}

	/**
	 * Prepare the dependsOn nodes
	 * @param srcBinding associated binding of the source dependent field; e.g. 




© 2015 - 2024 Weber Informatics LLC | Privacy Policy