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

org.zkoss.bind.impl.BinderImpl Maven / Gradle / Ivy

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

	Purpose:
		
	Description:
		
	History:
		Jul 29, 2011 6:08:51 PM, Created by henrichen

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

package org.zkoss.bind.impl;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.bind.BindComposer;
import org.zkoss.bind.BindContext;
import org.zkoss.bind.Binder;
import org.zkoss.bind.Converter;
import org.zkoss.bind.Form;
import org.zkoss.bind.FormExt;
import org.zkoss.bind.GlobalCommandEvent;
import org.zkoss.bind.Phase;
import org.zkoss.bind.PhaseListener;
import org.zkoss.bind.Property;
import org.zkoss.bind.PropertyChangeEvent;
import org.zkoss.bind.SimpleForm;
import org.zkoss.bind.Validator;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.DefaultCommand;
import org.zkoss.bind.annotation.DefaultGlobalCommand;
import org.zkoss.bind.annotation.GlobalCommand;
import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.sys.BindEvaluatorX;
import org.zkoss.bind.sys.BinderCtrl;
import org.zkoss.bind.sys.Binding;
import org.zkoss.bind.sys.CommandBinding;
import org.zkoss.bind.sys.ConditionType;
import org.zkoss.bind.sys.FormBinding;
import org.zkoss.bind.sys.LoadBinding;
import org.zkoss.bind.sys.LoadChildrenBinding;
import org.zkoss.bind.sys.LoadPropertyBinding;
import org.zkoss.bind.sys.PropertyBinding;
import org.zkoss.bind.sys.ReferenceBinding;
import org.zkoss.bind.sys.SaveBinding;
import org.zkoss.bind.sys.SaveFormBinding;
import org.zkoss.bind.sys.SavePropertyBinding;
import org.zkoss.bind.sys.TemplateResolver;
import org.zkoss.bind.sys.ValidationMessages;
import org.zkoss.bind.sys.debugger.BindingAnnotationInfoChecker;
import org.zkoss.bind.sys.debugger.BindingExecutionInfoCollector;
import org.zkoss.bind.sys.debugger.DebuggerFactory;
import org.zkoss.bind.sys.debugger.impl.info.AddCommandBindingInfo;
import org.zkoss.bind.sys.debugger.impl.info.AddBindingInfo;
import org.zkoss.bind.sys.debugger.impl.info.CommandInfo;
import org.zkoss.bind.sys.debugger.impl.info.EventInfo;
import org.zkoss.bind.sys.debugger.impl.info.NotifyChangeInfo;
import org.zkoss.bind.sys.tracker.Tracker;
import org.zkoss.bind.tracker.impl.TrackerImpl;
import org.zkoss.bind.xel.zel.BindELContext;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Library;
import org.zkoss.lang.Strings;
import org.zkoss.lang.reflect.Fields;
import org.zkoss.util.CacheMap;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.EventQueue;
import org.zkoss.zk.ui.event.EventQueues;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.metainfo.Annotation;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zk.ui.util.ComponentActivationListener;
import org.zkoss.zk.ui.util.Composer;
import org.zkoss.zk.ui.util.ExecutionInit;

/**
 * Implementation of Binder.
 * @author henrichen
 * @author dennischen
 * @since 6.0.0
 */
public class BinderImpl implements Binder,BinderCtrl,Serializable{

	private static final long serialVersionUID = 1463169907348730644L;
	
	private static final Logger _log = LoggerFactory.getLogger(BinderImpl.class);
	
	private static final Map RENDERERS = new HashMap();
	
	//control keys
	public static final String BINDING = "$BINDING$"; //a binding
	public static final String BINDER = "$BINDER$"; //the binder
	public static final String BINDCTX = "$BINDCTX$"; //bind context
	public static final String VAR = "$VAR$"; //variable name in a collection
	public static final String VM = "$VM$"; //the associated view model
	public static final String NOTIFYS = "$NOTIFYS$"; //changed properties to be notified
	public static final String VALIDATES = "$VALIDATES$"; //properties to be validated
	public static final String SRCPATH = "$SRCPATH$"; //source path that trigger @DependsOn tracking
	public static final String DEPENDS_ON_COMP = "$DEPENDS_ON_COMP"; //dependsOn component
	public static final String RENDERER_INSTALLED = "$RENDERER_INSTALLED$";
	
	public static final String LOAD_FORM_EXPRESSION = "$LOAD_FORM_EXPR$";//The attribute name of a loaded bean class, internal use only
	public static final String LOAD_FORM_COMPONENT = "$LOAD_FORM_COMP$";//The attribute name of a loaded bean class, internal use only
	
	public static final String IGNORE_TRACKER = "$IGNORE_TRACKER$"; //ignore adding currently binding to tracker, ex in init
	
	public static final String IGNORE_REF_VALUE = "$IGNORE_REF_VALUE$"; //ignore getting nested value form ref-binding when doing el evaluation.

	public static final String SAVE_BASE = "$SAVE_BASE$"; //bean base of a save operation
	public static final String ON_BIND_INIT = "onBindInit"; //do component binding initialization
	public static final String ON_BIND_CLEAN = "onBindClean"; //do component binding clean up
	public static final String MODEL = "$MODEL$"; //collection model for index tracking
	
	//events for dummy target
	private static final String ON_POST_COMMAND = "onPostCommand";
	private static final String ON_VMSGS_CHANGED = "onVMsgsChanged";
	
	//private control key
	private static final String FORM_ID = "$FORM_ID$";
	private static final String CHILDREN_ATTR = "$CHILDREN$";
	private static final String ACTIVATOR = "$ACTIVATOR$";//the activator that is stored in root comp
	
	//Command lifecycle result
	private static final int COMMAND_SUCCESS = 0;
	private static final int COMMAND_FAIL_VALIDATE = 1;
	
	
	//TODO make it configurable
	private final static Map, List> _initMethodCache = 
		new CacheMap, List>(600,CacheMap.DEFAULT_LIFETIME); //class,list

	private final static Map, Map>> _commandMethodCache = 
		new CacheMap, Map>>(200,CacheMap.DEFAULT_LIFETIME); //class,map
	
	private final static Map, Map>> _globalCommandMethodCache = 
		new CacheMap, Map>>(200,CacheMap.DEFAULT_LIFETIME); //class,map
	
	//command and default command method parsing and caching 
	private static final CachedItem NULL_METHOD = new CachedItem(null);
	private static final String COMMAND_METHOD_MAP_INIT = "$INIT_FLAG$";
	private static final String COMMAND_METHOD_DEFAULT = "$DEFAULT_FLAG$";
	private static final CommandMethodInfoProvider _commandMethodInfoProvider = new CommandMethodInfoProvider() {
		public String getAnnotationName() {
			return Command.class.getSimpleName();
		}
		public String getDefaultAnnotationName() {
			return DefaultCommand.class.getSimpleName();
		}
		public String[] getCommandName(Method method) {
			final Command cmd = method.getAnnotation(Command.class);
			return cmd==null?null:cmd.value();			
		}
		public boolean isDefaultMethod(Method method) {
			return method.getAnnotation(DefaultCommand.class)!=null;
		}
	};
	private static final CommandMethodInfoProvider _globalCommandMethodInfoProvider = new CommandMethodInfoProvider() {
		public String getAnnotationName() {
			return GlobalCommand.class.getSimpleName();
		}
		public String getDefaultAnnotationName() {
			return DefaultGlobalCommand.class.getSimpleName();
		}
		public String[] getCommandName(Method method) {
			final GlobalCommand cmd = method.getAnnotation(GlobalCommand.class);
			return cmd==null?null:cmd.value();			
		}
		public boolean isDefaultMethod(Method method) {
			return method.getAnnotation(DefaultGlobalCommand.class)!=null;
		}
	};
	
	
	private Component _rootComp;
	private BindEvaluatorX _eval;
	private PhaseListener _phaseListener;
	private boolean _phaseListenerSet = false;
	private static PhaseListener _sharedPhaseListener;
	private static boolean _sharedPhaseListenerSet = false;
	private Tracker _tracker;
	private final Component _dummyTarget = new AbstractComponent();//a dummy target for post command
	
	/* holds all binding in this binder */
	private final Map>> _bindings; //comp -> (evtnm | _fieldExpr | formid) -> bindings

	private final FormBindingHandler _formBindingHandler;
	private final PropertyBindingHandler _propertyBindingHandler;
	private final ChildrenBindingHandler _childrenBindingHandler;
	private final ReferenceBindingHandler _refBindingHandler;
	
	/* the relation of form and inner save-bindings */
	private Map> _assocFormSaveBindings;//form comp -> savebindings	
	private Map>> _reversedAssocFormSaveBindings;////associated comp -> binding -> associated save bindings of _formSaveBindingMap
	

	private final Map _listenerMap; //comp+evtnm -> eventlistener
	private final String _quename;
	private final String _quescope;
	private final QueueListener _queueListener;
	
	private ValidationMessages _validationMessages;
	private Set _hasValidators;//the key to mark they have validator
	
	private final Map> _templateResolvers;//comp,
	
	//flag to keep info of current vm has converter method or not
	private boolean _hasGetConverterMethod = true;
	
	//flag to keep info of current vm has validator method or not
	private boolean _hasGetValidatorMethod = true;

	//flag to keep info of the binding is initialized or not.
	private boolean _init = false;
	
	//flag to keep info that binder is in activating state
	private boolean _activating = false;
	//to help deferred activation when first execution
	private transient DeferredActivator _deferredActivator;
	
	private final ImplicitObjectContributor _implicitContributor;
	
	private static final String REF_HANDLER_CLASS_PROP = "org.zkoss.bind.ReferenceBindingHandler.class";
	
	public BinderImpl() {
		this(null,null);
	}
	
	public BinderImpl(String qname, String qscope) {
		_bindings = new HashMap>>();
		_formBindingHandler = new FormBindingHandler(); 
		_propertyBindingHandler = new PropertyBindingHandler();
		_childrenBindingHandler = new ChildrenBindingHandler();
		
		_formBindingHandler.setBinder(this);
		_propertyBindingHandler.setBinder(this);
		_childrenBindingHandler.setBinder(this);
		
		_refBindingHandler = MiscUtil.newInstanceFromProperty(REF_HANDLER_CLASS_PROP, null, ReferenceBindingHandler.class);
		if(_refBindingHandler!=null){
			_refBindingHandler.setBinder(this);
		}
		
		//zk-1548
		_implicitContributor = new ImplicitObjectContributorImpl();
		

		_assocFormSaveBindings = new HashMap>();
		_reversedAssocFormSaveBindings = new HashMap>>();
		
		_hasValidators = new HashSet();
		_templateResolvers = new HashMap>();
		_listenerMap = new HashMap();
		//use same queue name if user was not specified, 
		//this means, binder in same scope, same queue, they will share the notification by "base"."property" 
		_quename = qname != null && !Strings.isEmpty(qname) ? qname : DEFAULT_QUEUE_NAME;
		_quescope = qscope != null && !Strings.isBlank(qscope) ? qscope : DEFAULT_QUEUE_SCOPE;
		_queueListener = new QueueListener();
	}
	
