View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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  /**
27   * Parses number literals.
28   */
29  public final class NumberParser implements Serializable {
30      /**
31       */
32      private static final long serialVersionUID = 1L;
33      /** JEXL locale-neutral big decimal format. */
34      static final DecimalFormat BIGDF = new DecimalFormat("0.0b", new DecimalFormatSymbols(Locale.ROOT));
35      private static boolean isNegative(final Token token) {
36          return token != null && "-".equals(token.image);
37      }
38      static Number parseDouble(final Token negative, final Token s) {
39          return new NumberParser().assignReal(isNegative(negative), s.image).getLiteralValue();
40      }
41  
42      static Number parseInteger(final Token negative, final Token s) {
43          return new NumberParser().assignNatural(isNegative(negative), s.image).getLiteralValue();
44      }
45  
46      public NumberParser() {
47          this(null);
48      }
49  
50      public NumberParser(final Number number) {
51          if (number != null) {
52              this.literal = number;
53              this.clazz = number.getClass();
54          } else {
55              this.literal = null;
56              this.clazz = null;
57          }
58      }
59  
60      /** The type literal value. */
61      private Number literal;
62  
63      /** The expected class. */
64      private Class<? extends Number> clazz;
65  
66      /**
67       * Sets this node as a natural literal.
68       * Originally from OGNL.
69       * @param negative whether the natural should be negative
70       * @param natural the natural as string
71       * @return this parser instance
72       */
73      NumberParser assignNatural(final boolean negative, final String natural) {
74          String s = natural;
75          Number result;
76          Class<? extends Number> rclass;
77          // determine the base
78          final int base;
79          if (s.charAt(0) == '0') {
80              if (s.length() > 1 && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
81                  base = 16;
82                  s = s.substring(2); // Trim the 0x off the front
83              } else {
84                  base = 8;
85              }
86          } else {
87              base = 10;
88          }
89          // switch on suffix if any
90          final int last = s.length() - 1;
91          switch (s.charAt(last)) {
92              case 'l':
93              case 'L': {
94                  rclass = Long.class;
95                  final long l = Long.parseLong(s.substring(0, last), base);
96                  result = negative? -l : l;
97                  break;
98              }
99              case 'h':
100             case 'H': {
101                 rclass = BigInteger.class;
102                 final BigInteger bi = new BigInteger(s.substring(0, last), base);
103                 result = negative? bi.negate() : bi;
104                 break;
105             }
106             default: {
107                 // preferred literal class is integer
108                 rclass = Integer.class;
109                 try {
110                     final int i = Integer.parseInt(s, base);
111                     result = negative? -i : i;
112                 } catch (final NumberFormatException take2) {
113                     try {
114                         final long l = Long.parseLong(s, base);
115                         result = negative? -l : l;
116                     } catch (final NumberFormatException take3) {
117                         final BigInteger bi = new BigInteger(s, base);
118                         result = negative? bi.negate() : bi;
119                     }
120                 }
121             }
122         }
123         literal = result;
124         clazz = rclass;
125         return this;
126     }
127 
128     /**
129      * Sets this node as an (optionally) signed natural literal.
130      * Originally from OGNL.
131      * @param str the natural as string
132      * @return this parser instance
133      */
134     NumberParser assignNatural(final String str) {
135         String s;
136         // determine negative sign if any, ignore +
137         final boolean negative;
138         switch (str.charAt(0)) {
139             case '-':
140                 negative = true;
141                 s = str.substring(1);
142                 break;
143             case '+':
144                 negative = false;
145                 s = str.substring(1);
146                 break;
147             default:
148                 negative = false;
149                 s = str;
150         }
151         return assignNatural(negative, s);
152     }
153 
154     /**
155      * Sets this node as a real literal.
156      * Originally from OGNL.
157      * @param negative whether the real should be negative
158      * @param s the real as string
159      * @return this parser instance
160      */
161     NumberParser assignReal(final boolean negative, final String s) {
162         Number result;
163         Class<? extends Number> rclass;
164         if ("#NaN".equals(s) || "NaN".equals(s)) {
165             result = Double.NaN;
166             rclass = Double.class;
167         } else {
168             final int last = s.length() - 1;
169             switch (s.charAt(last)) {
170                 case 'b':
171                 case 'B': {
172                     rclass = BigDecimal.class;
173                     final BigDecimal bd = new BigDecimal(s.substring(0, last));
174                     result = negative? bd.negate() : bd;
175                     break;
176                 }
177                 case 'f':
178                 case 'F': {
179                     rclass = Float.class;
180                     final float f4 = Float.parseFloat(s.substring(0, last));
181                     result = negative? -f4 : f4;
182                     break;
183                 }
184                 case 'd':
185                 case 'D':
186                     rclass = Double.class;
187                     final double f8 = Double.parseDouble(s.substring(0, last));
188                     result = negative? -f8 : f8;
189                     break;
190                 default: {
191                     // preferred literal class is double
192                     rclass = Double.class;
193                     try {
194                         final double d = Double.parseDouble(s);
195                         result = negative? -d : d;
196                     } catch (final NumberFormatException take3) {
197                         final BigDecimal bd = new BigDecimal(s);
198                         result = negative? bd.negate() : bd;
199                     }
200                     break;
201                 }
202             }
203         }
204         literal = result;
205         clazz = rclass;
206         return this;
207     }
208 
209     /**
210      * Sets this node as an (optionally) signed real literal.
211      * Originally from OGNL.
212      * @param str the real as string
213      * @return this parser instance
214      */
215     NumberParser assignReal(final String str) {
216         String s;
217         // determine negative sign if any, ignore +
218         final boolean negative;
219         switch (str.charAt(0)) {
220             case '-':
221                 negative = true;
222                 s = str.substring(1);
223                 break;
224             case '+':
225                 negative = false;
226                 s = str.substring(1);
227                 break;
228             default:
229                 negative = false;
230                 s = str;
231         }
232         return assignReal(negative, s);
233     }
234 
235     Class<? extends Number> getLiteralClass() {
236         return clazz;
237     }
238 
239     Number getLiteralValue() {
240         return literal;
241     }
242 
243     boolean isInteger() {
244         return Integer.class.equals(clazz);
245     }
246 
247     @Override
248     public String toString() {
249         if (literal == null || clazz == null || Double.isNaN(literal.doubleValue())) {
250             return "NaN";
251         }
252         if (BigDecimal.class.equals(clazz)) {
253             synchronized (BIGDF) {
254                 return BIGDF.format(literal);
255             }
256         }
257         final StringBuilder strb = new StringBuilder(literal.toString());
258         if (Float.class.equals(clazz)) {
259             strb.append('f');
260         } else if (Double.class.equals(clazz)) {
261             strb.append('d');
262         } else if (BigInteger.class.equals(clazz)) {
263             strb.append('h');
264         } else if (Long.class.equals(clazz)) {
265             strb.append('l');
266         }
267         return strb.toString();
268     }
269 
270 }