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