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