/* AnnotateBinderHelper.java

	Purpose:
		
	Description:
		
	History:
		Sep 9, 2011 6:06:10 PM, Created by henrichen

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

package org.zkoss.bind.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.zkoss.bind.BindComposer;
import org.zkoss.bind.BindContext;
import org.zkoss.bind.Binder;
import org.zkoss.bind.Phase;
import org.zkoss.bind.impl.BinderUtil.UtilContext;
import org.zkoss.bind.sys.BindEvaluatorX;
import org.zkoss.bind.sys.BinderCtrl;
import org.zkoss.bind.sys.debugger.BindingAnnotationInfoChecker;
import org.zkoss.lang.Strings;
import org.zkoss.util.IllegalSyntaxException;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.ShadowElement;
import org.zkoss.zk.ui.metainfo.Annotation;
import org.zkoss.zk.ui.sys.ComponentCtrl;

/**
 * Helper class to parse binding annotations and create bindings. 
 * @author henrichen
 * @author dennischen
 * @since 6.0.0
 */
public class AnnotateBinderHelper {
	private final Binder _binder;

	public static final String INIT_ANNO = "init";
	public static final String BIND_ANNO = "bind";
	public static final String LOAD_ANNO = "load";
	public static final String SAVE_ANNO = "save";
	public static final String REFERENCE_ANNO = "ref";
	public static final String ID_ANNO = "id";
	public static final String VALIDATOR_ANNO = "validator";
	public static final String CONVERTER_ANNO = "converter";
	public static final String TEMPLATE_ANNO = "template";
	public static final String COMMAND_ANNO = "command";
	public static final String GLOBAL_COMMAND_ANNO = "global-command";

	public static final String FORM_ATTR = "form";
	public static final String VIEW_MODEL_ATTR = "viewModel";
	public static final String BINDER_ATTR = "binder";
	public static final String VALIDATION_MESSAGES_ATTR = "validationMessages";
	public static final String CHILDREN_ATTR = "children";

	//control key
	public static final String CHILDREN_KEY = "$CHILDREN$";

	public AnnotateBinderHelper(Binder binder) {
		_binder = binder;
	}

	public void initComponentBindings(Component comp) {
		processAllComponentsBindings(comp);
	}

	private void processAllComponentsBindings(Component comp) {
		final Binder selfBinder = BinderUtil.getBinder(comp);
		//check if a component was binded already(by any binder)
		if (selfBinder != null) //this component already binded ! skip all of its children
			return;

		BindingAnnotationInfoChecker checker = ((BinderCtrl) _binder).getBindingAnnotationInfoChecker();
		if (checker != null) {
			checker.checkBinding(_binder, comp);
		}

		processComponentBindings0(comp);
		for (final Iterator<Component> it = comp.getChildren().iterator(); it.hasNext();) {
			final Component kid = it.next();

			// it may be a nested binder.
			if (!kid.hasAttribute(BindComposer.BINDER_ID)) {
				processAllComponentsBindings(kid); //recursive to each child
			} else if (kid.hasAttribute(BinderCtrl.REMOVE_BINDINGS)) { // for nested binder
				kid.removeAttribute(BinderCtrl.REMOVE_BINDINGS);
				final Binder nestedBinder = (Binder) kid
						.getAttribute((String) kid.getAttribute(BindComposer.BINDER_ID));
				new AnnotateBinderHelper(nestedBinder).initComponentBindings(kid);
				BinderUtil.markHandling(kid, nestedBinder);
				((BinderImpl) nestedBinder).initQueue();
				((BinderImpl) nestedBinder).initActivator();
				nestedBinder.loadComponent(kid, true);
			}
		}

		// support shadow element
		if (comp instanceof ComponentCtrl) {
			for (ShadowElement se : ((ComponentCtrl) comp).getShadowRoots()) {
				processAllComponentsBindings((Component) se);
			}
		}
	}

