001/*
002 * Copyright 2002-2012 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.propertyeditors;
018
019import java.beans.PropertyEditorSupport;
020import java.text.DateFormat;
021import java.text.ParseException;
022import java.util.Date;
023
024import org.springframework.util.StringUtils;
025
026/**
027 * Property editor for {@code java.util.Date},
028 * supporting a custom {@code java.text.DateFormat}.
029 *
030 * <p>This is not meant to be used as system PropertyEditor but rather
031 * as locale-specific date editor within custom controller code,
032 * parsing user-entered number strings into Date properties of beans
033 * and rendering them in the UI form.
034 *
035 * <p>In web MVC code, this editor will typically be registered with
036 * {@code binder.registerCustomEditor}.
037 *
038 * @author Juergen Hoeller
039 * @since 28.04.2003
040 * @see java.util.Date
041 * @see java.text.DateFormat
042 * @see org.springframework.validation.DataBinder#registerCustomEditor
043 */
044public class CustomDateEditor extends PropertyEditorSupport {
045
046        private final DateFormat dateFormat;
047
048        private final boolean allowEmpty;
049
050        private final int exactDateLength;
051
052
053        /**
054         * Create a new CustomDateEditor instance, using the given DateFormat
055         * for parsing and rendering.
056         * <p>The "allowEmpty" parameter states if an empty String should
057         * be allowed for parsing, i.e. get interpreted as null value.
058         * Otherwise, an IllegalArgumentException gets thrown in that case.
059         * @param dateFormat DateFormat to use for parsing and rendering
060         * @param allowEmpty if empty strings should be allowed
061         */
062        public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty) {
063                this.dateFormat = dateFormat;
064                this.allowEmpty = allowEmpty;
065                this.exactDateLength = -1;
066        }
067
068        /**
069         * Create a new CustomDateEditor instance, using the given DateFormat
070         * for parsing and rendering.
071         * <p>The "allowEmpty" parameter states if an empty String should
072         * be allowed for parsing, i.e. get interpreted as null value.
073         * Otherwise, an IllegalArgumentException gets thrown in that case.
074         * <p>The "exactDateLength" parameter states that IllegalArgumentException gets
075         * thrown if the String does not exactly match the length specified. This is useful
076         * because SimpleDateFormat does not enforce strict parsing of the year part,
077         * not even with {@code setLenient(false)}. Without an "exactDateLength"
078         * specified, the "01/01/05" would get parsed to "01/01/0005". However, even
079         * with an "exactDateLength" specified, prepended zeros in the day or month
080         * part may still allow for a shorter year part, so consider this as just
081         * one more assertion that gets you closer to the intended date format.
082         * @param dateFormat DateFormat to use for parsing and rendering
083         * @param allowEmpty if empty strings should be allowed
084         * @param exactDateLength the exact expected length of the date String
085         */
086        public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty, int exactDateLength) {
087                this.dateFormat = dateFormat;
088                this.allowEmpty = allowEmpty;
089                this.exactDateLength = exactDateLength;
090        }
091
092
093        /**
094         * Parse the Date from the given text, using the specified DateFormat.
095         */
096        @Override
097        public void setAsText(String text) throws IllegalArgumentException {
098                if (this.allowEmpty && !StringUtils.hasText(text)) {
099                        // Treat empty String as null value.
100                        setValue(null);
101                }
102                else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) {
103                        throw new IllegalArgumentException(
104                                        "Could not parse date: it is not exactly" + this.exactDateLength + "characters long");
105                }
106                else {
107                        try {
108                                setValue(this.dateFormat.parse(text));
109                        }
110                        catch (ParseException ex) {
111                                throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
112                        }
113                }
114        }
115
116        /**
117         * Format the Date as String, using the specified DateFormat.
118         */
119        @Override
120        public String getAsText() {
121                Date value = (Date) getValue();
122                return (value != null ? this.dateFormat.format(value) : "");
123        }
124
125}