zkbind/src/main/java/org/zkoss/bind/impl/AnnotateBinderHelper.java

Summary

Maintainability
F
1 wk
Test Coverage
/* 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.LinkedHashMap;
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.zhtml.impl.AbstractTag;
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>();
        Binder commandBinder = _binder;
        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 LinkedHashMap<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) {
                commandBinder.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 LinkedHashMap<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) {
            if (comp instanceof AbstractTag) {
                return; // ignore since HTML5 has "form" attribute.
            }
            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;
    }
}