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.util.convert.converter; 018 019import java.math.BigDecimal; 020import java.text.DecimalFormat; 021import java.text.DecimalFormatSymbols; 022import java.text.NumberFormat; 023import java.util.Locale; 024import java.util.concurrent.ConcurrentHashMap; 025 026import org.apache.wicket.util.convert.ConversionException; 027 028 029/** 030 * Base class for all number converters. 031 * 032 * @author Jonathan Locke 033 * @param <N> 034 */ 035public abstract class AbstractNumberConverter<N extends Number> extends AbstractConverter<N> 036{ 037 private static final long serialVersionUID = 1L; 038 039 /** The date format to use */ 040 private final ConcurrentHashMap<Locale, NumberFormat> numberFormats = new ConcurrentHashMap<>(); 041 042 /** 043 * @param locale 044 * The locale 045 * @return Returns the numberFormat. 046 */ 047 public NumberFormat getNumberFormat(final Locale locale) 048 { 049 NumberFormat numberFormat = numberFormats.get(locale); 050 if (numberFormat == null) 051 { 052 numberFormat = newNumberFormat(locale); 053 054 if (numberFormat instanceof DecimalFormat) 055 { 056 // always try to parse BigDecimals 057 ((DecimalFormat)numberFormat).setParseBigDecimal(true); 058 } 059 060 NumberFormat tmpNumberFormat = numberFormats.putIfAbsent(locale, numberFormat); 061 if (tmpNumberFormat != null) 062 { 063 numberFormat = tmpNumberFormat; 064 } 065 } 066 // return a clone because NumberFormat.get..Instance use a pool 067 return (NumberFormat)numberFormat.clone(); 068 } 069 070 /** 071 * Creates a new {@link NumberFormat} for the given locale. The instance is later cached and is 072 * accessible through {@link #getNumberFormat(Locale)} 073 * 074 * @param locale 075 * @return number format 076 */ 077 protected abstract NumberFormat newNumberFormat(final Locale locale); 078 079 /** 080 * Parses a value as a String and returns a Number. 081 * 082 * @param value 083 * The object to parse (after converting with toString()) 084 * @param min 085 * The minimum allowed value or {@code null} if none 086 * @param max 087 * The maximum allowed value or {@code null} if none 088 * @param locale 089 * @return The number 090 * @throws ConversionException 091 * if value is unparsable or out of range 092 */ 093 protected BigDecimal parse(Object value, final BigDecimal min, final BigDecimal max, Locale locale) 094 { 095 if (locale == null) 096 { 097 locale = Locale.getDefault(Locale.Category.FORMAT); 098 } 099 100 if (value == null) 101 { 102 return null; 103 } 104 else if (value instanceof String) 105 { 106 char groupingSeparator = DecimalFormatSymbols.getInstance(locale).getGroupingSeparator(); 107 // Convert spaces to no-break space (groupingSeparator) as required by Java formats: 108 // http://bugs.sun.com/view_bug.do?bug_id=4510618 109 value = ((String)value).replaceAll("(\\d+)\\s(?=\\d)", "$1" + groupingSeparator); 110 } 111 112 final NumberFormat numberFormat = getNumberFormat(locale); 113 final N number = parse(numberFormat, value, locale); 114 115 if (number == null) 116 { 117 return null; 118 } 119 120 BigDecimal bigDecimal; 121 if (number instanceof BigDecimal) 122 { 123 bigDecimal = (BigDecimal)number; 124 } 125 else 126 { 127 // should occur rarely, see #getNumberFormat(Locale) 128 bigDecimal = new BigDecimal(number.toString()); 129 } 130 131 if (min != null && bigDecimal.compareTo(min) < 0) 132 { 133 throw newConversionException("Value cannot be less than " + min, value, locale) 134 .setFormat(numberFormat); 135 } 136 137 if (max != null && bigDecimal.compareTo(max) > 0) 138 { 139 throw newConversionException("Value cannot be greater than " + max, value, locale) 140 .setFormat(numberFormat); 141 } 142 143 return bigDecimal; 144 } 145 146 @Override 147 public String convertToString(final N value, final Locale locale) 148 { 149 NumberFormat fmt = getNumberFormat(locale); 150 if (fmt != null) 151 { 152 return fmt.format(value); 153 } 154 return value.toString(); 155 } 156}