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