001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.validation.validator;
018
019import java.io.Serializable;
020import java.util.Locale;
021
022import org.apache.wicket.behavior.Behavior;
023import org.apache.wicket.validation.IValidatable;
024import org.apache.wicket.validation.IValidationError;
025import org.apache.wicket.validation.IValidator;
026import org.apache.wicket.validation.ValidationError;
027
028/**
029 * Base class for validators that check if a given value falls within [min,max] range.
030 * 
031 * If either min or max are {@code null} they are not checked.
032 * 
033 * <p>
034 * Resource keys:
035 * <ul>
036 * <li>{@code <class.simpleName>.exact} if min==max</li>
037 * <li>{@code <class.simpleName>.range} if both min and max are not {@code null}</li>
038 * <li>{@code <class.simpleName>.minimum} if max is {@code null}</li>
039 * <li>{@code <class.simpleName>.maximum} if min is {@code null}</li>
040 * </ul>
041 * </p>
042 * 
043 * <p>
044 * Error Message Variables:
045 * <ul>
046 * <li>{@code name}: the id of {@code Component} that failed</li>
047 * <li>{@code label}: the label of the {@code Component} (either comes from
048 * {@code FormComponent.labelModel} or resource key {@code <form-id>.<form-component-id>}</li>
049 * <li>{@code input}: the input value</li>
050 * <li>{@code minimum}: the minimum allowed value</li>
051 * <li>{@code maximum}: the maximum allowed value</li>
052 * </ul>
053 * </p>
054 * 
055 * @param <R>
056 *            type of range value
057 * @param <V>
058 *            type of validatable
059 * 
060 * @author igor
061 */
062public abstract class AbstractRangeValidator<R extends Comparable<? super R> & Serializable, V extends Serializable>
063        extends Behavior implements IValidator<V>
064{
065        private static final long serialVersionUID = 1L;
066        private R minimum;
067        private R maximum;
068
069        /**
070         * Constructor that sets the minimum and maximum values.
071         * 
072         * @param minimum
073         *            the minimum value
074         * @param maximum
075         *            the maximum value
076         */
077        public AbstractRangeValidator(R minimum, R maximum)
078        {
079                setRange(minimum, maximum);
080        }
081
082        /**
083         * Constructor used for subclasses who want to set the range using
084         * {@link #setRange(Comparable, Comparable)}
085         */
086        protected AbstractRangeValidator()
087        {
088        }
089
090        /**
091         * Sets validator range
092         * 
093         * @param minimum
094         * @param maximum
095         */
096        protected final void setRange(R minimum, R maximum)
097        {
098                if (minimum == null && maximum == null)
099                {
100                        throw new IllegalArgumentException("Both minimum and maximum values cannot be null");
101                }
102                this.minimum = minimum;
103                this.maximum = maximum;
104        }
105
106        @Override
107        public void validate(IValidatable<V> validatable)
108        {
109                R value = getValue(validatable);
110                final R min = getMinimum();
111                final R max = getMaximum();
112                if ((min != null && value.compareTo(min) < 0) || (max != null && value.compareTo(max) > 0))
113                {
114                        Mode mode = getMode();
115                        ValidationError error = new ValidationError(this, mode.getVariation());
116                        if (min != null)
117                        {
118                                error.setVariable("minimum", min);
119                        }
120                        if (max != null)
121                        {
122                                error.setVariable("maximum", max);
123                        }
124                        if (mode == Mode.EXACT)
125                        {
126                                error.setVariable("exact", max);
127                        }
128                        validatable.error(decorate(error, validatable));
129                }
130        }
131
132        /**
133         * Gets the value that should be validated against the range
134         * 
135         * @param validatable
136         * @return value to validate
137         */
138        protected abstract R getValue(IValidatable<V> validatable);
139
140        /**
141         * Gets the minimum value.
142         * 
143         * @return minimum value
144         */
145        public R getMinimum()
146        {
147                return minimum;
148        }
149
150        /**
151         * Gets the maximum value.
152         * 
153         * @return maximum value
154         */
155        public R getMaximum()
156        {
157                return maximum;
158        }
159
160        /**
161         * Allows subclasses to decorate reported errors
162         * 
163         * @param error
164         * @param validatable
165         * @return decorated error
166         */
167        protected IValidationError decorate(IValidationError error, IValidatable<V> validatable)
168        {
169                return error;
170        }
171
172        /**
173         * Gets validation mode which is determined by whether min, max, or both values are provided
174         * 
175         * @return validation mode
176         */
177        public final Mode getMode()
178        {
179                final R min = getMinimum();
180                final R max = getMaximum();
181
182                if (min == null && max != null)
183                {
184                        return Mode.MAXIMUM;
185                }
186                else if (max == null && min != null)
187                {
188                        return Mode.MINIMUM;
189                }
190                else if ((min == null && max == null) || max.equals(min))
191                {
192                        return Mode.EXACT;
193                }
194                else
195                {
196                        return Mode.RANGE;
197                }
198        }
199
200        /**
201         * Validator mode
202         * 
203         * @author igor
204         */
205        public static enum Mode {
206                MINIMUM, MAXIMUM, RANGE, EXACT;
207
208                public String getVariation()
209                {
210                        return name().toLowerCase(Locale.ROOT);
211                }
212        }
213
214}