View Javadoc

    1   /*
    2    $Id: Numbers.java,v 1.3 2004/04/07 20:19:20 cpoirier Exp $
    3
    4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5
    6    Redistribution and use of this software and associated documentation
    7    ("Software"), with or without modification, are permitted provided
    8    that the following conditions are met:
    9
    10   1. Redistributions of source code must retain copyright
    11      statements and notices.  Redistributions must also contain a
    12      copy of this document.
    13
    14   2. Redistributions in binary form must reproduce the
    15      above copyright notice, this list of conditions and the
    16      following disclaimer in the documentation and/or other
    17      materials provided with the distribution.
    18
    19   3. The name "groovy" must not be used to endorse or promote
    20      products derived from this Software without prior written
    21      permission of The Codehaus.  For written permission,
    22      please contact info@codehaus.org.
    23
    24   4. Products derived from this Software may not be called "groovy"
    25      nor may "groovy" appear in their names without prior written
    26      permission of The Codehaus. "groovy" is a registered
    27      trademark of The Codehaus.
    28
    29   5. Due credit should be given to The Codehaus -
    30      http://groovy.codehaus.org/
    31
    32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
    33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
    34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
    36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    43   OF THE POSSIBILITY OF SUCH DAMAGE.
    44
    45   */
    46
    47  package org.codehaus.groovy.syntax;
    48
    49  import java.math.BigInteger;
    50  import java.math.BigDecimal;
    51
    52  /***
    53   *  Helper class for processing Groovy numeric literals.
    54   *
    55   *  @author Brian Larson
    56   *  @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
    57   *
    58   *  @version $Id: Numbers.java,v 1.3 2004/04/07 20:19:20 cpoirier Exp $
    59   */
    60
    61  public class Numbers
    62  {
    63
    64
    65
    66    //---------------------------------------------------------------------------
    67    // LEXING SUPPORT
    68
    69
    70     /***
    71      *  Returns true if the specified character is a base-10 digit.
    72      */
    73
    74      public static boolean isDigit( char c )
    75      {
    76          return c >= '0' && c <= '9';
    77      }
    78
    79
    80     /***
    81      *  Returns true if the specific character is a base-8 digit.
    82      */
    83
    84      public static boolean isOctalDigit( char c )
    85      {
    86          return c >= '0' && c <= '7';
    87      }
    88
    89
    90     /***
    91      *  Returns true if the specified character is a base-16 digit.
    92      */
    93
    94      public static boolean isHexDigit( char c )
    95      {
    96          return isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
    97      }
    98
    99
    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 }