edu.ku.brc.af.ui.forms.validation.FormValidator.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.af.ui.forms.validation.FormValidator.java

Source

/* Copyright (C) 2015, University of Kansas Center for Research
 * 
 * Specify Software Project, specify@ku.edu, Biodiversity Institute,
 * 1345 Jayhawk Boulevard, Lawrence, Kansas, 66045, USA
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU 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 edu.ku.brc.af.ui.forms.validation;

import static edu.ku.brc.ui.UIHelper.setControlSize;
import static org.apache.commons.lang.StringUtils.isNotEmpty;

import java.awt.Component;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.text.JTextComponent;

import org.apache.commons.jexl.Expression;
import org.apache.commons.jexl.JexlContext;
import org.apache.commons.jexl.JexlHelper;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import edu.ku.brc.ui.IconManager;
import edu.ku.brc.ui.UIHelper;
import edu.ku.brc.ui.UIRegistry;

/**
 * This class manages all the validators for a single form. One or all the UI components
 * can be validated and the form can have its own set of validation rukes for enabling or
 * disabling any of the controls depending on the state of other "named" controls.
 * Both UI Controls and thier labels are registered by name. When a component is disabled its
 * label can also be disabled.
 *
 * @code_status Beta
 *
 * @author rods
 *
 */
@SuppressWarnings("serial")
public class FormValidator implements ValidationListener, DataChangeListener {
    private static final Logger log = Logger.getLogger(FormValidator.class);

    public enum EnableType {
        ValidItems, ValidAndChangedItems, ValidNotNew
    }

    private String name = ""; // Optional for debugging

    // Form validation
    protected JexlContext jc = null;
    protected Expression exp = null;

    protected FormValidator parent;
    protected Vector<FormValidator> kids = new Vector<FormValidator>();

    protected List<FormValidationRuleIFace> formRules = new Vector<FormValidationRuleIFace>();
    protected Hashtable<String, DataChangeNotifier> dcNotifiers = new Hashtable<String, DataChangeNotifier>();
    protected List<UIValidator> validators = new Vector<UIValidator>();

    protected Hashtable<String, Component> fields = new Hashtable<String, Component>();
    protected Hashtable<String, JLabel> labels = new Hashtable<String, JLabel>();

    protected Hashtable<EnableType, Vector<Component>> enableHash = new Hashtable<EnableType, Vector<Component>>();
    protected JComponent saveComp = null;
    protected EnableType saveEnableType = EnableType.ValidAndChangedItems;

    protected boolean isTopLevel = false;
    protected boolean enabled = false;
    protected boolean hasChanged = false;
    protected boolean isNewObj = false;
    protected boolean isRequired = true;

    protected UIValidatable.ErrorType formValidationState = UIValidatable.ErrorType.Valid;
    protected UIValidatable.ErrorType kidsValState = UIValidatable.ErrorType.Valid;

    protected boolean processRulesAreOK = true;

    protected boolean ignoreValidationNotifications = false;
    protected boolean okToDataChangeNotification = true;

    protected JButton validationInfoBtn = null;

    // This is a list of listeners for when any data changes in the form
    protected List<DataChangeListener> dcListeners = new ArrayList<DataChangeListener>();
    protected List<ValidationListener> valListeners = new ArrayList<ValidationListener>();

    /**
     * Constructor.
     * @param parent the parent validator or null if top level
     */
    public FormValidator(final FormValidator parent) {
        this.parent = parent;

        jc = JexlHelper.createContext();
        addRuleObjectMapping("form", this);

        for (EnableType type : EnableType.values()) {
            enableHash.put(type, new Vector<Component>());
        }
    }

    /**
     * @return the parent
     */
    public FormValidator getParent() {
        return parent;
    }

    /**
     * @param saveComp
     * @param saveEnableType
     */
    public void setSaveComp(final JComponent saveComp, final EnableType saveEnableType) {
        this.saveComp = saveComp;
        this.saveEnableType = saveEnableType;
    }

    /**
     * @param parent the parent to set
     */
    public void setParent(FormValidator parent) {
        this.parent = parent;
    }

    /**
     * @return the validators
     */
    protected List<UIValidator> getValidators() {
        return validators;
    }

    /**
     * @return the isRequired
     */
    public boolean isRequired() {
        return isRequired;
    }

    /**
     * @param isRequired the isRequired to set
     */
    public void setRequired(boolean isRequired) {
        this.isRequired = isRequired;
    }