	private void processComponentBindings0(Component comp) {
		final List<String> props = AnnotationUtil.getNonSystemProperties(comp); // look every property has annotation
		for (final Iterator<?> it = props.iterator(); it.hasNext();) {
			final String propName = (String) it.next();
			if (isEventProperty(propName)) {
				processCommandBinding(comp, propName);
				processGlobalCommandBinding(comp, propName);
			} else if (FORM_ATTR.equals(propName)) {
				processFormBindings(comp);
			} else if (CHILDREN_ATTR.equals(propName)) {
				processChildrenBindings(comp);
			} else if (VIEW_MODEL_ATTR.equals(propName)) {
				//ignore
			} else if (BINDER_ATTR.equals(propName)) {
				//ignore
			} else if (VALIDATION_MESSAGES_ATTR.equals(propName)) {
				//ignore
			} else {
				processPropertyBindings(comp, propName);
			}
		}
		//don't mark the component is controlled, if we do this, it will always create a attribute map for a component.
		//and consume more memory, make performance worse.
		//if(!BinderUtil.isHandling(comp)){
		//	BinderUtil.markHandling(comp, _binder);
		//}
	}

	private boolean isEventProperty(String propName) {
		return propName.startsWith("on") && propName.length() >= 3 && Character.isUpperCase(propName.charAt(2));
	}

