001/* 002 * Copyright 2002-2016 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.util; 018 019import java.math.BigDecimal; 020import java.math.BigInteger; 021import java.text.DecimalFormat; 022import java.text.NumberFormat; 023import java.text.ParseException; 024import java.util.Collections; 025import java.util.HashSet; 026import java.util.Set; 027 028/** 029 * Miscellaneous utility methods for number conversion and parsing. 030 * <p>Mainly for internal use within the framework; consider Apache's 031 * Commons Lang for a more comprehensive suite of number utilities. 032 * 033 * @author Juergen Hoeller 034 * @author Rob Harrop 035 * @since 1.1.2 036 */ 037public abstract class NumberUtils { 038 039 private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE); 040 041 private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); 042 043 /** 044 * Standard number types (all immutable): 045 * Byte, Short, Integer, Long, BigInteger, Float, Double, BigDecimal. 046 */ 047 public static final Set<Class<?>> STANDARD_NUMBER_TYPES; 048 049 static { 050 Set<Class<?>> numberTypes = new HashSet<Class<?>>(8); 051 numberTypes.add(Byte.class); 052 numberTypes.add(Short.class); 053 numberTypes.add(Integer.class); 054 numberTypes.add(Long.class); 055 numberTypes.add(BigInteger.class); 056 numberTypes.add(Float.class); 057 numberTypes.add(Double.class); 058 numberTypes.add(BigDecimal.class); 059 STANDARD_NUMBER_TYPES = Collections.unmodifiableSet(numberTypes); 060 } 061 062 063 /** 064 * Convert the given number into an instance of the given target class. 065 * @param number the number to convert 066 * @param targetClass the target class to convert to 067 * @return the converted number 068 * @throws IllegalArgumentException if the target class is not supported 069 * (i.e. not a standard Number subclass as included in the JDK) 070 * @see java.lang.Byte 071 * @see java.lang.Short 072 * @see java.lang.Integer 073 * @see java.lang.Long 074 * @see java.math.BigInteger 075 * @see java.lang.Float 076 * @see java.lang.Double 077 * @see java.math.BigDecimal 078 */ 079 @SuppressWarnings("unchecked") 080 public static <T extends Number> T convertNumberToTargetClass(Number number, Class<T> targetClass) 081 throws IllegalArgumentException { 082 083 Assert.notNull(number, "Number must not be null"); 084 Assert.notNull(targetClass, "Target class must not be null"); 085 086 if (targetClass.isInstance(number)) { 087 return (T) number; 088 } 089 else if (Byte.class == targetClass) { 090 long value = checkedLongValue(number, targetClass); 091 if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { 092 raiseOverflowException(number, targetClass); 093 } 094 return (T) Byte.valueOf(number.byteValue()); 095 } 096 else if (Short.class == targetClass) { 097 long value = checkedLongValue(number, targetClass); 098 if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { 099 raiseOverflowException(number, targetClass); 100 } 101 return (T) Short.valueOf(number.shortValue()); 102 } 103 else if (Integer.class == targetClass) { 104 long value = checkedLongValue(number, targetClass); 105 if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { 106 raiseOverflowException(number, targetClass); 107 } 108 return (T) Integer.valueOf(number.intValue()); 109 } 110 else if (Long.class == targetClass) { 111 long value = checkedLongValue(number, targetClass); 112 return (T) Long.valueOf(value); 113 } 114 else if (BigInteger.class == targetClass) { 115 if (number instanceof BigDecimal) { 116 // do not lose precision - use BigDecimal's own conversion 117 return (T) ((BigDecimal) number).toBigInteger(); 118 } 119 else { 120 // original value is not a Big* number - use standard long conversion 121 return (T) BigInteger.valueOf(number.longValue()); 122 } 123 } 124 else if (Float.class == targetClass) { 125 return (T) Float.valueOf(number.floatValue()); 126 } 127 else if (Double.class == targetClass) { 128 return (T) Double.valueOf(number.doubleValue()); 129 } 130 else if (BigDecimal.class == targetClass) { 131 // always use BigDecimal(String) here to avoid unpredictability of BigDecimal(double) 132 // (see BigDecimal javadoc for details) 133 return (T) new BigDecimal(number.toString()); 134 } 135 else { 136 throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" + 137 number.getClass().getName() + "] to unsupported target class [" + targetClass.getName() + "]"); 138 } 139 } 140 141 /** 142 * Check for a {@code BigInteger}/{@code BigDecimal} long overflow 143 * before returning the given number as a long value. 144 * @param number the number to convert 145 * @param targetClass the target class to convert to 146 * @return the long value, if convertible without overflow 147 * @throws IllegalArgumentException if there is an overflow 148 * @see #raiseOverflowException 149 */ 150 private static long checkedLongValue(Number number, Class<? extends Number> targetClass) { 151 BigInteger bigInt = null; 152 if (number instanceof BigInteger) { 153 bigInt = (BigInteger) number; 154 } 155 else if (number instanceof BigDecimal) { 156 bigInt = ((BigDecimal) number).toBigInteger(); 157 } 158 // Effectively analogous to JDK 8's BigInteger.longValueExact() 159 if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) { 160 raiseOverflowException(number, targetClass); 161 } 162 return number.longValue(); 163 } 164 165 /** 166 * Raise an <em>overflow</em> exception for the given number and target class. 167 * @param number the number we tried to convert 168 * @param targetClass the target class we tried to convert to 169 * @throws IllegalArgumentException if there is an overflow 170 */ 171 private static void raiseOverflowException(Number number, Class<?> targetClass) { 172 throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" + 173 number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow"); 174 } 175 176 /** 177 * Parse the given {@code text} into a {@link Number} instance of the given 178 * target class, using the corresponding {@code decode} / {@code valueOf} method. 179 * <p>Trims the input {@code String} before attempting to parse the number. 180 * <p>Supports numbers in hex format (with leading "0x", "0X", or "#") as well. 181 * @param text the text to convert 182 * @param targetClass the target class to parse into 183 * @return the parsed number 184 * @throws IllegalArgumentException if the target class is not supported 185 * (i.e. not a standard Number subclass as included in the JDK) 186 * @see Byte#decode 187 * @see Short#decode 188 * @see Integer#decode 189 * @see Long#decode 190 * @see #decodeBigInteger(String) 191 * @see Float#valueOf 192 * @see Double#valueOf 193 * @see java.math.BigDecimal#BigDecimal(String) 194 */ 195 @SuppressWarnings("unchecked") 196 public static <T extends Number> T parseNumber(String text, Class<T> targetClass) { 197 Assert.notNull(text, "Text must not be null"); 198 Assert.notNull(targetClass, "Target class must not be null"); 199 String trimmed = StringUtils.trimAllWhitespace(text); 200 201 if (Byte.class == targetClass) { 202 return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed)); 203 } 204 else if (Short.class == targetClass) { 205 return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed)); 206 } 207 else if (Integer.class == targetClass) { 208 return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed)); 209 } 210 else if (Long.class == targetClass) { 211 return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed)); 212 } 213 else if (BigInteger.class == targetClass) { 214 return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed)); 215 } 216 else if (Float.class == targetClass) { 217 return (T) Float.valueOf(trimmed); 218 } 219 else if (Double.class == targetClass) { 220 return (T) Double.valueOf(trimmed); 221 } 222 else if (BigDecimal.class == targetClass || Number.class == targetClass) { 223 return (T) new BigDecimal(trimmed); 224 } 225 else { 226 throw new IllegalArgumentException( 227 "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]"); 228 } 229 } 230 231 /** 232 * Parse the given {@code text} into a {@link Number} instance of the 233 * given target class, using the supplied {@link NumberFormat}. 234 * <p>Trims the input {@code String} before attempting to parse the number. 235 * @param text the text to convert 236 * @param targetClass the target class to parse into 237 * @param numberFormat the {@code NumberFormat} to use for parsing (if 238 * {@code null}, this method falls back to {@link #parseNumber(String, Class)}) 239 * @return the parsed number 240 * @throws IllegalArgumentException if the target class is not supported 241 * (i.e. not a standard Number subclass as included in the JDK) 242 * @see java.text.NumberFormat#parse 243 * @see #convertNumberToTargetClass 244 * @see #parseNumber(String, Class) 245 */ 246 public static <T extends Number> T parseNumber(String text, Class<T> targetClass, NumberFormat numberFormat) { 247 if (numberFormat != null) { 248 Assert.notNull(text, "Text must not be null"); 249 Assert.notNull(targetClass, "Target class must not be null"); 250 DecimalFormat decimalFormat = null; 251 boolean resetBigDecimal = false; 252 if (numberFormat instanceof DecimalFormat) { 253 decimalFormat = (DecimalFormat) numberFormat; 254 if (BigDecimal.class == targetClass && !decimalFormat.isParseBigDecimal()) { 255 decimalFormat.setParseBigDecimal(true); 256 resetBigDecimal = true; 257 } 258 } 259 try { 260 Number number = numberFormat.parse(StringUtils.trimAllWhitespace(text)); 261 return convertNumberToTargetClass(number, targetClass); 262 } 263 catch (ParseException ex) { 264 throw new IllegalArgumentException("Could not parse number: " + ex.getMessage()); 265 } 266 finally { 267 if (resetBigDecimal) { 268 decimalFormat.setParseBigDecimal(false); 269 } 270 } 271 } 272 else { 273 return parseNumber(text, targetClass); 274 } 275 } 276 277 /** 278 * Determine whether the given {@code value} String indicates a hex number, 279 * i.e. needs to be passed into {@code Integer.decode} instead of 280 * {@code Integer.valueOf}, etc. 281 */ 282 private static boolean isHexNumber(String value) { 283 int index = (value.startsWith("-") ? 1 : 0); 284 return (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index)); 285 } 286 287 /** 288 * Decode a {@link java.math.BigInteger} from the supplied {@link String} value. 289 * <p>Supports decimal, hex, and octal notation. 290 * @see BigInteger#BigInteger(String, int) 291 */ 292 private static BigInteger decodeBigInteger(String value) { 293 int radix = 10; 294 int index = 0; 295 boolean negative = false; 296 297 // Handle minus sign, if present. 298 if (value.startsWith("-")) { 299 negative = true; 300 index++; 301 } 302 303 // Handle radix specifier, if present. 304 if (value.startsWith("0x", index) || value.startsWith("0X", index)) { 305 index += 2; 306 radix = 16; 307 } 308 else if (value.startsWith("#", index)) { 309 index++; 310 radix = 16; 311 } 312 else if (value.startsWith("0", index) && value.length() > 1 + index) { 313 index++; 314 radix = 8; 315 } 316 317 BigInteger result = new BigInteger(value.substring(index), radix); 318 return (negative ? result.negate() : result); 319 } 320 321}