001    /*
002     $Id: Numbers.java 1036 2004-04-07 20:19:21Z cpoirier $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    
047    package org.codehaus.groovy.syntax;
048    
049    import java.math.BigInteger;
050    import java.math.BigDecimal;
051    
052    /**
053     *  Helper class for processing Groovy numeric literals.
054     *
055     *  @author Brian Larson
056     *  @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
057     *
058     *  @version $Id: Numbers.java 1036 2004-04-07 20:19:21Z cpoirier $
059     */
060    
061    public class Numbers
062    {
063    
064    
065    
066      //---------------------------------------------------------------------------
067      // LEXING SUPPORT
068    
069    
070       /**
071        *  Returns true if the specified character is a base-10 digit.
072        */
073    
074        public static boolean isDigit( char c )
075        {
076            return c >= '0' && c <= '9';
077        }
078    
079    
080       /**
081        *  Returns true if the specific character is a base-8 digit.
082        */
083    
084        public static boolean isOctalDigit( char c )
085        {
086            return c >= '0' && c <= '7';
087        }
088    
089    
090       /**
091        *  Returns true if the specified character is a base-16 digit.
092        */
093    
094        public static boolean isHexDigit( char c )
095        {
096            return isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
097        }
098    
099    
100    
101       /**
102        *  Returns true if the specified character is a valid type specifier
103        *  for a numeric value.
104        */
105    
106        public static boolean isNumericTypeSpecifier( char c, boolean isDecimal )
107        {
108            if( isDecimal )
109            {
110                switch( c )
111                {
112                    case 'G':
113                    case 'g':
114                    case 'D':
115                    case 'd':
116                    case 'F':
117                    case 'f':
118                        return true;
119                }
120            }
121            else
122            {
123                switch( c )
124                {
125                    case 'G':
126                    case 'g':
127                    case 'I':
128                    case 'i':
129                    case 'L':
130                    case 'l':
131                        return true;
132                }
133            }
134    
135            return false;
136        }
137    
138    
139    
140    
141    
142      //---------------------------------------------------------------------------
143      // PARSING SUPPORT
144    
145    
146        private static final BigInteger MAX_LONG    = BigInteger.valueOf(Long.MAX_VALUE);
147        private static final BigInteger MIN_LONG    = BigInteger.valueOf(Long.MIN_VALUE);
148    
149        private static final BigInteger MAX_INTEGER = BigInteger.valueOf(Integer.MAX_VALUE);
150        private static final BigInteger MIN_INTEGER = BigInteger.valueOf(Integer.MIN_VALUE);
151    
152        private static final BigDecimal MAX_DOUBLE  = new BigDecimal(String.valueOf(Double.MAX_VALUE));
153        private static final BigDecimal MIN_DOUBLE  = MAX_DOUBLE.negate();
154    
155        private static final BigDecimal MAX_FLOAT   = new BigDecimal(String.valueOf(Float.MAX_VALUE));
156        private static final BigDecimal MIN_FLOAT   = MAX_FLOAT.negate();
157    
158    
159    
160       /**
161        *  Builds a Number from the given integer descriptor.  Creates the narrowest
162        *  type possible, or a specific type, if specified.
163        *
164        *  @param  text literal text to parse
165        *  @return instantiated Number object
166        *  @throws NumberFormatException if the number does not fit within the type
167        *          requested by the type specifier suffix (invalid numbers don't make
168        *          it here)
169        */
170    
171        public static Number parseInteger( String text )
172        {
173            char c = ' ';
174            int length = text.length();
175    
176    
177            //
178            // Strip off the sign, if present
179    
180            boolean negative = false;
181            if( (c = text.charAt(0)) == '-' || c == '+' )
182            {
183                negative = (c == '-');
184                text = text.substring( 1, length );
185                length -= 1;
186            }
187    
188    
189            //
190            // Determine radix (default is 10).
191    
192            int radix = 10;
193            if( text.charAt(0) == '0' && length > 1 )
194            {
195                if( (c = text.charAt(1)) == 'X' || c == 'x' )
196                {
197                    radix = 16;
198                    text = text.substring( 2, length);
199                    length -= 2;
200                }
201                else
202                {
203                    radix = 8;
204                }
205            }
206    
207    
208            //
209            // Strip off any type specifier and convert it to lower
210            // case, if present.
211    
212            char type = 'x';  // pick best fit
213            if( isNumericTypeSpecifier(text.charAt(length-1), false) )
214            {
215                type = Character.toLowerCase( text.charAt(length-1) );
216                text = text.substring( 0, length-1);
217    
218                length -= 1;
219            }
220    
221    
222            //
223            // Add the sign back, if necessary
224    
225            if( negative )
226            {
227                text = "-" + text;
228            }
229    
230    
231            //
232            // Build the specified type or, if no type was specified, the
233            // smallest type in which the number will fit.
234    
235            switch (type)
236            {
237                case 'i':
238                    return new Integer( Integer.parseInt(text, radix) );
239    
240                case 'l':
241                    return new Long( Long.parseLong(text, radix) );
242    
243                case 'g':
244                    return new BigInteger( text, radix );
245    
246                default:
247    
248                    //
249                    // If not specified, we will return the narrowest possible
250                    // of Integer, Long, and BigInteger.
251    
252                    BigInteger value = new BigInteger( text, radix );
253    
254                    if( value.compareTo(MAX_INTEGER) <= 0 && value.compareTo(MIN_INTEGER) >= 0 )
255                    {
256                        return new Integer(value.intValue());
257                    }
258                    else if( value.compareTo(MAX_LONG) <= 0 && value.compareTo(MIN_LONG) >= 0 )
259                    {
260                        return new Long(value.longValue());
261                    }
262    
263                    return value;
264            }
265        }
266    
267    
268    
269       /**
270        *  Builds a Number from the given decimal descriptor.  Uses BigDecimal,
271        *  unless, Double or Float is requested.
272        *
273        *  @param  text literal text to parse
274        *  @return instantiated Number object
275        *  @throws NumberFormatException if the number does not fit within the type
276        *          requested by the type specifier suffix (invalid numbers don't make
277        *          it here)
278        */
279    
280        public static Number parseDecimal( String text )
281        {
282            int length = text.length();
283    
284    
285            //
286            // Strip off any type specifier and convert it to lower
287            // case, if present.
288    
289            char type = 'x';
290            if( isNumericTypeSpecifier(text.charAt(length-1), true) )
291            {
292                type = Character.toLowerCase( text.charAt(length-1) );
293                text = text.substring( 0, length-1 );
294    
295                length -= 1;
296            }
297    
298    
299            //
300            // Build the specified type or default to BigDecimal
301    
302            BigDecimal value = new BigDecimal( text );
303            switch( type )
304            {
305                case 'f':
306                    if( value.compareTo(MAX_FLOAT) <= 0 && value.compareTo(MIN_FLOAT) >= 0)
307                    {
308                        return new Float( text );
309                    }
310                    throw new NumberFormatException( "out of range" );
311    
312                case 'd':
313                    if( value.compareTo(MAX_DOUBLE) <= 0 && value.compareTo(MIN_DOUBLE) >= 0)
314                    {
315                        return new Double( text );
316                    }
317                    throw new NumberFormatException( "out of range" );
318    
319                case 'g':
320                default:
321                    return value;
322            }
323        }
324    
325    }