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