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.NumberFormat;
021
022import org.springframework.lang.Nullable;
023import org.springframework.util.NumberUtils;
024import org.springframework.util.StringUtils;
025
026/**
027 * Property editor for any Number subclass such as Short, Integer, Long,
028 * BigInteger, Float, Double, BigDecimal. Can use a given NumberFormat for
029 * (locale-specific) parsing and rendering, or alternatively the default
030 * {@code decode} / {@code valueOf} / {@code toString} methods.
031 *
032 * <p>This is not meant to be used as system PropertyEditor but rather
033 * as locale-specific number editor within custom controller code,
034 * parsing user-entered number strings into Number properties of beans
035 * and rendering them in the UI form.
036 *
037 * <p>In web MVC code, this editor will typically be registered with
038 * {@code binder.registerCustomEditor} calls.
039 *
040 * @author Juergen Hoeller
041 * @since 06.06.2003
042 * @see Number
043 * @see java.text.NumberFormat
044 * @see org.springframework.validation.DataBinder#registerCustomEditor
045 */
046public class CustomNumberEditor extends PropertyEditorSupport {
047
048        private final Class<? extends Number> numberClass;
049
050        @Nullable
051        private final NumberFormat numberFormat;
052
053        private final boolean allowEmpty;
054
055
056        /**
057         * Create a new CustomNumberEditor instance, using the default
058         * {@code valueOf} methods for parsing and {@code toString}
059         * methods for rendering.
060         * <p>The "allowEmpty" parameter states if an empty String should
061         * be allowed for parsing, i.e. get interpreted as {@code null} value.
062         * Else, an IllegalArgumentException gets thrown in that case.
063         * @param numberClass the Number subclass to generate
064         * @param allowEmpty if empty strings should be allowed
065         * @throws IllegalArgumentException if an invalid numberClass has been specified
066         * @see org.springframework.util.NumberUtils#parseNumber(String, Class)
067         * @see Integer#valueOf
068         * @see Integer#toString
069         */
070        public CustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty) throws IllegalArgumentException {
071                this(numberClass, null, allowEmpty);
072        }
073
074        /**
075         * Create a new CustomNumberEditor instance, using the given NumberFormat
076         * for parsing and rendering.
077         * <p>The allowEmpty parameter states if an empty String should
078         * be allowed for parsing, i.e. get interpreted as {@code null} value.
079         * Else, an IllegalArgumentException gets thrown in that case.
080         * @param numberClass the Number subclass to generate
081         * @param numberFormat the NumberFormat to use for parsing and rendering
082         * @param allowEmpty if empty strings should be allowed
083         * @throws IllegalArgumentException if an invalid numberClass has been specified
084         * @see org.springframework.util.NumberUtils#parseNumber(String, Class, java.text.NumberFormat)
085         * @see java.text.NumberFormat#parse
086         * @see java.text.NumberFormat#format
087         */
088        public CustomNumberEditor(Class<? extends Number> numberClass,
089                        @Nullable NumberFormat numberFormat, boolean allowEmpty) throws IllegalArgumentException {
090
091                if (!Number.class.isAssignableFrom(numberClass)) {
092                        throw new IllegalArgumentException("Property class must be a subclass of Number");
093                }
094                this.numberClass = numberClass;
095                this.numberFormat = numberFormat;
096                this.allowEmpty = allowEmpty;
097        }
098
099
100        /**
101         * Parse the Number from the given text, using the specified NumberFormat.
102         */
103        @Override
104        public void setAsText(String text) throws IllegalArgumentException {
105                if (this.allowEmpty && !StringUtils.hasText(text)) {
106                        // Treat empty String as null value.
107                        setValue(null);
108                }
109                else if (this.numberFormat != null) {
110                        // Use given NumberFormat for parsing text.
111                        setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat));
112                }
113                else {
114                        // Use default valueOf methods for parsing text.
115                        setValue(NumberUtils.parseNumber(text, this.numberClass));
116                }
117        }
118
119        /**
120         * Coerce a Number value into the required target class, if necessary.
121         */
122        @Override
123        public void setValue(@Nullable Object value) {
124                if (value instanceof Number) {
125                        super.setValue(NumberUtils.convertNumberToTargetClass((Number) value, this.numberClass));
126                }
127                else {
128                        super.setValue(value);
129                }
130        }
131
132        /**
133         * Format the Number as String, using the specified NumberFormat.
134         */
135        @Override
136        public String getAsText() {
137                Object value = getValue();
138                if (value == null) {
139                        return "";
140                }
141                if (this.numberFormat != null) {
142                        // Use NumberFormat for rendering value.
143                        return this.numberFormat.format(value);
144                }
145                else {
146                        // Use toString method for rendering value.
147                        return value.toString();
148                }
149        }
150
151}