    /**
     * @param isNewObj the isNewObj to set
     */
    public void setNewObj(boolean isNewObj) {
        this.isNewObj = isNewObj;
        enableUIItems(getState() == UIValidatable.ErrorType.Valid && !isNewObj, EnableType.ValidItems);
        enableUIItems(getState() == UIValidatable.ErrorType.Valid && !isNewObj, EnableType.ValidNotNew);
        updateValidationBtnUIState();
    }

    /**
     * Adds a child validator.
     * @param val the validator
     */
    public void add(final FormValidator val) {
        //log.debug("Add Kid["+val.getName()+"] to "+name);
        kids.add(val);
    }

    /**
     * Removes a child validator.
     * @param val the validator.
     */
    public void remove(final FormValidator val) {
        //log.debug("Remove Kid["+val.getName()+"] from "+name);
        kids.remove(val);
    }

    /**
     * emoves all the children validators.
     */
    public void clearKids() {
        //log.debug("Parent "+name+" clearing kids:   "+kids.size());
        kids.clear();
    }

    /**
     * (Protected so other objects in the class can access it)
     * @return the kids
     */
    protected Vector<FormValidator> getKids() {
        return kids;
    }

    /**
     * @param comp
     * @param type
     */
    public void addEnableItem(final Component comp, final EnableType type) {

        Vector<Component> list = enableHash.get(type);
        list.add(comp);
        comp.setEnabled(false);
    }

    /**
     * @param comp
     * @param type
     */
    public void removeEnabledItem(final Component comp, final EnableType type) {
        Vector<Component> list = enableHash.get(type);
        list.remove(comp);
    }

    /**
     * Returns whether the form is valid
     * @return Returns whether it is alright to enable the OK btn
     */
    public boolean isFormValid() {
        return isFormValid(true);
    }

    /**
     * Returns whether the form is valid
     * @param checkKids whether to check the kids
     * @return Returns whether it is alright to enable the OK btn
     */
    protected boolean isFormValid(final boolean checkKids) {
        //log.debugprintln("isFormValid - Name: "+name+"  enabled "+enabled+"  hasChanged "+hasChanged+"  isNewObj "+isNewObj);

        if (checkKids) {
            for (FormValidator kid : kids) {
                if (!kid.isFormValid()) {
                    return false;
                }
            }
        }
        //log.debug("isFormValid - "+formValidationState);
        return formValidationState == UIValidatable.ErrorType.Valid && processRulesAreOK;
    }

    /**
     * Manually sets the state of the validator.
     * @param formValidationState the new state
     */
    public void setFormValidationState(UIValidatable.ErrorType formValidationState) {
        if (enabled) {
            if (this.formValidationState != formValidationState) {
                updateValidationBtnUIState();
            }
            this.formValidationState = formValidationState;
        }
    }

    /**
     * Returns the state of the form, which really means... return the worst
     * "state" that was found from all the validators.
     * @return the state of the form
     */
    public UIValidatable.ErrorType getState() {
        //UIValidatable.ErrorType val = !hasChanged && isNewObj && isFirstTime ? UIValidatable.ErrorType.Valid : processRulesAreOK ? formValidationState : UIValidatable.ErrorType.Error;
        //log.info(name+" ["+val+"]["+hasChanged+"]["+isNewObj+"]["+isFirstTime+"] "+formValidationState);
        //return !hasChanged && isNewObj && isFirstTime ? UIValidatable.ErrorType.Valid : processRulesAreOK ? formValidationState : UIValidatable.ErrorType.Error;

        return !hasChanged && isNewObj ? UIValidatable.ErrorType.Valid
                : processRulesAreOK ? formValidationState : UIValidatable.ErrorType.Error;
    }

    /**
     * @param state
     */
    public void setState(final UIValidatable.ErrorType state) {
        formValidationState = state;
        updateValidationBtnUIState();
    }

    /**
     * Returns whether the form has been changed
     * @return Returns whether the form has been changed
     */
    public boolean hasChanged() {
        for (FormValidator kid : kids) {
            if (kid.hasChanged()) {
                return true;
            }
        }
        return hasChanged;
    }

    /**
     * Sets whether the form will notify the listeners of data change notifications
     * @param okToDataChangeNotification whether to notify
     */
    public void setDataChangeNotification(final boolean okToDataChangeNotification) {
        this.okToDataChangeNotification = okToDataChangeNotification;
        for (FormValidator kid : kids) {
            kid.setDataChangeNotification(okToDataChangeNotification);
        }
    }

    /**
     * Helper to that adds a validation for enabling or disabling control by name
     * @param id the id of the rule
     * @param rule the rule that will be validated
     */
    public void addEnableRule(final String id, final String rule) {
        formRules.add(new RuleExpression(id, rule));
    }

