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 }