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.util.Locale;
020
021import org.apache.wicket.Component;
022import org.apache.wicket.markup.ComponentTag;
023import org.apache.wicket.validation.IValidatable;
024import org.apache.wicket.validation.IValidationError;
025import org.apache.wicket.validation.ValidationError;
026
027/**
028 * Validator for checking if length of a string falls within [min,max] range.
029 * 
030 * If either min or max are {@code null} they are not checked.
031 * 
032 * <p>
033 * If the component is attached to an {@code input} tag, a {@code maxlen} attribute will be added if
034 * the maximum is set.
035 * 
036 * *
037 * <p>
038 * Resource keys:
039 * <ul>
040 * <li>{@code StringValidator.exact} if min==max ({@link #exactLength(int)})</li>
041 * <li>{@code StringValidator.range} if both min and max are not {@code null}</li>
042 * <li>{@code StringValidator.minimum} if max is {@code null} ({@link #minimumLength(int)})</li>
043 * <li>{@code StringValidator.maximum} if min is {@code null} ({@link #maximumLength(int)})</li>
044 * </ul>
045 * </p>
046 * 
047 * <p>
048 * Error Message Variables:
049 * <ul>
050 * <li>{@code name}: the id of {@code Component} that failed</li>
051 * <li>{@code label}: the label of the {@code Component} (either comes from
052 * {@code FormComponent.labelModel} or resource key {@code <form-id>.<form-component-id>}</li>
053 * <li>{@code input}: the input value</li>
054 * <li>{@code length}: the length of the entered</li>
055 * <li>{@code minimum}: the minimum allowed length</li>
056 * <li>{@code maximum}: the maximum allowed length</li>
057 * </ul>
058 * </p>
059 * 
060 * @author igor
061 */
062public class StringValidator extends AbstractRangeValidator<Integer, String>
063{
064        private static final long serialVersionUID = 1L;
065
066        /**
067         * Constructor that sets the minimum and maximum length values.
068         * 
069         * @param minimum
070         *            the minimum length
071         * @param maximum
072         *            the maximum length
073         */
074        public StringValidator(Integer minimum, Integer maximum)
075        {
076                setRange(minimum, maximum);
077        }
078
079        /**
080         * Constructor used for subclasses who want to set the range using
081         * {@link #setRange(Comparable, Comparable)}
082         */
083        protected StringValidator()
084        {
085        }
086
087        @Override
088        protected Integer getValue(IValidatable<String> validatable)
089        {
090                return validatable.getValue().length();
091        }
092
093        @Override
094        protected IValidationError decorate(IValidationError error, IValidatable<String> validatable)
095        {
096                error = super.decorate(error, validatable);
097                if (error instanceof ValidationError)
098                {
099                        ((ValidationError)error).setVariable("length", validatable.getValue().length());
100                }
101                return error;
102        }
103
104        @Override
105        public void onComponentTag(Component component, ComponentTag tag)
106        {
107                super.onComponentTag(component, tag);
108
109                String tagName = tag.getName().toLowerCase(Locale.ROOT);
110                boolean hasLengthAttribute = hasLengthAttribute(tagName);
111
112                Integer maximum = getMaximum();
113                if (maximum != null && hasLengthAttribute)
114                {
115                        tag.put("maxlength", maximum);
116                }
117
118                Integer minimum = getMinimum();
119                if (minimum != null && hasLengthAttribute)
120                {
121                        tag.put("minlength", minimum);
122                }
123        }
124
125        protected boolean hasLengthAttribute(String tagName)
126        {
127                return "input".equalsIgnoreCase(tagName) || "textarea".equalsIgnoreCase(tagName);
128        }
129
130        /**
131         * @param length
132         * @return a {@link StringValidator} that generates an error if a string is not of an exact
133         *         length
134         */
135        public static StringValidator exactLength(int length)
136        {
137                return new StringValidator(length, length);
138        }
139
140        /**
141         * @param length
142         * @return a {@link StringValidator} that generates an error if a string exceeds a maximum
143         *         length
144         */
145        public static StringValidator maximumLength(int length)
146        {
147                return new StringValidator(null, length);
148        }
149
150        /**
151         * @param length
152         * @return a {@link StringValidator} that generates an error if a string is not of a minimum
153         *         length
154         */
155        public static StringValidator minimumLength(int length)
156        {
157                return new StringValidator(length, null);
158        }
159
160        /**
161         * @param minimum
162         * @param maximum
163         * @return a {@link StringValidator} that generates an error if the length of a string is not
164         *         between (inclusive) minimum and maximum
165         */
166        public static StringValidator lengthBetween(int minimum, int maximum)
167        {
168                return new StringValidator(minimum, maximum);
169        }
170
171}