/* BindExpressionBuilder.java Purpose: Description: History: Aug 15, 2011 11:04:37 AM, Created by henrichen Copyright (C) 2011 Potix Corporation. All Rights Reserved. */ package org.zkoss.bind.xel.zel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.bind.BindContext; import org.zkoss.bind.Binder; import org.zkoss.bind.Form; import org.zkoss.bind.impl.BinderImpl; import org.zkoss.bind.impl.LoadFormBindingImpl; import org.zkoss.bind.sys.BinderCtrl; import org.zkoss.bind.sys.Binding; import org.zkoss.bind.sys.SavePropertyBinding; import org.zkoss.bind.sys.tracker.Tracker; import org.zkoss.zel.ELContext; import org.zkoss.zel.ELException; import org.zkoss.zel.impl.lang.ExpressionBuilder; import org.zkoss.zel.impl.parser.AstIdentifier; import org.zkoss.zel.impl.parser.AstValue; import org.zkoss.zel.impl.parser.Node; import org.zkoss.zk.ui.Component; /** * Handle value dot series script for Binding. * @author henrichen * @since 6.0.0 */ public class BindExpressionBuilder extends ExpressionBuilder { private static final Logger _log = LoggerFactory.getLogger(BindExpressionBuilder.class); private static final String _isVisitedKey = BindExpressionBuilder.class + "_isVisted"; protected final BindELContext _ctx; public BindExpressionBuilder(String expression, ELContext ctx) throws ELException { super(expression, ctx); _ctx = (BindELContext) ctx; } //20110815, allow handle value dot series script (e.g. x.y.z ? a.b.c : d.e.f) //will call back for each token node. public void visit(Node node) throws ELException { super.visit(node); visitNode(node); } @SuppressWarnings("unchecked") private static List<String> getSrcList(BindContext ctx) { return (List<String>) ctx.getAttribute(BinderImpl.SRCPATH); } private static Component getDependsOnComponent(BindContext ctx) { return (Component) ctx.getAttribute(BinderImpl.DEPENDS_ON_COMP); } //to tracing load property dependency or form field(both save and load) //path example, [vm,.p1,.firstName] or [fx.firstName] protected void addTracking(List<String> series) { final Binding binding = _ctx.getBinding(); final boolean dotracker = !_ctx.ignoreTracker(); if (binding != null && series != null && !series.isEmpty()) { //to prevent unnecessary from-save in nested expression in from binding and cause save error //e.g @bind(fx.hash[fx.key]) or @bind(vm.hash[fx.key]) final Boolean isVisted = (Boolean) _ctx.getAttribute(_isVisitedKey); if (!Boolean.TRUE.equals(isVisted)) { _ctx.setAttribute(_isVisitedKey, Boolean.TRUE); } final Iterator<String> it = series.iterator(); final String prop = (String) it.next(); final Binder binder = binding.getBinder(); final Tracker tracker = ((BinderCtrl) binder).getTracker(); final BindContext bctx = (BindContext) _ctx.getAttribute(BinderImpl.BINDCTX); final List<String> srcpath = bctx != null ? getSrcList(bctx) : null; final Component dependsOnComp = bctx != null ? getDependsOnComponent(bctx) : null; final String[] srcprops = srcpath != null ? properties(srcpath) : null; //check if a PropertyBinding inside a Form final Component comp = bctx != null ? bctx.getComponent() : binding.getComponent(); final Object base = comp.getAttribute(prop, true); if (base instanceof Form) { final Form formBean = (Form) base; final String fieldName = fieldName(it); if (fieldName != null) { if (binding instanceof SavePropertyBinding && !Boolean.TRUE.equals(isVisted)) { if (_log.isDebugEnabled()) { _log.debug("add save-field '{}' to form '{}'", fieldName, formBean); } ((BinderCtrl) binder).addSaveFormFieldName(formBean, fieldName); ((BinderCtrl) binder).addFormAssociatedSaveBinding(comp, prop, (SavePropertyBinding) binding, fieldName); } } } //initialize Tracker per the series String[] props = properties(series); if (dotracker) { if (srcprops == null) { tracker.addTracking(comp, props, binding); } else { tracker.addDependsOn(comp, srcprops, binding, dependsOnComp, props); } } if (binding instanceof LoadFormBindingImpl) { ((LoadFormBindingImpl) binding).setSeriesLength(props.length); } } } protected void visitNode(Node node) { if (_ctx.getBinding() == null) return; //no need to build tracker, we are not in binding expression final List<String> path = new ArrayList<String>(); //find the path from AST value node or AST identifier node if (node instanceof AstValue) { for (int j = 0, len = node.jjtGetNumChildren(); j < len; ++j) { final Node kid = node.jjtGetChild(j); path.add(BindELContext.toNodeString(kid, new StringBuffer())); } addTracking(path); } else if (node instanceof AstIdentifier) { if (!(node.jjtGetParent() instanceof AstValue)) { //one variable series path.add(BindELContext.toNodeString(node, new StringBuffer())); addTracking(path); } } } //remove prefix '.' for properties private String[] properties(List<String> series) { final String[] props = new String[series.size()]; int j = 0; for (String prop : series) { if (prop.charAt(0) == '.') { prop = prop.substring(1); } props[j++] = prop; } return props; } //append rest field into form field name private String fieldName(Iterator<String> it) { final StringBuffer sb = new StringBuffer(); while (it.hasNext()) { sb.append(it.next()); } return sb.length() == 0 ? null : sb.charAt(0) == '.' ? sb.substring(1) : sb.toString(); } }