	private class QueueListener implements EventListener,Serializable{
		private static final long serialVersionUID = 1L;

		public void onEvent(Event event) throws Exception {
			//only when a event in queue is our event
			if(event instanceof PropertyChangeEvent){
				final PropertyChangeEvent evt = (PropertyChangeEvent) event;
				BinderImpl.this.doPropertyChange(evt.getBase(), evt.getProperty());
			}else if(event instanceof GlobalCommandEvent){
				final GlobalCommandEvent evt = (GlobalCommandEvent) event;
				final Set notifys = new LinkedHashSet();
				BinderImpl.this.doGlobalCommand(_rootComp, evt.getCommand(), evt.getArgs(), notifys);
				fireNotifyChanges(notifys);
				notifyVMsgsChanged();
			}
		}
	}
	
	protected void checkInit(){
		if(!_init){
			throw new UiException("binder is not initialized yet");
		}
	}

	/**
	 * @deprecated use {@link #init(Component, Object, Map)} instead
	 */
	public void init(Component comp, Object viewModel){
		init(comp,viewModel,null);
	}
	
	/**
	 * 
	 * 
	 * 
	 * @since 6.0.1
	 */
	public void init(Component comp, Object viewModel, Map initArgs){
		if(_init) throw new UiException("binder is already initialized");
		_init = true;
		
		_rootComp = comp;
		//initial associated view model
		setViewModel(viewModel);
		_dummyTarget.addEventListener(ON_POST_COMMAND, new PostCommandListener());
		_dummyTarget.addEventListener(ON_VMSGS_CHANGED, new VMsgsChangedListener());
		//subscribe queue 
		subscribeQueue(_quename, _quescope, _queueListener);
		
		if(viewModel instanceof Composer && !(viewModel instanceof BindComposer)){//do we need to warn this?
			//show a warn only
			_log.warn("you are using a composer [%s] as a view model",viewModel);
		}
		new AbstractAnnotatedMethodInvoker(Init.class, _initMethodCache){
			protected boolean shouldLookupSuperclass(Init annotation) {
				return annotation.superclass();
			}}.invokeMethod(this, initArgs);
		
		_rootComp.setAttribute(ACTIVATOR, new Activator());//keep only one instance in root comp
	}
	
	
	//called when onPropertyChange is fired to the subscribed event queue
	private void doPropertyChange(Object base, String prop) {
		if(_log.isDebugEnabled()){
			_log.debug("doPropertyChange:base=[%s],prop=[%s]",base,prop);
		}
		
		//zk-1468, 
		//ignore a coming ref-binding if the binder is the same since it was loaded already.
		if(base instanceof ReferenceBinding && ((ReferenceBinding)base).getBinder()==this){
			return;
		}
		
		final Tracker tracker = getTracker();
		final Set bindings = tracker.getLoadBindings(base, prop);
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		try{
			if(collector != null){
				collector.pushStack("NOTIFY_CHANGE");
				collector.addInfo(new NotifyChangeInfo(_rootComp,base, prop,"Size="+bindings.size()));
			}
			doPropertyChange0(base, prop, bindings);
		}finally{
			if(collector != null){
				collector.popStack();
			}
		}
	}
	private void doPropertyChange0(Object base, String prop,Set bindings) {
		for(LoadBinding binding : bindings) {
			//BUG 828, the sub-sequence binding might be removed after the previous loading.
			final Component comp = binding.getComponent();
			if(comp==null || comp.getPage()==null) continue;
			
			final BindContext ctx = BindContextUtil.newBindContext(this, binding, false, null, comp, null);
			if(binding instanceof PropertyBinding){
				BindContextUtil.setConverterArgs(this, comp, ctx, (PropertyBinding)binding);
			}
			
			try { 
				if(_log.isDebugEnabled()){
					_log.debug("doPropertyChange:binding.load(),binding=[%s],context=[%s]",binding,ctx);
				}
				doPrePhase(Phase.LOAD_BINDING, ctx);
				binding.load(ctx);
			} finally {
				doPostPhase(Phase.LOAD_BINDING, ctx);
			}
			
			
			//zk-1468, 
			//notify the ref-binding changed since other nested binder might use it
			if(binding instanceof ReferenceBinding && binding!=base){
				notifyChange(binding,".");
			}
			
			if(_validationMessages!=null){
				String attr = null;
				if(binding instanceof PropertyBinding){
					attr = ((PropertyBinding)binding).getFieldName();
				}else if(binding instanceof FormBinding){
					attr = ((FormBinding)binding).getFormId();
				}else{
					//ignore children binding
				}
				if(attr!=null && hasValidator(comp, attr)){
					_validationMessages.clearMessages(comp,attr);
				}
			}
		}
	}
	
	public void setViewModel(Object vm) {
		checkInit();
		_rootComp.setAttribute(BinderImpl.VM, vm);
		_hasGetConverterMethod = true;//reset to true
		_hasGetValidatorMethod = true;//reset to true
	}
	
	public Object getViewModel() {
		checkInit();
		return _rootComp.getAttribute(BinderImpl.VM);
	}
	
	//Note: assume system converter is state-less
	public Converter getConverter(String name) {
		checkInit();
		Converter converter = null;
		if(_hasGetConverterMethod){
			Object vm = getViewModel();
			Class clz = vm.getClass();
			Method m = null;
			Object result = null;
			try {
				m = clz.getMethod("getConverter", String.class);
			} catch (SecurityException x) {
				_hasGetConverterMethod = false;
			} catch (NoSuchMethodException e) {
				_hasGetConverterMethod = false;
			}
			if(m!=null){
				try {
					result = m.invoke(vm,name);
				} catch (IllegalArgumentException e) {
					throw new RuntimeException(e.getMessage(),e);
				} catch (IllegalAccessException e) {
					_hasGetConverterMethod = false;
				} catch (InvocationTargetException e) {
					throw new RuntimeException(e.getMessage(),e);
				}
				if(result != null && !(result instanceof Converter)){
					_hasGetConverterMethod = false;
				}else{
					converter = (Converter)result;
				}
			}
		}
		if(converter == null){
			converter = SystemConverters.get(name);
		}
		if (converter == null) {
			throw new UiException("Cannot find converter:" + name);
		}
		return converter;
	}
	
	//Note: assume system validator is state-less
	public Validator getValidator(String name) {
		checkInit();
		Validator validator = null;
		if(_hasGetValidatorMethod){
			Object vm = getViewModel();
			Class clz = vm.getClass();
			Method m = null;
			Object result = null;
			try {
				m = clz.getMethod("getValidator", String.class);
			} catch (SecurityException x) {
				_hasGetValidatorMethod = false;
			} catch (NoSuchMethodException e) {
				_hasGetValidatorMethod = false;
			}
			if(m!=null){
				try {
					result = m.invoke(vm,name);
				} catch (IllegalArgumentException e) {
					throw new RuntimeException(e.getMessage(),e);
				} catch (IllegalAccessException e) {
					_hasGetValidatorMethod = false;
				} catch (InvocationTargetException e) {
					throw new RuntimeException(e.getMessage(),e);
				}
				if(result != null && !(result instanceof Validator)){
					_hasGetValidatorMethod = false;
				}else{
					validator = (Validator)result;
				}
			}
		}
		if(validator == null){
			validator = SystemValidators.get(name);
		}
		if (validator == null) {
			throw new UiException("Cannot find validator:" + name);
		}
		return validator;
	}
	
	//Note: assume system renderer is state-less 
	protected Object getRenderer(String name) {
		Object renderer = RENDERERS.get(name);
		if (renderer == null && name.indexOf('.') > 0) { //might be a class path
			try {
				renderer = Classes.newInstanceByThread(name);
				RENDERERS.put(name, renderer); //assume renderer is state-less
			} catch (IllegalAccessException e) {
				throw new UiException(e.getMessage(),e);
			} catch (Exception e) {
				//ignore
			}
		}
		return renderer;
	}

	public BindEvaluatorX getEvaluatorX() {
		if (_eval == null) {
			_eval = new BindEvaluatorXImpl(null, org.zkoss.bind.xel.BindXelFactory.class);
		}
		return _eval;
	}
	
	public void storeForm(Component comp,String id, Form form){
		final String oldid = (String)comp.getAttribute(FORM_ID, Component.COMPONENT_SCOPE);
		//check if a form exist already, allow to store a form with same id again for replacing the form 
		if(oldid!=null && !oldid.equals(id)){
			throw new IllegalArgumentException("try to store 2 forms in same component id : 1st "+oldid+", 2nd "+id);
		}
		final Form oldForm = (Form)comp.getAttribute(id);
		
		if(oldForm==form) return;
		
		comp.setAttribute(FORM_ID, id);//mark it is a form component with the form id;
		comp.setAttribute(id, form);//after setAttribute, we can access fx in el.
		
		if(form instanceof FormExt){
			final FormExt fex = (FormExt)form;
			comp.setAttribute(id+"Status", fex.getStatus());//by convention fxStatus
			
			if(oldForm instanceof FormExt){//copy the filed information, this is for a form-init that assign a user form
				for(String fn:((FormExt)oldForm).getLoadFieldNames()){
					fex.addLoadFieldName(fn);
				}
				for(String fn:((FormExt)oldForm).getSaveFieldNames()){
					fex.addSaveFieldName(fn);
				}
			}
		}
	}
	
	public Form getForm(Component comp,String id){
		String oldid = (String)comp.getAttribute(FORM_ID, Component.COMPONENT_SCOPE);
		if(oldid==null || !oldid.equals(id)){
			//return null if the id is not correct
			return null;
		}
		return (Form)comp.getAttribute(id);
	}

