/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * 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 General Public License and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.rhq.core.gui.configuration;

import java.util.Collection;

import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIParameter;
import javax.faces.component.html.HtmlCommandLink;
import javax.faces.component.html.HtmlPanelGrid;
import javax.faces.component.html.HtmlPanelGroup;
import javax.faces.component.html.HtmlSelectBooleanCheckbox;
import javax.faces.context.FacesContext;

import org.ajax4jsf.component.html.HtmlAjaxCommandLink;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.richfaces.component.html.HtmlModalPanel;

import org.rhq.core.domain.configuration.AbstractPropertyMap;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyDefinitionDynamic;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionList;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionMap;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.gui.RequestParameterNameConstants;
import org.rhq.core.gui.configuration.helper.PropertyRenderingUtility;
import org.rhq.core.gui.configuration.propset.ConfigurationSetComponent;
import org.rhq.core.gui.util.FacesComponentUtility;
import org.rhq.core.gui.util.FacesExpressionUtility;
import org.rhq.core.gui.util.PropertyIdGeneratorUtility;
import org.rhq.core.template.TemplateEngine;

/**
* A factory that generates a tree of JSF components that depicts a given collection of JON {@link Property}s.
*
* @author Ian Springer
*/
public abstract class AbstractPropertyBagUIComponentTreeFactory {
    private final Log LOG = LogFactory.getLog(AbstractPropertyBagUIComponentTreeFactory.class);

    static final String DELETE_LIST_MEMBER_PROPERTY_FUNCTION = "DELETE_LIST_MEMBER_PROPERTY";
    static final String DELETE_OPEN_MAP_MEMBER_PROPERTY_FUNCTION = "DELETE_OPEN_MAP_MEMBER_PROPERTY";

    static final String PARAM_ID_SUFFIX = "-param";
    static final String PANEL_ID_SUFFIX = "-panel";

    protected static final String PROPERTY_SIMPLE_OVERRIDE_ACCESSOR_SUFFIX = "override";
    protected static final String PROPERTY_SIMPLE_VALUE_ACCESSOR_SUFFIX = "stringValue";
    protected static final String PROPERTY_LIST_VALUE_ACCESSOR_SUFFIX = "list";
    protected static final String PROPERTY_MAP_VALUE_ACCESSOR_SUFFIX = "map";

    private static final String VIEW_MAP_BUTTON_TITLE = "View Details";
    private static final String VIEW_MAP_BUTTON_LABEL = "View";
    private static final String EDIT_MAP_BUTTON_TITLE = "Edit";
    private static final String EDIT_MAP_BUTTON_LABEL = "Edit";
    private static final String DELETE_MAP_BUTTON_LABEL = "Delete";
    private static final String DELETE_MAP_BUTTON_TITLE = "Delete";
    private static final String ADD_NEW_MAP_BUTTON_LABEL = "Add New";
    private static final String ADD_NEW_MAP_BUTTON_TITLE = "Add New";

    private static final String NESTED_PROPERTIES_TABLE_STYLE_CLASS = "nested-properties-table";
    private static final String PROPERTIES_TABLE_HEADER_CELL_STYLE_CLASS = "properties-table-header-cell";
    private static final String NESTED_PROPERTIES_TABLE_HEADER_CELL_STYLE_CLASS = "nested-properties-table-header-cell";
    private static final String NESTED_PROPERTIES_TABLE_INDENT_CELL_STYLE_CLASS = "nested-properties-table-indent-cell";
    private static final String PROPERTY_ERROR_CELL_STYLE_CLASS = "property-error-cell";
    private static final String OPENMAP_PROPERTIES_TABLE_STYLE_CLASS = "openmap-properties-table";
    private static final String OPENMAP_PROPERTY_DISPLAY_NAME_CELL_STYLE_CLASS = "openmap-property-display-name-cell";
    private static final String OPENMAP_PROPERTY_VALUE_CELL_STYLE_CLASS = "openmap-property-value-cell";
    private static final String OPENMAP_PROPERTY_ACTIONS_CELL_STYLE_CLASS = "openmap-property-actions-cell";
    private static final String OPENMAP_PROPERTY_ERROR_CELL_STYLE_CLASS = "openmap-property-error-cell";
    private static final String LIST_PROPERTY_DISPLAY_NAME_CELL_STYLE_CLASS = "list-property-display-name-cell";
    private static final String LIST_PROPERTY_VALUE_CELL_STYLE_CLASS = "list-property-value-cell";
    private static final String LIST_PROPERTY_DESCRIPTION_CELL_STYLE_CLASS = "list-property-description-cell";
    private static final String LIST_PROPERTY_ENABLED_CELL_STYLE_CLASS = "list-property-enabled-cell";
    private static final String LIST_PROPERTY_CHILDREN_CELL_STYLE_CLASS = "list-property-children-cell";
    private static final String PROPERTY_MAP_SUMMARY_TABLE_STYLE_CLASS = "property-map-summary-table";
    private static final String PROPERTY_MAP_SUMMARY_DATA_HEADER_CELL_STYLE_CLASS = "property-map-summary-data-header-cell";
    private static final String PROPERTY_MAP_SUMMARY_HEADER_TEXT_STYLE_CLASS = "property-map-summary-header-text";
    private static final String PROPERTY_MAP_SUMMARY_BUTTONS_CELL_STYLE_CLASS = "property-map-summary-buttons-cell";
    private static final String PROPERTY_MAP_SUMMARY_DATA_CELL_STYLE_CLASS = "property-map-summary-data-cell";
    private static final String PROPERTY_MAP_SUMMARY_DATA_TEXT_STYLE_CLASS = "property-map-summary-data-text";
    private static final String PROPERTY_MAP_SUMMARY_BUTTON_FOOTER_STYLE_CLASS = "property-buttonfooterrow";
    private static final String BUTTONS_TABLE_STYLE_CLASS = "buttons-table";
    private static final String VALUES_DIFFER_TEXT_STYLE_CLASS = "values-differ-text";

    private AbstractConfigurationComponent config;
    private Collection<PropertyDefinition> propertyDefinitions;
    private AbstractPropertyMap propertyMap;
    private boolean topLevel;
    private String valueExpressionFormat;
    private String overrideExpressionFormat;
    private boolean isGroup;
    private HtmlModalPanel memberValuesModalPanel;

    public AbstractPropertyBagUIComponentTreeFactory(AbstractConfigurationComponent config,
        Collection<PropertyDefinition> propertyDefinitions, AbstractPropertyMap propertyMap, boolean topLevel,
        String valueExpressionFormat) {
        this.config = config;
        this.propertyDefinitions = propertyDefinitions;
        this.propertyMap = propertyMap;
        this.topLevel = topLevel;
        this.valueExpressionFormat = valueExpressionFormat;
        this.overrideExpressionFormat = getOverrideExpressionFormat(valueExpressionFormat);
        
        // The below variable is for the new group config impl being implemented by Ian.
        this.isGroup = (this.config instanceof ConfigurationSetComponent);
        if (this.isGroup) {
            ConfigurationSetComponent configurationSetComponent = (ConfigurationSetComponent) this.config;
            this.memberValuesModalPanel = configurationSetComponent.getPropSetModalPanel();
        }
    }

    private String getOverrideExpressionFormat(String valueExpression) {
        // replace '.stringValue}' suffix with '.override}'
        int index = valueExpression.lastIndexOf(".");
        return valueExpression.substring(0, index) + "." + PROPERTY_SIMPLE_OVERRIDE_ACCESSOR_SUFFIX + "}";
    }

    protected Integer getListIndex() {
        return null;
    }

    public UIComponent createUIComponentTree(String rowStyleClass) {
        HtmlPanelGroup rootPanel = FacesComponentUtility.createBlockPanel(this.config,
            FacesComponentUtility.NO_STYLE_CLASS);
        if (!this.propertyDefinitions.isEmpty() || (this.propertyMap instanceof PropertyMap)) {
            if (!this.propertyDefinitions.isEmpty()) {
                String tableStyleClass = this.topLevel ? CssStyleClasses.PROPERTIES_TABLE
                    : NESTED_PROPERTIES_TABLE_STYLE_CLASS;
                FacesComponentUtility.addVerbatimText(rootPanel, "\n\n<table class='" + tableStyleClass + "'>");
                int rowCount = 0;
                boolean alternateRowStyles = (rowStyleClass == null);
                if (containsSimpleProperties()) {
                    addSimplePropertiesTableHeaders(rootPanel); // the Name/Unset/Value/Description headers
                }

                // TODO: Display simple props before lists and maps, so the simple props immediately follow the
                //       Name/Unset/Value/Description header.
                for (PropertyDefinition propertyDefinition : this.propertyDefinitions) {
                    rowStyleClass = getRowStyleClass(rowStyleClass, rowCount, alternateRowStyles);
                    addProperty(rootPanel, propertyDefinition, rowStyleClass);
                    rowCount++;
                }
            } else {
                // If the map contains no member definitions, it means the map permits arbitrary simple properties as
                // members. We refer to such a map as an "open map."
                PropertyDefinitionMap propertyDefinitionMap = this.config.getConfigurationDefinition()
                    .getPropertyDefinitionMap(((PropertyMap) this.propertyMap).getName());
                FacesComponentUtility.addVerbatimText(rootPanel, "\n\n<table class='"
                    + OPENMAP_PROPERTIES_TABLE_STYLE_CLASS + "'>");
                addOpenMapMemberPropertiesTableHeaders(rootPanel, propertyDefinitionMap);
                for (Property property : this.propertyMap.getMap().values()) {
                    if (!(property instanceof PropertySimple)) {
                        throw new IllegalStateException("Open map " + this.propertyMap
                            + " contains non-simple property: " + property);
                    }

                    PropertySimple propertySimple = (PropertySimple) property;

                    // NOTE: Row colors never alternate for maps.
                    addOpenMapMemberProperty(rootPanel, propertyDefinitionMap, propertySimple, rowStyleClass);
                }

                addNewOpenMapMemberPropertyRow(rootPanel, propertyDefinitionMap);
            }

            FacesComponentUtility.addVerbatimText(rootPanel, "</table>");
        }

        return rootPanel;
    }

    private String getRowStyleClass(String rowStyleClass, int rowCount, boolean alternateRowStyles) {
        if (alternateRowStyles) {
            // add the odd/even row style
            rowStyleClass = ((rowCount % 2) == 0) ? CssStyleClasses.ROW_ODD : CssStyleClasses.ROW_EVEN;
        }

        return rowStyleClass;
    }

    private void addSimplePropertiesTableHeaders(UIComponent parent) {
        String headerCellStyleClass = this.topLevel ? PROPERTIES_TABLE_HEADER_CELL_STYLE_CLASS
            : NESTED_PROPERTIES_TABLE_HEADER_CELL_STYLE_CLASS;

        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr>");

        FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
        FacesComponentUtility.addOutputText(parent, this.config, "Name", FacesComponentUtility.NO_STYLE_CLASS);
        FacesComponentUtility.addVerbatimText(parent, "</th>");

        // TODO: Get rid of the Override column, once the new group config stuff is operational.
        if (this.config.isGroup()) {
            FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
            FacesComponentUtility.addOutputText(parent, this.config, "Override", FacesComponentUtility.NO_STYLE_CLASS);
            FacesComponentUtility.addVerbatimText(parent, "</th>");
        }

        FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
        FacesComponentUtility.addOutputText(parent, this.config, "Unset", FacesComponentUtility.NO_STYLE_CLASS);
        FacesComponentUtility.addVerbatimText(parent, "</th>");

        FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
        FacesComponentUtility.addOutputText(parent, this.config, "Value", FacesComponentUtility.NO_STYLE_CLASS);
        FacesComponentUtility.addVerbatimText(parent, "</th>");

        FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
        FacesComponentUtility.addOutputText(parent, this.config, "Description", FacesComponentUtility.NO_STYLE_CLASS);
        FacesComponentUtility.addVerbatimText(parent, "</th>");

        FacesComponentUtility.addVerbatimText(parent, "</tr>");
    }

    private boolean containsSimpleProperties() {
        boolean containsSimples = false;
        for (PropertyDefinition propertyDefinition : this.propertyDefinitions) {
            if (propertyDefinition instanceof PropertyDefinitionSimple) {
                containsSimples = true;
                break;
            }
        }

        return containsSimples;
    }

    private void addOpenMapMemberPropertiesTableHeaders(UIComponent parent, PropertyDefinitionMap propertyDefinitionMap) {
        String headerCellStyleClass = this.topLevel ? PROPERTIES_TABLE_HEADER_CELL_STYLE_CLASS
            : NESTED_PROPERTIES_TABLE_HEADER_CELL_STYLE_CLASS;

        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr>");

        FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
        FacesComponentUtility.addOutputText(parent, this.config, "Name", FacesComponentUtility.NO_STYLE_CLASS);
        FacesComponentUtility.addVerbatimText(parent, "</th>");

        FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
        FacesComponentUtility.addOutputText(parent, this.config, "Value", FacesComponentUtility.NO_STYLE_CLASS);
        FacesComponentUtility.addVerbatimText(parent, "</th>");

        if (!isReadOnly(propertyDefinitionMap)) {
            FacesComponentUtility.addVerbatimText(parent, "<th class='" + headerCellStyleClass + "'>");
            FacesComponentUtility.addOutputText(parent, this.config, "Actions", FacesComponentUtility.NO_STYLE_CLASS);
            FacesComponentUtility.addVerbatimText(parent, "</th>");
        }

        FacesComponentUtility.addVerbatimText(parent, "</tr>");
    }

    protected AbstractConfigurationComponent getConfigurationComponent() {
        return config;
    }

    private void addProperty(UIComponent parent, PropertyDefinition propertyDefinition, String rowStyleClass) {
        if (propertyDefinition instanceof PropertyDefinitionSimple) {
            addSimpleProperty(parent, (PropertyDefinitionSimple) propertyDefinition, rowStyleClass, config.getTemplateEngine());
        } else if (propertyDefinition instanceof PropertyDefinitionList) {
            addListProperty(parent, (PropertyDefinitionList) propertyDefinition, rowStyleClass);
        } else if (propertyDefinition instanceof PropertyDefinitionMap) {
            addMapProperty(parent, (PropertyDefinitionMap) propertyDefinition, rowStyleClass);
        } else if (propertyDefinition instanceof PropertyDefinitionDynamic) {
            addDynamicProperty(parent, (PropertyDefinitionDynamic) propertyDefinition, rowStyleClass);
        }
        else {
            throw new IllegalStateException("Unsupported subclass of " + PropertyDefinition.class.getName() + ".");
        }
    }

    /**
     * Adds a new property to the UI based on a dynamic property definition.
     *
     * @param parent                    owner in which the UI rendering of the property will live
     * @param propertyDefinitionDynamic describes the property being rendered
     * @param rowStyleClass             indicates the CSS style (i.e. background color) of this row in the property list
     */
    private void addDynamicProperty(UIComponent parent, PropertyDefinitionDynamic propertyDefinitionDynamic,
                                    String rowStyleClass) {
        addDebug(parent, true, ".addDynamicProperty()");

        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr class='" + rowStyleClass + "'>");

        PropertySimple propertySimple = this.propertyMap.getSimple(propertyDefinitionDynamic.getName());
        ValueExpression propertyValueExpression = createPropertyValueExpression(propertySimple.getName(),
            this.valueExpressionFormat);

        UIInput input = null;
        if (!this.isGroup || (propertySimple.getOverride() != null && propertySimple.getOverride())) {
            // We need to create the input component ahead of when we need to add it to the component tree, since we
            // need to know the input component's id in order to render the unset control.
            input = PropertyRenderingUtility.createInputForDynamicProperty(propertyDefinitionDynamic, propertySimple,
                propertyValueExpression, getListIndex(), this.isGroup, this.config.isReadOnly(), this.config
                    .isFullyEditable(), this.config.isPrevalidate());
        }

        FacesComponentUtility
            .addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_DISPLAY_NAME_CELL + "'>");
        PropertyRenderingUtility.addPropertyDisplayName(parent, propertyDefinitionDynamic, propertySimple, this.config
            .isReadOnly());
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_ENABLED_CELL + "'>");
        if (!propertyDefinitionDynamic.isRequired())
            PropertyRenderingUtility.addUnsetControl(parent, propertyDefinitionDynamic.isRequired(),
                propertyDefinitionDynamic.isReadOnly(), propertySimple, this.config.getListIndex(), input, this.isGroup,
                this.config.isReadOnly(), this.config.isFullyEditable());
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_VALUE_CELL + "'>");
        addPropertySimpleValue(parent, input, propertyValueExpression);
        FacesComponentUtility.addVerbatimText(parent, "<br/>");
        if (input != null)
            PropertyRenderingUtility.addMessageComponentForInput(parent, input);
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_DESCRIPTION_CELL + "'>");
        PropertyRenderingUtility.addPropertyDescription(parent, propertyDefinitionDynamic);
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "</tr>");

        addDebug(parent, false, ".addSimpleProperty()");
    }

    private void addSimpleProperty(UIComponent parent, PropertyDefinitionSimple propertyDefinitionSimple,
        String rowStyleClass, TemplateEngine templateEngine) {
        addDebug(parent, true, ".addSimpleProperty()");

        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr class='" + rowStyleClass + "'>");

        PropertySimple propertySimple = this.propertyMap.getSimple(propertyDefinitionSimple.getName());
        ValueExpression propertyValueExpression = createPropertyValueExpression(propertySimple.getName(),
            this.valueExpressionFormat);

        UIInput input = null;
        if (!this.isGroup || (propertySimple.getOverride() != null && propertySimple.getOverride()))
            // We need to create the input component ahead of when we need to add it to the component tree, since we
            // need to know the input component's id in order to render the unset control.
            input = PropertyRenderingUtility.createInputForSimpleProperty(propertyDefinitionSimple, propertySimple,
                propertyValueExpression, getListIndex(), this.isGroup, this.config.isReadOnly(), this.config
                    .isFullyEditable(), this.config.isPrevalidate(), templateEngine);

        FacesComponentUtility
            .addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_DISPLAY_NAME_CELL + "'>");
        PropertyRenderingUtility.addPropertyDisplayName(parent, propertyDefinitionSimple, propertySimple, this.config
            .isReadOnly());
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        // TODO: Get rid of the override checkbox once the group plugin config has been converted over to the new
        //       group config GUI.
        if (this.config.isGroup()) {
            FacesComponentUtility.addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_ENABLED_CELL + "'>");
            addPropertyOverrideControl(parent, propertyDefinitionSimple, input);
            FacesComponentUtility.addVerbatimText(parent, "</td>");
        }

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_ENABLED_CELL + "'>");
        if (!propertyDefinitionSimple.isRequired())
            PropertyRenderingUtility.addUnsetControl(parent, propertyDefinitionSimple.isRequired(),
                propertyDefinitionSimple.isReadOnly(), propertySimple, this.config.getListIndex(), input, this.isGroup,
                this.config.isReadOnly(), this.config.isFullyEditable());
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_VALUE_CELL + "'>");
        addPropertySimpleValue(parent, input, propertyValueExpression);
        FacesComponentUtility.addVerbatimText(parent, "<br/>");
        if (input != null)
            PropertyRenderingUtility.addMessageComponentForInput(parent, input);
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + CssStyleClasses.PROPERTY_DESCRIPTION_CELL + "'>");
        PropertyRenderingUtility.addPropertyDescription(parent, propertyDefinitionSimple);
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "</tr>");

        addDebug(parent, false, ".addSimpleProperty()");
    }

    private void addPropertySimpleValue(UIComponent parent, @Nullable UIInput input,
        ValueExpression propertyValueExpression) {
        if (!this.isGroup) {
            parent.getChildren().add(input);
            return;
        }

        HtmlPanelGrid panelGrid = FacesComponentUtility.addPanelGrid(parent, null, 2, CssStyleClasses.BUTTONS_TABLE);
        panelGrid.setColumnClasses("group-property-value-cell, group-property-members-icon-cell");
        if (input != null)
            panelGrid.getChildren().add(input);
        else
            FacesComponentUtility
                .addOutputText(panelGrid, null, "Member Values Differ", VALUES_DIFFER_TEXT_STYLE_CLASS);

        // Add the Members button which will display the "drill-down" modal panel.
        HtmlAjaxCommandLink ajaxCommandLink = FacesComponentUtility.createComponent(HtmlAjaxCommandLink.class);
        panelGrid.getChildren().add(ajaxCommandLink);
        ajaxCommandLink.setOncomplete("Richfaces.showModalPanel('"
            + this.memberValuesModalPanel.getClientId(FacesContext.getCurrentInstance()) + "');");
        //ajaxCommandLink.setReRender("rhq_propSet");
        String verb = this.config.isReadOnly() ? "View" : "Edit";
        String tooltip = verb + " Member Values";
        ajaxCommandLink.setTitle(tooltip);
        FacesComponentUtility.addParameter(ajaxCommandLink, null, "propertyExpressionString", propertyValueExpression
            .getExpressionString());
        FacesComponentUtility.addParameter(ajaxCommandLink, null, "refresh", ConfigRenderer.PROPERTY_SET_COMPONENT_ID);
        FacesComponentUtility.addGraphicImage(ajaxCommandLink, null, "/images/icn_member_configs.png", null);
    }

    private void addOpenMapMemberProperty(HtmlPanelGroup parent, PropertyDefinitionMap propertyDefinitionMap,
        PropertySimple propertySimple, String rowStyleClass) {
        addDebug(parent, true, ".addOpenMapMemberProperty()");
        String mapName = ((PropertyMap) this.propertyMap).getName();
        String memberName = propertySimple.getName();

        NullComponent wrapper = new NullComponent();
        parent.getChildren().add(wrapper);
        String wrapperId = PropertyIdGeneratorUtility.getIdentifier(propertySimple, getListIndex(), PANEL_ID_SUFFIX);
        wrapper.setId(wrapperId);

        FacesComponentUtility.addVerbatimText(wrapper, "\n\n<tr class='" + rowStyleClass + "'>");

        FacesComponentUtility.addVerbatimText(wrapper, "<td class='" + OPENMAP_PROPERTY_DISPLAY_NAME_CELL_STYLE_CLASS
            + "'>");
        FacesComponentUtility.addOutputText(wrapper, this.config, propertySimple.getName(),
            CssStyleClasses.PROPERTY_DISPLAY_NAME_TEXT);
        FacesComponentUtility.addVerbatimText(wrapper, "</td>");

        FacesComponentUtility.addVerbatimText(wrapper, "<td class='" + OPENMAP_PROPERTY_VALUE_CELL_STYLE_CLASS + "'>");
        String expressionString = String.format(valueExpressionFormat, propertySimple.getName());
        ValueExpression valueExpression = FacesExpressionUtility.createValueExpression(expressionString, String.class);
        UIInput input = null;
        if (!this.isGroup || propertySimple.getOverride() != null && propertySimple.getOverride())
            input = PropertyRenderingUtility.createInputForSimpleProperty(propertySimple, valueExpression, this.config
                .isReadOnly());
        addPropertySimpleValue(wrapper, input, valueExpression);
        FacesComponentUtility.addVerbatimText(wrapper, "</td>");

        if (!isReadOnly(propertyDefinitionMap)) {
            // add Actions column w/ delete button
            FacesComponentUtility.addVerbatimText(wrapper, "<td class='" + OPENMAP_PROPERTY_ACTIONS_CELL_STYLE_CLASS
                + "'>");
            HtmlCommandLink deleteLink = FacesComponentUtility.createComponent(HtmlCommandLink.class);
            wrapper.getChildren().add(deleteLink);

            deleteLink.setTitle(DELETE_MAP_BUTTON_TITLE);
            deleteLink.setImmediate(true); // skip validation (we only want to validate upon Save)

            // See http://jira.rhq-project.org/browse/RHQ-1792 for more info on why the below line is commented out.
            //deleteLink.setOnclick("return prepareInputsForSubmission(this.form);");

            FacesComponentUtility.addParameter(deleteLink, this.config, RequestParameterNameConstants.FUNCTION_PARAM,
                DELETE_OPEN_MAP_MEMBER_PROPERTY_FUNCTION);
            FacesComponentUtility.addParameter(deleteLink, this.config, RequestParameterNameConstants.MAP_NAME_PARAM,
                mapName);
            FacesComponentUtility.addParameter(deleteLink, this.config,
                RequestParameterNameConstants.MEMBER_NAME_PARAM, memberName);

            FacesComponentUtility.addButton(deleteLink, DELETE_MAP_BUTTON_LABEL, CssStyleClasses.BUTTON_SMALL);
            FacesComponentUtility.addVerbatimText(wrapper, "</td>");
        }

        FacesComponentUtility.addVerbatimText(wrapper, "</tr>");
        addDebug(wrapper, false, ".addOpenMapMemberProperty()");
    }

    /**
     * Adds a single row property
     */
    private void addListProperty(UIComponent parent, PropertyDefinitionList listPropertyDefinition, String rowStyleClass) {
        addDebug(parent, true, ".addListProperty()");
        if (!this.topLevel) {
            throw new IllegalStateException("Lists are only supported at the top level of a Configuration.");
        }

        PropertyList listProperty = this.propertyMap.getList(listPropertyDefinition.getName());

        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr class='" + rowStyleClass + "'>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + LIST_PROPERTY_DISPLAY_NAME_CELL_STYLE_CLASS
            + "'>");
        PropertyRenderingUtility.addPropertyDisplayName(parent, listPropertyDefinition, listProperty, this.config
            .isReadOnly());
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + LIST_PROPERTY_ENABLED_CELL_STYLE_CLASS + "' />");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + LIST_PROPERTY_VALUE_CELL_STYLE_CLASS + "' />");

        FacesComponentUtility
            .addVerbatimText(parent, "<td class='" + LIST_PROPERTY_DESCRIPTION_CELL_STYLE_CLASS + "'>");
        PropertyRenderingUtility.addPropertyDescription(parent, listPropertyDefinition);
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "</tr>");

        PropertyDefinition listMemberPropertyDefinition = listPropertyDefinition.getMemberDefinition();
        if (listMemberPropertyDefinition instanceof PropertyDefinitionMap) {
            if (!this.isGroup) {
                addListMemberMapProperties(parent, listProperty, (PropertyDefinitionMap) listMemberPropertyDefinition,
                    rowStyleClass);
            } else {
                addErrorRow(
                    parent,
                    "Viewing/editing of lists of"
                        + " properties is not yet supported for group Configurations."
                        + " To view/edit this property for group members, please go to the Configure>Current tab for each member separately.",
                    rowStyleClass);
            }
        } else {
            addErrorRow(parent, "Viewing/editing of lists of "
                + listMemberPropertyDefinition.getClass().getSimpleName()
                + " properties is not yet supported. Here's the list's toString() value for now: "
                + ((listProperty != null) ? listProperty.getList() : null), rowStyleClass);
        }

        addDebug(parent, false, ".addListProperty()");
    }

    private void addListMemberMapProperties(UIComponent parent, PropertyList listProperty,
        PropertyDefinitionMap listMemberPropertyDefinition, String rowStyleClass) {
        addDebug(parent, true, ".addListMemberMapProperties()");
        PropertyDefinitionMap listMemberMapPropertyDefinition = listMemberPropertyDefinition;
        validateMapDefinition(listMemberMapPropertyDefinition);

        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr class='" + rowStyleClass + "'>\n");
        FacesComponentUtility.addVerbatimText(parent, "<td colspan='4' class='"
            + LIST_PROPERTY_CHILDREN_CELL_STYLE_CLASS + "'>");

        Collection<PropertyDefinition> mapSummaryPropertyDefinitions = listMemberMapPropertyDefinition
            .getSummaryPropertyDefinitions();

        // Start the "List of maps" table
        FacesComponentUtility.addVerbatimText(parent, "\n<!-- start list of maps -->\n");
        FacesComponentUtility.addVerbatimText(parent, "<table class='" + PROPERTY_MAP_SUMMARY_TABLE_STYLE_CLASS
            + "'>\n");
        FacesComponentUtility.addVerbatimText(parent, "<tbody>\n");

        // Add the row for the headers
        FacesComponentUtility.addVerbatimText(parent, "<tr>\n");

        // Add column header cells...
        for (PropertyDefinition summaryPropertyDefinition : listMemberMapPropertyDefinition
            .getSummaryPropertyDefinitions()) {
            // We can safely cast here, because we already validated the map member definitions.
            PropertyDefinitionSimple mapMemberSimplePropertyDefinition = (PropertyDefinitionSimple) listMemberMapPropertyDefinition
                .get(summaryPropertyDefinition.getName());
            FacesComponentUtility.addVerbatimText(parent, "<th class='"
                + PROPERTY_MAP_SUMMARY_DATA_HEADER_CELL_STYLE_CLASS + "'>");
            FacesComponentUtility.addOutputText(parent, this.config,
                mapMemberSimplePropertyDefinition.getDisplayName(), PROPERTY_MAP_SUMMARY_HEADER_TEXT_STYLE_CLASS);
            FacesComponentUtility.addVerbatimText(parent, "</th>\n");
        }

        // add the "Actions" header
        FacesComponentUtility.addVerbatimText(parent, "<th class='" + PROPERTY_MAP_SUMMARY_DATA_HEADER_CELL_STYLE_CLASS
            + "'>");
        FacesComponentUtility.addOutputText(parent, this.config, "Actions",
            PROPERTY_MAP_SUMMARY_HEADER_TEXT_STYLE_CLASS);
        FacesComponentUtility.addVerbatimText(parent, "</th>\n");
        FacesComponentUtility.addVerbatimText(parent, "</tr>\n");

        // Add body cells...
        // NOTE: We only display the View button if the config component is read-only. If the list is read-only, members
        //       cannot be added or deleted, but existing members can still be edited.
        String viewEditButtonLabel = (this.config.isReadOnly()) ? VIEW_MAP_BUTTON_LABEL : EDIT_MAP_BUTTON_LABEL;
        String viewEditButtonTitle = (this.config.isReadOnly()) ? VIEW_MAP_BUTTON_TITLE : EDIT_MAP_BUTTON_TITLE;
        for (int index = 0; index < listProperty.getList().size(); index++) {
            addListMemberMapProperty(parent, listProperty, listMemberMapPropertyDefinition, viewEditButtonLabel,
                viewEditButtonTitle, index);
        }
        //HtmlPanelGrid buttonsPanelGrid = FacesComponentUtility.addPanelGrid(parent, this.configurationUIBean, 1, BUTTONS_TABLE_STYLE_CLASS);

        if (!isReadOnly(listMemberMapPropertyDefinition.getParentPropertyListDefinition())) {
            FacesComponentUtility.addVerbatimText(parent, "<tr>\n");
            FacesComponentUtility.addVerbatimText(parent, "<td colspan='" + (mapSummaryPropertyDefinitions.size() + 1)
                + "' class='" + PROPERTY_MAP_SUMMARY_BUTTON_FOOTER_STYLE_CLASS + "'>");

            // add-new button
            HtmlCommandLink addNewLink = FacesComponentUtility.addCommandLink(parent, this.config);
            addNewLink.setTitle(ADD_NEW_MAP_BUTTON_TITLE);
            MethodExpression actionExpression = FacesExpressionUtility.createMethodExpression(
                "#{ConfigHelperUIBean.addNewMap}", String.class, new Class[0]);
            addNewLink.setActionExpression(actionExpression);
            FacesComponentUtility.addParameter(addNewLink, this.config, RequestParameterNameConstants.LIST_NAME_PARAM,
                listProperty.getName());
            FacesComponentUtility.addParameter(addNewLink, this.config, RequestParameterNameConstants.LIST_INDEX_PARAM,
                String.valueOf(listProperty.getList().size()));
            FacesComponentUtility.addButton(addNewLink, ADD_NEW_MAP_BUTTON_LABEL, CssStyleClasses.BUTTON_SMALL);
            FacesComponentUtility.addVerbatimText(parent, "</td></tr>\n");
        }

        FacesComponentUtility.addVerbatimText(parent, "</tbody></table>\n\n");
        FacesComponentUtility.addVerbatimText(parent, "<!-- end List of Maps -->\n\n");

        FacesComponentUtility.addVerbatimText(parent, "</td></tr>");
        addDebug(parent, false, ".addListMemberMapProperties()");
    }

    private void addListMemberMapProperty(UIComponent parent, PropertyList listProperty,
        PropertyDefinitionMap listMemberMapPropertyDefinition, String viewEditButtonLabel, String viewEditButtonTitle,
        int index) {
        addDebug(parent, true, ".addListMemberMapProperty()");
        Property listMemberProperty = listProperty.getList().get(index);
        String listName = listProperty.getName();
        if (!(listMemberProperty instanceof PropertyMap)) {
            throw new IllegalStateException("Property '" + listName
                + "' is defined as a list of maps but contains one or more non-map members.");
        }

        PropertyMap listMemberPropertyMap = (PropertyMap) listMemberProperty;

        NullComponent wrapper = new NullComponent();
        parent.getChildren().add(wrapper);
        String wrapperId = PropertyIdGeneratorUtility.getIdentifier(listMemberProperty, index, PANEL_ID_SUFFIX);
        wrapper.setId(wrapperId);

        FacesComponentUtility.addVerbatimText(wrapper, "<tr>\n");

        // add the simple property data cells
        addPropertyMapSummaryDataCells(wrapper, listMemberMapPropertyDefinition, listMemberPropertyMap);

        int numberOfButtons = 1;
        if (!this.config.isReadOnly()) {
            numberOfButtons++;
        }

        // add the Actions cell
        FacesComponentUtility.addVerbatimText(wrapper, "<td class='" + PROPERTY_MAP_SUMMARY_BUTTONS_CELL_STYLE_CLASS
            + "'>");
        HtmlPanelGrid buttonsPanelGrid = FacesComponentUtility.addPanelGrid(wrapper, this.config, numberOfButtons,
            BUTTONS_TABLE_STYLE_CLASS);

        // view/edit button
        HtmlCommandLink viewEditLink = FacesComponentUtility.addCommandLink(buttonsPanelGrid, this.config);
        viewEditLink.setTitle(viewEditButtonTitle);
        MethodExpression actionExpression = FacesExpressionUtility.createMethodExpression(
            "#{ConfigHelperUIBean.accessMap}", String.class, new Class[0]);
        viewEditLink.setActionExpression(actionExpression);
        FacesComponentUtility.addParameter(viewEditLink, this.config, RequestParameterNameConstants.LIST_NAME_PARAM,
            listName);
        String listIndex = String.valueOf(index);
        FacesComponentUtility.addParameter(viewEditLink, this.config, RequestParameterNameConstants.LIST_INDEX_PARAM,
            listIndex);
        int configId = this.config.getConfiguration().getId();
        FacesComponentUtility.addParameter(viewEditLink, this.config, RequestParameterNameConstants.CONFIG_ID_PARAM,
            String.valueOf(configId));
        FacesComponentUtility.addButton(viewEditLink, viewEditButtonLabel, CssStyleClasses.BUTTON_SMALL);

        if (!isReadOnly(listMemberMapPropertyDefinition.getParentPropertyListDefinition())) {
            // delete button
            HtmlCommandLink deleteLink = FacesComponentUtility.addCommandLink(buttonsPanelGrid, this.config);
            deleteLink.setTitle(DELETE_MAP_BUTTON_TITLE);
            //deleteLink.setImmediate(true);

            FacesComponentUtility.addParameter(deleteLink, this.config, RequestParameterNameConstants.FUNCTION_PARAM,
                DELETE_LIST_MEMBER_PROPERTY_FUNCTION);
            FacesComponentUtility.addParameter(deleteLink, this.config, RequestParameterNameConstants.LIST_NAME_PARAM,
                listName);
            UIParameter listIndexParam = FacesComponentUtility.addParameter(deleteLink, this.config,
                RequestParameterNameConstants.LIST_INDEX_PARAM, listIndex);
            String paramId = PropertyIdGeneratorUtility.getIdentifier(listMemberProperty, index, PARAM_ID_SUFFIX);
            listIndexParam.setId(paramId);

            FacesComponentUtility.addButton(deleteLink, DELETE_MAP_BUTTON_LABEL, CssStyleClasses.BUTTON_SMALL);
        }

        FacesComponentUtility.addVerbatimText(wrapper, "</td>\n");

        FacesComponentUtility.addVerbatimText(wrapper, "</tr>\n");
        addDebug(parent, false, ".addListMemberMapProperty()");
    }

    private void addPropertyMapSummaryDataCells(UIComponent parent,
        PropertyDefinitionMap listMemberMapPropertyDefinition, PropertyMap listMemberMapProperty) {
        for (PropertyDefinition summaryPropertyDefinition : listMemberMapPropertyDefinition
            .getSummaryPropertyDefinitions()) {
            Property mapMemberProperty = listMemberMapProperty.get(summaryPropertyDefinition.getName());
            if (!(mapMemberProperty instanceof PropertySimple)) {
                throw new IllegalStateException("Property '" + mapMemberProperty.getName()
                    + "' is defined as a map of simples but contains one or more non-simple members.");
            }

            PropertySimple mapMemberSimpleProperty = (PropertySimple) mapMemberProperty;
            FacesComponentUtility.addVerbatimText(parent, "<td class='" + PROPERTY_MAP_SUMMARY_DATA_CELL_STYLE_CLASS
                + "'>");
            if (mapMemberSimpleProperty.getStringValue() == null) {
                FacesComponentUtility.addOutputText(parent, this.config, "not set",
                    CssStyleClasses.REQUIRED_MARKER_TEXT);
            } else {
                FacesComponentUtility.addOutputText(parent, this.config, mapMemberSimpleProperty.getStringValue(),
                    PROPERTY_MAP_SUMMARY_DATA_TEXT_STYLE_CLASS);
            }

            FacesComponentUtility.addVerbatimText(parent, "</td>\n");
        }
    }

    private void validateMapDefinition(PropertyDefinitionMap propertyDefinitionMap) {
        for (PropertyDefinition mapMemberPropertyDefinition : propertyDefinitionMap.getPropertyDefinitions().values()) {
            if (!(mapMemberPropertyDefinition instanceof PropertyDefinitionSimple)) {
                throw new IllegalStateException("Only maps of simple properties are supported.");
            }
        }
    }

    private void addMapProperty(UIComponent parent, PropertyDefinitionMap propertyDefinitionMap, String rowStyleClass) {
        if (!this.topLevel) {
            throw new IllegalStateException(
                "Maps are only supported at the top level of a Configuration or within a top-level List.");
        }

        validateMapDefinition(propertyDefinitionMap);

        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr class='" + rowStyleClass + "'>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + LIST_PROPERTY_DISPLAY_NAME_CELL_STYLE_CLASS
            + "'>");
        PropertyRenderingUtility.addPropertyDisplayName(parent, propertyDefinitionMap, this.propertyMap
            .get(propertyDefinitionMap.getName()), this.config.isReadOnly());
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + LIST_PROPERTY_ENABLED_CELL_STYLE_CLASS + "' />");

        FacesComponentUtility.addVerbatimText(parent, "<td class='" + LIST_PROPERTY_VALUE_CELL_STYLE_CLASS + "' />");

        FacesComponentUtility
            .addVerbatimText(parent, "<td class='" + LIST_PROPERTY_DESCRIPTION_CELL_STYLE_CLASS + "'>");
        PropertyRenderingUtility.addPropertyDescription(parent, propertyDefinitionMap);
        FacesComponentUtility.addVerbatimText(parent, "</td>");

        FacesComponentUtility.addVerbatimText(parent, "</tr>");

        addMapMemberProperties(parent, propertyDefinitionMap.getName(), rowStyleClass);
    }

    private void addMapMemberProperties(UIComponent parent, String mapName, String rowStyleClass) {
        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr class='" + rowStyleClass + "'>\n");
        FacesComponentUtility.addVerbatimText(parent, "<td colspan='4' class='"
            + LIST_PROPERTY_CHILDREN_CELL_STYLE_CLASS + "'>");
        HtmlPanelGrid spacerPanelGrid = FacesComponentUtility.addPanelGrid(parent, this.config, 2,
            FacesComponentUtility.NO_STYLE_CLASS);
        spacerPanelGrid.setWidth("100%");
        spacerPanelGrid.setColumnClasses(NESTED_PROPERTIES_TABLE_INDENT_CELL_STYLE_CLASS + ","
            + FacesComponentUtility.NO_STYLE_CLASS);
        FacesComponentUtility.addBlockPanel(spacerPanelGrid, this.config, FacesComponentUtility.NO_STYLE_CLASS);
        AbstractPropertyBagUIComponentTreeFactory propertyListUIComponentTreeFactory = new MapInConfigurationUIComponentTreeFactory(
            this.config, mapName);
        spacerPanelGrid.getChildren().add(
            propertyListUIComponentTreeFactory.createUIComponentTree(CssStyleClasses.ROW_EVEN));
        parent.getChildren().add(spacerPanelGrid);
        FacesComponentUtility.addVerbatimText(parent, "</td></tr>\n");
    }

    private void addNewOpenMapMemberPropertyRow(UIComponent parent, PropertyDefinitionMap propertyDefinitionMap) {
        if (!isReadOnly(propertyDefinitionMap)) {
            FacesComponentUtility.addVerbatimText(parent, "\n\n<tr><td colspan='3' class='"
                + PROPERTY_MAP_SUMMARY_BUTTON_FOOTER_STYLE_CLASS + "'>");

            // add-new button
            HtmlCommandLink addNewLink = FacesComponentUtility.addCommandLink(parent, this.config);
            addNewLink.setTitle(ADD_NEW_MAP_BUTTON_TITLE);

            //addNewLink.setImmediate(true); // skip validation (we only want to validate upon Save)
            MethodExpression actionExpression = FacesExpressionUtility.createMethodExpression(
                "#{ConfigHelperUIBean.addNewOpenMapMemberProperty}", String.class, new Class[0]);
            addNewLink.setActionExpression(actionExpression);
            FacesComponentUtility.addParameter(addNewLink, this.config, RequestParameterNameConstants.MAP_NAME_PARAM,
                propertyDefinitionMap.getName());
            FacesComponentUtility.addButton(addNewLink, ADD_NEW_MAP_BUTTON_LABEL, CssStyleClasses.BUTTON_SMALL);
            FacesComponentUtility.addVerbatimText(parent, "</td></tr>");
        }
    }

    private void addErrorRow(UIComponent parent, String errorMsg, String rowStyleClass) {
        addDebug(parent, true, ".addErrorRow()");
        FacesComponentUtility.addVerbatimText(parent, "\n\n<tr class='" + rowStyleClass + "'><td colspan='4' class='"
            + PROPERTY_ERROR_CELL_STYLE_CLASS + "'><h4>" + errorMsg + "</h4></td></tr>");
        addDebug(parent, false, ".addErrorRow()");
    }

    @NotNull
    private HtmlSelectBooleanCheckbox createInputOverrideForSimpleProperty(
        PropertyDefinitionSimple propertyDefinitionSimple) {
        HtmlSelectBooleanCheckbox input = FacesComponentUtility.createComponent(HtmlSelectBooleanCheckbox.class,
            this.config);

        // Find the actual property corresponding to this property def, and use that to create the component id.
        Property property = this.propertyMap.get(propertyDefinitionSimple.getName());
        if (property != null) {
            // add suffix to prevent collision with value input identifier for property
            String propertyId = PropertyIdGeneratorUtility.getIdentifier(property, getListIndex(), "override");
            input.setId(propertyId);

            Boolean shouldOverride = ((PropertySimple) property).getOverride();
            FacesComponentUtility.setOverride(input, (shouldOverride != null && shouldOverride));
        }

        setInputOverrideExpression(input, propertyDefinitionSimple.getName());

        return input;
    }

    private void addPropertyOverrideControl(UIComponent parent, PropertyDefinitionSimple propertyDefinitionSimple,
        UIInput valueInput) {
        HtmlSelectBooleanCheckbox overrideCheckbox = createInputOverrideForSimpleProperty(propertyDefinitionSimple);
        parent.getChildren().add(overrideCheckbox);
        overrideCheckbox.setValue(isOverride(propertyDefinitionSimple));
        if (isReadOnly(propertyDefinitionSimple)) {
            FacesComponentUtility.setDisabled(overrideCheckbox, true);
        }

        /* TODO: use unsetCheckbox and valueInput to implement the following logic against dom elements in javascript:
         *
         * if override input is not checked, disable unsetCheckbox and dissble valueInput dom elements   * if override
         * input is checked    , enabled unsetCheckbox   ** only cascade enable valueInput dom elements if the
         * unsetCheckbox is NOT unset
         */
        StringBuilder onclick = new StringBuilder();
        for (String valueInputHtmlDomReference : PropertyRenderingUtility.getHtmlDomReferences(valueInput)) {
            onclick.append("setInputOverride(").append(valueInputHtmlDomReference).append(", this.checked);");
        }

        overrideCheckbox.setOnclick(onclick.toString());
    }

    /**
     * Binds the value of the specified UIInput to an EL expression corresponding to the Configuration property's
     * override attribute with the specified name.
     */
    @SuppressWarnings("deprecation")
    private void setInputOverrideExpression(HtmlSelectBooleanCheckbox input, String propertyName) {
        // e.g.: #{configuration.simpleProperties['useJavaContext'].override}
        String expression = String.format(this.overrideExpressionFormat, propertyName);
        ValueExpression valueExpression = FacesExpressionUtility.createValueExpression(expression, Boolean.class);
        input.setValueExpression("value", valueExpression);
    }

    private Boolean isOverride(PropertyDefinition propertyDefinition) {
        if (!(propertyDefinition instanceof PropertyDefinitionSimple)) {
            return false;
        }

        Property property = this.propertyMap.get(propertyDefinition.getName());
        if (property == null) {
            return false;
        }

        Boolean override = ((PropertySimple) property).getOverride();
        return ((override != null) && override);
    }

    private boolean isReadOnly(PropertyDefinition propertyDefinition) {
        // A fully editable config overrides any other means of setting read only.
        return (!this.config.isFullyEditable() && (this.config.isReadOnly() || (propertyDefinition.isReadOnly() && !isInvalidRequiredProperty(propertyDefinition))));
    }

    private boolean isInvalidRequiredProperty(PropertyDefinition propertyDefinition) {
        boolean isInvalidRequiredProperty = false;

        if ((propertyDefinition instanceof PropertyDefinitionSimple) && propertyDefinition.isRequired()) {
            PropertySimple propertySimple = this.propertyMap.getSimple(propertyDefinition.getName());
            String errorMessage = propertySimple.getErrorMessage();

            if ((null == propertySimple.getStringValue()) || "".equals(propertySimple.getStringValue())
                || ((null != errorMessage) && (!"".equals(errorMessage.trim())))) {
                // Required properties with no value, or an invalid value (assumed if we see an error message) should never
                // be set to read-only, otherwise the user will have no way to give the property a value and thereby
                // get things to a valid state.
                isInvalidRequiredProperty = true;
            }
        }

        return isInvalidRequiredProperty;
    }

    /**
     * simple method that will add debugging HTML comments so that I can figure out which part of the HTML is generated
     * by which part of this class
     *
     * @param component  the component this comment should be added
     * @param start      true if this is the "START" comment, false if it is the "END" comment
     * @param methodName the name of the method this is calling from
     */
    private void addDebug(UIComponent component, boolean start, String methodName) {
        if (LOG.isDebugEnabled()) {
            StringBuilder msg = new StringBuilder("\n<!--");
            msg.append(start ? " START " : " END ");
            msg.append(this.getClass().getSimpleName());
            msg.append(methodName);
            msg.append(" -->\n ");
            FacesComponentUtility.addVerbatimText(component, msg);
        }
    }

    /**
     * Binds the value of the specified UIInput to an EL expression corresponding to the Configuration property with the
     * specified name.
     */
    @SuppressWarnings( { "JavaDoc" })
    private static ValueExpression createPropertyValueExpression(String propertyName, String valueExpressionFormat) {
        // e.g.: #{configuration.simpleProperties['useJavaContext'].stringValue}
        String expression = String.format(valueExpressionFormat, propertyName);
        ValueExpression valueExpression = FacesExpressionUtility.createValueExpression(expression, String.class);
        return valueExpression;
    }

}