	private void processCommandBinding(Component comp, String propName) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Collection<Annotation> anncol = compCtrl.getAnnotations(propName, COMMAND_ANNO);
		if (anncol.size() == 0)
			return;
		if (anncol.size() > 1) {
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
					"Allow only one command binding for event " + propName + " of " + comp, comp));
		}
		final Annotation ann = anncol.iterator().next();

		final Map<String, String[]> attrs = ann.getAttributes(); //(tag, tagExpr)
		Map<String, String[]> args = null;
		final List<String> cmdExprs = new ArrayList<String>();
		for (final Iterator<Entry<String, String[]>> it = attrs.entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				cmdExprs.add(AnnotationUtil.testString(tagExpr, ann));
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}

		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			for (String cmd : cmdExprs) {
				_binder.addCommandBinding(comp, propName, cmd, parsedArgs);
			}
		} finally {
			BinderUtil.popContext();
		}
	}

	private void processGlobalCommandBinding(Component comp, String propName) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Collection<Annotation> anncol = compCtrl.getAnnotations(propName, GLOBAL_COMMAND_ANNO);
		if (anncol.size() == 0)
			return;
		if (anncol.size() > 1) {
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
					"Allow only one global-command binding for event " + propName + " of " + comp, comp));
		}
		final Annotation ann = anncol.iterator().next();

		final Map<String, String[]> attrs = ann.getAttributes(); //(tag, tagExpr)
		Map<String, String[]> args = null;
		final List<String> cmdExprs = new ArrayList<String>();
		for (final Iterator<Entry<String, String[]>> it = attrs.entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				cmdExprs.add(AnnotationUtil.testString(tagExpr, ann));
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}

		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			for (String cmd : cmdExprs) {
				_binder.addGlobalCommandBinding(comp, propName, cmd, parsedArgs);
			}
		} finally {
			BinderUtil.popContext();
		}
	}

	private BindContext doPreInitPhase(Component comp, String propName) {
		if (_binder instanceof BinderImpl) {
			BindContext ctx = BindContextUtil.newBindContext(_binder, null, false, propName, comp, null);
			((BinderImpl) _binder).doPrePhase(Phase.INITIAL_BINDING, ctx);
			return ctx;
		}
		return null;
	}

	private void processPropertyBindings(Component comp, String propName) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;

		//validator and converter information
		ExpressionAnnoInfo validatorInfo = parseValidator(comp, propName);
		ExpressionAnnoInfo converterInfo = parseConverter(comp, propName);

		BindContext ctx = null;
		try {

			//scan init
			Collection<Annotation> initannos = compCtrl.getAnnotations(propName, INIT_ANNO);
			if (initannos.size() > 1) {
				throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
						"Allow only one @init for " + propName + " of " + comp, initannos.iterator().next()));
			} else if (initannos.size() == 1) {
				processPropertyInit(comp, propName, initannos.iterator().next(), converterInfo);
			}

			Collection<Annotation> annos = compCtrl.getAnnotations(propName); //init in the annotation with the sequence

			for (Annotation anno : annos) {
				if (anno.getName().equals(BIND_ANNO)) {
					if (ctx == null) {
						ctx = doPreInitPhase(comp, propName);
					}
					processPropertyPromptBindings(comp, propName, anno, converterInfo, validatorInfo);
				} else if (anno.getName().equals(LOAD_ANNO)) {
					if (ctx == null) {
						ctx = doPreInitPhase(comp, propName);
					}
					processPropertyLoadBindings(comp, propName, anno, converterInfo);
				} else if (anno.getName().equals(SAVE_ANNO)) {
					if (ctx == null) {
						ctx = doPreInitPhase(comp, propName);
					}
					processPropertySaveBindings(comp, propName, anno, converterInfo, validatorInfo);
				} else if (anno.getName().equals(REFERENCE_ANNO)) {
					if (ctx == null) {
						ctx = doPreInitPhase(comp, propName);
					}
					processReferenceBinding(comp, propName, anno);
				}
			}

			ExpressionAnnoInfo templateInfo = parseTemplate(comp, propName);
			if (templateInfo != null) {
				_binder.setTemplate(comp, propName, templateInfo.expr, templateInfo.args);
			}
		} finally {
			if (_binder instanceof BinderImpl && ctx != null) {
				((BinderImpl) _binder).doPostPhase(Phase.INITIAL_BINDING, ctx);
			}
		}
	}

	private void processReferenceBinding(Component comp, String propName, Annotation ann) {
		String loadExpr = null;

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				loadExpr = AnnotationUtil.testString(tagExpr, ann);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addReferenceBinding(comp, propName, loadExpr, parsedArgs);
		} finally {
			BinderUtil.popContext();
		}

	}

	private void processPropertyInit(Component comp, String propName, Annotation ann,
			ExpressionAnnoInfo converterInfo) {
		String initExpr = null;

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				initExpr = AnnotationUtil.testString(tagExpr, ann);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addPropertyInitBinding(comp, propName, initExpr, parsedArgs,
					converterInfo == null ? null : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	//process @bind(expr) 
	private void processPropertyPromptBindings(Component comp, String propName, Annotation ann,
			ExpressionAnnoInfo converterInfo, ExpressionAnnoInfo validatorInfo) {
		String expr = null;
		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				expr = AnnotationUtil.testString(tagExpr, ann);
			} else if ("before".equals(tag)) {
				throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
						"@bind is for prompt binding only, doesn't support before commands, check property " + propName
								+ " of " + comp,
						ann));
			} else if ("after".equals(tag)) {
				throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
						"@bind is for prompt binding only, doesn't support after commands, check property " + propName
								+ " of " + comp,
						ann));
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}

		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);

		try {
			UtilContext ctx = BinderUtil.pushContext();
			ctx.setIgnoreAccessCreationWarn(true);
			ctx.setCurrentLocation(ann.getLocation());

			_binder.addPropertyLoadBindings(comp, propName, expr, null, null, parsedArgs,
					converterInfo == null ? null : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args);

			_binder.addPropertySaveBindings(comp, propName, expr, null, null, parsedArgs,
					converterInfo == null ? null : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args,
					validatorInfo == null ? null : validatorInfo.expr,
					validatorInfo == null ? null : validatorInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	private void addCommand(Component comp, List<String> cmds, String[] cmdExprs) {
		for (String cmdExpr : (String[]) cmdExprs) {
			addCommand(comp, cmds, cmdExpr);
		}
	}

	private void addCommand(Component comp, List<String> cmds, String cmdExpr) {
		String cmd = BindEvaluatorXUtil.eval(_binder.getEvaluatorX(), comp, cmdExpr, String.class);
		if (Strings.isEmpty(cmd)) {
			throw new IllegalSyntaxException(
					MiscUtil.formatLocationMessage("command of expression " + cmdExpr + " is empty", comp));
		}
		cmds.add(cmd);
	}

	private void processPropertyLoadBindings(Component comp, String propName, Annotation ann,
			ExpressionAnnoInfo converterInfo) {
		String loadExpr = null;
		final List<String> beforeCmds = new ArrayList<String>();
		final List<String> afterCmds = new ArrayList<String>();

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				loadExpr = AnnotationUtil.testString(tagExpr, ann);
			} else if ("before".equals(tag)) {
				addCommand(comp, beforeCmds, tagExpr);
			} else if ("after".equals(tag)) {
				addCommand(comp, afterCmds, tagExpr);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addPropertyLoadBindings(comp, propName, loadExpr,
					beforeCmds.size() == 0 ? null : beforeCmds.toArray(new String[beforeCmds.size()]),
					afterCmds.size() == 0 ? null : afterCmds.toArray(new String[afterCmds.size()]), parsedArgs,
					converterInfo == null ? null : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	private void processPropertySaveBindings(Component comp, String propName, Annotation ann,
			ExpressionAnnoInfo converterInfo, ExpressionAnnoInfo validatorInfo) {
		String saveExpr = null;
		final List<String> beforeCmds = new ArrayList<String>();
		final List<String> afterCmds = new ArrayList<String>();

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				saveExpr = AnnotationUtil.testString(tagExpr, ann);
			} else if ("before".equals(tag)) {
				addCommand(comp, beforeCmds, tagExpr);
			} else if ("after".equals(tag)) {
				addCommand(comp, afterCmds, tagExpr);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addPropertySaveBindings(comp, propName, saveExpr,
					beforeCmds.size() == 0 ? null : beforeCmds.toArray(new String[beforeCmds.size()]),
					afterCmds.size() == 0 ? null : afterCmds.toArray(new String[afterCmds.size()]), parsedArgs,
					converterInfo == null ? null : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args,
					validatorInfo == null ? null : validatorInfo.expr,
					validatorInfo == null ? null : validatorInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	private void processFormBindings(Component comp) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final BindEvaluatorX eval = _binder.getEvaluatorX();
		//validator information
		ExpressionAnnoInfo validatorInfo = parseValidator(comp, FORM_ATTR);

		String formId = null;

		Collection<Annotation> idannos = compCtrl.getAnnotations(FORM_ATTR, ID_ANNO);
		if (idannos.size() == 0) {
			throw new IllegalSyntaxException(
					MiscUtil.formatLocationMessage("@id is not found for a form binding of " + comp, comp));
		} else if (idannos.size() > 1) {
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
					"Allow only one @id for a form binding of " + comp, idannos.iterator().next()));
		}

		final Annotation idanno = idannos.iterator().next();
		final String idExpr = idanno.getAttribute("value");

		if (idExpr != null) {
			formId = BindEvaluatorXUtil.eval(eval, comp, idExpr, String.class);
		}
		if (formId == null) {
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
					"value of @id is not found for a form binding of " + compCtrl + ", exprssion is " + idExpr,
					idanno));
		}

		//scan init first
		Collection<Annotation> initannos = compCtrl.getAnnotations(FORM_ATTR, INIT_ANNO);
		if (initannos.size() > 1) {
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
					"Allow only one @init for " + FORM_ATTR + " of " + comp, initannos.iterator().next()));
		} else if (initannos.size() == 1) {
			processFormInit(comp, formId, initannos.iterator().next());
		}

		Collection<Annotation> annos = compCtrl.getAnnotations(FORM_ATTR); //get all annotation in the form with the order.

		for (Annotation anno : annos) {
			if (anno.getName().equals(LOAD_ANNO)) {
				processFormLoadBindings(comp, formId, anno);
			} else if (anno.getName().equals(SAVE_ANNO)) {
				processFormSaveBindings(comp, formId, anno, validatorInfo);
			}
		}
	}

	private void processFormInit(Component comp, String formId, Annotation ann) {
		String initExpr = null;

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				initExpr = AnnotationUtil.testString(tagExpr, ann);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addFormInitBinding(comp, formId, initExpr, parsedArgs);
		} finally {
			BinderUtil.popContext();
		}
	}

	private void processFormLoadBindings(Component comp, String formId, Annotation ann) {
		String loadExpr = null;
		final List<String> beforeCmds = new ArrayList<String>();
		final List<String> afterCmds = new ArrayList<String>();

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				loadExpr = AnnotationUtil.testString(tagExpr, ann);
			} else if ("before".equals(tag)) {
				addCommand(comp, beforeCmds, tagExpr);
			} else if ("after".equals(tag)) {
				addCommand(comp, afterCmds, tagExpr);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addFormLoadBindings(comp, formId, loadExpr,
					beforeCmds.size() == 0 ? null : beforeCmds.toArray(new String[beforeCmds.size()]),
					afterCmds.size() == 0 ? null : afterCmds.toArray(new String[afterCmds.size()]), parsedArgs);
		} finally {
			BinderUtil.popContext();
		}
	}

	private void processFormSaveBindings(Component comp, String formId, Annotation ann,
			ExpressionAnnoInfo validatorInfo) {
		String saveExpr = null;
		final List<String> beforeCmds = new ArrayList<String>();
		final List<String> afterCmds = new ArrayList<String>();

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				saveExpr = AnnotationUtil.testString(tagExpr, ann);
			} else if ("before".equals(tag)) {
				addCommand(comp, beforeCmds, tagExpr);
			} else if ("after".equals(tag)) {
				addCommand(comp, afterCmds, tagExpr);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addFormSaveBindings(comp, formId, saveExpr,
					beforeCmds.size() == 0 ? null : beforeCmds.toArray(new String[beforeCmds.size()]),
					afterCmds.size() == 0 ? null : afterCmds.toArray(new String[afterCmds.size()]), parsedArgs,
					validatorInfo == null ? null : validatorInfo.expr,
					validatorInfo == null ? null : validatorInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	private void processChildrenBindings(Component comp) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		ExpressionAnnoInfo converterInfo = parseConverter(comp, CHILDREN_ATTR);
		//scan init first
		Collection<Annotation> initannos = compCtrl.getAnnotations(CHILDREN_ATTR, INIT_ANNO);
		if (initannos.size() > 1) {
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
					"Allow only one @init for " + CHILDREN_ATTR + " of " + comp, initannos.iterator().next()));
		} else if (initannos.size() == 1) {
			processChildrenInit(comp, initannos.iterator().next(), converterInfo);
		}

		Collection<Annotation> annos = compCtrl.getAnnotations(CHILDREN_ATTR); //get all annotation in the children with the order.

		for (Annotation anno : annos) {
			if (anno.getName().equals(BIND_ANNO)) {
				processChildrenPromptBindings(comp, anno, converterInfo);
			} else if (anno.getName().equals(LOAD_ANNO)) {
				processChildrenLoadBindings(comp, anno, converterInfo);
			}
		}

		ExpressionAnnoInfo templateInfo = parseTemplate(comp, CHILDREN_ATTR);
		if (templateInfo != null) {
			//use special CHILDREN_KEY to avoid conflict 
			_binder.setTemplate(comp, CHILDREN_KEY, templateInfo.expr, templateInfo.args);
		}
	}

	private void processChildrenInit(Component comp, Annotation ann, ExpressionAnnoInfo converterInfo) {
		String initExpr = null;

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				initExpr = AnnotationUtil.testString(tagExpr, ann);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addChildrenInitBinding(comp, initExpr, parsedArgs,
					converterInfo == null ? getDefaultChildBindingConverter() : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	private String getDefaultChildBindingConverter() {
		if (SystemConverters.get("childrenBinding") != null) {
			return "'childrenBinding'";
		}
		return null;
	}

	private void processChildrenPromptBindings(Component comp, Annotation ann, ExpressionAnnoInfo converterInfo) {
		String expr = null;
		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				expr = AnnotationUtil.testString(tagExpr, ann);
			} else if ("before".equals(tag)) {
				throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
						"@bind is for prompt binding only, doesn't support before commands, check property "
								+ CHILDREN_ATTR + " of " + comp,
						comp));
			} else if ("after".equals(tag)) {
				throw new IllegalSyntaxException(MiscUtil.formatLocationMessage(
						"@bind is for prompt binding only, doesn't support after commands, check property "
								+ CHILDREN_ATTR + " of " + comp,
						comp));
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}

		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addChildrenLoadBindings(comp, expr, null, null, parsedArgs,
					converterInfo == null ? getDefaultChildBindingConverter() : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	private void processChildrenLoadBindings(Component comp, Annotation ann, ExpressionAnnoInfo converterInfo) {
		String loadExpr = null;
		final List<String> beforeCmds = new ArrayList<String>();
		final List<String> afterCmds = new ArrayList<String>();

		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				loadExpr = AnnotationUtil.testString(tagExpr, ann);
			} else if ("before".equals(tag)) {
				addCommand(comp, beforeCmds, tagExpr);
			} else if ("after".equals(tag)) {
				addCommand(comp, afterCmds, tagExpr);
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		final Map<String, Object> parsedArgs = args == null ? null
				: BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		try {
			BinderUtil.pushContext().setCurrentLocation(ann.getLocation());
			_binder.addChildrenLoadBindings(comp, loadExpr,
					beforeCmds.size() == 0 ? null : beforeCmds.toArray(new String[beforeCmds.size()]),
					afterCmds.size() == 0 ? null : afterCmds.toArray(new String[afterCmds.size()]), parsedArgs,
					converterInfo == null ? getDefaultChildBindingConverter() : converterInfo.expr,
					converterInfo == null ? null : converterInfo.args);
		} finally {
			BinderUtil.popContext();
		}
	}

	private ExpressionAnnoInfo parseConverter(Component comp, String propName) {
		final Collection<Annotation> annos = ((ComponentCtrl) comp).getAnnotations(propName, CONVERTER_ANNO);
		if (annos.size() == 0)
			return null;
		if (annos.size() > 1) {
			throw new IllegalSyntaxException(
					MiscUtil.formatLocationMessage("Allow only one converter for " + propName + " of " + comp, comp));
		}
		final Annotation ann = annos.iterator().next();

		ExpressionAnnoInfo info = new ExpressionAnnoInfo();
		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				info.expr = AnnotationUtil.testString(tagExpr, ann);
			} else { // other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		if (Strings.isBlank(info.expr)) {
			throw new IllegalSyntaxException(MiscUtil
					.formatLocationMessage("value of converter is empty, check " + propName + " of " + comp, comp));
		}
		info.args = args == null ? null : BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		return info;
	}

	private ExpressionAnnoInfo parseValidator(Component comp, String propName) {
		final Collection<Annotation> annos = ((ComponentCtrl) comp).getAnnotations(propName, VALIDATOR_ANNO);
		if (annos.size() == 0)
			return null;
		if (annos.size() > 1) {
			throw new IllegalSyntaxException(
					MiscUtil.formatLocationMessage("Allow only one validator for " + propName + " of " + comp, comp));
		}
		final Annotation ann = annos.iterator().next();
		ExpressionAnnoInfo info = new ExpressionAnnoInfo();
		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				info.expr = AnnotationUtil.testString(tagExpr, ann);
			} else { // other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		if (Strings.isBlank(info.expr)) {
			throw new IllegalSyntaxException(MiscUtil
					.formatLocationMessage("value of validator is empty, check " + propName + " of " + comp, comp));
		}
		info.args = args == null ? null : BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		return info;
	}

	private ExpressionAnnoInfo parseTemplate(Component comp, String propName) {
		final Collection<Annotation> annos = ((ComponentCtrl) comp).getAnnotations(propName, TEMPLATE_ANNO);
		if (annos.size() == 0)
			return null;
		if (annos.size() > 1) {
			throw new IllegalSyntaxException(
					MiscUtil.formatLocationMessage("Allow only one template for " + propName + " of " + comp, comp));
		}
		final Annotation ann = annos.iterator().next();
		ExpressionAnnoInfo info = new ExpressionAnnoInfo();
		Map<String, String[]> args = null;
		for (final Iterator<Entry<String, String[]>> it = ann.getAttributes().entrySet().iterator(); it.hasNext();) {
			final Entry<String, String[]> entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				info.expr = AnnotationUtil.testString(tagExpr, ann);
			} else { // other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap<String, String[]>();
				}
				args.put(tag, tagExpr);
			}
		}
		if (Strings.isBlank(info.expr)) {
			throw new IllegalSyntaxException(
					MiscUtil.formatLocationMessage("Must specify a template for " + propName + " of " + comp, comp));
		}
		info.args = args == null ? null : BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(), args);
		return info;
	}

	private static class ExpressionAnnoInfo {
		Map<String, Object> args;
		String expr;
	}
}
