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 }