org.codhaus.groovy.grails.validation.ext.ConstrainedPropertyGunn.java Source code

Java tutorial

Introduction

Here is the source code for org.codhaus.groovy.grails.validation.ext.ConstrainedPropertyGunn.java

Source

/* Copyright 2004-2005 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.codhaus.groovy.grails.validation.ext;

import groovy.lang.MissingPropertyException;
import groovy.lang.Range;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codhaus.groovy.grails.validation.BlankConstraint;
import org.codhaus.groovy.grails.validation.Constraint;
import org.codhaus.groovy.grails.validation.ConstraintFactory;
import org.codhaus.groovy.grails.validation.CreditCardConstraint;
import org.codhaus.groovy.grails.validation.EmailConstraint;
import org.codhaus.groovy.grails.validation.InListConstraint;
import org.codhaus.groovy.grails.validation.MatchesConstraint;
import org.codhaus.groovy.grails.validation.MaxConstraint;
import org.codhaus.groovy.grails.validation.MaxSizeConstraint;
import org.codhaus.groovy.grails.validation.MinConstraint;
import org.codhaus.groovy.grails.validation.MinSizeConstraint;
import org.codhaus.groovy.grails.validation.NotEqualConstraint;
import org.codhaus.groovy.grails.validation.NullableConstraint;
import org.codhaus.groovy.grails.validation.RangeConstraint;
import org.codhaus.groovy.grails.validation.ScaleConstraint;
import org.codhaus.groovy.grails.validation.SizeConstraint;
import org.codhaus.groovy.grails.validation.UrlConstraint;
import org.codhaus.groovy.grails.validation.ValidatorConstraint;
import org.codhaus.groovy.grails.validation.VetoingConstraint;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.context.MessageSource;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Provides the ability to set contraints against a properties of a class. Constraints can either be
 * set via the property setters or via the <pre>applyConstraint(String constraintName, Object constrainingValue)</pre>
 * in combination with a constraint constant. Example:
 *
 * <code>
 *      ...
 *
 *         ConstrainedProperty cp = new ConstrainedProperty(owningClass, propertyName, propertyType);
 *      if (cp.supportsConstraint(ConstrainedProperty.EMAIL_CONSTRAINT)) {
 *          cp.applyConstraint(ConstrainedProperty.EMAIL_CONSTRAINT, new Boolean(true));
 *      }
 * </code>
 *
 * Alternatively constraints can be applied directly using the java bean getters/setters if a static (as oposed to dynamic)
 * approach to constraint creation is possible:
 *
 * <code>
 *       cp.setEmail(true)
 * </code>
 * @author Graeme Rocher
 * @since 07-Nov-2005
 */
@SuppressWarnings("serial")
public class ConstrainedPropertyGunn {

    public static final String DEFAULT_NULL_MESSAGE_CODE = "default.null.message";
    public static final String DEFAULT_INVALID_MIN_SIZE_MESSAGE_CODE = "default.invalid.min.size.message";
    public static final String DEFAULT_INVALID_MAX_SIZE_MESSAGE_CODE = "default.invalid.max.size.message";
    public static final String DEFAULT_NOT_EQUAL_MESSAGE_CODE = "default.not.equal.message";
    public static final String DEFAULT_INVALID_MIN_MESSAGE_CODE = "default.invalid.min.message";
    public static final String DEFAULT_INVALID_MAX_MESSAGE_CODE = "default.invalid.max.message";
    public static final String DEFAULT_INVALID_SIZE_MESSAGE_CODE = "default.invalid.size.message";
    public static final String DEFAULT_NOT_INLIST_MESSAGE_CODE = "default.not.inlist.message";
    public static final String DEFAULT_INVALID_RANGE_MESSAGE_CODE = "default.invalid.range.message";
    public static final String DEFAULT_INVALID_EMAIL_MESSAGE_CODE = "default.invalid.email.message";
    public static final String DEFAULT_INVALID_CREDIT_CARD_MESSAGE_CODE = "default.invalid.creditCard.message";
    public static final String DEFAULT_INVALID_URL_MESSAGE_CODE = "default.invalid.url.message";
    public static final String DEFAULT_INVALID_VALIDATOR_MESSAGE_CODE = "default.invalid.validator.message";
    public static final String DEFAULT_DOESNT_MATCH_MESSAGE_CODE = "default.doesnt.match.message";
    public static final String DEFAULT_BLANK_MESSAGE_CODE = "default.blank.message";

    //protected static final ResourceBundle bundle = ResourceBundle.getBundle("org.codehaus.groovy.grails.validation.DefaultErrorMessages");

    private static final String DEFAULT_BLANK_MESSAGE = ""; // bundle.getString(DEFAULT_BLANK_MESSAGE_CODE);
    private static final String DEFAULT_DOESNT_MATCH_MESSAGE = ""; // bundle.getString(DEFAULT_DOESNT_MATCH_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_URL_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_URL_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_CREDIT_CARD_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_CREDIT_CARD_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_EMAIL_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_EMAIL_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_RANGE_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_RANGE_MESSAGE_CODE);
    private static final String DEFAULT_NOT_IN_LIST_MESSAGE = ""; // bundle.getString(DEFAULT_NOT_INLIST_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_SIZE_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_SIZE_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_MAX_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_MAX_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_MIN_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_MIN_MESSAGE_CODE);
    private static final String DEFAULT_NOT_EQUAL_MESSAGE = ""; // bundle.getString(DEFAULT_NOT_EQUAL_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_MAX_SIZE_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_MAX_SIZE_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_MIN_SIZE_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_MIN_SIZE_MESSAGE_CODE);
    private static final String DEFAULT_NULL_MESSAGE = ""; // bundle.getString(DEFAULT_NULL_MESSAGE_CODE);
    private static final String DEFAULT_INVALID_VALIDATOR_MESSAGE = ""; // bundle.getString(DEFAULT_INVALID_VALIDATOR_MESSAGE_CODE);

    public static final String CREDIT_CARD_CONSTRAINT = "creditCard";
    public static final String EMAIL_CONSTRAINT = "email";
    public static final String BLANK_CONSTRAINT = "blank";
    public static final String RANGE_CONSTRAINT = "range";
    public static final String IN_LIST_CONSTRAINT = "inList";
    public static final String URL_CONSTRAINT = "url";
    public static final String MATCHES_CONSTRAINT = "matches";
    public static final String SIZE_CONSTRAINT = "size";
    public static final String MIN_CONSTRAINT = "min";
    public static final String MAX_CONSTRAINT = "max";
    public static final String MAX_SIZE_CONSTRAINT = "maxSize";
    public static final String MIN_SIZE_CONSTRAINT = "minSize";
    public static final String SCALE_CONSTRAINT = "scale";
    public static final String NOT_EQUAL_CONSTRAINT = "notEqual";
    public static final String NULLABLE_CONSTRAINT = "nullable";
    public static final String VALIDATOR_CONSTRAINT = "validator";
    public static final String CASCADE_CONSTRAINT = "cascade";

    public static final String INVALID_SUFFIX = ".invalid";
    public static final String EXCEEDED_SUFFIX = ".exceeded";
    public static final String NOTMET_SUFFIX = ".notmet";
    public static final String NOT_PREFIX = "not.";
    public static final String TOOBIG_SUFFIX = ".toobig";
    public static final String TOOLONG_SUFFIX = ".toolong";
    public static final String TOOSMALL_SUFFIX = ".toosmall";
    public static final String TOOSHORT_SUFFIX = ".tooshort";

    protected static Map<String, List<Object>> constraints = new HashMap<String, List<Object>>();
    public static final Map<String, String> DEFAULT_MESSAGES = new HashMap<String, String>();

    static {
        DEFAULT_MESSAGES.put(DEFAULT_BLANK_MESSAGE_CODE, DEFAULT_BLANK_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_DOESNT_MATCH_MESSAGE_CODE, DEFAULT_DOESNT_MATCH_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_CREDIT_CARD_MESSAGE_CODE, DEFAULT_INVALID_CREDIT_CARD_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_EMAIL_MESSAGE_CODE, DEFAULT_INVALID_EMAIL_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_MAX_MESSAGE_CODE, DEFAULT_INVALID_MAX_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_MAX_SIZE_MESSAGE_CODE, DEFAULT_INVALID_MAX_SIZE_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_MIN_MESSAGE_CODE, DEFAULT_INVALID_MIN_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_MIN_SIZE_MESSAGE_CODE, DEFAULT_INVALID_MIN_SIZE_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_RANGE_MESSAGE_CODE, DEFAULT_INVALID_RANGE_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_SIZE_MESSAGE_CODE, DEFAULT_INVALID_SIZE_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_URL_MESSAGE_CODE, DEFAULT_INVALID_URL_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_NOT_EQUAL_MESSAGE_CODE, DEFAULT_NOT_EQUAL_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_NOT_INLIST_MESSAGE_CODE, DEFAULT_NOT_IN_LIST_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_NULL_MESSAGE_CODE, DEFAULT_NULL_MESSAGE);
        DEFAULT_MESSAGES.put(DEFAULT_INVALID_VALIDATOR_MESSAGE_CODE, DEFAULT_INVALID_VALIDATOR_MESSAGE);

        constraints.put(CREDIT_CARD_CONSTRAINT, new ArrayList<Object>() {
            {
                add(CreditCardConstraint.class);
            }
        });
        constraints.put(EMAIL_CONSTRAINT, new ArrayList<Object>() {
            {
                add(EmailConstraint.class);
            }
        });
        constraints.put(BLANK_CONSTRAINT, new ArrayList<Object>() {
            {
                add(BlankConstraint.class);
            }
        });
        constraints.put(RANGE_CONSTRAINT, new ArrayList<Object>() {
            {
                add(RangeConstraint.class);
            }
        });
        constraints.put(IN_LIST_CONSTRAINT, new ArrayList<Object>() {
            {
                add(InListConstraint.class);
            }
        });
        constraints.put(URL_CONSTRAINT, new ArrayList<Object>() {
            {
                add(UrlConstraint.class);
            }
        });
        constraints.put(SIZE_CONSTRAINT, new ArrayList<Object>() {
            {
                add(SizeConstraint.class);
            }
        });
        constraints.put(MATCHES_CONSTRAINT, new ArrayList<Object>() {
            {
                add(MatchesConstraint.class);
            }
        });
        constraints.put(MIN_CONSTRAINT, new ArrayList<Object>() {
            {
                add(MinConstraint.class);
            }
        });
        constraints.put(MAX_CONSTRAINT, new ArrayList<Object>() {
            {
                add(MaxConstraint.class);
            }
        });
        constraints.put(MAX_SIZE_CONSTRAINT, new ArrayList<Object>() {
            {
                add(MaxSizeConstraint.class);
            }
        });
        constraints.put(MIN_SIZE_CONSTRAINT, new ArrayList<Object>() {
            {
                add(MinSizeConstraint.class);
            }
        });
        constraints.put(SCALE_CONSTRAINT, new ArrayList<Object>() {
            {
                add(ScaleConstraint.class);
            }
        });
        constraints.put(NULLABLE_CONSTRAINT, new ArrayList<Object>() {
            {
                add(NullableConstraint.class);
            }
        });
        constraints.put(NOT_EQUAL_CONSTRAINT, new ArrayList<Object>() {
            {
                add(NotEqualConstraint.class);
            }
        });
        constraints.put(VALIDATOR_CONSTRAINT, new ArrayList<Object>() {
            {
                add(ValidatorConstraint.class);
            }
        });
        /*
        constraints.put(CASCADE_CONSTRAINT, new ArrayList<Object>() {{
         add(CascadeConstraint.class);
        }});
        */
    }

    protected static final Log LOG = LogFactory.getLog(ConstrainedPropertyGunn.class);

    // move these to subclass

    protected String propertyName;
    protected Class<?> propertyType;

    protected Map<String, Constraint> appliedConstraints = new LinkedHashMap<String, Constraint>();
    protected Class<?> owningClass;
    private BeanWrapper bean;

    // simple constraints
    private boolean display = true; // whether the property should be displayed
    private boolean editable = true; // whether the property is editable
    //private boolean file; // whether the property is a file
    private int order; // what order to property appears in
    private String format; // the format of the property (for example a date pattern)
    private String widget; // the widget to use to render the property
    private boolean password; // whether the property is a password
    @SuppressWarnings("rawtypes")
    private Map attributes = Collections.EMPTY_MAP; // a map of attributes of property
    protected MessageSource messageSource;
    private Map<String, Object> metaConstraints = new HashMap<String, Object>();

    /**
     * Constructs a new ConstrainedProperty for the given arguments.
     *
     * @param clazz The owning class
     * @param propertyName The name of the property
     * @param propertyType The property type
     */
    public ConstrainedPropertyGunn(Class<?> clazz, String propertyName, Class<?> propertyType) {
        owningClass = clazz;
        this.propertyName = propertyName;
        this.propertyType = propertyType;
        bean = new BeanWrapperImpl(this);
    }

    public static void removeConstraint(String name, Class<?> constraintClass) {
        Assert.hasLength(name, "Argument [name] cannot be null");

        List<Object> objects = getOrInitializeConstraint(name);
        objects.remove(constraintClass);
        List<Object> toRemove = new ArrayList<Object>();
        for (Object object : objects) {
            if (constraintClass.isInstance(object)) {
                toRemove.add(object);
            }
        }
        objects.removeAll(toRemove);
    }

    public static void removeConstraint(String name) {
        Assert.hasLength(name, "Argument [name] cannot be null");

        List<Object> objects = getOrInitializeConstraint(name);
        objects.clear();
    }

    public static void registerNewConstraint(String name, Class<?> constraintClass) {
        Assert.hasLength(name, "Argument [name] cannot be null");
        if (constraintClass == null || !Constraint.class.isAssignableFrom(constraintClass)) {
            throw new IllegalArgumentException(
                    "Argument [constraintClass] with value [" + constraintClass + "] is not a valid constraint");
        }

        List<Object> objects = getOrInitializeConstraint(name);
        objects.add(constraintClass);
    }

    private static List<Object> getOrInitializeConstraint(String name) {
        List<Object> objects = constraints.get(name);
        if (objects == null) {
            objects = new ArrayList<Object>();
            constraints.put(name, objects);
        }
        return objects;
    }

    public static void registerNewConstraint(String name, ConstraintFactory factory) {
        Assert.hasLength(name, "Argument [name] cannot be null or blank");
        Assert.notNull(factory, "Argument [factory] cannot be null");
        List<Object> objects = getOrInitializeConstraint(name);
        objects.add(factory);
    }

    public static boolean hasRegisteredConstraint(String constraintName) {
        return constraints.containsKey(constraintName) && constraints.get(constraintName).size() > 0;
    }

    /**
     * @return Returns the appliedConstraints.
     */
    public Collection<Constraint> getAppliedConstraints() {
        return appliedConstraints.values();
    }

    /**
     * Obtains an applied constraint by name.
     * @param name The name of the constraint
     * @return The applied constraint
     */
    public Constraint getAppliedConstraint(String name) {
        return appliedConstraints.get(name);
    }

    /**
     * @param constraintName The name of the constraint to check
     * @return Returns true if the specified constraint name is being applied to this property
     */
    public boolean hasAppliedConstraint(String constraintName) {
        return appliedConstraints.containsKey(constraintName);
    }

    /**
     * @return Returns the propertyType.
     */
    public Class<?> getPropertyType() {
        return propertyType;
    }

    /**
     * @return Returns the max.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Comparable getMax() {
        Comparable maxValue = null;

        MaxConstraint maxConstraint = (MaxConstraint) appliedConstraints.get(MAX_CONSTRAINT);
        RangeConstraint rangeConstraint = (RangeConstraint) appliedConstraints.get(RANGE_CONSTRAINT);

        if (maxConstraint != null || rangeConstraint != null) {
            Comparable maxConstraintValue = maxConstraint == null ? null : maxConstraint.getMaxValue();
            Comparable rangeConstraintHighValue = rangeConstraint == null ? null
                    : rangeConstraint.getRange().getTo();

            if (maxConstraintValue != null && rangeConstraintHighValue != null) {
                maxValue = (maxConstraintValue.compareTo(rangeConstraintHighValue) < 0) ? maxConstraintValue
                        : rangeConstraintHighValue;
            } else if (maxConstraintValue == null && rangeConstraintHighValue != null) {
                maxValue = rangeConstraintHighValue;
            } else if (maxConstraintValue != null && rangeConstraintHighValue == null) {
                maxValue = maxConstraintValue;
            }
        }

        return maxValue;
    }

    /**
     * @param max The max to set.
     */
    @SuppressWarnings("rawtypes")
    public void setMax(Comparable max) {
        if (max == null) {
            appliedConstraints.remove(MAX_CONSTRAINT);
            return;
        }

        if (!propertyType.equals(max.getClass())) {
            throw new MissingPropertyException(MAX_CONSTRAINT, propertyType);
        }

        Range r = getRange();
        if (r != null) {
            LOG.warn("Range constraint already set ignoring constraint [" + MAX_CONSTRAINT + "] for value [" + max
                    + "]");
            return;
        }

        Constraint c = appliedConstraints.get(MAX_CONSTRAINT);
        if (c == null) {
            c = new MaxConstraint();
            c.setOwningClass(owningClass);
            c.setPropertyName(propertyName);
            appliedConstraints.put(MAX_CONSTRAINT, c);
        }
        c.setParameter(max);
    }

    /**
     * @return Returns the min.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Comparable getMin() {
        Comparable minValue = null;

        MinConstraint minConstraint = (MinConstraint) appliedConstraints.get(MIN_CONSTRAINT);
        RangeConstraint rangeConstraint = (RangeConstraint) appliedConstraints.get(RANGE_CONSTRAINT);

        if (minConstraint != null || rangeConstraint != null) {
            Comparable minConstraintValue = minConstraint != null ? minConstraint.getMinValue() : null;
            Comparable rangeConstraintLowValue = rangeConstraint != null ? rangeConstraint.getRange().getFrom()
                    : null;

            if (minConstraintValue != null && rangeConstraintLowValue != null) {
                minValue = (minConstraintValue.compareTo(rangeConstraintLowValue) > 0) ? minConstraintValue
                        : rangeConstraintLowValue;
            } else if (minConstraintValue == null && rangeConstraintLowValue != null) {
                minValue = rangeConstraintLowValue;
            } else if (minConstraintValue != null && rangeConstraintLowValue == null) {
                minValue = minConstraintValue;
            }
        }

        return minValue;
    }

    /**
     * @param min The min to set.
     */
    @SuppressWarnings("rawtypes")
    public void setMin(Comparable min) {
        if (min == null) {
            appliedConstraints.remove(MIN_CONSTRAINT);
            return;
        }

        if (!propertyType.equals(min.getClass())) {
            throw new MissingPropertyException(MIN_CONSTRAINT, propertyType);
        }

        Range r = getRange();
        if (r != null) {
            LOG.warn("Range constraint already set ignoring constraint [" + MIN_CONSTRAINT + "] for value [" + min
                    + "]");
            return;
        }

        Constraint c = appliedConstraints.get(MIN_CONSTRAINT);
        if (c == null) {
            c = new MinConstraint();
            c.setOwningClass(owningClass);
            c.setPropertyName(propertyName);
            appliedConstraints.put(MIN_CONSTRAINT, c);
        }
        c.setParameter(min);
    }

    /**
     * @return Returns the inList.
     */
    @SuppressWarnings("rawtypes")
    public List getInList() {
        InListConstraint c = (InListConstraint) appliedConstraints.get(IN_LIST_CONSTRAINT);
        return c == null ? null : c.getList();
    }

    /**
     * @param inList The inList to set.
     */
    @SuppressWarnings("rawtypes")
    public void setInList(List inList) {
        Constraint c = appliedConstraints.get(IN_LIST_CONSTRAINT);
        if (inList == null) {
            appliedConstraints.remove(IN_LIST_CONSTRAINT);
        } else {
            if (c == null) {
                c = new InListConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(IN_LIST_CONSTRAINT, c);
            }
            c.setParameter(inList);
        }
    }

    /**
     * @return Returns the range.
     */
    @SuppressWarnings("rawtypes")
    public Range getRange() {
        RangeConstraint c = (RangeConstraint) appliedConstraints.get(RANGE_CONSTRAINT);
        return c == null ? null : c.getRange();
    }

    /**
     * @param range The range to set.
     */
    @SuppressWarnings("rawtypes")
    public void setRange(Range range) {
        if (appliedConstraints.containsKey(MAX_CONSTRAINT)) {
            LOG.warn("Setting range constraint on property [" + propertyName + "] of class [" + owningClass
                    + "] forced removal of max constraint");
            appliedConstraints.remove(MAX_CONSTRAINT);
        }
        if (appliedConstraints.containsKey(MIN_CONSTRAINT)) {
            LOG.warn("Setting range constraint on property [" + propertyName + "] of class [" + owningClass
                    + "] forced removal of min constraint");
            appliedConstraints.remove(MIN_CONSTRAINT);
        }
        if (range == null) {
            appliedConstraints.remove(RANGE_CONSTRAINT);
        } else {
            Constraint c = appliedConstraints.get(RANGE_CONSTRAINT);
            if (c == null) {
                c = new RangeConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(RANGE_CONSTRAINT, c);
            }
            c.setParameter(range);
        }
    }

    /**
     * @return The scale, if defined for this property; null, otherwise
     */
    public Integer getScale() {
        ScaleConstraint scaleConstraint = (ScaleConstraint) appliedConstraints.get(SCALE_CONSTRAINT);
        return scaleConstraint == null ? null : scaleConstraint.getScale();
    }

    /**
     * @return Returns the size.
     */
    @SuppressWarnings("rawtypes")
    public Range getSize() {
        SizeConstraint c = (SizeConstraint) appliedConstraints.get(SIZE_CONSTRAINT);
        return c == null ? null : c.getRange();
    }

    /**
     * @param size The size to set.
     */
    @SuppressWarnings("rawtypes")
    public void setSize(Range size) {
        Constraint c = appliedConstraints.get(SIZE_CONSTRAINT);
        if (size == null) {
            appliedConstraints.remove(SIZE_CONSTRAINT);
        } else {
            if (c == null) {
                c = new SizeConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(SIZE_CONSTRAINT, c);
            }
            c.setParameter(size);
        }
    }

    /**
     * @return the blank.
     */
    public boolean isBlank() {
        Object cons = appliedConstraints.get(BLANK_CONSTRAINT);
        return cons == null || (Boolean) ((BlankConstraint) cons).getParameter();
    }

    /**
     * @param blank The blank to set.
     */
    public void setBlank(boolean blank) {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("Blank constraint can only be applied to a String property",
                    BLANK_CONSTRAINT, owningClass);
        }

        if (blank) {
            Constraint c = appliedConstraints.get(BLANK_CONSTRAINT);
            if (c == null) {
                c = new BlankConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(BLANK_CONSTRAINT, c);
            }
            c.setParameter(blank);
        } else {
            appliedConstraints.remove(BLANK_CONSTRAINT);
        }
    }

    /**
     * @return Returns the email.
     */
    public boolean isEmail() {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("Email constraint only applies to a String property",
                    EMAIL_CONSTRAINT, owningClass);
        }

        return appliedConstraints.containsKey(EMAIL_CONSTRAINT);
    }

    /**
     * @param email The email to set.
     */
    public void setEmail(boolean email) {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("Email constraint can only be applied to a String property",
                    EMAIL_CONSTRAINT, owningClass);
        }

        Constraint c = appliedConstraints.get(EMAIL_CONSTRAINT);
        if (email) {
            if (c == null) {
                c = new EmailConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(EMAIL_CONSTRAINT, c);
            }
            c.setParameter(true);
        } else {
            if (c != null) {
                appliedConstraints.remove(EMAIL_CONSTRAINT);
            }
        }
    }

    private boolean isNotValidStringType() {
        return !CharSequence.class.isAssignableFrom(propertyType);
    }

    /**
     * @return Returns the creditCard.
     */
    public boolean isCreditCard() {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("CreditCard constraint only applies to a String property",
                    CREDIT_CARD_CONSTRAINT, owningClass);
        }

        return appliedConstraints.containsKey(CREDIT_CARD_CONSTRAINT);
    }

    /**
     * @param creditCard The creditCard to set.
     */
    public void setCreditCard(boolean creditCard) {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("CreditCard constraint only applies to a String property",
                    CREDIT_CARD_CONSTRAINT, owningClass);
        }

        Constraint c = appliedConstraints.get(CREDIT_CARD_CONSTRAINT);
        if (creditCard) {
            if (c == null) {
                c = new CreditCardConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(CREDIT_CARD_CONSTRAINT, c);
            }
            c.setParameter(true);
        } else {
            if (c != null) {
                appliedConstraints.remove(CREDIT_CARD_CONSTRAINT);
            }
        }
    }

    /**
     * @return Returns the matches.
     */
    public String getMatches() {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("Matches constraint only applies to a String property",
                    MATCHES_CONSTRAINT, owningClass);
        }
        MatchesConstraint c = (MatchesConstraint) appliedConstraints.get(MATCHES_CONSTRAINT);
        return c == null ? null : c.getRegex();
    }

    /**
     * @param regex The matches to set.
     */
    public void setMatches(String regex) {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("Matches constraint can only be applied to a String property",
                    MATCHES_CONSTRAINT, owningClass);
        }

        Constraint c = appliedConstraints.get(MATCHES_CONSTRAINT);
        if (regex == null) {
            appliedConstraints.remove(MATCHES_CONSTRAINT);
        } else {
            if (c == null) {
                c = new MatchesConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(MATCHES_CONSTRAINT, c);
            }
            c.setParameter(regex);
        }
    }

    /**
     * @return Returns the notEqual.
     */
    public Object getNotEqual() {
        NotEqualConstraint c = (NotEqualConstraint) appliedConstraints.get(NOT_EQUAL_CONSTRAINT);
        return c == null ? null : c.getNotEqualTo();
    }

    /**
     * @return Returns the maxSize.
     */
    public Integer getMaxSize() {
        Integer maxSize = null;

        MaxSizeConstraint maxSizeConstraint = (MaxSizeConstraint) appliedConstraints.get(MAX_SIZE_CONSTRAINT);
        SizeConstraint sizeConstraint = (SizeConstraint) appliedConstraints.get(SIZE_CONSTRAINT);

        if (maxSizeConstraint != null || sizeConstraint != null) {
            int maxSizeConstraintValue = maxSizeConstraint == null ? Integer.MAX_VALUE
                    : maxSizeConstraint.getMaxSize();
            int sizeConstraintHighValue = sizeConstraint == null ? Integer.MAX_VALUE
                    : sizeConstraint.getRange().getToInt();
            maxSize = Math.min(maxSizeConstraintValue, sizeConstraintHighValue);
        }

        return maxSize;
    }

    /**
     * @param maxSize The maxSize to set.
     */
    public void setMaxSize(Integer maxSize) {
        Constraint c = appliedConstraints.get(MAX_SIZE_CONSTRAINT);
        if (c == null) {
            c = new MaxSizeConstraint();
            c.setOwningClass(owningClass);
            c.setPropertyName(propertyName);
            appliedConstraints.put(MAX_SIZE_CONSTRAINT, c);
        }
        c.setParameter(maxSize);
    }

    /**
     * @return Returns the minSize.
     */
    public Integer getMinSize() {
        Integer minSize = null;

        MinSizeConstraint minSizeConstraint = (MinSizeConstraint) appliedConstraints.get(MIN_SIZE_CONSTRAINT);
        SizeConstraint sizeConstraint = (SizeConstraint) appliedConstraints.get(SIZE_CONSTRAINT);

        if (minSizeConstraint != null || sizeConstraint != null) {
            int minSizeConstraintValue = minSizeConstraint == null ? Integer.MIN_VALUE
                    : minSizeConstraint.getMinSize();
            int sizeConstraintLowValue = sizeConstraint == null ? Integer.MIN_VALUE
                    : sizeConstraint.getRange().getFromInt();

            minSize = Math.max(minSizeConstraintValue, sizeConstraintLowValue);
        }

        return minSize;
    }

    /**
     * @param minSize The minLength to set.
     */
    public void setMinSize(Integer minSize) {
        Constraint c = appliedConstraints.get(MIN_SIZE_CONSTRAINT);
        if (c == null) {
            c = new MinSizeConstraint();
            c.setOwningClass(owningClass);
            c.setPropertyName(propertyName);
            appliedConstraints.put(MIN_SIZE_CONSTRAINT, c);
        }
        c.setParameter(minSize);
    }

    /**
     * @param notEqual The notEqual to set.
     */
    public void setNotEqual(Object notEqual) {
        if (notEqual == null) {
            appliedConstraints.remove(NOT_EQUAL_CONSTRAINT);
        } else {
            Constraint c = new NotEqualConstraint();
            c.setOwningClass(owningClass);
            c.setPropertyName(propertyName);
            c.setParameter(notEqual);
            appliedConstraints.put(NOT_EQUAL_CONSTRAINT, c);
        }
    }

    /**
     * @return Returns the nullable.
     */
    public boolean isNullable() {
        if (appliedConstraints.containsKey(NULLABLE_CONSTRAINT)) {
            NullableConstraint nc = (NullableConstraint) appliedConstraints.get(NULLABLE_CONSTRAINT);
            return nc.isNullable();
        }

        return false;
    }

    /**
     * @param nullable The nullable to set.
     */
    public void setNullable(boolean nullable) {
        NullableConstraint nc = (NullableConstraint) appliedConstraints.get(NULLABLE_CONSTRAINT);
        if (nc == null) {
            nc = new NullableConstraint();
            nc.setOwningClass(owningClass);
            nc.setPropertyName(propertyName);
            appliedConstraints.put(NULLABLE_CONSTRAINT, nc);
        }

        nc.setParameter(nullable);
    }

    /**
     * @return Returns the propertyName.
     */
    public String getPropertyName() {
        return propertyName;
    }

    /**
     * @param propertyName The propertyName to set.
     */
    public void setPropertyName(String propertyName) {
        this.propertyName = propertyName;
    }

    /**
     * @return Returns the url.
     */
    public boolean isUrl() {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("URL constraint can only be applied to a String property",
                    URL_CONSTRAINT, owningClass);
        }
        return appliedConstraints.containsKey(URL_CONSTRAINT);
    }

    /**
     * @param url The url to set.
     */
    public void setUrl(boolean url) {
        if (isNotValidStringType()) {
            throw new MissingPropertyException("URL constraint can only be applied to a String property",
                    URL_CONSTRAINT, owningClass);
        }

        Constraint c = appliedConstraints.get(URL_CONSTRAINT);
        if (url) {
            if (c == null) {
                c = new UrlConstraint();
                c.setOwningClass(owningClass);
                c.setPropertyName(propertyName);
                appliedConstraints.put(URL_CONSTRAINT, c);
            }
            c.setParameter(true);
        } else {
            if (c != null) {
                appliedConstraints.remove(URL_CONSTRAINT);
            }
        }
    }

    /**
     * @return Returns the display.
     */
    public boolean isDisplay() {
        return display;
    }

    /**
     * @param display The display to set.
     */
    public void setDisplay(boolean display) {
        this.display = display;
    }

    /**
     * @return Returns the editable.
     */
    public boolean isEditable() {
        return editable;
    }

    /**
     * @param editable The editable to set.
     */
    public void setEditable(boolean editable) {
        this.editable = editable;
    }

    /**
     * @return Returns the order.
     */
    public int getOrder() {
        return order;
    }

    /**
     * @param order The order to set.
     */
    public void setOrder(int order) {
        this.order = order;
    }

    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    public boolean isPassword() {
        return password;
    }

    public void setPassword(boolean password) {
        this.password = password;
    }

    @SuppressWarnings("rawtypes")
    public Map getAttributes() {
        return attributes;
    }

    @SuppressWarnings("rawtypes")
    public void setAttributes(Map attributes) {
        this.attributes = attributes;
    }

    public String getWidget() {
        return widget;
    }

    public void setWidget(String widget) {
        this.widget = widget;
    }

    /**
     * The message source used to evaluate error messages
     * @param source The MessageSource instance to use to resolve messages
     */
    public void setMessageSource(MessageSource source) {
        messageSource = source;
    }

    /**
     * Validate this constrainted property against specified property value
     *
     * @param target The target object to validate
     * @param propertyValue The value of the property to validate
     * @param errors The Errors instances to report errors to
     */
    public void validate(Object target, Object propertyValue, Errors errors) {
        List<Constraint> delayedConstraints = new ArrayList<Constraint>();

        // validate only vetoing constraints first, putting non-vetoing into delayedConstraints
        for (Constraint c : appliedConstraints.values()) {
            if (c instanceof VetoingConstraint) {
                c.setMessageSource(messageSource);
                // stop validation process when constraint vetoes
                if (((VetoingConstraint) c).validateWithVetoing(target, propertyValue, errors)) {
                    return;
                }
            } else {
                delayedConstraints.add(c);
            }
        }

        // process non-vetoing constraints
        for (Constraint c : delayedConstraints) {
            c.setMessageSource(messageSource);
            c.validate(target, propertyValue, errors);
        }
    }

    /**
     * Checks with this ConstraintedProperty instance supports applying the specified constraint.
     *
     * @param constraintName The name of the constraint
     * @return true if the constraint is supported
     */
    public boolean supportsContraint(String constraintName) {

        if (!constraints.containsKey(constraintName)) {
            return bean.isWritableProperty(constraintName);
        }

        try {
            Constraint c = instantiateConstraint(constraintName, false);
            return c != null; // && c.supports(propertyType);
        } catch (Exception e) {
            LOG.error("Exception thrown instantiating constraint [" + constraintName + "] to class [" + owningClass
                    + "]", e);
            throw new RuntimeException("Exception thrown instantiating  constraint [" + constraintName
                    + "] to class [" + owningClass + "]");
        }
    }

    /**
     * Applies a constraint for the specified name and consraint value.
     *
     * @param constraintName The name of the constraint
     * @param constrainingValue The constraining value
     *
     * @throws ConstraintException Thrown when the specified constraint is not supported by this ConstrainedProperty. Use <code>supportsContraint(String constraintName)</code> to check before calling
     */
    public void applyConstraint(String constraintName, Object constrainingValue) {

        if (constraints.containsKey(constraintName)) {
            if (constrainingValue == null) {
                appliedConstraints.remove(constraintName);
            } else {
                try {
                    Constraint c = instantiateConstraint(constraintName, true);
                    if (c != null) {
                        c.setOwningClass(owningClass);
                        c.setParameter(constrainingValue);
                        appliedConstraints.put(constraintName, c);
                    }
                } catch (Exception e) {
                    LOG.error("Exception thrown applying constraint [" + constraintName + "] to class ["
                            + owningClass + "] for value [" + constrainingValue + "]: " + e.getMessage(), e);
                    throw new RuntimeException(
                            "Exception thrown applying constraint [" + constraintName + "] to class [" + owningClass
                                    + "] for value [" + constrainingValue + "]: " + e.getMessage(),
                            e);
                }
            }
        } else if (bean.isWritableProperty(constraintName)) {
            bean.setPropertyValue(constraintName, constrainingValue);
        } else {
            throw new RuntimeException("Constraint [" + constraintName + "] is not supported for property ["
                    + propertyName + "] of class [" + owningClass + "] with type [" + propertyType + "]");
        }
    }

    private Constraint instantiateConstraint(String constraintName, boolean validate)
            throws InstantiationException, IllegalAccessException {
        List<Object> candidateConstraints = constraints.get(constraintName);

        for (Object constraintFactory : candidateConstraints) {

            Constraint c;
            if (constraintFactory instanceof ConstraintFactory) {
                c = ((ConstraintFactory) constraintFactory).newInstance();
            } else {
                c = (Constraint) ((Class<?>) constraintFactory).newInstance();
            }

            //c.setOwningClass(owningClass);
            c.setPropertyName(propertyName);

            if (validate && c.isValid()) {
                return c;
            }
            if (!validate) {
                return c;
            }

        }
        return null;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ConstrainedPropertyGunn{");
        sb.append("propertyName='").append(propertyName).append('\'');
        sb.append(", propertyType=").append(propertyType);
        sb.append(", appliedConstraints=").append(appliedConstraints);
        sb.append(", owningClass=").append(owningClass);
        sb.append(", bean=").append(bean);
        sb.append(", display=").append(display);
        sb.append(", editable=").append(editable);
        sb.append(", order=").append(order);
        sb.append(", format='").append(format).append('\'');
        sb.append(", widget='").append(widget).append('\'');
        sb.append(", password=").append(password);
        sb.append(", attributes=").append(attributes);
        sb.append(", messageSource=").append(messageSource);
        sb.append(", metaConstraints=").append(metaConstraints);
        sb.append('}');
        return sb.toString();
    }

    /**
     * Adds a meta constraints which is a non-validating informational constraint.
     *
     * @param name The name of the constraint
     * @param value The value
     */
    public void addMetaConstraint(String name, Object value) {
        metaConstraints.put(name, value);
    }

    /**
     * Obtains the value of the named meta constraint.
     * @param name The name of the constraint
     * @return The value
     */
    public Object getMetaConstraintValue(String name) {
        return metaConstraints.get(name);
    }
}