    /**
     * Helper to that adds a validation for enabling or disabling control by name
     * @param rule the rule that will be validated
     */
    public void addRule(final FormValidationRuleIFace rule) {
        formRules.add(rule);
    }

    /**
     * Tells all the validators that are required not to validate
     */
    public void setUIValidatorsToNew(boolean isNew) {
        if (enabled) {
            for (UIValidator uiv : validators) {
                uiv.setAsNew(isNew);
            }
            updateValidationBtnUIState();

            for (FormValidator kid : kids) {
                kid.setUIValidatorsToNew(isNew);
            }
        }
    }

    /**
     * 
     */
    public void setUIValidatorsToNotChanged() {
        this.hasChanged = false;
        for (UIValidator uiv : validators) {
            uiv.setChanged(false);
        }

        if (enabled) {
            updateValidationBtnUIState();
        }

        for (FormValidator kid : kids) {
            kid.setUIValidatorsToNotChanged();
        }

    }

    /**
     * Return true if validaot ris registering changes.
     * @return true if validaot ris registering changes.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * @param parentFV
     * @param enable
     * @param type
     */
    protected void enabledTreeForUI(final FormValidator parentFV, final boolean enable, final EnableType type) {
        enableUIItems(enable, type);

        for (FormValidator kid : parentFV.kids) {
            enabledTreeForUI(kid, enable, type);
        }
    }

    /**
     * Sets whether validator should except changes, this also sets the the Validated state to Valid
     * and set the hasChanged to false when disabling.
     * @param enabled true to enable it false to disable it.
     */
    public void setEnabled(final boolean enabled) {
        //log.debug("SetEnabled: "+name+" "+enabled);

        if (this.enabled != enabled) {
            if (!enabled) {
                hasChanged = false;
                formValidationState = UIValidatable.ErrorType.Valid;
                kidsValState = UIValidatable.ErrorType.Valid;
                resetFields();
            }

            this.enabled = enabled;

            // Enable / Diable the Validator (NOT the UI associated with the validator)
            for (FormValidator kid : kids) {
                kid.setEnabled(enabled);
            }

            //enableUIItems(enabled, saveEnableType);
        }
    }

    /**
     * @return the isTopLevel
     */
    public boolean isTopLevel() {
        return isTopLevel;
    }

    /**
     * @param isTopLevel the isTopLevel to set
     */
    public void setTopLevel(boolean isTopLevel) {
        this.isTopLevel = isTopLevel;
    }

    /**
     * @return the isNewObj
     */
    public boolean isNewObj() {
        return isNewObj;
    }

    /**
     * Resets the form, typically after new data has arrived. For new objects it sets the validation state to "Valid"
     * for non-new objects it validates the form.
     * 
     * @param isNewObj true if it is a new data object, false if not.
     */
    public void reset(final boolean isNewObjArg) {
        if (enabled) {
            // setHasChanged(false); // Don't call this it cascades.

            hasChanged = false;

            resetFields();

            setDataChangeNotification(true); // this doesn't effect validation notifications

            if (isNewObjArg) {
                isNewObj = isNewObjArg;
                //setFormValidationState(UIValidatable.ErrorType.Valid); 

            } else {
                validateForm();
            }

            updateValidationBtnUIState();

            for (FormValidator kid : kids) {
                kid.reset(isNewObjArg);
            }
        }
    }

    /**
     * Evaluate all the enable/disable rules and set the control and label
     */
    public boolean processFormRules() {
        boolean formIsOK = true;

        boolean debug = false;
        if (debug) {
            log.debug(name + " ****** processFormRules  ");
            Map<?, ?> map = jc.getVars();
            Object[] keys = map.keySet().toArray();
            for (Object key : keys) {
                log.debug(name + " ### [" + key + "][" + map.get(key).getClass().toString() + "]");
            }
        }

        //log.debug("processFormRules ["+name+"]------------------------------------------------- Number of Rules: "+formRules.size());
        for (FormValidationRuleIFace rule : formRules) {
            try {
                // Now evaluate the expression, getting the result
                boolean result = rule.evaluate(jc);
                //log.debug("Result ["+result+"] for ID["+rule.getId()+"]  Rule["+((RuleExpression)rule).expression.getExpression()+"]");
                if (rule.getScope() == FormValidationRuleIFace.Scope.Field) {
                    Component comp = getComp(rule.getId());
                    if (comp != null) {
                        //log.debug("    comp.setEnabled("+result+") "+comp.getClass().toString());
                        comp.setEnabled(result);
                    }

                    JLabel lbl = labels.get(rule.getId());
                    if (lbl != null) {
                        lbl.setEnabled(result);
                    }

                } else if (rule.getScope() == FormValidationRuleIFace.Scope.Form) {
                    formIsOK &= result;
                }

            } catch (Exception ex) {
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(FormValidator.class, ex);
                log.error(name + " " + ex.toString());
                formIsOK = false;
                //ex.printStackTrace();
            }
        }
        return formIsOK;
    }

    /**
     * @param textField textField to be hooked up
     * @param id id of control
     * @param isRequiredArg whether the field must be filled in
     * @param valType the type of validation to do
     * @param valStr the validation rule where the subject is its name
     * @param changeListenerOnly indicates whether to create a validator
     */
    public void hookupTextField(final JTextComponent textField, final String id, final boolean isRequiredArg,
            final UIValidator.Type valType, final String valStr, final boolean changeListenerOnly) {

        fields.put(id, textField);

        UIValidator.Type type = isRequiredArg ? UIValidator.Type.Changed : valType;

        UIValidator uiv = null;
        if (StringUtils.isEmpty(valStr)) {
            if (valType != UIValidator.Type.None) {
                uiv = createValidator(textField, valType);
            }
        } else {
            uiv = changeListenerOnly ? null : createValidator(textField, type, valStr);
        }

        if (uiv != null) {
            DataChangeNotifier dcn = new DataChangeNotifier(id, textField, uiv);
            dcn.addDataChangeListener(this);

            dcNotifiers.put(id, dcn);

            if (type == UIValidator.Type.Changed || isRequiredArg || changeListenerOnly) {
                textField.getDocument().addDocumentListener(dcn);

            } else if (type == UIValidator.Type.Focus) {
                textField.addFocusListener(dcn);

            } else {
                // Do nothing for UIValidator.Type.OK
            }
        }

        addRuleObjectMapping(id, textField);
    }

    /**
     * Hooks up generic component to be validated
     * @param comp component to be hooked up
     * @param id id of control
     * @param valType the type of validation to do
     * @param valStr the validation rule where the subject is its name
     * @param changeListenerOnly indicates whether to create a validator
     * @return the component passed in
     */
    public DataChangeNotifier hookupComponent(final JComponent comp, final String id,
            final UIValidator.Type valType, final String valStr, final boolean changeListenerOnly) {
        fields.put(id, comp);

        UIValidator uiv = null;
        if (StringUtils.isEmpty(valStr)) {
            uiv = createValidator(comp, valType);
        } else {
            uiv = changeListenerOnly ? null : createValidator(comp, valType, valStr);
        }
        DataChangeNotifier dcn = new DataChangeNotifier(id, comp, uiv);
        dcn.addDataChangeListener(this);

        dcNotifiers.put(id, dcn);

        addRuleObjectMapping(id, comp);

        return dcn;
    }

    /**
     * Create a label and register it
     * @param labelName the logical name
     * @param labelStr the string to be shown in the label
     * @return returns the new JLabel
     */
    public JLabel createLabel(final String labelName, final String labelStr) {
        JLabel lbl = UIHelper.createLabel(labelStr);
        setControlSize(lbl);
        labels.put(labelName, lbl);
        return lbl;
    }

    /**
     * Register the label by name
     * @param labelName the logical name of the label
     * @param lbl the label component
     * @return return the label that is passed in
     */
    public JLabel addUILabel(final String labelName, final JLabel lbl) {
        labels.put(labelName, lbl);
        return lbl;
    }

    /**
     * Gets a component by id and returns it as a ValTextField
     * @param id the id of the component
     * @return returns the component by id
     */
    public ValTextField getTextField(final String id) {
        Component comp = fields.get(id);
        if (comp instanceof ValTextField) {
            return (ValTextField) comp;
        }
        throw new RuntimeException("Desired JComponent [" + id + "]is not of type ValTextField" + comp);
    }

    /**
     * Returns a component by name
     * @param id the name of the component
     * @return Returns a component by name
     */
    public Component getComp(final String id) {
        return fields.get(id);
    }

    /**
     * Adds a generic Component by name and return it
     * @param id the id of the component
     * @param comp the component to add
     * @return returns the passed in component after it is registered
     */
    public Component addUIComp(final String id, final Component comp) {
        fields.put(id, comp);
        addRuleObjectMapping(id, comp);

        //log.debug(" Adding ["+id+"]["+comp.getClass().toString()+"] to validator.");

        return comp;
    }

    /**
     * Returns the Text of the label for a control's Id (strips ':' from end of string)
     * @param id the unique identifier
     * @return the text of the label
     */
    public String getLabelTextForId(final String id) {
        JLabel label = labels.get(id);
        if (label != null) {
            String labelStr = label.getText();
            if (labelStr.endsWith(":")) {
                return labelStr.substring(0, labelStr.length() - 1);
            }
            return labelStr;
        }
        return "";
    }

    /**
     * Adds a component (or object) that can be referred to oby name in a validatoin rule.
     * @param id the name of the component
     * @param comp the component
     */
    @SuppressWarnings("unchecked")
    public void addRuleObjectMapping(final String id, final Object comp) {
        if (isNotEmpty(id) && comp != null) {
            if (jc != null && jc.getVars() != null) {
                jc.getVars().put(id, comp);
            }

        } else if (!UIRegistry.isRelease()) {
            throw new RuntimeException("id[" + id + "] or Comp[" + comp + "] is null.");
        }
    }

    /**
     * Creates a validator for the control (usually a JTextField) that can have a string as a default value.
     * @param comp the component to be validated (MUST implement UIValidatable)
     * @param valType the type of validation to occur
     * @param valStr the default value
     * @return the validator for the control
     */
    protected UIValidator createValidator(final JComponent comp, final UIValidator.Type valType,
            final String valStr) {
        if (comp instanceof UIValidatable) {
            UIValidator validator = new UIValidator(comp, valType, valStr);
            validator.setJc(jc);
            validator.addValidationListener(this);
            validators.add(validator);

            return validator;

        }
        // else
        throw new RuntimeException("Component is NOT an UIValidatable " + comp);
    }

    /**
     * Create a validator for any generic UI control (defaults to be validated as Type.OK)
     * @param comp the component to be validated (MUST implement UIValidatable)
     * @param valType the type of validation to occur
     * @return the validator for the control
     */
    public UIValidator createValidator(JComponent comp, UIValidator.Type valType) {
        UIValidator validator = new UIValidator(comp, valType);
        validator.setJc(jc);
        validator.addValidationListener(this);
        validators.add(validator);
        return validator;
    }

    /**
     * This will evaluate the forms "rules" but none of the fields, instead
     * it asks each validatable field if it is ok (because they will have already
     * been validated.
     */
    protected void checkForValidForm() {
        processRulesAreOK = processFormRules();

        //log.debug("checkForValidForm ["+name+"]");
        //log.debug(name+" checkForValidForm -> formValidationState - processFormRules ["+formValidationState+"]");

        if (processRulesAreOK) {
            formValidationState = UIValidatable.ErrorType.Valid;
            for (UIValidator uiv : validators) {
                if (uiv.getComp().isEnabled()) {
                    if (uiv.isInError()) {
                        switch (uiv.getUIV().getState()) {
                        case Valid:
                            break;

                        case Incomplete:
                            if (formValidationState == UIValidatable.ErrorType.Valid) {
                                formValidationState = UIValidatable.ErrorType.Incomplete;
                            }
                            break;

                        case Error:
                            formValidationState = UIValidatable.ErrorType.Error;
                            break;
                        }

                        // Assumes Error is th worst, so if it is "less than" an Error i.e. Incompete
                        // then we don't want to override an error with a lesser state
                        if (formValidationState == UIValidatable.ErrorType.Error) {
                            break;

                        }
                    }
                }
            }
        }

        boolean isValid = getState() == UIValidatable.ErrorType.Valid;
        enableUIItems(isValid && hasChanged, EnableType.ValidAndChangedItems);
        enableUIItems(isValid, EnableType.ValidItems);
        enableUIItems(isValid && !isNewObj, EnableType.ValidNotNew);

        //log.debug(">>>>>> isValid: "+isValid+" hasCHanged: "+hasChanged+" Val & New: "+(isValid && !isNewObj));

        //log.debug(name);
        if (parent != null) {
            FormValidator parentFV = parent;
            parentFV.validateForm();
            while (parentFV.parent != null) {
                parentFV = parentFV.parent;
                parentFV.validateForm();
            }
        }
    }

    /**
     * @return the top most validator
     */
    protected FormValidator getRoot() {
        if (parent == null) {
            return this;
        }

        FormValidator parentFV = parent;
        while (parentFV.parent != null) {
            parentFV = parent;
        }
        return parentFV;
    }

    /**
     * @param ignoreValidationNotifications the ignoreValidationNotifications to set
     */
    public void setIgnoreValidationNotifications(boolean ignoreValidationNotifications) {
        this.ignoreValidationNotifications = ignoreValidationNotifications;
    }

    /**
     * Validates the fields.
     */
    public void validateForm() {
        if (enabled) {
            //log.debug("validateForm ["+name+"] ");

            boolean curIgnoreVal = ignoreValidationNotifications; // cache the value in case it has already been set
            ignoreValidationNotifications = true;

            kidsValState = UIValidatable.ErrorType.Valid;
            for (FormValidator kid : kids) {
                kid.validateForm();

                if (kid.getState().ordinal() > kidsValState.ordinal()) {
                    kidsValState = kid.getState();
                }
                /*if (kid.getState() != UIValidatable.ErrorType.Valid)
                {
                if (kidsValState != UIValidatable.ErrorType.Error)
                {
                    kidsValState = kid.getState();
                }
                }*/
            }

            processRulesAreOK = processFormRules();
            if (processRulesAreOK) {
                formValidationState = UIValidatable.ErrorType.Valid;

                // We need to go ahead and validate everything even if processFormRules fails
                // because the user will need the visual feed back on the form for which fields are in error
                for (DataChangeNotifier dcn : dcNotifiers.values()) {
                    if (dcn.isEnabled()) {
                        dcn.manualCheckForDataChanged();

                        UIValidator uiv = dcn.getUIV();
                        if (uiv != null && !uiv.validate()) {
                            switch (uiv.getUIV().getState()) {
                            case Valid:
                                break;

                            case Incomplete:
                                if (formValidationState == UIValidatable.ErrorType.Valid) {
                                    formValidationState = UIValidatable.ErrorType.Incomplete;
                                }
                                break;

                            case Error:
                                formValidationState = UIValidatable.ErrorType.Error;
                                break;
                            }
                        }
                    }
                }
            } else {
                formValidationState = UIValidatable.ErrorType.Error;
            }

            //log.debug(name + " - kidsValState "+kidsValState+"  formValidationState "+formValidationState);

            if (kidsValState.ordinal() > formValidationState.ordinal()) {
                formValidationState = kidsValState;
            }

            //log.debug("validateForm ["+name+"] State: "+formValidationState);

            boolean isValid = isFormValid();
            enableUIItems(hasChanged && isValid, EnableType.ValidAndChangedItems);
            enableUIItems(isValid, EnableType.ValidItems);
            enableUIItems(isValid && !isNewObj, EnableType.ValidNotNew);

            updateValidationBtnUIState();

            ignoreValidationNotifications = curIgnoreVal;
        }
    }

    /**
     * IMPORTANT CHANGE: rods 05/04/11 will now only cascade upwards
     * when the parent is false and the child is true. It use to 
     * cascade false up through the parents.
     * 
     * @param changed true/false
     */
    protected void cascadeHasChanged(final boolean changed) {
        hasChanged = changed;

        if (parent != null) {
            FormValidator parentFV = parent;
            while (parentFV != null) {
                if (!parentFV.hasChanged && changed) {
                    parentFV.hasChanged = changed;
                }
                //log.debug("Parent Val "+parentFV.getName()+" hasChanged "+changed);
                parentFV = parentFV.parent;
            }
        }
    }

    /**
     * Sets the DataChangeNotifier to change = true if it has one.
     * @param comp the component to check
     */
    public void setDataChangeInNotifier(final Component comp) {
        if (comp != null) {
            cascadeHasChanged(true);

            for (DataChangeNotifier dcn : dcNotifiers.values()) {
                UIValidator uiv = dcn.getUIV();
                if (uiv != null && uiv.getComp() == comp) {
                    dcn.setDataChanged(true);
                }
            }
        }
    }

    /**
     * Reset all dataChangedNotifiers
     */
    public void resetFields() {
        if (enabled) {
            for (Enumeration<DataChangeNotifier> e = dcNotifiers.elements(); e.hasMoreElements();) {
                DataChangeNotifier dcn = e.nextElement();
                dcn.reset();
            }
        }
    }

    /**
     * Creates and register a DataChangeNotifier
     * @param id the id
     * @param comp the component
     * @param uiv the UI validator
     * @return the dcn
     */
    public DataChangeNotifier createDataChangeNotifer(final String id, final Component comp,
            final UIValidator uiv) {
        DataChangeNotifier dcn = new DataChangeNotifier(id, comp, uiv);
        dcn.addDataChangeListener(this);
        dcNotifiers.put(id, dcn);

        if (uiv != null && !validators.contains(uiv)) {
            validators.add(uiv);
        }
        return dcn;
    }

    /**
     * Clean up internal data
     */
    public void cleanUp() {
        jc = null;
        exp = null;

        for (FormValidator kid : kids) {
            kid.cleanUp();
        }
        parent = null;

        for (DataChangeNotifier dcn : dcNotifiers.values()) {
            dcn.cleanUp();
        }
        dcNotifiers.clear();

        for (UIValidator uv : validators) {
            uv.cleanUp();
        }
        validators.clear();

        fields.clear();
        labels.clear();

        for (FormValidationRuleIFace rule : formRules) {
            if (rule instanceof RuleExpression) {
                ((RuleExpression) rule).cleanUp();
            }
        }
        formRules.clear();

        for (Vector<Component> list : enableHash.values()) {
            list.clear();
        }
        dcListeners.clear();
        valListeners.clear();
    }

    /**
     * Adds validation listener
     * @param l the listener
     */
    public void addDataChangeListener(final DataChangeListener l) {
        dcListeners.add(l);
    }

    /**
     * Removes validation listener
     * @param l the listener
     */
    public void removeDataChangeListener(final DataChangeListener l) {
        dcListeners.remove(l);
    }

    /**
     * Adds validation listener
     * @param l the listener
     */
    public void addValidationListener(final ValidationListener l) {
        valListeners.add(l);
    }

    /**
     * Removes validation listener
     * @param l the listener
     */
    public void removeValidationListener(final ValidationListener l) {
        valListeners.remove(l);
    }

    /**
     * Returns the name of the validator (optional)
     * @return the name of the validator (optional)
     */
    public String getName() {
        return name;
    }

    /**
     * Sets the name of the validator (optional for debugging)
     * @param name the name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Returns the Map of Notifiers
     * @return the Map of Notifiers
     */
    public Map<String, DataChangeNotifier> getDCNs() {
        return dcNotifiers;
    }

    /**
     * Dumps the State of the Validation State
     * @param doBrief just the state of validator, when false it dumps everthing about all the DCNs and validators
     */
    public void dumpState(final boolean doBrief, final int level) {
        String displayName = (name != null ? name : "No Name");

        if (doBrief) {
            log.debug(level + " *** " + displayName + "  isValid: " + isFormValid() + " " + getState());

        } else {
            StringBuilder strBuf = new StringBuilder(64);
            log.debug("\n" + level + " ------------" + displayName + "-------------");
            log.debug("Valid: " + isFormValid());
            log.debug("\n");

            int maxLen = 0;
            for (DataChangeNotifier dcn : dcNotifiers.values()) {
                int len = dcn.getId().length();
                maxLen = Math.max(maxLen, len);
            }

            for (DataChangeNotifier dcn : dcNotifiers.values()) {
                String nm = dcn.getId();
                strBuf.setLength(0);
                strBuf.append(nm);
                for (int i = 0; i <= (maxLen - nm.length()); i++)
                    strBuf.append(" ");
                UIValidator uiv = dcn.getUIV();
                if (uiv != null) {
                    strBuf.append("UIV: ");
                    strBuf.append(uiv.getType());
                    strBuf.append(" ");
                    strBuf.append(uiv.getUIV().getState());
                }

                log.debug(strBuf.toString());
            }
            log.debug("-------------------------");
        }

        for (FormValidator kid : kids) {
            kid.dumpState(doBrief, level + 1);
        }
    }

    /**
     * @param validationInfoBtn
     */
    public void setValidationBtn(final JButton valInfoBtn) {
        this.validationInfoBtn = valInfoBtn;

        // This for debugging validation, makes it easier to see the validation feedback
        // validationInfoBtn.setBorder(BorderFactory.createLineBorder(Color.BLACK));
    }

    /**
     * Sets the visual state on the validation btn. 
     */
    public void updateValidationBtnUIState() {
        //log.debug("updateValidationBtnUIState ["+name+"] "+getState());
        if (validationInfoBtn != null) {
            boolean enable = true;
            ImageIcon icon = IconManager.getIcon("ValidationValid", IconManager.IconSize.Std16);
            UIValidatable.ErrorType state = getState();

            if (kidsValState.ordinal() > state.ordinal()) {
                state = kidsValState;
            }

            if (state == UIValidatable.ErrorType.Incomplete) {
                icon = IconManager.getIcon("ValidationWarning", IconManager.IconSize.Std16);

            } else if (state == UIValidatable.ErrorType.Error) {
                icon = IconManager.getIcon("ValidationError", IconManager.IconSize.Std16);
            } else {
                enable = false;
            }
            validationInfoBtn.setEnabled(enable);
            validationInfoBtn.setIcon(icon);
        }
    }

    //-----------------------------------------------------
    // ValidationListener
    //-----------------------------------------------------

