com.jaspersoft.jasperserver.api.engine.common.service.impl.ActionModel.java Source code

Java tutorial

Introduction

Here is the source code for com.jaspersoft.jasperserver.api.engine.common.service.impl.ActionModel.java

Source

/*
 * Copyright (C) 2005 - 2014 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package com.jaspersoft.jasperserver.api.engine.common.service.impl;

/**
 * Created by IntelliJ IDEA.
 * User: Papanii (a.k.a Code Pimp)
 * Original Author: Angus Croll
 * Date: Feb 10, 2010
 * Time: 2:42:21 PM
 */
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.StringTokenizer;
import java.util.regex.Matcher;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.Element;

import com.jaspersoft.jasperserver.core.util.XMLUtil;
import com.jaspersoft.jasperserver.core.util.RegexUtil;
import com.jaspersoft.jasperserver.api.engine.common.service.ActionModelSupport;

public class ActionModel {
    private final static Log log = LogFactory.getLog(ActionModel.class);

    private static final ActionModel _singleTon = new ActionModel();

    //ELEMENTS...
    private static final String SIMPLE_ACTION_ELEM = "simpleAction";
    private static final String INPUT_ACTION_ELEM = "inputAction";
    private static final String SELECT_ACTION_ELEM = "selectAction";
    private static final String SEPARATOR_ELEM = "separator";
    private static final String CONDITION_ELEM = "condition";
    private static final String OPTION_ELEM = "option";
    private static final String GENERATED_OPTIONS_ELEM = "generatedOptions";
    private static final String GENERATE_FROM_TEMPLATE_ELEM = "generateFromTemplate";
    private static final String LABEL_OPTION_ELEM = "labelOption";

    //ATTRIBUTES....
    //general
    private static final String NAME_ATTR = "name";
    private static final String ID_ATTR = "id";
    private static final String TEST_ATTR = "test";
    private static final String TEST_ARGS_ATTR = "testArgs";
    private static final String CLIENT_TEST_ATTR = "clientTest";
    private static final String CLIENT_TEST_ARGS_ATTR = "clientTestArgs";
    private static final String ACTION_ATTR = "action";
    private static final String ACTION_ARGS_ATTR = "actionArgs";
    private static final String SELECTION_CONSTRAINT_ATTR = "selectionConstraint";
    private static final String BUTTON_ATTR = "button";
    private static final String DISABLED_ATTR = "disabled";
    //labels
    private static final String LABEL_KEY_ATTR = "labelKey";
    private static final String LABEL_CONDITION_ATTR = "labelCondition";
    private static final String LABEL_FUNCTION_ATTR = "labelFunction";
    private static final String LABEL_FUNCTION_ARGS_ATTR = "labelFunctionArgs";
    private static final String LABEL_EXPRESSION_ATTR = "labelExpression";
    private static final String FUNCTION_RESPONSE_ATTR = "functionResponse";
    //options
    private static final String IS_SELECTED_TEST_ATTR = "isSelectedTest";
    private static final String IS_SELECTED_TEST_ARGS_ATTR = "isSelectedTestArgs";
    private static final String FUNCTION_ATTR = "function";
    private static final String FUNCTION_ARGS_ATTR = "functionArgs";
    private static final String ALLOWS_INPUT_TEST_ATTR = "allowsInputTest";
    private static final String ALLOWS_INPUT_TEST_ARGS_ATTR = "allowsInputTestArgs";
    private static final String NO_ICON_INDENT_ATTR = "noIconIndent";

    //JSON objects...
    private static final String TYPE_KEY = "type";
    private static final String ID_KEY = "id";
    private static final String TEXT_KEY = "text";
    private static final String CLIENT_TEST_KEY = "clientTest";
    private static final String CLIENT_TEST_ARGS_KEY = "clientTestArgs";
    private static final String ACTION_KEY = "action";
    private static final String ACTION_ARGS_KEY = "actionArgs";
    private static final String IS_SELECTED_TEST_KEY = "isSelectedTest";
    private static final String IS_SELECTED_TEST_ARGS_KEY = "isSelectedTestArgs";
    private static final String ALLOWS_INPUT_TEST_KEY = "allowsInputTest";
    private static final String ALLOWS_INPUT_TEST_ARGS_KEY = "allowsInputTestArgs";
    private static final String NO_ICON_INDENT_KEY = "noIndent";
    private static final String CHILDREN_KEY = "children";
    private static final String SELECTION_CONSTRAINT_KEY = "selectionConstraint";
    private static final String BUTTON_KEY = "button";
    private static final String DISABLED_KEY = "isDisabled";
    private static final String CLASS_NAME = "className";

    //Reserved symbols on client
    private static final String RES_SELECTED_SERVER = "${selected}";
    private static final String RES_EVENT_SERVER = "${event}";
    private static final String RES_LABEL_SERVER = "${label}";
    private static final String RES_OPTION_ID_SERVER = "${optionId}";
    private static final String RES_OPTION_VALUE_SERVER = "${optionValue}";
    private static final String RES_TEMPLATE_INJECTION_INDEX = "${templateInjectionIndex}";
    private static final String RES_TEMPLATE_INJECTION_ID = "${templateInjectionId}";
    private static final String RES_TEMPLATE_INJECTION_VALUE = "${templateInjectionValue}";

    //JS constants on client (see actionModel.js for java-script ActionModel object)
    private static final String SIMPLE_VALUE = "simpleAction";
    private static final String SELECT_VALUE = "selectAction";
    private static final String INPUT_VALUE = "inputAction";
    private static final String OPTION_VALUE = "optionAction";
    private static final String SEPARATOR_VALUE = "separator";
    //reserved variables on client
    private static final String RES_LABEL_CLIENT = "$label";
    //private static final String RES_ID_CLIENT = "$id";
    private static final String RES_SELECTED_CLIENT = "$selected";
    private static final String RES_EVENT_CLIENT = "$event";

    //maps action model xml elem names to client model names
    private static Map<String, String> actionTypesMap = new HashMap<String, String>();

    //actions that fire an action directly (eg simple actions or options but not drop down selectors)
    private static List<String> directActionTypes = new ArrayList<String>();

    //actions that have child actions
    private static List<String> parentActionTypes = new ArrayList<String>();

    //special string mapping
    private static Map<String, String> specialStringMap = new HashMap<String, String>();

    //regex patterns
    private static String resourceKeyRegularExpression = "\\$R\\{[a-zA-Z0-9_.]*\\}";
    private static String arrayStringDelimiterRegularExpression = "@@";

    //values used when instantiating templates - make global so we don't have to keep passing around
    private static int templateInjectionIndex = 0; //zero based iteration index
    private static String templateInjectionId = "";
    private static String templateInjectionValue = "";

    static {
        //map element names to equivalent client constants
        actionTypesMap.put(SIMPLE_ACTION_ELEM, SIMPLE_VALUE);
        actionTypesMap.put(SELECT_ACTION_ELEM, SELECT_VALUE);
        actionTypesMap.put(INPUT_ACTION_ELEM, INPUT_VALUE);
        actionTypesMap.put(OPTION_ELEM, OPTION_VALUE);
        actionTypesMap.put(SEPARATOR_ELEM, SEPARATOR_VALUE);

        directActionTypes.add(SIMPLE_ACTION_ELEM);
        directActionTypes.add(OPTION_ELEM);

        parentActionTypes.add(SELECT_ACTION_ELEM);
        parentActionTypes.add(CONDITION_ELEM);

        specialStringMap.put(RES_LABEL_SERVER, RES_LABEL_CLIENT);
        specialStringMap.put(RES_SELECTED_SERVER, RES_SELECTED_CLIENT);
        specialStringMap.put(RES_EVENT_SERVER, RES_EVENT_CLIENT);

    }

    //private constructor
    private ActionModel() {
    }

    //factory method for singleton
    public synchronized static ActionModel getInstance() {
        return ActionModel._singleTon;
    }

    public Document generateActionModelDocument(InputStream stream) {
        try {
            return XMLUtil.toDocument(stream);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public String generateClientActionModel(ActionModelSupport actionModelInterface, Document actionModel)
            throws Exception {
        StringBuffer sb = new StringBuffer();
        sb.append("{");

        Element root = actionModel.getRootElement();
        Iterator contextIt = root.getChildren().iterator();
        while (contextIt.hasNext()) {
            Element context = (Element) contextIt.next();
            //context test
            if (context.getAttributeValue(TEST_ATTR) != null) {
                if (!shouldInclude(actionModelInterface, context)) {
                    continue;
                }
            }

            appendContext(context, sb);
            sb.append("[");
            generateChildActions(context, actionModelInterface, sb);
            sb.append("]");
            if (contextIt.hasNext()) {
                sb.append(",");
            }
        }
        int lastCharIndex = sb.length() - 1;
        if (sb.charAt(lastCharIndex) == ',') {
            sb.deleteCharAt(lastCharIndex);
        }
        sb.append("}");
        return sb.toString();
    }

    /*
     * Create an JSON object for each child action
     */
    private static void generateChildActions(Element parentNode, ActionModelSupport actionModelInterface,
            StringBuffer sb) throws Exception {
        Iterator actionIt = parentNode.getChildren().iterator();
        while (actionIt.hasNext()) {
            Element action = (Element) actionIt.next();
            if (action.getName().equals(CONDITION_ELEM)) {
                //Action is contingent on view model (server) condition
                if (shouldInclude(actionModelInterface, action)) {
                    //condition passed the test - the real action is the condition's child
                    //action = (Element)action.getChildren().get(0);
                    generateChildActions(action, actionModelInterface, sb);
                }
            } else {
                //special cases - generated selections
                if (action.getName().equals(GENERATED_OPTIONS_ELEM)) {
                    generateOptions(actionModelInterface, sb, action);
                } else if (action.getName().equals(GENERATE_FROM_TEMPLATE_ELEM)) {
                    generateFromTemplate(actionModelInterface, sb, action);
                } else {
                    //OK now we have an action for sure - render as a client action
                    generateAction(actionModelInterface, sb, action);
                }
                if (actionIt.hasNext()) {
                    //sb.append(",");
                }
            }
        }
    }

    private static void generateAction(ActionModelSupport actionModelInterface, StringBuffer sb, Element action)
            throws Exception {
        String actionType = action.getName();
        //Opener
        Character latestChar = sb.charAt(sb.length() - 1);
        if (latestChar == ']' || latestChar == '}') {
            sb.append(",");
        }
        sb.append("{");

        //attributes
        appendType(actionType, sb);
        appendStandardProperty(ID_KEY, action.getAttributeValue(ID_ATTR), sb);
        appendStandardProperty(CLIENT_TEST_KEY, action.getAttributeValue(CLIENT_TEST_ATTR), sb);
        appendStandardArgs(CLIENT_TEST_ARGS_KEY, action.getAttributeValue(CLIENT_TEST_ARGS_ATTR), sb);
        appendStandardProperty(SELECTION_CONSTRAINT_KEY, action.getAttributeValue(SELECTION_CONSTRAINT_ATTR), sb);
        appendStandardProperty(ALLOWS_INPUT_TEST_KEY, action.getAttributeValue(ALLOWS_INPUT_TEST_ATTR), sb);
        appendStandardArgs(ALLOWS_INPUT_TEST_ARGS_KEY, action.getAttributeValue(ALLOWS_INPUT_TEST_ARGS_ATTR), sb);
        appendStandardProperty(NO_ICON_INDENT_KEY, action.getAttributeValue(NO_ICON_INDENT_ATTR), sb);
        appendStandardProperty(BUTTON_KEY, action.getAttributeValue(BUTTON_ATTR), sb);
        appendStandardProperty(DISABLED_KEY, action.getAttributeValue(DISABLED_ATTR), sb);

        if (action.getAttributeValue(CLASS_NAME) != null) {
            appendStandardProperty(CLASS_NAME, action.getAttributeValue(CLASS_NAME), sb);
        }
        if (hasText(actionType)) {
            appendText(action, actionModelInterface, sb);
        }
        if (firesAction(actionType)) {
            appendStandardProperty(ACTION_KEY, action.getAttributeValue(ACTION_ATTR), sb);
            appendStandardArgs(ACTION_ARGS_KEY, action.getAttributeValue(ACTION_ARGS_ATTR), sb);
        }
        if (indicatesSelection(action)) {
            appendStandardProperty(IS_SELECTED_TEST_KEY, action.getAttributeValue(IS_SELECTED_TEST_ATTR), sb);
            appendStandardArgs(IS_SELECTED_TEST_ARGS_KEY, action.getAttributeValue(IS_SELECTED_TEST_ARGS_ATTR), sb);
        }
        if (hasChildren(actionType)) {
            appendChildren(action, actionModelInterface, sb);
        }

        //Final closure
        sb.append("}");
    }

    private static void appendContext(Element context, StringBuffer sb) {
        sb.append("\"");
        sb.append(context.getAttributeValue(NAME_ATTR));
        sb.append("\"");
        sb.append(":");
    }

    private static void appendType(String actionType, StringBuffer sb) {
        //key
        sb.append("\"");
        sb.append(TYPE_KEY);
        sb.append("\": ");
        //value
        sb.append("\"");
        sb.append(actionTypesMap.get(actionType));
        sb.append("\"");
    }

    private static void appendStandardProperty(String key, String value, StringBuffer sb) {
        if (value != null) {
            sb.append(",");
            //key
            sb.append("\"");
            sb.append(key);
            sb.append("\": ");
            //value
            sb.append("\"");
            sb.append(value);
            sb.append("\"");
        }
    }

    private static void appendStandardArgs(String key, String args, StringBuffer sb) {
        if (args == null) {
            return;
        }
        sb.append(",");
        //key
        sb.append("\"");
        sb.append(key);
        sb.append("\": ");
        //value
        sb.append(formatDelimitedStringAsArrayString(args));
    }

    private static void appendText(Element action, ActionModelSupport actionModelContext, StringBuffer sb)
            throws Exception {
        sb.append(",");
        //key
        sb.append("\"");
        sb.append(TEXT_KEY);
        sb.append("\": ");
        //get actual text
        String labelKey = getLabelKey(action, actionModelContext);
        //value
        sb.append("\"");
        sb.append(getLocalizedValue(labelKey, actionModelContext));
        sb.append("\"");
    }

    private static String getLabelKey(Element action, ActionModelSupport actionModelContext) throws Exception {
        //simple i18n key or hard coded value?
        String labelKey = action.getAttributeValue(LABEL_KEY_ATTR);
        if (labelKey != null) {
            return swapOutSpecialStrings(labelKey);
        }
        //conditional?
        String labelCondition = action.getAttributeValue(LABEL_CONDITION_ATTR);
        if (labelCondition != null) {
            return getLabelForCondition(action, actionModelContext, labelCondition);
        }
        //function based?
        String labelFunction = action.getAttributeValue(LABEL_FUNCTION_ATTR);
        if (labelFunction != null) {
            return getLabelForFunction(action, actionModelContext, labelFunction,
                    action.getAttributeValue(LABEL_FUNCTION_ARGS_ATTR));
        }

        throw new Exception("could not derive action text for action " + action);
    }

    private static String getLabelForCondition(Element elem, ActionModelSupport actionModelContext,
            String labelCondition) throws Exception {
        Method labelMethod = actionModelContext.getClass().getMethod(labelCondition, new Class[0]);
        String methodResult = (labelMethod.invoke(actionModelContext, new Object[0])).toString();

        Iterator labelOptionsIt = elem.getChildren(LABEL_OPTION_ELEM).iterator();
        while (labelOptionsIt.hasNext()) {
            Element thisLabelOption = (Element) labelOptionsIt.next();
            if (thisLabelOption.getAttributeValue(FUNCTION_RESPONSE_ATTR).equals(methodResult)) {
                return getLabelKey(thisLabelOption, actionModelContext);
            }
        }
        return null;
    }

    private static String getLabelForFunction(Element elem, ActionModelSupport actionModelContext,
            String labelFunction, String labelFunctionArgsString) throws Exception {
        Object[] args = labelFunctionArgsString == null ? new Object[0]
                : RegexUtil.getResourceKeyPattern(arrayStringDelimiterRegularExpression)
                        .split(labelFunctionArgsString);
        Class[] argTypes = RegexUtil.getArgTypesFromArgs(args);
        Method thisMethod = actionModelContext.getClass().getMethod(labelFunction, argTypes);
        return thisMethod.invoke(actionModelContext, args).toString();
    }

    private static void appendChildren(Element action, ActionModelSupport actionModelContext, StringBuffer sb)
            throws Exception {
        sb.append(",");
        //key
        sb.append("\"");
        sb.append(CHILDREN_KEY);
        sb.append("\": ");
        //value
        sb.append("[");
        generateChildActions(action, actionModelContext, sb);
        sb.append("]");
    }

    /*
     * Generate Option Elements Programmatically
     * The generator function can return either a Map or a Collection
     */
    private static void generateOptions(ActionModelSupport actionModelContext, StringBuffer sb, Element action)
            throws Exception {
        //The generator function can return either a Map or a Collection
        //if it returns a map then use key and value for optionId and optionValue respectively
        //if it returns a Collection then use each element (toString) as both optionId and optionValue
        //Use optionValues as defaults for labels
        //Use optionIds as defaults for tests, functions etc.
        Object options = getGenerationSource(actionModelContext, action.getAttributeValue(FUNCTION_ATTR),
                action.getAttributeValue(FUNCTION_ARGS_ATTR));
        boolean usingMap;
        Iterator optionsIt;
        if (options instanceof Map) {
            optionsIt = ((Map) options).keySet().iterator();
            usingMap = true;
        } else {
            optionsIt = ((Collection) options).iterator();
            usingMap = false;
        }
        while (optionsIt.hasNext()) {
            Object thisOption = optionsIt.next();
            String optionId = thisOption.toString();
            String optionValue;
            if (usingMap) {
                optionValue = ((Map) options).get(thisOption).toString();
            } else {
                optionValue = optionId;
            }
            Element optionAction = new Element(OPTION_ELEM);
            String labelExpression = action.getAttributeValue(LABEL_EXPRESSION_ATTR);
            if (labelExpression != null) {
                optionAction.setAttribute(LABEL_KEY_ATTR,
                        generateOptionLabel(actionModelContext, labelExpression, optionValue, optionId));
            } else {
                //default label is the option value
                optionAction.setAttribute(LABEL_KEY_ATTR, optionValue);
            }
            optionAction.setAttribute(ACTION_ATTR, action.getAttributeValue(ACTION_ATTR));
            optionAction.setAttribute(ACTION_ARGS_ATTR,
                    getAsOptionArgs(action.getAttributeValue(ACTION_ARGS_ATTR), optionValue, optionId));

            if (action.getAttributeValue(IS_SELECTED_TEST_ATTR) != null) {
                optionAction.setAttribute(IS_SELECTED_TEST_ATTR, action.getAttributeValue(IS_SELECTED_TEST_ATTR));
                optionAction.setAttribute(IS_SELECTED_TEST_ARGS_ATTR, getAsOptionArgs(
                        action.getAttributeValue(IS_SELECTED_TEST_ARGS_ATTR), optionValue, optionId));
            }
            if (action.getAttributeValue(CLIENT_TEST_ATTR) != null) {
                optionAction.setAttribute(CLIENT_TEST_ATTR, action.getAttributeValue(CLIENT_TEST_ATTR));
                optionAction.setAttribute(CLIENT_TEST_ARGS_ATTR,
                        getAsOptionArgs(action.getAttributeValue(CLIENT_TEST_ARGS_ATTR), optionValue, optionId));
            }
            if (action.getAttributeValue(ALLOWS_INPUT_TEST_ATTR) != null) {
                optionAction.setAttribute(ALLOWS_INPUT_TEST_ATTR, action.getAttributeValue(ALLOWS_INPUT_TEST_ATTR));
                optionAction.setAttribute(ALLOWS_INPUT_TEST_ARGS_ATTR, getAsOptionArgs(
                        action.getAttributeValue(ALLOWS_INPUT_TEST_ARGS_ATTR), optionValue, optionId));
            }

            if (action.getAttributeValue(CLASS_NAME) != null) {
                optionAction.setAttribute(CLASS_NAME, action.getAttributeValue(CLASS_NAME));
            }
            if (action.getAttributeValue(ID_ATTR) != null) {
                optionAction.setAttribute(ID_ATTR,
                        getAsOptionArgs(action.getAttributeValue(ID_ATTR), optionValue, optionId));
            } else {
                //default id is the option id
                optionAction.setAttribute(ID_ATTR, optionId);
            }

            generateAction(actionModelContext, sb, optionAction);
            if (optionsIt.hasNext()) {
                sb.append(",");
            }
        }
    }

    private static String getAsOptionArgs(String argsString, String optionValue, String optionId) {
        if (argsString == null) {
            //if no explicit arg string supplied, use the optionId itself as an arg
            return optionId;
        }
        argsString = argsString.replace(RES_OPTION_VALUE_SERVER, optionValue);
        argsString = argsString.replace(RES_OPTION_ID_SERVER, optionId);

        return argsString;
    }

    private static String generateOptionLabel(ActionModelSupport actionModelContext, String labelExpression,
            String optionValue, String optionId) {
        //first substitute option value (assumes just one of these)
        labelExpression = labelExpression.replace(RES_OPTION_VALUE_SERVER, optionValue);
        labelExpression = labelExpression.replace(RES_OPTION_ID_SERVER, optionId);
        // now look for $R{...} pattern and resolve i18n value (assumes just one of these)
        Matcher matcher = RegexUtil.getResourceKeyPattern(resourceKeyRegularExpression).matcher(labelExpression);
        if (matcher.find()) {
            String resourceKeyPattern = labelExpression.substring(matcher.start(), matcher.end());
            String resourceKey = resourceKeyPattern.substring(3, resourceKeyPattern.length() - 1);
            String translation = getLocalizedValue(resourceKey, actionModelContext);
            return labelExpression.replace(resourceKeyPattern, translation);
        } else {
            return labelExpression;
        }
    }

    /*
     * Generate Multiple Actions Programatically, based on the enclosed template structure
     * Enclosed template can be of any form, and can include multiple actions and nested actions
     * The generator function can return either a Map or a Collection
     */
    private static void generateFromTemplate(ActionModelSupport actionModelContext, StringBuffer sb, Element action)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, Exception {
        //The generator function can return either a Map or a Collection
        //if it returns a map then use key and value for templateInjectId and templateInjectValue respectively
        //if it returns a Collection then use each element (toString) as both templateInjectId and templateInjectValue
        //
        Object templateInjectors = getGenerationSource(actionModelContext, action.getAttributeValue(FUNCTION_ATTR),
                action.getAttributeValue(FUNCTION_ARGS_ATTR));
        boolean usingMap;
        Iterator templateInjectorsIt;
        if (templateInjectors instanceof Map) {
            templateInjectorsIt = ((Map) templateInjectors).keySet().iterator();
            usingMap = true;
        } else {
            templateInjectorsIt = ((Collection) templateInjectors).iterator();
            usingMap = false;
        }
        templateInjectionIndex = -1;
        while (templateInjectorsIt.hasNext()) {
            templateInjectionIndex++;
            Object thisTemplateInjector = templateInjectorsIt.next();
            templateInjectionId = thisTemplateInjector.toString();
            if (usingMap) {
                templateInjectionValue = ((Map) templateInjectors).get(thisTemplateInjector).toString();
            } else {
                templateInjectionValue = templateInjectionId;
            }
            Iterator<Element> childActionIt = action.getChildren().iterator();
            while (childActionIt.hasNext()) {
                Element childAction = childActionIt.next();
                generateAction(actionModelContext, sb, childAction);
            }
            if (templateInjectorsIt.hasNext()) {
                sb.append(",");
            }
        }
    }

    private static String getLocalizedValue(String labelKey, ActionModelSupport actionModelContext) {
        return actionModelContext.getMessage(labelKey);
    }

    private static boolean firesAction(String actionType) {
        return directActionTypes.contains(actionType);
    }

    private static boolean hasText(String actionType) {
        return !actionType.equals(SEPARATOR_ELEM);
    }

    private static boolean indicatesSelection(Element action) {
        return action.getAttribute(IS_SELECTED_TEST_ATTR) != null;
    }

    private static boolean hasChildren(String actionType) {
        return parentActionTypes.contains(actionType);
    }

    private static boolean shouldInclude(ActionModelSupport actionModelContext, Element action) throws Exception {
        boolean passingResult = true;
        String test = action.getAttributeValue(TEST_ATTR);
        if (test.indexOf("!") == 0) {
            //leading ! means test for false
            test = test.substring(1);
            passingResult = false;
        }
        String testArgsString = action.getAttributeValue(TEST_ARGS_ATTR);
        Object result = false;
        Method testMethod = null;
        Object[] testArgs = testArgsString == null ? new Object[0]
                : RegexUtil.getResourceKeyPattern(arrayStringDelimiterRegularExpression).split(testArgsString);
        Class[] testArgTypes = RegexUtil.getArgTypesFromArgs(testArgs);
        try {
            testMethod = actionModelContext.getClass().getMethod(test, testArgTypes);
            result = testMethod.invoke(actionModelContext, testArgs);
        } catch (NoSuchMethodException ex) {
            log.error("Test method '" + test + "' not found. Please correct the action model xml. "
                    + "All underlying elements are skipped.");
            return false; //no method - default to  don't include
        } catch (IllegalAccessException ex) {
            return false; //can't access - default to  don't include
        } catch (InvocationTargetException ex) {
            return false; //can't invoke - default to  don't include
        }
        if (result instanceof Boolean) {
            return ((Boolean) result).booleanValue() == passingResult;
        } else {
            throw new Exception("Test " + testMethod + "should return a boolean");
        }
    }

    /*
     * Returns either a Map or a Collection to be injected into option or template
     */
    private static Object getGenerationSource(ActionModelSupport actionModelContext, String function,
            String functionArgsString)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, Exception {
        Object[] args = functionArgsString == null ? new Object[0]
                : RegexUtil.getResourceKeyPattern(arrayStringDelimiterRegularExpression).split(functionArgsString);
        Class[] argTypes = RegexUtil.getArgTypesFromArgs(args);
        Method optionsMethod = actionModelContext.getClass().getMethod(function, argTypes);
        Object options = optionsMethod.invoke(actionModelContext, args);
        if (options instanceof Collection || options instanceof Map) {
            return options;
        } else {
            throw new Exception("function " + optionsMethod + "should return a Collection or a Map");
        }
    }

    /*
     * e.g. ${selected} is for currently selected objects on client
     */
    private static String swapOutSpecialStrings(String input) {
        Iterator keysIt = specialStringMap.keySet().iterator();
        while (keysIt.hasNext()) {
            String thisKey = (String) keysIt.next();
            input = input.replace(thisKey, specialStringMap.get(thisKey));
        }
        //global template var injection
        input = input.replace(RES_TEMPLATE_INJECTION_INDEX, templateInjectionIndex + "");
        input = input.replace(RES_TEMPLATE_INJECTION_ID, templateInjectionId);
        input = input.replace(RES_TEMPLATE_INJECTION_VALUE, templateInjectionValue);
        return input;
    }

    /*
     * "Elephant@@${label}@@Polar Bear@@Zebra" -> "['Elephant','$label','Polar Bear','Zebra']"
     * Swaps out reserved strings too
     */
    private static StringBuffer formatDelimitedStringAsArrayString(String delimitedString) {
        StringTokenizer tokenizer = new StringTokenizer(delimitedString, "@@");
        StringBuffer valueBuffer = new StringBuffer();
        valueBuffer.append("[");
        while (tokenizer.hasMoreTokens()) {
            valueBuffer.append("\"");
            valueBuffer.append(swapOutSpecialStrings(tokenizer.nextToken()));
            valueBuffer.append("\"");
            if (tokenizer.hasMoreTokens()) {
                valueBuffer.append(",");
            }
        }
        valueBuffer.append("]");
        return valueBuffer;
    }

}