001/*
002 * Copyright 2002-2018 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.lang.Nullable;
025import org.springframework.util.StringUtils;
026
027/**
028 * Property editor for {@code java.util.Date},
029 * supporting a custom {@code java.text.DateFormat}.
030 *
031 * <p>This is not meant to be used as system PropertyEditor but rather
032 * as locale-specific date editor within custom controller code,
033 * parsing user-entered number strings into Date properties of beans
034 * and rendering them in the UI form.
035 *
036 * <p>In web MVC code, this editor will typically be registered with
037 * {@code binder.registerCustomEditor}.
038 *
039 * @author Juergen Hoeller
040 * @since 28.04.2003
041 * @see java.util.Date
042 * @see java.text.DateFormat
043 * @see org.springframework.validation.DataBinder#registerCustomEditor
044 */
045public class CustomDateEditor extends PropertyEditorSupport {
046
047        private final DateFormat dateFormat;
048
049        private final boolean allowEmpty;
050
051        private final int exactDateLength;
052
053
054        /**
055         * Create a new CustomDateEditor instance, using the given DateFormat
056         * for parsing and rendering.
057         * <p>The "allowEmpty" parameter states if an empty String should
058         * be allowed for parsing, i.e. get interpreted as null value.
059         * Otherwise, an IllegalArgumentException gets thrown in that case.
060         * @param dateFormat the DateFormat to use for parsing and rendering
061         * @param allowEmpty if empty strings should be allowed
062         */
063        public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty) {
064                this.dateFormat = dateFormat;
065                this.allowEmpty = allowEmpty;
066                this.exactDateLength = -1;
067        }
068
069        /**
070         * Create a new CustomDateEditor instance, using the given DateFormat
071         * for parsing and rendering.
072         * <p>The "allowEmpty" parameter states if an empty String should
073         * be allowed for parsing, i.e. get interpreted as null value.
074         * Otherwise, an IllegalArgumentException gets thrown in that case.
075         * <p>The "exactDateLength" parameter states that IllegalArgumentException gets
076         * thrown if the String does not exactly match the length specified. This is useful
077         * because SimpleDateFormat does not enforce strict parsing of the year part,
078         * not even with {@code setLenient(false)}. Without an "exactDateLength"
079         * specified, the "01/01/05" would get parsed to "01/01/0005". However, even
080         * with an "exactDateLength" specified, prepended zeros in the day or month
081         * part may still allow for a shorter year part, so consider this as just
082         * one more assertion that gets you closer to the intended date format.
083         * @param dateFormat the DateFormat to use for parsing and rendering
084         * @param allowEmpty if empty strings should be allowed
085         * @param exactDateLength the exact expected length of the date String
086         */
087        public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty, int exactDateLength) {
088                this.dateFormat = dateFormat;
089                this.allowEmpty = allowEmpty;
090                this.exactDateLength = exactDateLength;
091        }
092
093
094        /**
095         * Parse the Date from the given text, using the specified DateFormat.
096         */
097        @Override
098        public void setAsText(@Nullable String text) throws IllegalArgumentException {
099                if (this.allowEmpty && !StringUtils.hasText(text)) {
100                        // Treat empty String as null value.
101                        setValue(null);
102                }
103                else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) {
104                        throw new IllegalArgumentException(
105                                        "Could not parse date: it is not exactly" + this.exactDateLength + "characters long");
106                }
107                else {
108                        try {
109                                setValue(this.dateFormat.parse(text));
110                        }
111                        catch (ParseException ex) {
112                                throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
113                        }
114                }
115        }
116
117        /**
118         * Format the Date as String, using the specified DateFormat.
119         */
120        @Override
121        public String getAsText() {
122                Date value = (Date) getValue();
123                return (value != null ? this.dateFormat.format(value) : "");
124        }
125
126}