	private void removeForm(Component comp){
		String id = (String)comp.getAttribute(FORM_ID, Component.COMPONENT_SCOPE);
		if(id!=null){
			comp.removeAttribute(FORM_ID);
			comp.removeAttribute(id);
			comp.removeAttribute(id+"Status");
		}
	}
	
	
	public void addFormInitBinding(Component comp, String id, String initExpr, Map initArgs) {
		checkInit();
		if(Strings.isBlank(id)){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("form id is blank",comp));
		}
		if(initExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("initExpr is null for component "+comp+", form "+id,comp));
		}
		
		
		Form form = getForm(comp,id);
		if(form==null){
			storeForm(comp,id,new SimpleForm());
		}
		
		addFormInitBinding0(comp,id,initExpr,initArgs);

	}
	
	private void addFormInitBinding0(Component comp, String formId, String initExpr, Map bindingArgs) {
		
		if(_log.isDebugEnabled()){
			_log.debug("add init-form-binding: comp=[%s],form=[%s],expr=[%s]", comp,formId,initExpr);
		}
		final String attr = formId;
		
		InitFormBindingImpl binding = new InitFormBindingImpl(this, comp, attr, initExpr, bindingArgs);
		
		addBinding(comp, attr, binding);
		final BindingKey bkey = getBindingKey(comp, attr);
		_formBindingHandler.addInitBinding(bkey, binding);
		
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.addInfo(new AddBindingInfo(AddBindingInfo.FORM_INIT,comp,null, binding.getPropertyString(), formId,bindingArgs, null));
		}
	}
	
	
	public void addFormLoadBindings(Component comp, String id,
			String loadExpr, String[] beforeCmds, String[] afterCmds,
			Map bindingArgs) {
		checkInit();
		if(Strings.isBlank(id)){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("form id is blank",comp));
		}
		if(loadExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("loadExpr is null for component "+comp+", form "+id,comp));
		}
		
		Form form = getForm(comp,id);
		if(form==null){
			storeForm(comp,id,new SimpleForm());
		}
		
		addFormLoadBindings0(comp,id,loadExpr,beforeCmds,afterCmds,bindingArgs);
	}

	
	public void addFormSaveBindings(Component comp, String id, String saveExpr,
			String[] beforeCmds, String[] afterCmds,
			Map bindingArgs, String validatorExpr,
			Map validatorArgs) {
		checkInit();
		if(Strings.isBlank(id)){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("form id is blank",comp));
		}
		if(saveExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("saveExpr is null for component "+comp+", form "+id,comp));
		}
		
		Form form = getForm(comp,id);
		if(form==null){
			storeForm(comp,id,new SimpleForm());
		}

		addFormSaveBindings0(comp, id, saveExpr, beforeCmds, afterCmds, bindingArgs, validatorExpr, validatorArgs);
	}

	private void addFormLoadBindings0(Component comp, String formId, String loadExpr, String[] beforeCmds, String[] afterCmds, Map bindingArgs) {
		final boolean prompt = isPrompt(beforeCmds,afterCmds);
		final String attr = formId;
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(prompt){
			final LoadFormBindingImpl binding = new LoadFormBindingImpl(this, comp, formId, loadExpr,ConditionType.PROMPT,null, bindingArgs);
			addBinding(comp, attr, binding);
			final BindingKey bkey = getBindingKey(comp, attr);
			_formBindingHandler.addLoadPromptBinding(bkey, binding);
			
			if(collector!=null){
				collector.addInfo(new AddBindingInfo(AddBindingInfo.FORM_LOAD,comp,null, binding.getPropertyString(), formId, bindingArgs,null));
			}
		}else{
			if(beforeCmds!=null && beforeCmds.length>0){
				for(String cmd:beforeCmds){
					final LoadFormBindingImpl binding = new LoadFormBindingImpl(this, comp, formId, loadExpr,ConditionType.BEFORE_COMMAND,cmd, bindingArgs);
					addBinding(comp, attr, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add before command-load-form-binding: comp=[%s],attr=[%s],expr=[%s],command=[%s]", comp,attr,loadExpr,cmd);
					}
					_formBindingHandler.addLoadBeforeBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.FORM_LOAD,comp,"before = '"+cmd+"'", binding.getPropertyString(), formId, bindingArgs,null));
					}
				}
			}
			if(afterCmds!=null && afterCmds.length>0){
				for(String cmd:afterCmds){
					final LoadFormBindingImpl binding = new LoadFormBindingImpl(this, comp, formId, loadExpr,ConditionType.AFTER_COMMAND,cmd, bindingArgs);
					addBinding(comp, attr, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add after command-load-form-binding: comp=[%s],attr=[%s],expr=[%s],command=[%s]", comp,attr,loadExpr,cmd);
					}
					_formBindingHandler.addLoadAfterBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.FORM_LOAD,comp,"after = '"+cmd+"'", binding.getPropertyString(), formId, bindingArgs,null));
					}
				}
			}
		}
	}

	private void addFormSaveBindings0(Component comp, String formId, String saveExpr, 
			String[] beforeCmds, String[] afterCmds, Map bindingArgs,
			String validatorExpr,Map validatorArgs) {
		final boolean prompt = isPrompt(beforeCmds,afterCmds);
		if(prompt){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("a save-form-binding have to set with a before|after command condition",comp));
		}
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(beforeCmds!=null && beforeCmds.length>0){
			for(String cmd:beforeCmds){
				final SaveFormBindingImpl binding = new SaveFormBindingImpl(this, comp, formId, saveExpr, ConditionType.BEFORE_COMMAND, cmd, bindingArgs, validatorExpr, validatorArgs);
				addBinding(comp, formId, binding);
				if(_log.isDebugEnabled()){
					_log.debug("add before command-save-form-binding: comp=[%s],attr=[%s],expr=[%s],command=[%s]", comp,formId,saveExpr,cmd);
				}
				_formBindingHandler.addSaveBeforeBinding(cmd, binding);
				
				if(collector!=null){
					collector.addInfo(new AddBindingInfo(AddBindingInfo.FORM_SAVE, comp, "before = '"+cmd+"'", formId ,binding.getPropertyString(), bindingArgs,null));
				}
			}
		}
		if(afterCmds!=null && afterCmds.length>0){
			for(String cmd:afterCmds){
				final SaveFormBindingImpl binding = new SaveFormBindingImpl(this, comp, formId, saveExpr, ConditionType.AFTER_COMMAND, cmd, bindingArgs, validatorExpr, validatorArgs);
				addBinding(comp, formId, binding);
				if(_log.isDebugEnabled()){
					_log.debug("add after command-save-form-binding: comp=[%s],attr=[%s],expr=[%s],command=[%s]", comp,formId,saveExpr,cmd);
				}
				_formBindingHandler.addSaveAfterBinding(cmd, binding);
				
				if(collector!=null){
					collector.addInfo(new AddBindingInfo(AddBindingInfo.FORM_SAVE,comp, "after = '"+cmd+"'", formId ,binding.getPropertyString(), bindingArgs,null));
				}
			}
		}
		if(validatorExpr!=null){
			BindingKey bkey = getBindingKey(comp, formId);
			if(!_hasValidators.contains(bkey)){
				_hasValidators.add(bkey);
			}
		}
	}
	
	
	public void addPropertyInitBinding(Component comp, String attr,
			String initExpr,Map initArgs,
			String converterExpr, Map converterArgs) {
		checkInit();
		if(initExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("initExpr is null for "+attr+" of "+comp,comp));
		}
		if (Strings.isBlank(converterExpr)) {
			converterExpr = getSystemConverter(comp, attr);
			if (converterExpr != null) {
				converterExpr = "'"+converterExpr+"'";
			}
		}
		
		addPropertyInitBinding0(comp,attr,initExpr,initArgs,converterExpr,converterArgs);
		
		initRendererIfAny(comp,attr);
	}

	
	public void addPropertyLoadBindings(Component comp, String attr,
			String loadExpr, String[] beforeCmds, String[] afterCmds, Map bindingArgs,
			String converterExpr, Map converterArgs) {
		checkInit();
		if(loadExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("loadExpr is null for component "+comp+", attr "+attr,comp));
		}
		if (Strings.isBlank(converterExpr)) {
			converterExpr = getSystemConverter(comp, attr);
			if (converterExpr != null) {
				converterExpr = "'"+converterExpr+"'";
			}
		}
		
		addPropertyLoadBindings0(comp, attr, loadExpr, beforeCmds, afterCmds, bindingArgs, converterExpr, converterArgs);
		
		initRendererIfAny(comp,attr);
	}

	
	public void addPropertySaveBindings(Component comp, String attr,
			String saveExpr, String[] beforeCmds, String[] afterCmds,
			Map bindingArgs, String converterExpr,
			Map converterArgs, String validatorExpr,
			Map validatorArgs) {
		checkInit();
		if(saveExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("saveExpr is null for component "+comp+", attr "+attr,comp));
		}
		if (Strings.isBlank(converterExpr)) {
			converterExpr = getSystemConverter(comp, attr);
			if (converterExpr != null) {
				converterExpr = "'"+converterExpr+"'";
			}
		}
		if (Strings.isBlank(validatorExpr)) {
			validatorExpr = getSystemValidator(comp, attr);
			if (validatorExpr != null) {
				validatorExpr = "'"+validatorExpr+"'";
			}
		}

		addPropertySaveBindings0(comp, attr, saveExpr, beforeCmds, afterCmds, bindingArgs, 
				converterExpr, converterArgs, validatorExpr, validatorArgs);
	}
	
	
	private void addPropertyInitBinding0(Component comp, String attr,
			String initExpr, Map bindingArgs, String converterExpr, Map converterArgs) {
		
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation ann = AnnotationUtil.getSystemAnnotation(compCtrl, attr);
		String loadrep = null;
		Class attrType = null;//default is any class
		if (ann != null) {
			final Map attrs = ann.getAttributes(); //(tag, tagExpr)
			loadrep = AnnotationUtil.testString(attrs.get(Binder.LOAD_REPLACEMENT),ann); //check replacement of attr when loading
			
			final String type = AnnotationUtil.testString(attrs.get(Binder.LOAD_TYPE),ann); //check type of attr when loading
			if (type != null) {
				try {
					attrType = Classes.forNameByThread(type);
				} catch (ClassNotFoundException e) {
					throw new UiException(e.getMessage(),e);
				}
			}
		}
		loadrep = loadrep == null ? attr : loadrep;
		
		if(_log.isDebugEnabled()){
			_log.debug("add init-binding: comp=[%s],attr=[%s],expr=[%s],converter=[%s]", comp,attr,initExpr,converterArgs);
		}
		
		InitPropertyBindingImpl binding = new InitPropertyBindingImpl(this, comp, attr, loadrep, attrType, initExpr, bindingArgs, converterExpr, converterArgs);
		
		addBinding(comp, attr, binding); 
		final BindingKey bkey = getBindingKey(comp, attr);
		_propertyBindingHandler.addInitBinding(bkey, binding);
		
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.addInfo(new AddBindingInfo(AddBindingInfo.PROP_INIT, comp, null, binding.getPropertyString(), binding.getFieldName(), bindingArgs,null));
		}
	}

	private String getSystemConverter(Component comp, String attr) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation ann = AnnotationUtil.getSystemAnnotation(compCtrl,attr);
		if (ann != null) {
			final Map attrs = ann.getAttributes(); //(tag, tagExpr)
			return AnnotationUtil.testString(attrs.get(Binder.CONVERTER),ann); //system converter if exists
		}
		return null;
	}
	
	private String getSystemValidator(Component comp, String attr) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation ann = AnnotationUtil.getSystemAnnotation(compCtrl, attr);
		if (ann != null) {
			final Map attrs = ann.getAttributes(); //(tag, tagExpr)
			return AnnotationUtil.testString(attrs.get(Binder.VALIDATOR),ann); //system validator if exists
		}
		return null;
	}
	
	private void initRendererIfAny(Component comp,String attr) {
		final Object installed = comp.getAttribute(BinderImpl.RENDERER_INSTALLED);
		if (installed != null) { //renderer was set already init
			return;
		}
		
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation ann = AnnotationUtil.getSystemAnnotation(compCtrl, null);
		final Map attrs = ann != null ? ann.getAttributes() : null; //(tag, tagExpr)
		
		//only set up renderer when has model binding. (or will get error in no-model + selectedTab case
		final String installAttr = "model";//TODO make it configurable in lang-addon.xml
		if (attrs!=null && installAttr.equals(attr)) {
			final String rendererName = AnnotationUtil.testString(attrs.get(Binder.RENDERER),ann); //renderer if any
			//setup renderer
			if (rendererName != null) { //there was system renderer
				String[] values = null;
				if(rendererName.indexOf("=")!=-1){
					values = rendererName.split("=", 2);//zk 6.0.0
				}else{
					values = rendererName.split(":", 2);//after zk 6.0.1
				}
				
				if (values != null) {
					final Object renderer = getRenderer(values[1]);
					//check if user has set a renderer
					Object old = null;
					try {
						old = Fields.get(comp, values[0]);
					} catch (NoSuchMethodException e1) {
						//ignore
					}
					if (old == null) {
						try {
							Fields.set(comp, values[0], renderer, false);
						} catch (Exception  e) {
							throw new UiException(e.getMessage(),e);
						}
						
						if(renderer instanceof TemplateRendererCtrl){
							((TemplateRendererCtrl)renderer).setAttributeName(attr);
						}
					}
					
					comp.setAttribute(BinderImpl.RENDERER_INSTALLED,"");//mark installed
				}
			}
		}
	}
	
	private void addPropertyLoadBindings0(Component comp, String attr,
			String loadExpr, String[] beforeCmds, String[] afterCmds, Map bindingArgs,
			String converterExpr, Map converterArgs) {
		final boolean prompt = isPrompt(beforeCmds,afterCmds);
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		//check attribute _accessInfo natural characteristics to register Command event listener
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation ann = AnnotationUtil.getSystemAnnotation(compCtrl, attr);
		//check which attribute of component should load to component on which event.
		//the event is usually a engine lifecycle event.
		//ex, listbox's 'selectedIndex' should be loaded to component on 'onAfterRender'
		String evtnm = null;
		String loadRep = null;
		Class attrType = null;//default is any class
		if (ann != null) {
			final Map attrs = ann.getAttributes(); //(tag, tagExpr)
			final String rw = (String) AnnotationUtil.testString(attrs.get(Binder.ACCESS),ann); //_accessInfo right, "both|save|load", default to load
			if (rw != null && !"both".equals(rw) && !"load".equals(rw)) { //save only, skip
				return;
			}
			evtnm = AnnotationUtil.testString(attrs.get(Binder.LOAD_EVENT),ann); //check trigger event for loading
			
			loadRep = AnnotationUtil.testString(attrs.get(Binder.LOAD_REPLACEMENT),ann); //check replacement of attr when loading
			
			final String type = AnnotationUtil.testString(attrs.get(Binder.LOAD_TYPE),ann); //check type of attr when loading
			if(type!=null){
				try {
					attrType = Classes.forNameByThread(type);
				} catch (ClassNotFoundException e) {
					throw new UiException(e.getMessage(),e);
				}
			}
		}
		loadRep = loadRep == null ? attr : loadRep;
		
		if(prompt){
			if(_log.isDebugEnabled()){
				_log.debug("add event(prompt)-load-binding: comp=[%s],attr=[%s],expr=[%s],evtnm=[%s],converter=[%s]", comp,attr,loadExpr,evtnm,converterArgs);
			}
			LoadPropertyBindingImpl binding = new LoadPropertyBindingImpl(this, comp, attr, loadRep, attrType, loadExpr, ConditionType.PROMPT, null,  bindingArgs, converterExpr,converterArgs);
			addBinding(comp, attr, binding);
			
			if (evtnm != null) { //special case, load on an event, ex, onAfterRender of listbox on selectedItem
				registerCommandEventListener(comp, evtnm); //prompt
				addBinding(comp, evtnm, binding);//to mark evtnm has a this binding, so we can remove it
				final BindingKey bkey = getBindingKey(comp, evtnm);
				_propertyBindingHandler.addLoadEventBinding(comp, bkey, binding);
			}
			//if no command , always add to prompt binding, a prompt binding will be load when , 
			//1.load a component property binding
			//2.property change (TODO, DENNIS, ISSUE, I think loading of property change is triggered by tracker in doPropertyChange, not by prompt-binding 
			final BindingKey bkey = getBindingKey(comp, attr);
			_propertyBindingHandler.addLoadPromptBinding(comp, bkey, binding);
			
			if(collector!=null){
				collector.addInfo(new AddBindingInfo(AddBindingInfo.PROP_LOAD, comp, evtnm, binding.getPropertyString(), binding.getFieldName(), bindingArgs,null));
			}
		}else{
			if(beforeCmds!=null && beforeCmds.length>0){
				for(String cmd:beforeCmds){
					LoadPropertyBindingImpl binding = new LoadPropertyBindingImpl(this, comp, attr, loadRep, attrType, loadExpr, ConditionType.BEFORE_COMMAND, cmd, bindingArgs, converterExpr, converterArgs);
					addBinding(comp, attr, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add before command-load-binding: comp=[%s],att=r[%s],expr=[%s],converter=[%s]", comp,attr,loadExpr,converterExpr);
					}
					_propertyBindingHandler.addLoadBeforeBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.PROP_LOAD, comp, "before = '"+cmd+"'", binding.getPropertyString(), binding.getFieldName(), bindingArgs,null));
					}
				}
			}
			if(afterCmds!=null && afterCmds.length>0){
				for(String cmd:afterCmds){
					LoadPropertyBindingImpl binding = new LoadPropertyBindingImpl(this, comp, attr, loadRep, attrType, loadExpr,  ConditionType.AFTER_COMMAND, cmd, bindingArgs, converterExpr,converterArgs);
					addBinding(comp, attr, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add after command-load-binding: comp=[%s],att=r[%s],expr=[%s],converter=[%s]", comp,attr,loadExpr,converterExpr);
					}
					_propertyBindingHandler.addLoadAfterBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.PROP_LOAD, comp, "after = '"+cmd+"'", binding.getPropertyString(), binding.getFieldName(), bindingArgs,null));
					}
				}
			}
		}
	}
	
	private void addPropertySaveBindings0(Component comp, String attr, String saveExpr, String[] beforeCmds, String[] afterCmds, Map bindingArgs,
			String converterExpr, Map converterArgs, String validatorExpr, Map validatorArgs) {
		final boolean prompt = isPrompt(beforeCmds,afterCmds);
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		//check attribute _accessInfo natural characteristics to register Command event listener 
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation ann = AnnotationUtil.getSystemAnnotation(compCtrl, attr);
		//check which attribute of component should fire save on which event.
		//ex, listbox's 'selectedIndex' should be loaded to component on 'onSelect'
		//ex, checkbox's 'checked' should be saved to bean on 'onCheck'
		String evtnm = null;
		String saveRep = null;
		if (ann != null) {
			final Map attrs = ann.getAttributes(); //(tag, tagExpr)
			final String rw = AnnotationUtil.testString(attrs.get(Binder.ACCESS),ann); //_accessInfo right, "both|save|load", default to load
			if (!"both".equals(rw) && !"save".equals(rw)) { //load only, skip
				if(BinderUtil.hasContext() && BinderUtil.getContext().isIgnoreAccessCreationWarn()){
					return;
				}
				_log.warn(MiscUtil.formatLocationMessage("component "+comp+" doesn't support to save attribute "+attr,comp));
				return;
			}
			evtnm = AnnotationUtil.testString(attrs.get(Binder.SAVE_EVENT),ann); //check trigger event for saving
			
			saveRep = AnnotationUtil.testString(attrs.get(Binder.SAVE_REPLACEMENT),ann); //check replacement of attr when saving
		}
		if (evtnm == null) { 
			//no trigger event, since the value never change of component, so both prompt and command are useless
			if(BinderUtil.hasContext() && BinderUtil.getContext().isIgnoreAccessCreationWarn()){
				return;
			}
			_log.warn(MiscUtil.formatLocationMessage("component "+comp+" doesn't has event to save attribute "+attr,comp));
			return;
		}
		saveRep = saveRep == null ? attr : saveRep;
		
		if(prompt){
			final SavePropertyBindingImpl binding = new SavePropertyBindingImpl(this, comp, attr, saveRep, saveExpr, ConditionType.PROMPT, null, bindingArgs, converterExpr, converterArgs, validatorExpr, validatorArgs);
			addBinding(comp, attr, binding);
			if(_log.isDebugEnabled()){
				_log.debug("add event(prompt)-save-binding: comp=[%s],attr=[%s],expr=[%s],evtnm=[%s],converter=[%s],validate=[%s]", comp,attr,saveExpr,evtnm,converterExpr,validatorExpr);
			}
			registerCommandEventListener(comp, evtnm); //prompt
			addBinding(comp, evtnm, binding);//to mark evtnm has a this binding, so we can remove it in removeComponent
			final BindingKey bkey = getBindingKey(comp, evtnm);
			_propertyBindingHandler.addSavePromptBinding(comp, bkey, binding);
			
			if(collector!=null){
				collector.addInfo(new AddBindingInfo(AddBindingInfo.PROP_SAVE, comp, null, binding.getFieldName(),binding.getPropertyString(), bindingArgs,null));
			}
		}else{
			if(beforeCmds!=null && beforeCmds.length>0){
				for(String cmd:beforeCmds){
					final SavePropertyBindingImpl binding = new SavePropertyBindingImpl(this, comp, attr, saveRep, saveExpr, ConditionType.BEFORE_COMMAND, cmd, bindingArgs, converterExpr, converterArgs, validatorExpr, validatorArgs);
					addBinding(comp, attr, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add before command-save-binding: comp=[%s],att=r[%s],expr=[%s],converter=[%s],validator=[%s]", comp,attr,saveExpr,converterExpr,validatorExpr);
					}
					_propertyBindingHandler.addSaveBeforeBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.PROP_SAVE, comp, "before = '"+cmd+"'", binding.getFieldName(),binding.getPropertyString(), bindingArgs,null));
					}
				}
			}
			if(afterCmds!=null && afterCmds.length>0){
				for(String cmd:afterCmds){
					final SavePropertyBindingImpl binding = new SavePropertyBindingImpl(this, comp, attr, saveRep, saveExpr, ConditionType.AFTER_COMMAND, cmd, bindingArgs, converterExpr, converterArgs, validatorExpr, validatorArgs);
					addBinding(comp, attr, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add after command-save-binding: comp=[%s],att=r[%s],expr=[%s],converter=[%s],validator=[%s]", comp,attr,saveExpr,converterExpr,validatorExpr);
					}
					_propertyBindingHandler.addSaveAfterBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.PROP_SAVE, comp, "after = '"+cmd+"'", binding.getFieldName(),binding.getPropertyString(), bindingArgs,null));
					}
				}
			}
		}
		
		if(validatorExpr!=null){
			BindingKey bkey = getBindingKey(comp, attr);
			if(!_hasValidators.contains(bkey)){
				_hasValidators.add(bkey);
			}
		}
	}
	
	
	@Deprecated
	public void addChildrenInitBinding(Component comp, String initExpr,Map initArgs) {
		this.addChildrenInitBinding(comp, initExpr, initArgs,null,null);
	}
	
	public void addChildrenInitBinding(Component comp, String initExpr,Map initArgs,
			String converterExpr, Map converterArgs) {
		checkInit();
		if(initExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("initExpr is null for children of "+comp,comp));
		}
		addChildrenInitBinding0(comp,initExpr,initArgs,converterExpr,converterArgs);
	}
	
	
	@Deprecated
	public void addChildrenLoadBindings(Component comp,  String loadExpr, String[] beforeCmds, String[] afterCmds, Map bindingArgs) {
		addChildrenLoadBindings(comp, loadExpr, beforeCmds, afterCmds, bindingArgs,null, null);
	}
	
	public void addChildrenLoadBindings(Component comp,  String loadExpr, String[] beforeCmds, String[] afterCmds, Map bindingArgs,
			String converterExpr, Map converterArgs) {
		checkInit();
		if(loadExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("loadExpr is null for children of "+comp,comp));
		}
		addChildrenLoadBindings0(comp, loadExpr, beforeCmds, afterCmds, bindingArgs,converterExpr,converterArgs);
	}
	
	private void addChildrenInitBinding0(Component comp, String initExpr, Map bindingArgs,
			String converterExpr, Map converterArgs) {
		
		if(_log.isDebugEnabled()){
			_log.debug("add children-init-binding: comp=[%s],expr=[%s]", comp,initExpr);
		}
		
		InitChildrenBindingImpl binding = new InitChildrenBindingImpl(this, comp, initExpr, bindingArgs,converterExpr,converterArgs);
		
		addBinding(comp, CHILDREN_ATTR, binding); 
		final BindingKey bkey = getBindingKey(comp, CHILDREN_ATTR);
		_childrenBindingHandler.addInitBinding(bkey, binding);
		
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.addInfo(new AddBindingInfo(AddBindingInfo.CHILDREN_INIT, comp, null, binding.getPropertyString(), null, bindingArgs,null));
		}
		
	}
	
	private void addChildrenLoadBindings0(Component comp, String loadExpr, String[] beforeCmds, String[] afterCmds, Map bindingArgs,
			String converterExpr, Map converterArgs) {
		final boolean prompt = isPrompt(beforeCmds,afterCmds);
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(prompt){
			if(_log.isDebugEnabled()){
				_log.debug("add event(prompt)-children-load-binding: comp=[%s],expr=[%s]", comp,loadExpr);
			}
			LoadChildrenBindingImpl binding = new LoadChildrenBindingImpl(this, comp, loadExpr, ConditionType.PROMPT, null,  bindingArgs,converterExpr,converterArgs);
			addBinding(comp, CHILDREN_ATTR, binding);
			
			final BindingKey bkey = getBindingKey(comp, CHILDREN_ATTR);
			_childrenBindingHandler.addLoadPromptBinding(comp, bkey, binding);
			
			if(collector!=null){
				collector.addInfo(new AddBindingInfo(AddBindingInfo.CHILDREN_LOAD,comp, null, binding.getPropertyString(), null, bindingArgs,null));
			}
			
		}else{
			if(beforeCmds!=null && beforeCmds.length>0){
				for(String cmd:beforeCmds){
					LoadChildrenBindingImpl binding = new LoadChildrenBindingImpl(this, comp, loadExpr, ConditionType.BEFORE_COMMAND, cmd, bindingArgs,converterExpr,converterArgs);
					addBinding(comp, CHILDREN_ATTR, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add before command children-load-binding: comp=[%s],expr=[%s],cmd=[%s]", comp,loadExpr, cmd);
					}
					_childrenBindingHandler.addLoadBeforeBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.CHILDREN_LOAD, comp, "before = '"+cmd+"'", binding.getPropertyString(), null, bindingArgs,null));
					}
				}
			}
			if(afterCmds!=null && afterCmds.length>0){
				for(String cmd:afterCmds){
					LoadChildrenBindingImpl binding = new LoadChildrenBindingImpl(this, comp, loadExpr,  ConditionType.AFTER_COMMAND, cmd, bindingArgs,converterExpr,converterArgs);
					addBinding(comp, CHILDREN_ATTR, binding);
					if(_log.isDebugEnabled()){
						_log.debug("add after command children-load-binding: comp=[%s],expr=[%s],cmd=[%s]", comp,loadExpr, cmd);
					}
					_childrenBindingHandler.addLoadAfterBinding(cmd, binding);
					
					if(collector!=null){
						collector.addInfo(new AddBindingInfo(AddBindingInfo.CHILDREN_LOAD, comp, "after = '"+cmd+"'", binding.getPropertyString(), null, bindingArgs,null));
					}
				}
			}
		}
	}
	
	
	public void addReferenceBinding(Component comp, String attr,  String loadExpr, Map bindingArgs) {
		checkInit();
		if(loadExpr==null){
			throw new IllegalArgumentException(MiscUtil.formatLocationMessage("loadExpr is null for reference of "+comp,comp));
		}
		addReferenceBinding0(comp, attr, loadExpr, bindingArgs);
	}
	
	private void addReferenceBinding0(Component comp, String attr, String loadExpr, Map bindingArgs) {
		if(_log.isDebugEnabled()){
			_log.debug("add reference-binding: comp=[%s],attr=[%s],expr=[%s]", comp,attr,loadExpr);
		}
		ReferenceBindingImpl binding = new ReferenceBindingImpl(this, comp, attr, loadExpr);
		
		if(_refBindingHandler!=null){
			_refBindingHandler.addReferenceBinding(comp, attr, binding);
		}else{
			throw new UiException(MiscUtil.formatLocationMessage("ref binding handler is not supported in current runtime.",comp));
		}
		
		addBinding(comp, attr, binding);
		
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.addInfo(new AddBindingInfo(AddBindingInfo.REFERENCE,comp, null, binding.getPropertyString(), "self."+attr, bindingArgs,null));
		}
	}

	private boolean isPrompt(String[] beforeCmds, String[] afterCmds){
		return (beforeCmds==null || beforeCmds.length==0) && (afterCmds==null || afterCmds.length==0);
	}

	public void addCommandBinding(Component comp, String evtnm, String commandExpr, Map args) {
		checkInit();
		final CommandBindingImpl binding = new CommandBindingImpl(this, comp, evtnm, commandExpr, args);
		addBinding(comp, evtnm, binding);
		registerCommandEventListener(comp, evtnm, binding, false);
		
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.addInfo(new AddCommandBindingInfo(AddCommandBindingInfo.VIEWMODEL,comp, evtnm, binding.getCommandString(), args,null));
		}
	}
	
	public void addGlobalCommandBinding(Component comp, String evtnm, String commandExpr, Map args) {
		checkInit();
		final CommandBindingImpl binding = new CommandBindingImpl(this, comp, evtnm, commandExpr, args);
		addBinding(comp, evtnm, binding);
		registerCommandEventListener(comp, evtnm, binding, true);
		
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.addInfo(new AddCommandBindingInfo(AddCommandBindingInfo.GLOBAL,comp, evtnm, binding.getCommandString(), args,null));
		}
	}
	
	//associate event to CommandBinding
	private void registerCommandEventListener(Component comp, String evtnm, CommandBinding command,boolean global) {
		final CommandEventListener listener = getCommandEventListener(comp,evtnm);
		if(global){
			listener.setGlobalCommand(command);
		}else{
			listener.setCommand(command);
		}
	}
	
	//associate event to prompt
	private void registerCommandEventListener(Component comp, String evtnm) {
		final CommandEventListener listener = getCommandEventListener(comp,evtnm);
		listener.setPrompt(true);
	}
	
	private CommandEventListener getCommandEventListener(Component comp, String evtnm){
		final BindingKey bkey = getBindingKey(comp, evtnm);
		CommandEventListener listener = _listenerMap.get(bkey);
		if (listener == null) {
			listener = new CommandEventListener(comp);
			comp.addEventListener(evtnm, listener);
			_listenerMap.put(bkey, listener);
		}
		return listener;
	}
	
	private void removeEventCommandListenerIfExists(Component comp, String evtnm) {
		final BindingKey bkey = getBindingKey(comp, evtnm);
		final CommandEventListener listener = _listenerMap.remove(bkey);
		if (listener != null) {
			comp.removeEventListener(evtnm, listener);
		}
	}
	
	private class CommandEventListener implements EventListener, Serializable{
		private static final long serialVersionUID = 1L;
	//event used to trigger command
		private boolean _prompt = false;
		private CommandBinding _commandBinding;
		private CommandBinding _globalCommandBinding;
		final private Component _target;
		
		CommandEventListener(Component target){
			_target = target;
		}
		
		private void setCommand(CommandBinding command) {
			_commandBinding = command;
		}
		
		private void setGlobalCommand(CommandBinding command) {
			_globalCommandBinding = command;
		}
		
		private void setPrompt(boolean prompt) {
			_prompt = prompt;
		}
		
		public void onEvent(Event event) throws Exception {
			BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
			try{
				if(collector!=null){
					collector.pushStack("ON_EVENT");
					collector.addInfo(new EventInfo(event.getTarget(),event.getName(),null));
				}
				onEvent0(event);
			}catch(Exception x){
				_log.error(x.getMessage(),x);
				throw x;
			}finally{
				if(collector!=null){
					collector.popStack();
				}
			}
		}
		
		private void onEvent0(Event event) throws Exception {
			//command need to be confirmed shall be execute first!
			//must sort the command sequence?
			
			//BUG 619, event may come from children of some component, 
			//ex tabbox.onSelect is form tab, so we cannot depend on event's target
			final Component comp = _target;//_target is always equals _commandBinding.getComponent();
			final String evtnm = event.getName();
			final Set notifys = new LinkedHashSet();
			
			int cmdResult = COMMAND_SUCCESS; //command execution result, default to success
			boolean promptResult = true;
			String command = null;
			if(_log.isDebugEnabled()){
				_log.debug("====Start command event [%s]",event);
			}
			//BUG ZK-757, The timing of saving textbox's value attribute to ViewModel is later than command execution on onChange event
			//We should save the prompt with validation first. 
			//For a prompt binding that also binds with a command, that should not be mixed with command
			//If user concern the timing of prompt save and validation with command, they should use condition not prompt  
			if(_prompt){
				promptResult = BinderImpl.this.doSaveEvent(comp, event, notifys); //save on event
			}
			
			if (_commandBinding != null) {
				final BindEvaluatorX eval = getEvaluatorX();
				command = (String) eval.getValue(null, comp, ((CommandBindingImpl)_commandBinding).getCommand());
				if(!Strings.isEmpty(command)){//avoid the execution of a empty command.
					
					//ZK-1032 Able to wire Event to command method
					Map implicit = null;
					if(_implicitContributor!=null){
						implicit = _implicitContributor.contirbuteCommandObject(BinderImpl.this,_commandBinding,event);
					}
					
					final Map args = BindEvaluatorXUtil.evalArgs(eval, comp, _commandBinding.getArgs(),implicit);
					
					cmdResult = BinderImpl.this.doCommand(comp, _commandBinding, command, event, args, notifys);
				}
			}

			//load prompt only when prompt result is success
			if (_prompt && promptResult) {
				if(_log.isDebugEnabled()){
					_log.debug("This is a prompt command");
				}
				BinderImpl.this.doLoadEvent(comp, event); //load on event
			}

			notifyVMsgsChanged();//always, no better way to know which properties of validation are changed
			
			if(_log.isDebugEnabled()){
				_log.debug("There are [%s] property need to be notify after event = [%s], command = [%s]",notifys.size(),evtnm, command);
			}
			fireNotifyChanges(notifys);
			
			//post global command only when command success
			if (cmdResult==COMMAND_SUCCESS && _globalCommandBinding != null) {
				final BindEvaluatorX eval = getEvaluatorX();
				command = (String) eval.getValue(null, comp, ((CommandBindingImpl)_globalCommandBinding).getCommand());
				if(!Strings.isEmpty(command)){//avoid the execution of a empty command.
					
					//ZK-1791 @global-command does not provide predefined "event" variable
					Map implicit = null;
					if(_implicitContributor!=null){
						implicit = _implicitContributor.contirbuteCommandObject(BinderImpl.this,_commandBinding,event);
					}
					
					final Map args = BindEvaluatorXUtil.evalArgs(eval, comp, _globalCommandBinding.getArgs(),implicit);
					//post global command
					postGlobalCommand(comp, _globalCommandBinding ,command ,event, args);
				}
			}
			
			if(_log.isDebugEnabled()){
				_log.debug("====End command event [%s]",event);
			}
		}
	}
	
	private class VMsgsChangedListener implements EventListener,Serializable{
		private static final long serialVersionUID = 1L;
		public void onEvent(Event event) throws Exception {
			if(_validationMessages!=null){
				Set notify = new HashSet();
				notify.add(new PropertyImpl(_validationMessages,".",null));
				fireNotifyChanges(notify);
			}
		}
	}
	
	/**
	 * @since 6.0.1
	 */
	
	public boolean isActivating(){
		return _activating;
	}
	
	private void notifyVMsgsChanged(){
		if(_validationMessages!=null){
			//ZK-722 Validation message is not clear after form binding loaded
			//defer the validation notify as possible
			Events.postEvent(-1, _dummyTarget, new Event(ON_VMSGS_CHANGED));
		}
	}
	
	public void sendCommand(String command, Map args) {
		checkInit();
		final Set notifys = new HashSet();
		//args come from user, we don't eval it. 
		doCommand(_rootComp, null, command, null, args, notifys);
		fireNotifyChanges(notifys);
	}

	private void fireNotifyChanges(Set notifys) {
		for(Property prop : notifys) {
			notifyChange(prop.getBase(), prop.getProperty());
		}
	}
	
	public void postCommand(String command, Map args) {
		checkInit();
		final Event evt = new Event(ON_POST_COMMAND,_dummyTarget,new Object[]{command,args});
		Events.postEvent(evt);
	}
	
	
	/**
	 * @param comp the component that trigger the command, major life cycle of binding (on event trigger)
	 * @param commandBinding the command binding, nullable
	 * @param command command is the command name after evaluation
	 * @param evt event that fire this command, nullable
	 * @param commandArgs the passed in argument for executing command
	 * @param notifys container for properties that is to be notifyChange
	 * @return the result of the doCommand, COMMAND_SUCCESS or COMMAND_FAIL_VALIDATE 
	 */
	private int doCommand(Component comp, CommandBinding commandBinding, String command, Event evt, Map commandArgs, Set notifys) {
		final String evtnm = evt == null ? null : evt.getName();
		if(_log.isDebugEnabled()){
			_log.debug("Start doCommand comp=[%s],command=[%s],evtnm=[%s]",comp,command,evtnm);
		}
		BindContext ctx = BindContextUtil.newBindContext(this, commandBinding, false, command, comp, evt);
		BindContextUtil.setCommandArgs(this, comp, ctx, commandArgs);
		try {
			doPrePhase(Phase.COMMAND, ctx); //begin of Command
			boolean success = true;
			
			final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
			if(collector!=null){
				collector.addInfo(new CommandInfo(CommandInfo.ON_COMMAND,comp,evtnm,
						commandBinding==null?null:BindEvaluatorXUtil.getExpressionString(((CommandBindingImpl)commandBinding).getCommand()),
								command, commandArgs,null));
			}
			
			//validate
			success = doValidate(comp, command, evt, ctx, notifys);
			if (!success) {
				return COMMAND_FAIL_VALIDATE;
			}
			
			//save before command bindings
			doSaveBefore(comp, command, evt, ctx, notifys);
			
			//load before command bindings
			doLoadBefore(comp, command, ctx);
			
			//execute command
			doExecute(comp, command, commandArgs, ctx, notifys);
			
			//save after command bindings
			doSaveAfter(comp, command, evt, ctx, notifys);
			
			//load after command bindings
			doLoadAfter(comp, command, ctx);
			if(_log.isDebugEnabled()){
				_log.debug("End doCommand");
			}
			return COMMAND_SUCCESS;
		} finally {
			doPostPhase(Phase.COMMAND, ctx); //end of Command
		}
		
	}
	
	private void doGlobalCommand(Component comp, String command, Map commandArgs, Set notifys) {
		if(_log.isDebugEnabled()){
			_log.debug("Start doGlobalCommand comp=[%s],command=[%s]",comp,command);
		}
		
		
		BindContext ctx = BindContextUtil.newBindContext(this, null, false, command, comp, null);
		BindContextUtil.setCommandArgs(this, comp, ctx, commandArgs);
		try {
			doPrePhase(Phase.GLOBAL_COMMAND, ctx); //begin of Command
			
			final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
			if(collector!=null){
				collector.addInfo(new CommandInfo(CommandInfo.ON_GLOBAL_COMMAND,comp,null,null, command, commandArgs,null));
			}
			
			//execute command
			doGlobalCommandExecute(comp, command, commandArgs, ctx, notifys);
		} finally {
			doPostPhase(Phase.GLOBAL_COMMAND, ctx); //end of Command
		}
	}
	
	private void doGlobalCommandExecute(Component comp, String command, Map commandArgs, BindContext ctx,Set notifys) {
		try {
			if(_log.isDebugEnabled()){
				_log.debug("before doGlobalCommandExecute comp=[%s],command=[%s]",comp,command);
			}
			doPrePhase(Phase.EXECUTE, ctx);
			
			final Object viewModel = getViewModel();
			
			Method method = getCommandMethod(viewModel.getClass(), command, _globalCommandMethodInfoProvider,_globalCommandMethodCache);
			
			if (method != null) {
				
				BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
				if(collector!=null){
					collector.addInfo(new CommandInfo(CommandInfo.EXECUTE_GLOBAL,comp,
							null,null,command,commandArgs, method.toString()));
				}
				
				ParamCall parCall = createParamCall(ctx);
				if(commandArgs != null){
					parCall.setBindingArgs(commandArgs);
				}
				
				parCall.call(viewModel, method);
				
				notifys.addAll(BindELContext.getNotifys(method, viewModel,
						(String) null, (Object) null, ctx)); // collect notifyChange
			}else{
				//do nothing
				if(_log.isDebugEnabled()){
					_log.debug("no global command method in [%s]", viewModel);
				}
			}
			if(_log.isDebugEnabled()){
				_log.debug("after doGlobalCommandExecute notifys=[%s]", notifys);
			}
		} finally {
			doPostPhase(Phase.EXECUTE, ctx);
		}
	}
	
	/*package*/ void doPrePhase(Phase phase, BindContext ctx) {
		BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.pushStack(phase.name());
		}
		final PhaseListener listener = getPhaseListener();
		if (listener != null) {
			listener.prePhase(phase, ctx);
		}
	}
	
	/*package*/ void doPostPhase(Phase phase, BindContext ctx) {
		final PhaseListener listener = getPhaseListener();
		
		if (listener != null) {
			listener.postPhase(phase, ctx);
		}
		BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		if(collector!=null){
			collector.popStack();
		}
	}

	//for event -> prompt only, no command 
	private boolean doSaveEvent(Component comp, Event evt, Set notifys) {
		final String evtnm = evt == null ? null : evt.getName();
		if(_log.isDebugEnabled()){
			_log.debug("doSaveEvent comp=[%s],evtnm=[%s],notifys=[%s]",comp,evtnm,notifys);
		}
		final BindingKey bkey = getBindingKey(comp, evtnm);
		return _propertyBindingHandler.doSaveEvent(bkey, comp, evt, notifys);
	}
	
	//for event -> prompt only, no command
	private void doLoadEvent(Component comp, Event evt) {
		if(_log.isDebugEnabled()){
			_log.debug("doLoadEvent comp=[%s],evtnm=[%s]",comp,evt.getName());
		}
		final BindingKey bkey = getBindingKey(comp, evt.getName()); 
		_propertyBindingHandler.doLoadEvent(bkey, comp, evt);
	}
	
	//doCommand -> doValidate
	private boolean doValidate(Component comp, String command, Event evt, BindContext ctx, Set notifys) {
		final Set validates = new HashSet();
		try {
			if(_log.isDebugEnabled()){
				_log.debug("doValidate comp=[%s],command=[%s],evt=[%s],context=[%s]",comp,command,evt,ctx);
			}
			doPrePhase(Phase.VALIDATE, ctx);
			
			//we collect properties that need to be validated, than validate one-by-one
			ValidationHelper vHelper = new ValidationHelper(this,new ValidationHelper.InfoProvider() {
				public Map> getSaveFormBeforeBindings() {
					return _formBindingHandler.getSaveFormBeforeBindings();
				}		
				public Map> getSaveFormAfterBindings() {
					return _formBindingHandler.getSaveFormAfterBindings();
				}
				public Map> getSaveBeforeBindings() {
					return _propertyBindingHandler.getSaveBeforeBindings();
				}
				public Map> getSaveAfterBindings() {
					return _propertyBindingHandler.getSaveAfterBindings();
				}
				public BindingKey getBindingKey(Component comp, String attr) {
					return BinderImpl.this.getBindingKey(comp,attr);
				}
			});
			
			//collect Property of special command for validation in validates
			vHelper.collectSaveBefore(comp, command, evt, validates);
			vHelper.collectSaveAfter(comp, command, evt, validates);
			
			//do validation (defined by application)
			if (validates.isEmpty()) {
				return true;
			} else {
				if(_log.isDebugEnabled()){
					_log.debug("doValidate validates=[%s]",validates);
				}
				boolean valid = true;
				
				//ZK-878 Exception if binding a form with errorMessage
				//To handle wrong value exception when getting a component value.
				for(Property p :validates){
					if(p instanceof WrongValuePropertyImpl){
						for(WrongValueException wve:((WrongValuePropertyImpl)p).getWrongValueExceptions()){
							//refer to UiEngineImpl#handleError()
							Component wvc = wve.getComponent();
							if(wvc!=null){
								wve = ((ComponentCtrl)wvc).onWrongValue(wve);
								if (wve != null) {
									Component c = wve.getComponent();
									if (c == null) c = wvc;
									Clients.wrongValue(c, wve.getMessage());
								}
							}
						}
						valid = false;
					}
				}
				
				Map properties = _propertyBindingHandler.toCollectedProperties(validates);
				valid &= vHelper.validateSaveBefore(comp, command, properties,valid,notifys);
				valid &= vHelper.validateSaveAfter(comp, command, properties,valid,notifys);
				
				
				return valid;
			}
		} catch (Exception e) {
			throw new UiException(e.getMessage(),e);
		} finally {
			doPostPhase(Phase.VALIDATE, ctx);
		}
	}
	
	private ParamCall createParamCall(BindContext ctx){
		final ParamCall call = new ParamCall();
		call.setBinder(this);
		call.setBindContext(ctx);
		final Component comp = ctx.getComponent();
		if(comp!=null){
			call.setComponent(comp);
		}
		final Execution exec = Executions.getCurrent();
		if(exec!=null){
			call.setExecution(exec);
		}
		
		return call;
	}
	
	
	private void doExecute(Component comp, String command, Map commandArgs, BindContext ctx, Set notifys) {
		try {
			if(_log.isDebugEnabled()){
				_log.debug("before doExecute comp=[%s],command=[%s],notifys=[%s]",comp,command,notifys);
			}
			doPrePhase(Phase.EXECUTE, ctx);
			
			final Object viewModel = getViewModel();
			
			Method method = getCommandMethod(viewModel.getClass(), command, _commandMethodInfoProvider, _commandMethodCache);
			
			if (method != null) {
				
				BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
				if(collector!=null){
					collector.addInfo(new CommandInfo(CommandInfo.EXECUTE,comp,null,null,command,commandArgs,method.toString()));
				}
				
				ParamCall parCall = createParamCall(ctx);
				if(commandArgs != null){
					parCall.setBindingArgs(commandArgs);
				}
				
				parCall.call(viewModel, method);
				
				notifys.addAll(BindELContext.getNotifys(method, viewModel,
						(String) null, (Object) null, ctx)); // collect notifyChange
			}else{
				throw new UiException(MiscUtil.formatLocationMessage("cannot find any method that is annotated for the command "+command+" with @Command in "+viewModel,comp));
			}
			if(_log.isDebugEnabled()){
				_log.debug("after doExecute notifys=[%s]", notifys);
			}
		} finally {
			doPostPhase(Phase.EXECUTE, ctx);
		}
	}

	
	private static interface CommandMethodInfoProvider {
		String getAnnotationName();
		String getDefaultAnnotationName();
		
		String[] getCommandName(Method method);
		boolean isDefaultMethod(Method m);
		
	}
	
	private Method getCommandMethod(Class clz, String command, CommandMethodInfoProvider cmdInfo,Map, Map>> cache) {
		Map> methods;
		synchronized(cache){
			methods = cache.get(clz);
			if(methods==null){
				methods = new HashMap>();
				cache.put(clz, methods);
			}
		}
		CachedItem method = null;
		synchronized(methods){
			method = methods.get(command);
			if(method!=null){//quick check and return
				return method.value;
			}else if(methods.get(COMMAND_METHOD_MAP_INIT)!=null){
				//map is already initialized, check default method.
				method = methods.get(COMMAND_METHOD_DEFAULT);//get default
				if(method!=null){
					return method.value;
				}
				return null;
			}
			methods.clear();
			//scan
			for(Method m : clz.getMethods()){
				if(cmdInfo.isDefaultMethod(m)){
					if(methods.get(COMMAND_METHOD_DEFAULT)!=null){
						throw new UiException("there are more than one "+cmdInfo.getDefaultAnnotationName()+" method in "+clz+", "+methods.get(COMMAND_METHOD_DEFAULT).value+" and "+m);
					}
					methods.put(COMMAND_METHOD_DEFAULT, new CachedItem(m));
				}

				String[] vals = cmdInfo.getCommandName(m);
				if(vals==null) continue;
				if(vals.length==0){
					vals = new String[]{m.getName()};//command name from method.
				}
				for(String val:vals){
					val = val.trim();
					if(methods.get(val)!=null){
						throw new UiException("there are more than one "+cmdInfo.getAnnotationName()+" method "+val+" in "+clz+", "+methods.get(val).value+" and "+m);
					}
					methods.put(val, new CachedItem(m));
				}
			}
			
			methods.put(COMMAND_METHOD_MAP_INIT, NULL_METHOD);//mark this map has been initialized.
		}
		
		method = methods.get(command);
		if(method!=null){
			return method.value;
		}
		method = methods.get(COMMAND_METHOD_DEFAULT);//get default
		return method==null?null:method.value;
	}

	//doCommand -> doSaveBefore
	private void doSaveBefore(Component comp, String command, Event evt,  BindContext ctx, Set notifys) {
		if(_log.isDebugEnabled()){
			_log.debug("doSaveBefore, comp=[%s],command=[%s],evt=[%s],notifys=[%s]",comp,command,evt,notifys);
		}
		try {
			doPrePhase(Phase.SAVE_BEFORE, ctx);		
			_propertyBindingHandler.doSaveBefore(comp, command, evt, notifys);
			_formBindingHandler.doSaveBefore(comp, command, evt, notifys);
		} finally {
			doPostPhase(Phase.SAVE_BEFORE, ctx);
		}
	}

	
	private void doSaveAfter(Component comp, String command, Event evt, BindContext ctx, Set notifys) {
		if(_log.isDebugEnabled()){
			_log.debug("doSaveAfter, comp=[%s],command=[%s],evt=[%s],notifys=[%s]",comp,command,evt,notifys);
		}
		try {
			doPrePhase(Phase.SAVE_AFTER, ctx);
			_propertyBindingHandler.doSaveAfter(comp, command, evt, notifys);
			_formBindingHandler.doSaveAfter(comp, command, evt, notifys);
		} finally {
			doPostPhase(Phase.SAVE_AFTER, ctx);
		}		
		
	}

	
	private void doLoadBefore(Component comp, String command, BindContext ctx) {
		if(_log.isDebugEnabled()){
			_log.debug("doLoadBefore, comp=[%s],command=[%s]",comp,command);
		}
		try {
			doPrePhase(Phase.LOAD_BEFORE, ctx);		
			_propertyBindingHandler.doLoadBefore(comp, command);
			_formBindingHandler.doLoadBefore(comp, command);
			_childrenBindingHandler.doLoadBefore(comp,command);
		} finally {
			doPostPhase(Phase.LOAD_BEFORE, ctx);
		}
	}
	
	private void doLoadAfter(Component comp, String command, BindContext ctx) {
		if(_log.isDebugEnabled()){
			_log.debug("doLoadAfter, comp=[%s],command=[%s]",comp,command);
		}
		try {
			doPrePhase(Phase.LOAD_AFTER, ctx);
			_propertyBindingHandler.doLoadAfter(comp, command);
			_formBindingHandler.doLoadAfter(comp, command);
			_childrenBindingHandler.doLoadAfter(comp,command);
		} finally {
			doPostPhase(Phase.LOAD_AFTER, ctx);
		}		

	}
	
	/**
	 * Remove all bindings that associated with the specified component.
	 * @param comp the component
	 */
	public void removeBindings(Component comp) {
		checkInit();
		if(_rootComp==comp){
			//the binder component was detached, unregister queue
			unsubscribeQueue(_quename, _quescope, _queueListener);
			_rootComp.removeAttribute(ACTIVATOR);
		}
		if(_validationMessages!=null){
			_validationMessages.clearMessages(comp);
		}
		
		final Map> attrMap = _bindings.remove(comp);
		if (attrMap != null) {
			final Set removed = new HashSet();
			for(Entry> entry : attrMap.entrySet()) {
				final String key = entry.getKey(); 
				removeBindings(comp, key);
				removed.addAll(entry.getValue());
			}
			if (!removed.isEmpty()) {
				removeBindings(removed);
			}
		}
		
		removeFormAssociatedSaveBinding(comp);
		removeForm(comp);
		
		removeTemplateResolver(comp);
		if(_refBindingHandler!=null){
			_refBindingHandler.removeReferenceBinding(comp);
		}
		
		//remove trackings
		TrackerImpl tracker = (TrackerImpl) getTracker();
		tracker.removeTrackings(comp);

		BinderUtil.unmarkHandling(comp);
	}

	/**
	 * Remove all bindings that associated with the specified component and key (_fieldExpr|evtnm|formid).
	 * @param comp the component
	 * @param key can be component attribute, event name, or form id
	 */
	public void removeBindings(Component comp, String key) {
		checkInit();
		removeEventCommandListenerIfExists(comp, key); //_listenerMap; //comp+evtnm -> eventlistener
		
		final BindingKey bkey = getBindingKey(comp, key);
		final Set removed = new HashSet();
		
		_formBindingHandler.removeBindings(bkey,removed);
		_propertyBindingHandler.removeBindings(bkey, removed);
		_childrenBindingHandler.removeBindings(bkey, removed);
		if(_validationMessages!=null){
			_validationMessages.clearMessages(comp,key);
		}
		_hasValidators.remove(bkey);
		
		removeTemplateResolver(comp,key);
		
		if(_refBindingHandler!=null){
			_refBindingHandler.removeReferenceBinding(comp,key);
		}
		
		removeBindings(removed);
	}
	
	
	public List getLoadPromptBindings(Component comp, String attr) {
		checkInit();
		final List bindings = new ArrayList();
		final BindingKey bkey = getBindingKey(comp, attr);
		final List loadBindings = _propertyBindingHandler.getLoadPromptBindings(bkey);
		if(loadBindings!=null && loadBindings.size()>0){
			bindings.addAll(loadBindings);
		}
		if(bindings.size()==0){//optimize, they are exclusive
			List childrenLoadBindings = _childrenBindingHandler.getLoadPromptBindings(bkey);
			if(childrenLoadBindings!=null && childrenLoadBindings.size()>0){
				bindings.addAll(childrenLoadBindings);
			}
		}
		return bindings;
	}


	private void removeBindings(Collection removed) {
		_formBindingHandler.removeBindings(removed);
		_propertyBindingHandler.removeBindings(removed);
		_childrenBindingHandler.removeBindings(removed);
	}
	
	private void addBinding(Component comp, String attr, Binding binding) {
		Map> attrMap = _bindings.get(comp);
		if (attrMap == null) {
			//bug 657, we have to keep the attribute assignment order.
			attrMap = new LinkedHashMap>(); 
			_bindings.put(comp, attrMap);
		}
		List bindings = attrMap.get(attr);
		if (bindings == null) {
			bindings = new ArrayList();
			attrMap.put(attr, bindings);
		}
		bindings.add(binding);
		
		//associate component with this binder, which means, one component can only bind by one binder
		BinderUtil.markHandling(comp,this);
	}
	
	
	
	
	public void setTemplate(Component comp, String attr, String templateExpr, Map templateArgs){
		Map resolvers = _templateResolvers.get(comp);
		if(resolvers==null){
			resolvers = new HashMap();
			_templateResolvers.put(comp, resolvers);
		}	
		resolvers.put(attr, newTemplateResolverImpl(this, comp,attr,templateExpr,templateArgs));
	}
	
	//ZK-1787 When the viewModel tell binder to reload a list, the other component that bind a bean in the list will reload again
	@SuppressWarnings("unchecked")
	private TemplateResolver newTemplateResolverImpl(BinderImpl binderImpl, Component comp, String attr,
			String templateExpr, Map templateArgs) {
		String clznm = Library.getProperty("org.zkoss.bind.TemplateResolver.class",TemplateResolverImpl.class.getName());
		try {
			Class clz = (Class)Classes.forNameByThread(clznm);
			Constructor c = clz.getDeclaredConstructor(Binder.class,Component.class,String.class,String.class,Map.class);
			TemplateResolver resolver = c.newInstance(binderImpl, comp, attr,templateExpr, templateArgs);
			return resolver;
		} catch (Exception e) {
			throw new UiException("Can't initialize template resolver ",e);
		}
	}

	public TemplateResolver getTemplateResolver(Component comp, String attr){
		Map resolvers = _templateResolvers.get(comp);
		return resolvers==null?null:resolvers.get(attr);
	}
	
	private void removeTemplateResolver(Component comp,String attr){
		Map resolvers = _templateResolvers.get(comp);
		if(resolvers!=null){
			resolvers.remove(attr);
		}
	}
	private void removeTemplateResolver(Component comp){
		_templateResolvers.remove(comp);
	}
	

	@SuppressWarnings("unchecked")
	public Tracker getTracker() {
		if (_tracker == null) {
			String clznm = Library.getProperty("org.zkoss.bind.Tracker.class");
			if(clznm!=null){
				Class clz;
				try {
					clz = (Class)Classes.forNameByThread(clznm);
					_tracker = clz.newInstance();
				} catch (Exception e) {
					throw new UiException("Can't initialize tracker",e);
				} 
			}else
				_tracker = new TrackerImpl();
		}
		return _tracker;
	}
	
	/**
	 * Internal Use only. init and load the component
	 */
	public void loadComponent(Component comp,boolean loadinit) {
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		try{
//			if(collector!=null){
//				collector.pushStack("BINDER_API");
//			}
			loadComponent0(comp,loadinit);
		}finally{
//			if(collector!=null){
//				collector.popStack();
//			}
		}
	}
	
	private void loadComponent0(Component comp,boolean loadinit) {
		loadComponentProperties0(comp,loadinit);
		
		for(Component kid = comp.getFirstChild(); kid != null; kid = kid.getNextSibling()) {
			loadComponent0(kid,loadinit); //recursive
		}
	}
	
	private void loadComponentProperties0(Component comp,boolean loadinit) {
		
		final Map> compBindings = _bindings.get(comp);
		if (compBindings != null) {// if component is not registered in this binder, do nothing.
			for(String key : compBindings.keySet()) {
				final BindingKey bkey = getBindingKey(comp, key);
				if(loadinit){
					_formBindingHandler.doInit(comp,bkey);
				}
				_formBindingHandler.doLoad(comp,bkey);
			}
			for(String key : compBindings.keySet()) {
				final BindingKey bkey = getBindingKey(comp, key);
				if(loadinit){
					_propertyBindingHandler.doInit(comp,bkey);
				}
				_propertyBindingHandler.doLoad(comp,bkey);
			}
			for(String key : compBindings.keySet()) {
				final BindingKey bkey = getBindingKey(comp, key);
				if(loadinit){
					_childrenBindingHandler.doInit(comp,bkey);
				}
					
				_childrenBindingHandler.doLoad(comp,bkey);
			}
		}
	}
	
	public void notifyChange(Object base, String attr) {
		checkInit();
		if(_log.isDebugEnabled()){
			_log.debug("notifyChange base=[%s],attr=[%s]",base,attr);
		}
		getEventQueue().publish(new PropertyChangeEvent(_rootComp, base, attr));
	}
	
	private void postGlobalCommand(Component comp, CommandBinding commandBinding, String command, Event evt, Map args) {
		if(_log.isDebugEnabled()){
			_log.debug("postGlobalCommand command=[%s], args=[%s]",command,args);
		}
		
		final BindingExecutionInfoCollector collector = getBindingExecutionInfoCollector();
		try{
			if(collector!=null){
				collector.pushStack("POST_GLOBAL_COMMAND");
				collector.addInfo(new CommandInfo(CommandInfo.POST_GLOBAL,comp,evt==null?null:evt.getName(),
						BindEvaluatorXUtil.getExpressionString(((CommandBindingImpl)commandBinding).getCommand()), command, args,null));
			}
		
			getEventQueue().publish(new GlobalCommandEvent(_rootComp, command, args));
		}finally{
			if(collector!=null){
				collector.popStack();
			}
		}
	}

	
	public void setPhaseListener(PhaseListener listener) {
		_phaseListener = listener;
		_phaseListenerSet = true;
	}
	
	
	public PhaseListener getPhaseListener(){
		if(_phaseListenerSet){
			//return local phase listener if it was set by setPahseListener
			return _phaseListener;
		}
		//otherwise, check if there is a shared default phase listener.
		if (!_sharedPhaseListenerSet) {
			synchronized (BinderImpl.class) {
				_sharedPhaseListenerSet = true;
				String clz = Library.getProperty(PHASE_LISTENER_CLASS_KEY);
				if (!Strings.isEmpty(clz)) {
					try {
						_sharedPhaseListener = (PhaseListener) Classes.forNameByThread(clz).newInstance();
					} catch (Exception e) {
						_log.error("Error when initial phase listener:"+clz , e);
					}
				}
			}
		}
		return _sharedPhaseListener;
	}

	private void subscribeQueue(String quename, String quescope, EventListener listener) {
		EventQueue que = EventQueues.lookup(quename, quescope, true);
		que.subscribe(listener);
	}
	
	private void unsubscribeQueue(String quename, String quescope, EventListener listener) {
		EventQueue que = EventQueues.lookup(quename, quescope, false);
		if(que!=null){
			que.unsubscribe(listener);
		}
	}
	
	private boolean isSubscribed(String quename, String quescope, EventListener listener) {
		EventQueue que = EventQueues.lookup(quename, quescope, false);
		return que==null?false:que.isSubscribed(listener);
	}
	
	protected EventQueue getEventQueue() {
		return EventQueues.lookup(_quename, _quescope, true);
	}

	// create a unique id base on component's uuid and attr
	private BindingKey getBindingKey(Component comp, String attr) {
		return new BindingKey(comp,attr);
	}

	private class PostCommandListener implements EventListener,Serializable{
		private static final long serialVersionUID = 1L;

		@SuppressWarnings("unchecked")
		public void onEvent(Event event) throws Exception {
			Object[] data = (Object[])event.getData();
			String command = (String)data[0];
			Map args = (Map)data[1]; 
			sendCommand(command, args);
		}
	}
	

	private void removeFormAssociatedSaveBinding(Component comp) {
		_assocFormSaveBindings.remove(comp);
		Map> associated = _reversedAssocFormSaveBindings.remove(comp);
		if(associated!=null){
			Set>> entries = associated.entrySet();
			for(Entry> entry:entries){
				entry.getValue().remove(entry.getKey());
			}
		}
	}
	
	
	public void addFormAssociatedSaveBinding(Component associatedComp, String formId, SaveBinding saveBinding, String fieldName){
		checkInit();
		//find the form component by form id and a associated/nested component
		Component formComp = lookupAossicatedFormComponent(formId,associatedComp);
		if(formComp==null){
			throw new UiException("cannot find any form "+formId+" with "+associatedComp);
		}
		Set bindings = _assocFormSaveBindings.get(formComp);
		if(bindings==null){
			bindings = new LinkedHashSet();//keep the order
			_assocFormSaveBindings.put(formComp, bindings);
		}
		bindings.add(saveBinding);
		
		//keep the reverse association , so we can remove it if the associated component is detached (and the form component is not).
		Map> reverseMap = _reversedAssocFormSaveBindings.get(associatedComp);
		if(reverseMap==null){
			reverseMap = new HashMap>();
			_reversedAssocFormSaveBindings.put(associatedComp, reverseMap);
		}
		reverseMap.put(saveBinding,bindings);
		
		//ZK-1017 Property of a form is not correct when validation
		//ZK-1005 ZK 6.0.1 validation fails on nested bean
		((SavePropertyBindingImpl)saveBinding).setFormFieldInfo(formComp, formId, fieldName);
	}
	
	private Component lookupAossicatedFormComponent(String formId,Component associatedComp) {
		String fid = null;
		Component p = associatedComp;
		while(p!=null){
			fid = (String)p.getAttribute(FORM_ID);//check in default component scope
			if(fid!=null && fid.equals(formId)){
				break;
			}
			p = p.getParent();
		}
		
		return p;
	}

	
	public Set getFormAssociatedSaveBindings(Component comp){
		checkInit();
		Set bindings = _assocFormSaveBindings.get(comp);
		if(bindings==null){
			return Collections.emptySet();
		}
		return new LinkedHashSet(bindings);//keep the order
	}

	//utility to simply hold a value which might be null
	private static class CachedItem {
		final T value;
		public CachedItem(T value){
			this.value = value;
		}
	}
	
	public boolean hasValidator(Component comp, String attr){
		BindingKey bkey = getBindingKey(comp, attr);
		return _hasValidators.contains(bkey);
	}

	
	public Component getView() {
		checkInit();
		return _rootComp;
	}

	
	public ValidationMessages getValidationMessages() {
		return _validationMessages;
	}

	
	public void setValidationMessages(ValidationMessages messages) {
		_validationMessages = messages;
	}

	/**
	 * did activate when the session is activating
	 */
	private void didActivate() {
		_activating = true;
		try{
			_log.debug("didActivate : [%s]",BinderImpl.this);
			//re-tie value to tracker.
			loadComponent(_rootComp, false);
		}finally{
			_activating = false;
		}
	}
 
	/**
	 * object that store in root component to help activating.
	 */
	private class Activator implements ComponentActivationListener,Serializable{
		private static final long serialVersionUID = 1L;
		
		public void didActivate(Component comp) {
			if(_rootComp.equals(comp)){
				//zk 1442, don't do multiple subscribed if didActivate is called every request (e.x. jboss5)
				if(!isSubscribed(_quename, _quescope, _queueListener))
					subscribeQueue(_quename, _quescope, _queueListener);
				if(_deferredActivator==null){
					//defer activation to execution only for the first didActivate when failover
					comp.getDesktop().addListener(_deferredActivator = new DeferredActivator());
				}
			}
		}
		
		public void willPassivate(Component comp) {
			//zk 1442, do nothing
		}
	}
	
	/**
	 * object that store in desktop listener to help activating.
	 * it do the activation when first execution come into
	 */
	private class DeferredActivator implements ExecutionInit, Serializable{
		private static final long serialVersionUID = 1L;
		
		public void init(Execution exec, Execution parent) throws Exception {
			Desktop desktop = exec.getDesktop();
			desktop.removeListener(_deferredActivator);
			BinderImpl.this.didActivate();
		}	
	}

	
	public BindingExecutionInfoCollector getBindingExecutionInfoCollector(){
		DebuggerFactory factory = DebuggerFactory.getInstance();
		return factory==null?null:factory.getExecutionInfoCollector();
	}
	
	public BindingAnnotationInfoChecker getBindingAnnotationInfoChecker(){
		DebuggerFactory factory = DebuggerFactory.getInstance();
		return factory==null?null:factory.getAnnotationInfoChecker();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy