View Javadoc
1   /*
2    * CSVeed (https://github.com/42BV/CSVeed)
3    *
4    * Copyright 2013-2023 CSVeed.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of The Apache Software License,
8    * Version 2.0 which accompanies this distribution, and is available at
9    * https://www.apache.org/licenses/LICENSE-2.0.txt
10   */
11  package org.csveed.bean.conversion;
12  
13  import static org.csveed.bean.conversion.ConversionUtil.trimAllWhitespace;
14  
15  import java.math.BigDecimal;
16  import java.math.BigInteger;
17  import java.text.DecimalFormat;
18  import java.text.NumberFormat;
19  import java.text.ParseException;
20  
21  /**
22   * The Class NumberUtils.
23   */
24  public abstract class NumberUtils {
25  
26      /**
27       * Instantiates a new number utils.
28       */
29      private NumberUtils() {
30          // Do not allow instantiation of static utils class
31      }
32  
33      /**
34       * Convert number to target class.
35       *
36       * @param <T>
37       *            the generic type
38       * @param number
39       *            the number
40       * @param targetClass
41       *            the target class
42       *
43       * @return the t
44       *
45       * @throws IllegalArgumentException
46       *             the illegal argument exception
47       */
48      @SuppressWarnings("unchecked")
49      public static <T extends Number> T convertNumberToTargetClass(Number number, Class<T> targetClass)
50              throws IllegalArgumentException {
51  
52          if (targetClass.isInstance(number)) {
53              return (T) number;
54          }
55          if (targetClass.equals(Byte.class)) {
56              long value = number.longValue();
57              if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
58                  raiseOverflowException(number, targetClass);
59              }
60              return (T) Byte.valueOf(number.byteValue());
61          }
62          if (targetClass.equals(Short.class)) {
63              long value = number.longValue();
64              if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
65                  raiseOverflowException(number, targetClass);
66              }
67              return (T) Short.valueOf(number.shortValue());
68          }
69          if (targetClass.equals(Integer.class)) {
70              long value = number.longValue();
71              if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
72                  raiseOverflowException(number, targetClass);
73              }
74              return (T) Integer.valueOf(number.intValue());
75          }
76          if (targetClass.equals(Long.class)) {
77              return (T) Long.valueOf(number.longValue());
78          }
79          if (targetClass.equals(BigInteger.class)) {
80              if (number instanceof BigDecimal) {
81                  // do not lose precision - use BigDecimal's own conversion
82                  return (T) ((BigDecimal) number).toBigInteger();
83              }
84              // original value is not a Big* number - use standard long conversion
85              return (T) BigInteger.valueOf(number.longValue());
86          }
87          if (targetClass.equals(Float.class)) {
88              return (T) Float.valueOf(number.floatValue());
89          }
90          if (targetClass.equals(Double.class)) {
91              return (T) Double.valueOf(number.doubleValue());
92          }
93          if (targetClass.equals(BigDecimal.class)) {
94              // always use BigDecimal(String) here to avoid unpredictability of BigDecimal(double)
95              // (see BigDecimal javadoc for details)
96              return (T) new BigDecimal(number.toString());
97          }
98          throw new IllegalArgumentException("Could not convert number [" + number + "] of type ["
99                  + number.getClass().getName() + "] to unknown target class [" + targetClass.getName() + "]");
100     }
101 
102     /**
103      * Raise overflow exception.
104      *
105      * @param number
106      *            the number
107      * @param targetClass
108      *            the target class
109      */
110     private static void raiseOverflowException(Number number, Class targetClass) {
111         throw new IllegalArgumentException("Could not convert number [" + number + "] of type ["
112                 + number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow");
113     }
114 
115     /**
116      * Parses the number.
117      *
118      * @param <T>
119      *            the generic type
120      * @param text
121      *            the text
122      * @param targetClass
123      *            the target class
124      *
125      * @return the t
126      */
127     @SuppressWarnings("unchecked")
128     public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
129         String trimmed = trimAllWhitespace(text);
130 
131         if (targetClass.equals(Byte.class)) {
132             return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed));
133         }
134         if (targetClass.equals(Short.class)) {
135             return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed));
136         }
137         if (targetClass.equals(Integer.class)) {
138             return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));
139         }
140         if (targetClass.equals(Long.class)) {
141             return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));
142         }
143         if (targetClass.equals(BigInteger.class)) {
144             return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed));
145         }
146         if (targetClass.equals(Float.class)) {
147             return (T) Float.valueOf(trimmed);
148         }
149         if (targetClass.equals(Double.class)) {
150             return (T) Double.valueOf(trimmed);
151         }
152         if (targetClass.equals(BigDecimal.class) || targetClass.equals(Number.class)) {
153             return (T) new BigDecimal(trimmed);
154         }
155         throw new IllegalArgumentException(
156                 "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
157     }
158 
159     /**
160      * Parses the number.
161      *
162      * @param <T>
163      *            the generic type
164      * @param text
165      *            the text
166      * @param targetClass
167      *            the target class
168      * @param numberFormat
169      *            the number format
170      *
171      * @return the t
172      */
173     public static <T extends Number> T parseNumber(String text, Class<T> targetClass, NumberFormat numberFormat) {
174         if (numberFormat != null) {
175             DecimalFormat decimalFormat = null;
176             boolean resetBigDecimal = false;
177             if (numberFormat instanceof DecimalFormat) {
178                 decimalFormat = (DecimalFormat) numberFormat;
179                 if (BigDecimal.class.equals(targetClass) && !decimalFormat.isParseBigDecimal()) {
180                     decimalFormat.setParseBigDecimal(true);
181                     resetBigDecimal = true;
182                 }
183             }
184             try {
185                 Number number = numberFormat.parse(trimAllWhitespace(text));
186                 return convertNumberToTargetClass(number, targetClass);
187             } catch (ParseException ex) {
188                 throw new IllegalArgumentException("Could not parse number: " + ex.getMessage());
189             } finally {
190                 if (resetBigDecimal && decimalFormat != null) {
191                     decimalFormat.setParseBigDecimal(false);
192                 }
193             }
194         }
195         return parseNumber(text, targetClass);
196     }
197 
198     /**
199      * Checks if is hex number.
200      *
201      * @param value
202      *            the value
203      *
204      * @return true, if is hex number
205      */
206     private static boolean isHexNumber(String value) {
207         int index = value.startsWith("-") ? 1 : 0;
208         return value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index);
209     }
210 
211     /**
212      * Decode big integer.
213      *
214      * @param value
215      *            the value
216      *
217      * @return the big integer
218      */
219     private static BigInteger decodeBigInteger(String value) {
220         int radix = 10;
221         int index = 0;
222         boolean negative = false;
223 
224         // Handle minus sign, if present.
225         if (value.startsWith("-")) {
226             negative = true;
227             index++;
228         }
229 
230         // Handle radix specifier, if present.
231         if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
232             index += 2;
233             radix = 16;
234         } else if (value.startsWith("#", index)) {
235             index++;
236             radix = 16;
237         } else if (value.startsWith("0", index) && value.length() > 1 + index) {
238             index++;
239             radix = 8;
240         }
241 
242         BigInteger result = new BigInteger(value.substring(index), radix);
243         return negative ? result.negate() : result;
244     }
245 
246 }