001    // Protocol Buffers - Google's data interchange format
002    // Copyright 2008 Google Inc.
003    // http://code.google.com/p/protobuf/
004    //
005    // Licensed under the Apache License, Version 2.0 (the "License");
006    // you may not use this file except in compliance with the License.
007    // You may obtain a copy of the License at
008    //
009    //      http://www.apache.org/licenses/LICENSE-2.0
010    //
011    // Unless required by applicable law or agreed to in writing, software
012    // distributed under the License is distributed on an "AS IS" BASIS,
013    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014    // See the License for the specific language governing permissions and
015    // limitations under the License.
016    
017    package org.fusesource.hawtbuf.proto.compiler;
018    
019    import java.io.IOException;
020    import java.math.BigInteger;
021    import java.nio.CharBuffer;
022    import java.util.regex.Matcher;
023    import java.util.regex.Pattern;
024    
025    import org.fusesource.hawtbuf.Buffer;
026    import org.fusesource.hawtbuf.UTF8Buffer;
027    
028    /** 
029     * Provide ascii text parsing and formatting support for proto2 instances.
030     * The implementation largely follows google/protobuf/text_format.cc.
031     *
032     * HRC: I wish the original class was not package protected so we did not need
033     * to copy this file over.  We need to request that the protobuf folks open 
034     * this class up amoung a few others. 
035     * 
036     * @author wenboz@google.com Wenbo Zhu
037     * @author kenton@google.com Kenton Varda
038     */
039    public final class TextFormat {
040    
041      /** Convert an unsigned 32-bit integer to a string. */
042      private static String unsignedToString(int value) {
043        if (value >= 0) {
044          return Integer.toString(value);
045        } else {
046          return Long.toString(((long) value) & 0x00000000FFFFFFFFL);
047        }
048      }
049    
050      /** Convert an unsigned 64-bit integer to a string. */
051      private static String unsignedToString(long value) {
052        if (value >= 0) {
053          return Long.toString(value);
054        } else {
055          // Pull off the most-significant bit so that BigInteger doesn't think
056          // the number is negative, then set it again using setBit().
057          return BigInteger.valueOf(value & 0x7FFFFFFFFFFFFFFFL)
058                           .setBit(63).toString();
059        }
060      }
061    
062      // =================================================================
063      // Parsing
064    
065      /**
066       * Represents a stream of tokens parsed from a {@code String}.
067       *
068       * <p>The Java standard library provides many classes that you might think
069       * would be useful for implementing this, but aren't.  For example:
070       *
071       * <ul>
072       * <li>{@code java.io.StreamTokenizer}:  This almost does what we want -- or,
073       *   at least, something that would get us close to what we want -- except
074       *   for one fatal flaw:  It automatically un-escapes strings using Java
075       *   escape sequences, which do not include all the escape sequences we
076       *   need to support (e.g. '\x').
077       * <li>{@code java.util.Scanner}:  This seems like a great way at least to
078       *   parse regular expressions out of a stream (so we wouldn't have to load
079       *   the entire input into a single string before parsing).  Sadly,
080       *   {@code Scanner} requires that tokens be delimited with some delimiter.
081       *   Thus, although the text "foo:" should parse to two tokens ("foo" and
082       *   ":"), {@code Scanner} would recognize it only as a single token.
083       *   Furthermore, {@code Scanner} provides no way to inspect the contents
084       *   of delimiters, making it impossible to keep track of line and column
085       *   numbers.
086       * </ul>
087       *
088       * <p>Luckily, Java's regular expression support does manage to be useful to
089       * us.  (Barely:  We need {@code Matcher.usePattern()}, which is new in
090       * Java 1.5.)  So, we can use that, at least.  Unfortunately, this implies
091       * that we need to have the entire input in one contiguous string.
092       */
093      private static final class Tokenizer {
094        private final CharSequence text;
095        private final Matcher matcher;
096        private String currentToken;
097    
098        // The character index within this.text at which the current token begins.
099        private int pos = 0;
100    
101        // The line and column numbers of the current token.
102        private int line = 0;
103        private int column = 0;
104    
105        // The line and column numbers of the previous token (allows throwing
106        // errors *after* consuming).
107        private int previousLine = 0;
108        private int previousColumn = 0;
109    
110        private static Pattern WHITESPACE =
111          Pattern.compile("(\\s|(#.*$))+", Pattern.MULTILINE);
112        private static Pattern TOKEN = Pattern.compile(
113          "[a-zA-Z_][0-9a-zA-Z_+-]*|" +                 // an identifier
114          "[0-9+-][0-9a-zA-Z_.+-]*|" +                  // a number
115          "\"([^\"\n\\\\]|\\\\.)*(\"|\\\\?$)|" +        // a double-quoted string
116          "\'([^\"\n\\\\]|\\\\.)*(\'|\\\\?$)",          // a single-quoted string
117          Pattern.MULTILINE);
118    
119        private static Pattern DOUBLE_INFINITY = Pattern.compile(
120          "-?inf(inity)?",
121          Pattern.CASE_INSENSITIVE);
122        private static Pattern FLOAT_INFINITY = Pattern.compile(
123          "-?inf(inity)?f?",
124          Pattern.CASE_INSENSITIVE);
125        private static Pattern FLOAT_NAN = Pattern.compile(
126          "nanf?",
127          Pattern.CASE_INSENSITIVE);
128    
129        /** Construct a tokenizer that parses tokens from the given text. */
130        public Tokenizer(CharSequence text) {
131          this.text = text;
132          this.matcher = WHITESPACE.matcher(text);
133          skipWhitespace();
134          nextToken();
135        }
136    
137        /** Are we at the end of the input? */
138        public boolean atEnd() {
139          return currentToken.length() == 0;
140        }
141    
142        /** Advance to the next token. */
143        public void nextToken() {
144          previousLine = line;
145          previousColumn = column;
146    
147          // Advance the line counter to the current position.
148          while (pos < matcher.regionStart()) {
149            if (text.charAt(pos) == '\n') {
150              ++line;
151              column = 0;
152            } else {
153              ++column;
154            }
155            ++pos;
156          }
157    
158          // Match the next token.
159          if (matcher.regionStart() == matcher.regionEnd()) {
160            // EOF
161            currentToken = "";
162          } else {
163            matcher.usePattern(TOKEN);
164            if (matcher.lookingAt()) {
165              currentToken = matcher.group();
166              matcher.region(matcher.end(), matcher.regionEnd());
167            } else {
168              // Take one character.
169              currentToken = String.valueOf(text.charAt(pos));
170              matcher.region(pos + 1, matcher.regionEnd());
171            }
172    
173            skipWhitespace();
174          }
175        }
176    
177        /**
178         * Skip over any whitespace so that the matcher region starts at the next
179         * token.
180         */
181        private void skipWhitespace() {
182          matcher.usePattern(WHITESPACE);
183          if (matcher.lookingAt()) {
184            matcher.region(matcher.end(), matcher.regionEnd());
185          }
186        }
187    
188        /**
189         * If the next token exactly matches {@code token}, consume it and return
190         * {@code true}.  Otherwise, return {@code false} without doing anything.
191         */
192        public boolean tryConsume(String token) {
193          if (currentToken.equals(token)) {
194            nextToken();
195            return true;
196          } else {
197            return false;
198          }
199        }
200    
201        /**
202         * If the next token exactly matches {@code token}, consume it.  Otherwise,
203         * throw a {@link ParseException}.
204         */
205        public void consume(String token) throws ParseException {
206          if (!tryConsume(token)) {
207            throw parseException("Expected \"" + token + "\".");
208          }
209        }
210    
211        /**
212         * Returns {@code true} if the next token is an integer, but does
213         * not consume it.
214         */
215        public boolean lookingAtInteger() {
216          if (currentToken.length() == 0) {
217            return false;
218          }
219    
220          char c = currentToken.charAt(0);
221          return ('0' <= c && c <= '9') ||
222                 c == '-' || c == '+';
223        }
224    
225        /**
226         * If the next token is an identifier, consume it and return its value.
227         * Otherwise, throw a {@link ParseException}.
228         */
229        public String consumeIdentifier() throws ParseException {
230          for (int i = 0; i < currentToken.length(); i++) {
231            char c = currentToken.charAt(i);
232            if (('a' <= c && c <= 'z') ||
233                ('A' <= c && c <= 'Z') ||
234                ('0' <= c && c <= '9') ||
235                (c == '_') || (c == '.')) {
236              // OK
237            } else {
238              throw parseException("Expected identifier.");
239            }
240          }
241    
242          String result = currentToken;
243          nextToken();
244          return result;
245        }
246    
247        /**
248         * If the next token is a 32-bit signed integer, consume it and return its
249         * value.  Otherwise, throw a {@link ParseException}.
250         */
251        public int consumeInt32() throws ParseException {
252          try {
253            int result = parseInt32(currentToken);
254            nextToken();
255            return result;
256          } catch (NumberFormatException e) {
257            throw integerParseException(e);
258          }
259        }
260    
261        /**
262         * If the next token is a 32-bit unsigned integer, consume it and return its
263         * value.  Otherwise, throw a {@link ParseException}.
264         */
265        public int consumeUInt32() throws ParseException {
266          try {
267            int result = parseUInt32(currentToken);
268            nextToken();
269            return result;
270          } catch (NumberFormatException e) {
271            throw integerParseException(e);
272          }
273        }
274    
275        /**
276         * If the next token is a 64-bit signed integer, consume it and return its
277         * value.  Otherwise, throw a {@link ParseException}.
278         */
279        public long consumeInt64() throws ParseException {
280          try {
281            long result = parseInt64(currentToken);
282            nextToken();
283            return result;
284          } catch (NumberFormatException e) {
285            throw integerParseException(e);
286          }
287        }
288    
289        /**
290         * If the next token is a 64-bit unsigned integer, consume it and return its
291         * value.  Otherwise, throw a {@link ParseException}.
292         */
293        public long consumeUInt64() throws ParseException {
294          try {
295            long result = parseUInt64(currentToken);
296            nextToken();
297            return result;
298          } catch (NumberFormatException e) {
299            throw integerParseException(e);
300          }
301        }
302    
303        /**
304         * If the next token is a double, consume it and return its value.
305         * Otherwise, throw a {@link ParseException}.
306         */
307        public double consumeDouble() throws ParseException {
308          // We need to parse infinity and nan separately because
309          // Double.parseDouble() does not accept "inf", "infinity", or "nan".
310          if (DOUBLE_INFINITY.matcher(currentToken).matches()) {
311            boolean negative = currentToken.startsWith("-");
312            nextToken();
313            return negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
314          }
315          if (currentToken.equalsIgnoreCase("nan")) {
316            nextToken();
317            return Double.NaN;
318          }
319          try {
320            double result = Double.parseDouble(currentToken);
321            nextToken();
322            return result;
323          } catch (NumberFormatException e) {
324            throw floatParseException(e);
325          }
326        }
327    
328        /**
329         * If the next token is a float, consume it and return its value.
330         * Otherwise, throw a {@link ParseException}.
331         */
332        public float consumeFloat() throws ParseException {
333          // We need to parse infinity and nan separately because
334          // Float.parseFloat() does not accept "inf", "infinity", or "nan".
335          if (FLOAT_INFINITY.matcher(currentToken).matches()) {
336            boolean negative = currentToken.startsWith("-");
337            nextToken();
338            return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
339          }
340          if (FLOAT_NAN.matcher(currentToken).matches()) {
341            nextToken();
342            return Float.NaN;
343          }
344          try {
345            float result = Float.parseFloat(currentToken);
346            nextToken();
347            return result;
348          } catch (NumberFormatException e) {
349            throw floatParseException(e);
350          }
351        }
352    
353        /**
354         * If the next token is a boolean, consume it and return its value.
355         * Otherwise, throw a {@link ParseException}.
356         */
357        public boolean consumeBoolean() throws ParseException {
358          if (currentToken.equals("true")) {
359            nextToken();
360            return true;
361          } else if (currentToken.equals("false")) {
362            nextToken();
363            return false;
364          } else {
365            throw parseException("Expected \"true\" or \"false\".");
366          }
367        }
368    
369        /**
370         * If the next token is a string, consume it and return its (unescaped)
371         * value.  Otherwise, throw a {@link ParseException}.
372         */
373        public String consumeString() throws ParseException {
374          return new UTF8Buffer(consumeBuffer()).toString();
375        }
376    
377        /**
378         * If the next token is a string, consume it, unescape it as a
379         * {@link Buffer}, and return it.  Otherwise, throw a
380         * {@link ParseException}.
381         */
382        public Buffer consumeBuffer() throws ParseException {
383          char quote = currentToken.length() > 0 ? currentToken.charAt(0) : '\0';
384          if (quote != '\"' && quote != '\'') {
385            throw parseException("Expected string.");
386          }
387    
388          if (currentToken.length() < 2 ||
389              currentToken.charAt(currentToken.length() - 1) != quote) {
390            throw parseException("String missing ending quote.");
391          }
392    
393          try {
394            String escaped = currentToken.substring(1, currentToken.length() - 1);
395            Buffer result = unescapeBytes(escaped);
396            nextToken();
397            return result;
398          } catch (InvalidEscapeSequence e) {
399            throw parseException(e.getMessage());
400          }
401        }
402    
403        /**
404         * Returns a {@link ParseException} with the current line and column
405         * numbers in the description, suitable for throwing.
406         */
407        public ParseException parseException(String description) {
408          // Note:  People generally prefer one-based line and column numbers.
409          return new ParseException(
410            (line + 1) + ":" + (column + 1) + ": " + description);
411        }
412    
413        /**
414         * Returns a {@link ParseException} with the line and column numbers of
415         * the previous token in the description, suitable for throwing.
416         */
417        public ParseException parseExceptionPreviousToken(String description) {
418          // Note:  People generally prefer one-based line and column numbers.
419          return new ParseException(
420            (previousLine + 1) + ":" + (previousColumn + 1) + ": " + description);
421        }
422    
423        /**
424         * Constructs an appropriate {@link ParseException} for the given
425         * {@code NumberFormatException} when trying to parse an integer.
426         */
427        private ParseException integerParseException(NumberFormatException e) {
428          return parseException("Couldn't parse integer: " + e.getMessage());
429        }
430    
431        /**
432         * Constructs an appropriate {@link ParseException} for the given
433         * {@code NumberFormatException} when trying to parse a float or double.
434         */
435        private ParseException floatParseException(NumberFormatException e) {
436          return parseException("Couldn't parse number: " + e.getMessage());
437        }
438      }
439    
440      /** Thrown when parsing an invalid text format message. */
441      public static class ParseException extends IOException {
442        public ParseException(String message) {
443          super(message);
444        }
445      }
446    
447      private static final int BUFFER_SIZE = 4096;
448    
449      // TODO(chrisn): See if working around java.io.Reader#read(CharBuffer)
450      // overhead is worthwhile
451      private static StringBuilder toStringBuilder(Readable input)
452          throws IOException {
453        StringBuilder text = new StringBuilder();
454        CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE);
455        while (true) {
456          int n = input.read(buffer);
457          if (n == -1) {
458            break;
459          }
460          buffer.flip();
461          text.append(buffer, 0, n);
462        }
463        return text;
464      }
465    
466    
467      // =================================================================
468      // Utility functions
469      //
470      // Some of these methods are package-private because Descriptors.java uses
471      // them.
472    
473      /**
474       * Escapes bytes in the format used in protocol buffer text format, which
475       * is the same as the format used for C string literals.  All bytes
476       * that are not printable 7-bit ASCII characters are escaped, as well as
477       * backslash, single-quote, and double-quote characters.  Characters for
478       * which no defined short-hand escape sequence is defined will be escaped
479       * using 3-digit octal sequences.
480       */
481      static String escapeBytes(Buffer input) {
482        StringBuilder builder = new StringBuilder(input.getLength());
483        for (int i = 0; i < input.getLength(); i++) {
484          byte b = input.get(i);
485          switch (b) {
486            // Java does not recognize \a or \v, apparently.
487            case 0x07: builder.append("\\a" ); break;
488            case '\b': builder.append("\\b" ); break;
489            case '\f': builder.append("\\f" ); break;
490            case '\n': builder.append("\\n" ); break;
491            case '\r': builder.append("\\r" ); break;
492            case '\t': builder.append("\\t" ); break;
493            case 0x0b: builder.append("\\v" ); break;
494            case '\\': builder.append("\\\\"); break;
495            case '\'': builder.append("\\\'"); break;
496            case '"' : builder.append("\\\""); break;
497            default:
498              if (b >= 0x20) {
499                builder.append((char) b);
500              } else {
501                builder.append('\\');
502                builder.append((char) ('0' + ((b >>> 6) & 3)));
503                builder.append((char) ('0' + ((b >>> 3) & 7)));
504                builder.append((char) ('0' + (b & 7)));
505              }
506              break;
507          }
508        }
509        return builder.toString();
510      }
511    
512      /**
513       * Un-escape a byte sequence as escaped using
514       * {@link #escapeBytes(Buffer)}.  Two-digit hex escapes (starting with
515       * "\x") are also recognized.
516       */
517      static Buffer unescapeBytes(CharSequence input)
518          throws InvalidEscapeSequence {
519        byte[] result = new byte[input.length()];
520        int pos = 0;
521        for (int i = 0; i < input.length(); i++) {
522          char c = input.charAt(i);
523          if (c == '\\') {
524            if (i + 1 < input.length()) {
525              ++i;
526              c = input.charAt(i);
527              if (isOctal(c)) {
528                // Octal escape.
529                int code = digitValue(c);
530                if (i + 1 < input.length() && isOctal(input.charAt(i + 1))) {
531                  ++i;
532                  code = code * 8 + digitValue(input.charAt(i));
533                }
534                if (i + 1 < input.length() && isOctal(input.charAt(i + 1))) {
535                  ++i;
536                  code = code * 8 + digitValue(input.charAt(i));
537                }
538                result[pos++] = (byte)code;
539              } else {
540                switch (c) {
541                  case 'a' : result[pos++] = 0x07; break;
542                  case 'b' : result[pos++] = '\b'; break;
543                  case 'f' : result[pos++] = '\f'; break;
544                  case 'n' : result[pos++] = '\n'; break;
545                  case 'r' : result[pos++] = '\r'; break;
546                  case 't' : result[pos++] = '\t'; break;
547                  case 'v' : result[pos++] = 0x0b; break;
548                  case '\\': result[pos++] = '\\'; break;
549                  case '\'': result[pos++] = '\''; break;
550                  case '"' : result[pos++] = '\"'; break;
551    
552                  case 'x':
553                    // hex escape
554                    int code = 0;
555                    if (i + 1 < input.length() && isHex(input.charAt(i + 1))) {
556                      ++i;
557                      code = digitValue(input.charAt(i));
558                    } else {
559                      throw new InvalidEscapeSequence(
560                        "Invalid escape sequence: '\\x' with no digits");
561                    }
562                    if (i + 1 < input.length() && isHex(input.charAt(i + 1))) {
563                      ++i;
564                      code = code * 16 + digitValue(input.charAt(i));
565                    }
566                    result[pos++] = (byte)code;
567                    break;
568    
569                  default:
570                    throw new InvalidEscapeSequence(
571                      "Invalid escape sequence: '\\" + c + "'");
572                }
573              }
574            } else {
575              throw new InvalidEscapeSequence(
576                "Invalid escape sequence: '\\' at end of string.");
577            }
578          } else {
579            result[pos++] = (byte)c;
580          }
581        }
582    
583        return new Buffer(result, 0, pos);
584      }
585    
586      /**
587       * Thrown by {@link TextFormat#unescapeBytes} and
588       * {@link TextFormat#unescapeText} when an invalid escape sequence is seen.
589       */
590      static class InvalidEscapeSequence extends IOException {
591        public InvalidEscapeSequence(String description) {
592          super(description);
593        }
594      }
595    
596      /**
597       * Like {@link #escapeBytes(Buffer)}, but escapes a text string.
598       * Non-ASCII characters are first encoded as UTF-8, then each byte is escaped
599       * individually as a 3-digit octal escape.  Yes, it's weird.
600       */
601      static String escapeText(String input) {
602        return escapeBytes(new UTF8Buffer(input));
603      }
604    
605      /**
606       * Un-escape a text string as escaped using {@link #escapeText(String)}.
607       * Two-digit hex escapes (starting with "\x") are also recognized.
608       */
609      static String unescapeText(String input) throws InvalidEscapeSequence {
610        return new UTF8Buffer(unescapeBytes(input)).toString();
611      }
612    
613      /** Is this an octal digit? */
614      private static boolean isOctal(char c) {
615        return '0' <= c && c <= '7';
616      }
617    
618      /** Is this a hex digit? */
619      private static boolean isHex(char c) {
620        return ('0' <= c && c <= '9') ||
621               ('a' <= c && c <= 'f') ||
622               ('A' <= c && c <= 'F');
623      }
624    
625      /**
626       * Interpret a character as a digit (in any base up to 36) and return the
627       * numeric value.  This is like {@code Character.digit()} but we don't accept
628       * non-ASCII digits.
629       */
630      private static int digitValue(char c) {
631        if ('0' <= c && c <= '9') {
632          return c - '0';
633        } else if ('a' <= c && c <= 'z') {
634          return c - 'a' + 10;
635        } else {
636          return c - 'A' + 10;
637        }
638      }
639    
640      /**
641       * Parse a 32-bit signed integer from the text.  Unlike the Java standard
642       * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
643       * and "0" to signify hexidecimal and octal numbers, respectively.
644       */
645      static int parseInt32(String text) throws NumberFormatException {
646        return (int) parseInteger(text, true, false);
647      }
648    
649      /**
650       * Parse a 32-bit unsigned integer from the text.  Unlike the Java standard
651       * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
652       * and "0" to signify hexidecimal and octal numbers, respectively.  The
653       * result is coerced to a (signed) {@code int} when returned since Java has
654       * no unsigned integer type.
655       */
656      static int parseUInt32(String text) throws NumberFormatException {
657        return (int) parseInteger(text, false, false);
658      }
659    
660      /**
661       * Parse a 64-bit signed integer from the text.  Unlike the Java standard
662       * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
663       * and "0" to signify hexidecimal and octal numbers, respectively.
664       */
665      static long parseInt64(String text) throws NumberFormatException {
666        return parseInteger(text, true, true);
667      }
668    
669      /**
670       * Parse a 64-bit unsigned integer from the text.  Unlike the Java standard
671       * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
672       * and "0" to signify hexidecimal and octal numbers, respectively.  The
673       * result is coerced to a (signed) {@code long} when returned since Java has
674       * no unsigned long type.
675       */
676      static long parseUInt64(String text) throws NumberFormatException {
677        return parseInteger(text, false, true);
678      }
679    
680      private static long parseInteger(String text,
681                                       boolean isSigned,
682                                       boolean isLong)
683                                       throws NumberFormatException {
684        int pos = 0;
685    
686        boolean negative = false;
687        if (text.startsWith("-", pos)) {
688          if (!isSigned) {
689            throw new NumberFormatException("Number must be positive: " + text);
690          }
691          ++pos;
692          negative = true;
693        }
694    
695        int radix = 10;
696        if (text.startsWith("0x", pos)) {
697          pos += 2;
698          radix = 16;
699        } else if (text.startsWith("0", pos)) {
700          radix = 8;
701        }
702    
703        String numberText = text.substring(pos);
704    
705        long result = 0;
706        if (numberText.length() < 16) {
707          // Can safely assume no overflow.
708          result = Long.parseLong(numberText, radix);
709          if (negative) {
710            result = -result;
711          }
712    
713          // Check bounds.
714          // No need to check for 64-bit numbers since they'd have to be 16 chars
715          // or longer to overflow.
716          if (!isLong) {
717            if (isSigned) {
718              if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) {
719                throw new NumberFormatException(
720                  "Number out of range for 32-bit signed integer: " + text);
721              }
722            } else {
723              if (result >= (1L << 32) || result < 0) {
724                throw new NumberFormatException(
725                  "Number out of range for 32-bit unsigned integer: " + text);
726              }
727            }
728          }
729        } else {
730          BigInteger bigValue = new BigInteger(numberText, radix);
731          if (negative) {
732            bigValue = bigValue.negate();
733          }
734    
735          // Check bounds.
736          if (!isLong) {
737            if (isSigned) {
738              if (bigValue.bitLength() > 31) {
739                throw new NumberFormatException(
740                  "Number out of range for 32-bit signed integer: " + text);
741              }
742            } else {
743              if (bigValue.bitLength() > 32) {
744                throw new NumberFormatException(
745                  "Number out of range for 32-bit unsigned integer: " + text);
746              }
747            }
748          } else {
749            if (isSigned) {
750              if (bigValue.bitLength() > 63) {
751                throw new NumberFormatException(
752                  "Number out of range for 64-bit signed integer: " + text);
753              }
754            } else {
755              if (bigValue.bitLength() > 64) {
756                throw new NumberFormatException(
757                  "Number out of range for 64-bit unsigned integer: " + text);
758              }
759            }
760          }
761    
762          result = bigValue.longValue();
763        }
764    
765        return result;
766      }
767    }