    /**
     * Helper methods for turning on the "default" OK button after a validation was completed.
     * @param itsOKToEnable indicates whether it is OK to enable the "OK" button
     */
    protected void enableUIItems(final boolean itsOKToEnable, final EnableType type) {
        //log.debug(name+" hasChanged ["+hasChanged+"]     itsOKToEnable ["+itsOKToEnable+ "]    enableItems: [" + type+"]");

        for (Component comp : enableHash.get(type)) {
            comp.setEnabled(itsOKToEnable);
        }
        updateValidationBtnUIState();

        //log.debug("UIValidator.isIgnoreAllValidation() "+UIValidator.isIgnoreAllValidation());
        //log.debug(name+" type "+type+"  itsOKToEnable "+itsOKToEnable);
        if ((type == saveEnableType || type == EnableType.ValidAndChangedItems) && saveComp != null
                && !UIValidator.isIgnoreAllValidation()) {
            if (itsOKToEnable) {
                saveComp.setEnabled(isFormValid(true));
            } else {
                saveComp.setEnabled(false);
            }
        }
    }

    /**
     * Asks the Save button to update the enabled state based on whether the form is valid.
     */
    public void updateSaveUIEnabledState() {
        if (saveComp != null) {
            saveComp.setEnabled(isFormValid(true));
        }
    }

    /* (non-Javadoc)
      * @see ValidationListener#wasValidated(UIValidator)
      */
    public void wasValidated(final UIValidator validator) {
        // When the form has been asked manually to be validated then ignore the notifications
        if (!ignoreValidationNotifications) {
            if (validator != null && validators.contains(validator)) {
                cascadeHasChanged(true);
            }
            checkForValidForm();

            for (ValidationListener vcl : valListeners) {
                vcl.wasValidated(validator);
            }
            updateValidationBtnUIState();
        }

        if (parent != null) {
            //parent.wasValidated(validator);
        }
    }

    /**
     * Walk up to the Root validator and validate it.
     */
    public void validateRoot() {
        FormValidator fvParent = this;
        while (fvParent.getParent() != null) {
            fvParent = fvParent.getParent();
        }
        fvParent.validateForm();
    }

    //-----------------------------------------------------
    // DataChangeListener
    //-----------------------------------------------------

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.forms.validation.DataChangeListener#dataChanged(java.lang.String, java.awt.Component, edu.ku.brc.ui.forms.validation.DataChangeNotifier)
     */
    public void dataChanged(final String dcName, final Component comp, DataChangeNotifier dcn) {
        if (!okToDataChangeNotification || UIValidator.isIgnoreAllValidation()) {
            return;
        }
        //log.debug("DataChangeListener "+name + " was changed");

        // Here is the big assumption:
        // The validation system is built on the premise that when a control changes
        // it is validated before it calls all of it's listeners to tell them it has been changed.
        //
        // This means that this form has already received a callback "wasValidated" and validated
        // the entire form. we don't have to do anything here but flip the boolean that data has changed
        // and then see if the button can enabled
        // But if the DataChangeNotifier didn't have a UIValidator then we need to re-validate the
        // form because "wasValidated" wouldn't have been called

        // rods - 08/01/2008 - Moved here before valifing the root 
        // instead of below and afterward
        cascadeHasChanged(true);

        UIValidator uiv = dcn != null ? dcn.getUIV() : null;
        if (uiv == null) {

            validateRoot();

        } else {
            //log.debug("About validateForm: ["+name+"] "+enabled);
            //validateForm();
        }

        //cascadeHasChanged(true);

        // Notify anyone else who is listening to the form for changes
        for (DataChangeListener dcl : dcListeners) {
            dcl.dataChanged(dcName, comp, dcn);
        }

        boolean isValid = formValidationState == UIValidatable.ErrorType.Valid;
        //log.debug(hasChanged+"  "+isValid);

        enableUIItems(hasChanged && isValid, EnableType.ValidAndChangedItems);
        //enableUIItems(isValid && !isNewObj, EnableType.ValidItems);
        enableUIItems(isValid, EnableType.ValidItems);
        enableUIItems(isValid && !isNewObj, EnableType.ValidNotNew);

        updateValidationBtnUIState();
    }

    /**
     * Sets whether the data in the form has changed and cascades
     * it up through the parents to the top. NOTE: It only cascades
     * changes from false to true.
     * 
     * @param hasChanged whether the data in the form has changed
     */
    public void setHasChanged(boolean hasChanged) {
        //log.debug("setHasChanged ["+name+"] "+hasChanged);
        cascadeHasChanged(hasChanged);
    }

}