1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3.parser;
18
19 import java.io.Serializable;
20 import java.math.BigDecimal;
21 import java.math.BigInteger;
22 import java.text.DecimalFormat;
23 import java.text.DecimalFormatSymbols;
24 import java.util.Locale;
25
26 import org.apache.commons.jexl3.JexlArithmetic;
27 import org.apache.commons.jexl3.JexlEngine;
28
29
30
31
32 public final class NumberParser implements Serializable {
33
34
35
36 private static final long serialVersionUID = 1L;
37
38
39
40
41
42
43
44
45
46 private static int maxBigIntegerDigits(final int base) {
47 final JexlEngine engine = JexlEngine.getThreadEngine();
48 if (engine != null) {
49 final long precision = engine.getArithmetic().getMathContext().getPrecision();
50 if (precision > 0 && precision < Integer.MAX_VALUE) {
51
52
53 final int maxBits = (int) (precision * 10 / 3 + 1);
54 final int bitsPerDigit;
55 if (base == 16) {
56 bitsPerDigit = 4;
57 } else if (base == 8) {
58 bitsPerDigit = 3;
59 } else {
60
61 return (maxBits * 3) / 10;
62 }
63 return maxBits / bitsPerDigit;
64 }
65 }
66 return JexlArithmetic.MAX_BIGINTEGER_DIGITS;
67 }
68
69
70 static final DecimalFormat BIGDF = new DecimalFormat("0.0b", new DecimalFormatSymbols(Locale.ROOT));
71 private static boolean isNegative(final Token token) {
72 return token != null && "-".equals(token.image);
73 }
74 static Number parseDouble(final Token negative, final Token s) {
75 return new NumberParser().assignReal(isNegative(negative), s.image).getLiteralValue();
76 }
77
78 static Number parseInteger(final Token negative, final Token s) {
79 return new NumberParser().assignNatural(isNegative(negative), s.image).getLiteralValue();
80 }
81
82 public NumberParser() {
83 this(null);
84 }
85
86 public NumberParser(final Number number) {
87 if (number != null) {
88 this.literal = number;
89 this.clazz = number.getClass();
90 } else {
91 this.literal = null;
92 this.clazz = null;
93 }
94 }
95
96
97 private Number literal;
98
99
100 private Class<? extends Number> clazz;
101
102
103
104
105
106
107
108
109
110 NumberParser assignNatural(final boolean negative, final String natural) {
111 String s = natural;
112 Number result;
113 Class<? extends Number> rclass;
114
115 final int base;
116 if (s.charAt(0) == '0') {
117 if (s.length() > 1 && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
118 base = 16;
119 s = s.substring(2);
120 } else {
121 base = 8;
122 }
123 } else {
124 base = 10;
125 }
126 final int maxDigits = maxBigIntegerDigits(base);
127
128 final int last = s.length() - 1;
129 switch (s.charAt(last)) {
130 case 'l':
131 case 'L': {
132 rclass = Long.class;
133 final long l = Long.parseLong(s.substring(0, last), base);
134 result = negative? -l : l;
135 break;
136 }
137 case 'h':
138 case 'H': {
139 rclass = BigInteger.class;
140 if (last > maxDigits) {
141 throw new NumberFormatException(
142 "BigInteger literal too long: " + last
143 + " > " + maxDigits);
144 }
145 final BigInteger bi = new BigInteger(s.substring(0, last), base);
146 result = negative? bi.negate() : bi;
147 break;
148 }
149 default: {
150
151 rclass = Integer.class;
152 try {
153 final int i = Integer.parseInt(s, base);
154 result = negative? -i : i;
155 } catch (final NumberFormatException take2) {
156 try {
157 final long l = Long.parseLong(s, base);
158 result = negative? -l : l;
159 } catch (final NumberFormatException take3) {
160 if (s.length() > maxDigits) {
161 throw new NumberFormatException(
162 "BigInteger literal too long: " + s.length()
163 + " > " + maxDigits);
164 }
165 final BigInteger bi = new BigInteger(s, base);
166 result = negative? bi.negate() : bi;
167 }
168 }
169 }
170 }
171 literal = result;
172 clazz = rclass;
173 return this;
174 }
175
176
177
178
179
180
181
182
183 NumberParser assignNatural(final String str) {
184 String s;
185
186 final boolean negative;
187 switch (str.charAt(0)) {
188 case '-':
189 negative = true;
190 s = str.substring(1);
191 break;
192 case '+':
193 negative = false;
194 s = str.substring(1);
195 break;
196 default:
197 negative = false;
198 s = str;
199 }
200 return assignNatural(negative, s);
201 }
202
203
204
205
206
207
208
209
210
211 NumberParser assignReal(final boolean negative, final String s) {
212 Number result;
213 Class<? extends Number> rclass;
214 if ("#NaN".equals(s) || "NaN".equals(s)) {
215 result = Double.NaN;
216 rclass = Double.class;
217 } else {
218 final int last = s.length() - 1;
219 switch (s.charAt(last)) {
220 case 'b':
221 case 'B': {
222 rclass = BigDecimal.class;
223 final BigDecimal bd = new BigDecimal(s.substring(0, last));
224 result = negative? bd.negate() : bd;
225 break;
226 }
227 case 'f':
228 case 'F': {
229 rclass = Float.class;
230 final float f4 = Float.parseFloat(s.substring(0, last));
231 result = negative? -f4 : f4;
232 break;
233 }
234 case 'd':
235 case 'D':
236 rclass = Double.class;
237 final double f8 = Double.parseDouble(s.substring(0, last));
238 result = negative? -f8 : f8;
239 break;
240 default: {
241
242 rclass = Double.class;
243 try {
244 final double d = Double.parseDouble(s);
245 result = negative? -d : d;
246 } catch (final NumberFormatException take3) {
247 final BigDecimal bd = new BigDecimal(s);
248 result = negative? bd.negate() : bd;
249 }
250 break;
251 }
252 }
253 }
254 literal = result;
255 clazz = rclass;
256 return this;
257 }
258
259
260
261
262
263
264
265
266 NumberParser assignReal(final String str) {
267 String s;
268
269 final boolean negative;
270 switch (str.charAt(0)) {
271 case '-':
272 negative = true;
273 s = str.substring(1);
274 break;
275 case '+':
276 negative = false;
277 s = str.substring(1);
278 break;
279 default:
280 negative = false;
281 s = str;
282 }
283 return assignReal(negative, s);
284 }
285
286 Class<? extends Number> getLiteralClass() {
287 return clazz;
288 }
289
290 Number getLiteralValue() {
291 return literal;
292 }
293
294 boolean isInteger() {
295 return Integer.class.equals(clazz);
296 }
297
298 @Override
299 public String toString() {
300 if (literal == null || clazz == null || Double.isNaN(literal.doubleValue())) {
301 return "NaN";
302 }
303 if (BigDecimal.class.equals(clazz)) {
304 synchronized (BIGDF) {
305 return BIGDF.format(literal);
306 }
307 }
308 final StringBuilder strb = new StringBuilder(literal.toString());
309 if (Float.class.equals(clazz)) {
310 strb.append('f');
311 } else if (Double.class.equals(clazz)) {
312 strb.append('d');
313 } else if (BigInteger.class.equals(clazz)) {
314 strb.append('h');
315 } else if (Long.class.equals(clazz)) {
316 strb.append('l');
317 }
318 return strb.toString();
319 }
320
321 }