001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.stream.file; 033 034import java.io.BufferedReader; 035import java.io.FileNotFoundException; 036import java.io.FileReader; 037import java.io.IOException; 038import java.io.InputStream; 039import java.io.InputStreamReader; 040import java.io.Reader; 041import java.io.StreamTokenizer; 042import java.net.URL; 043import java.util.ArrayList; 044import java.util.Arrays; 045import java.util.HashMap; 046 047import org.graphstream.stream.SourceBase; 048import org.graphstream.ui.geom.Point3; 049 050/** 051 * Base for various graph file input. 052 * 053 * <p> 054 * This class is a piece of crap. However it is still used in many places... :-( 055 * TODO use a parser generator to replace it. 056 * </p> 057 * 058 * <p> 059 * This class provides parsing utilities to help the creation of new graph 060 * readers/parsers. It handles a stack of input files that allow to easily 061 * implements "includes" (that is interrupting the parsing of a file to input 062 * another one). It wraps stream tokenizers allowing to eat or get specific 063 * token types easily. 064 * </p> 065 * 066 * <p> 067 * It is well suited for graph formats using text (not binary), but not for XML 068 * based files where a real XML parser would probably be better. 069 * </p> 070 */ 071public abstract class FileSourceBase extends SourceBase implements FileSource { 072 // Attributes 073 074 /** 075 * The quote character. Can be changed in descendants. 076 */ 077 protected int QUOTE_CHAR = '"'; 078 079 /** 080 * The comment character. Can be changed in descendants. 081 */ 082 protected int COMMENT_CHAR = '#'; 083 084 /** 085 * Is EOL significant?. 086 */ 087 protected boolean eol_is_significant = false; 088 089 /** 090 * Stack of tokenizers/filenames. Each tokenizer is open on a file. When an 091 * include is found, the current tokenizer is pushed on the stack and a new 092 * one for the included file is created. Once the included file is parsed, 093 * the tokenizer is popped of the stack and the previous one is used. 094 */ 095 protected ArrayList<CurrentFile> tok_stack = new ArrayList<CurrentFile>(); 096 097 /** 098 * Current tokenizer. 099 */ 100 protected StreamTokenizer st; 101 102 /** 103 * Current file name. 104 */ 105 protected String filename; 106 107 /** 108 * Map of unknown attributes to corresponding classes. 109 */ 110 protected HashMap<String, String> attribute_classes = new HashMap<String, String>(); 111 112 // Constructors 113 114 /** 115 * No-op constructor. 116 */ 117 protected FileSourceBase() { 118 } 119 120 /** 121 * Setup the reader End-Of-Line policy. 122 * 123 * @param eol_is_significant 124 * If true EOL will be returned as a token, else it is ignored. 125 */ 126 protected FileSourceBase(boolean eol_is_significant) { 127 this.eol_is_significant = eol_is_significant; 128 } 129 130 /** 131 * Setup the reader End-Of-Line policy and specific comment and quote 132 * characters. 133 * 134 * @param eol_is_significant 135 * If true EOL will be returned as a token, else it is ignored. 136 * @param commentChar 137 * Character used for one line comments. 138 * @param quoteChar 139 * Character used to enclose quotations. 140 */ 141 protected FileSourceBase(boolean eol_is_significant, int commentChar, int quoteChar) { 142 this.eol_is_significant = eol_is_significant; 143 144 this.COMMENT_CHAR = commentChar; 145 this.QUOTE_CHAR = quoteChar; 146 } 147 148 // Access 149 150 // Command -- Complete modeField. 151 152 public void readAll(String filename) throws IOException { 153 begin(filename); 154 while (nextEvents()) 155 ; 156 end(); 157 } 158 159 public void readAll(URL url) throws IOException { 160 begin(url); 161 while (nextEvents()) 162 ; 163 end(); 164 } 165 166 public void readAll(InputStream stream) throws IOException { 167 begin(stream); 168 while (nextEvents()) 169 ; 170 end(); 171 } 172 173 public void readAll(Reader reader) throws IOException { 174 begin(reader); 175 while (nextEvents()) 176 ; 177 end(); 178 } 179 180 // Commands -- By-event modeField. 181 182 public void begin(String filename) throws IOException { 183 pushTokenizer(filename); 184 } 185 186 public void begin(InputStream stream) throws IOException { 187 pushTokenizer(stream); 188 } 189 190 public void begin(URL url) throws IOException { 191 pushTokenizer(url); 192 } 193 194 public void begin(Reader reader) throws IOException { 195 pushTokenizer(reader); 196 } 197 198 public abstract boolean nextEvents() throws IOException; 199 200 public void end() throws IOException { 201 popTokenizer(); 202 } 203 204 // Command 205 206 /** 207 * Declare that when <code>attribute</code> is found, the corresponding 208 * <code>attribute_class</code> must be instantiated and inserted in the 209 * current element being parsed. This is equivalent to the "map" keyword of 210 * the GML file. An attribute appears in a GML file as a name followed by a 211 * "[...]" block. The contents of this block defines sub-attributes that 212 * must map to public fields of the attribute. Only attributes that are not 213 * handled specifically by this parser can be added. 214 * 215 * @param attribute 216 * must name the attribute. 217 * @param attribute_class 218 * must be the complete name of a Java class that will represent 219 * the attribute. 220 */ 221 public void addAttributeClass(String attribute, String attribute_class) { 222 attribute_classes.put(attribute, attribute_class); 223 } 224 225 // Command -- Parsing -- Include mechanism 226 227 /** 228 * Include the content of a <code>file</code>. This pushes a new tokenizer 229 * on the input stack, calls the {@link #continueParsingInInclude()} method 230 * (that must be implemented to read the include contents) and when finished 231 * pops the tokenizer of the input stack. 232 */ 233 protected void include(String file) throws IOException { 234 pushTokenizer(file); 235 continueParsingInInclude(); 236 popTokenizer(); 237 } 238 239 /** 240 * Must be implemented to read the content of an include. The current 241 * tokenizer will be set to the included file. When this method returns, the 242 * include file will be closed an parsing will continue where it was before 243 * inclusion. 244 */ 245 protected abstract void continueParsingInInclude() throws IOException; 246 247 /** 248 * Push a tokenizer created from a file name on the file stack and make it 249 * current. 250 * 251 * @param file 252 * Name of the file used as source for the tokenizer. 253 */ 254 protected void pushTokenizer(String file) throws IOException { 255 StreamTokenizer tok; 256 CurrentFile cur; 257 Reader reader; 258 259 try { 260 reader = createReaderFrom(file); 261 tok = createTokenizer(reader); 262 263 cur = new CurrentFile(file, tok, reader); 264 } catch (FileNotFoundException e) { 265 throw new IOException("cannot read file '" + file 266 + "', not found: " + e.getMessage()); 267 } 268 269 configureTokenizer(tok); 270 tok_stack.add(cur); 271 272 st = tok; 273 filename = file; 274 } 275 276 /** 277 * Create a reader for by the tokenizer. 278 * 279 * @param file 280 * File name to be opened. 281 * @return a reader for the tokenizer. 282 * @throws FileNotFoundException 283 * If the given file does not exist or un readable. 284 */ 285 protected Reader createReaderFrom(String file) throws FileNotFoundException { 286 return new BufferedReader(new FileReader(file)); 287 } 288 289 /** 290 * Create a stream that can be read by the tokenizer. 291 * 292 * @param stream 293 * Input stream to be open as a reader. 294 * @return a reader for the tokenizer. 295 */ 296 protected Reader createReaderFrom(InputStream stream) { 297 return new BufferedReader(new InputStreamReader(stream)); 298 } 299 300 301 /** 302 * Push a tokenizer created from a stream on the file stack and make it 303 * current. 304 * 305 * @param url 306 * The URL used as source for the tokenizer. 307 */ 308 protected void pushTokenizer(URL url) throws IOException { 309 pushTokenizer(url.openStream(), url.toString()); 310 } 311 312 /** 313 * Push a tokenizer created from a stream on the file stack and make it 314 * current. 315 * 316 * @param stream 317 * The stream used as source for the tokenizer. 318 */ 319 protected void pushTokenizer(InputStream stream) throws IOException { 320 pushTokenizer(stream, "<?input-stream?>"); 321 } 322 323 /** 324 * Push a tokenizer created from a stream on the file stack and make it 325 * current. 326 * 327 * @param stream 328 * The stream used as source for the tokenizer. 329 * @param name 330 * The name of the input stream. 331 */ 332 protected void pushTokenizer(InputStream stream, String name) 333 throws IOException { 334 StreamTokenizer tok; 335 CurrentFile cur; 336 Reader reader; 337 338 reader = createReaderFrom(stream); 339 tok = createTokenizer(reader); 340 cur = new CurrentFile(name, tok, reader); 341 342 configureTokenizer(tok); 343 tok_stack.add(cur); 344 345 st = tok; 346 filename = name; 347 } 348 349 350 351 /** 352 * Push a tokenizer created from a reader on the file stack and make it 353 * current. 354 * 355 * @param reader 356 * The reader used as source for the tokenizer. 357 */ 358 protected void pushTokenizer(Reader reader) throws IOException { 359 StreamTokenizer tok; 360 CurrentFile cur; 361 362 tok = createTokenizer(reader); 363 cur = new CurrentFile("<?reader?>", tok,reader); 364 configureTokenizer(tok); 365 tok_stack.add(cur); 366 367 st = tok; 368 filename = "<?reader?>"; 369 370 } 371 372 /** 373 * Create a tokenizer from an input source. 374 * 375 * @param reader 376 * The reader. 377 * @return The new tokenizer. 378 * @throws IOException 379 * For any I/O error. 380 */ 381 private StreamTokenizer createTokenizer(Reader reader) 382 throws IOException { 383 return new StreamTokenizer(new BufferedReader(reader)); 384 } 385 386 387 /** 388 * Method to override to configure the tokenizer behaviour. It is called 389 * each time a tokenizer is created (for the parsed file and all included 390 * files). 391 */ 392 protected void configureTokenizer(StreamTokenizer tok) throws IOException { 393 if (COMMENT_CHAR > 0) 394 tok.commentChar(COMMENT_CHAR); 395 tok.quoteChar(QUOTE_CHAR); 396 tok.eolIsSignificant(eol_is_significant); 397 tok.wordChars('_', '_'); 398 tok.parseNumbers(); 399 } 400 401 /** 402 * Remove the current tokenizer from the stack and restore the previous one 403 * (if any). 404 */ 405 protected void popTokenizer() throws IOException { 406 int n = tok_stack.size(); 407 408 if (n <= 0) 409 throw new RuntimeException("poped one too many tokenizer"); 410 411 n -= 1; 412 413 CurrentFile cur = tok_stack.remove(n); 414 cur.reader.close(); 415 416 if (n > 0) { 417 n -= 1; 418 419 cur = tok_stack.get(n); 420 421 st = cur.tok; 422 filename = cur.file; 423 } 424 } 425 426 // Low level parsing 427 428 /** 429 * Push back the last read thing, so that it can be read anew. This allows 430 * to explore one token ahead, and if not corresponding to what is expected, 431 * go back. 432 */ 433 protected void pushBack() { 434 st.pushBack(); 435 } 436 437 /** 438 * Read EOF or report garbage at end of file. 439 */ 440 protected void eatEof() throws IOException { 441 int tok = st.nextToken(); 442 443 if (tok != StreamTokenizer.TT_EOF) 444 parseError("garbage at end of file, expecting EOF, " + gotWhat(tok)); 445 } 446 447 /** 448 * Read EOL. 449 */ 450 protected void eatEol() throws IOException { 451 int tok = st.nextToken(); 452 453 if (tok != StreamTokenizer.TT_EOL) 454 parseError("expecting EOL, " + gotWhat(tok)); 455 } 456 457 /** 458 * Read EOL or EOF. 459 * 460 * @return The token read StreamTokenizer.TT_EOL or StreamTokenizer.TT_EOF. 461 */ 462 protected int eatEolOrEof() throws IOException { 463 int tok = st.nextToken(); 464 465 if (tok != StreamTokenizer.TT_EOL && tok != StreamTokenizer.TT_EOF) 466 parseError("expecting EOL or EOF, " + gotWhat(tok)); 467 468 return tok; 469 } 470 471 /** 472 * Read an expected <code>word</code> token or generate a parse error. 473 */ 474 protected void eatWord(String word) throws IOException { 475 int tok = st.nextToken(); 476 477 if (tok != StreamTokenizer.TT_WORD) 478 parseError("expecting `" + word + "', " + gotWhat(tok)); 479 480 if (!st.sval.equals(word)) 481 parseError("expecting `" + word + "' got `" + st.sval + "'"); 482 } 483 484 /** 485 * Read an expected word among the given word list or generate a parse 486 * error. 487 * 488 * @param words 489 * The expected words. 490 */ 491 protected void eatWords(String... words) throws IOException { 492 int tok = st.nextToken(); 493 494 if (tok != StreamTokenizer.TT_WORD) 495 parseError("expecting one of `[" + Arrays.toString(words) + "]', " + gotWhat(tok)); 496 497 boolean found = false; 498 499 for (String word : words) { 500 if (st.sval.equals(word)) { 501 found = true; 502 break; 503 } 504 } 505 506 if (!found) 507 parseError("expecting one of `[" + Arrays.toString(words) + "]', got `" + st.sval + "'"); 508 } 509 510 /** 511 * Eat either a word or another, and return the eated one. 512 * 513 * @param word1 514 * The first word to eat. 515 * @param word2 516 * The alternative word to eat. 517 * @return The word eaten. 518 */ 519 protected String eatOneOfTwoWords(String word1, String word2) 520 throws IOException { 521 int tok = st.nextToken(); 522 523 if (tok != StreamTokenizer.TT_WORD) 524 parseError("expecting `" + word1 + "' or `" + word2 + "', " 525 + gotWhat(tok)); 526 527 if (st.sval.equals(word1)) 528 return word1; 529 530 if (st.sval.equals(word2)) 531 return word2; 532 533 parseError("expecting `" + word1 + "' or `" + word2 + "' got `" 534 + st.sval + "'"); 535 return null; 536 } 537 538 /** 539 * Eat the expected symbol or generate a parse error. 540 */ 541 protected void eatSymbol(char symbol) throws IOException { 542 int tok = st.nextToken(); 543 544 if (tok != symbol) 545 parseError("expecting symbol `" + symbol + "', " + gotWhat(tok)); 546 } 547 548 /** 549 * Eat one of the list of expected <code>symbols</code> or generate a parse 550 * error none of <code>symbols</code> can be found. 551 */ 552 protected int eatSymbols(String symbols) throws IOException { 553 int tok = st.nextToken(); 554 int n = symbols.length(); 555 boolean f = false; 556 557 for (int i = 0; i < n; ++i) { 558 if (tok == symbols.charAt(i)) { 559 f = true; 560 i = n; 561 } 562 } 563 564 if (!f) 565 parseError("expecting one of symbols `" + symbols + "', " 566 + gotWhat(tok)); 567 568 return tok; 569 } 570 571 /** 572 * Eat the expected <code>word</code> or push back what was read so that it 573 * can be read anew. 574 */ 575 protected void eatWordOrPushbak(String word) throws IOException { 576 int tok = st.nextToken(); 577 578 if (tok != StreamTokenizer.TT_WORD) 579 pushBack(); 580 581 if (!st.sval.equals(word)) 582 pushBack(); 583 } 584 585 /** 586 * Eat the expected <code>symbol</code> or push back what was read so that 587 * it can be read anew. 588 */ 589 protected void eatSymbolOrPushback(char symbol) throws IOException { 590 int tok = st.nextToken(); 591 592 if (tok != symbol) 593 pushBack(); 594 } 595 596 /** 597 * Eat all until an EOL is found. The EOL is also eaten. This works only if 598 * EOL is significant (else it does nothing). 599 */ 600 protected void eatAllUntilEol() throws IOException { 601 if (!eol_is_significant) 602 return; 603 604 int tok = st.nextToken(); 605 606 if (tok == StreamTokenizer.TT_EOF) 607 return; 608 609 while ((tok != StreamTokenizer.TT_EOL) 610 && (tok != StreamTokenizer.TT_EOF)) { 611 tok = st.nextToken(); 612 } 613 } 614 615 /** 616 * Eat all availables EOLs. 617 */ 618 protected void eatAllEols() throws IOException { 619 if (!eol_is_significant) 620 return; 621 622 int tok = st.nextToken(); 623 624 while (tok == StreamTokenizer.TT_EOL) 625 tok = st.nextToken(); 626 627 pushBack(); 628 } 629 630 /** 631 * Read a word or generate a parse error. 632 */ 633 protected String getWord() throws IOException { 634 int tok = st.nextToken(); 635 636 if (tok != StreamTokenizer.TT_WORD) 637 parseError("expecting a word, " + gotWhat(tok)); 638 639 return st.sval; 640 } 641 642 /** 643 * Get a symbol. 644 */ 645 protected char getSymbol() throws IOException { 646 int tok = st.nextToken(); 647 648 if (tok > 0 && tok != StreamTokenizer.TT_WORD 649 && tok != StreamTokenizer.TT_NUMBER 650 && tok != StreamTokenizer.TT_EOL 651 && tok != StreamTokenizer.TT_EOF && tok != QUOTE_CHAR 652 && tok != COMMENT_CHAR) { 653 return (char) tok; 654 } 655 656 parseError("expecting a symbol, " + gotWhat(tok)); 657 return (char) 0; // Never reached. 658 } 659 660 /** 661 * Get a symbol or push back what was read so that it can be read anew. If 662 * no symbol is found, 0 is returned. 663 */ 664 protected char getSymbolOrPushback() throws IOException { 665 int tok = st.nextToken(); 666 667 if (tok > 0 && tok != StreamTokenizer.TT_WORD 668 && tok != StreamTokenizer.TT_NUMBER 669 && tok != StreamTokenizer.TT_EOL 670 && tok != StreamTokenizer.TT_EOF && tok != QUOTE_CHAR 671 && tok != COMMENT_CHAR) { 672 return (char) tok; 673 } 674 675 pushBack(); 676 677 return (char) 0; 678 } 679 680 /** 681 * Read a string constant (between quotes) or generate a parse error. Return 682 * the content of the string without the quotes. 683 */ 684 protected String getString() throws IOException { 685 int tok = st.nextToken(); 686 687 if (tok != QUOTE_CHAR) 688 parseError("expecting a string constant, " + gotWhat(tok)); 689 690 return st.sval; 691 } 692 693 /** 694 * Read a word or number or generate a parse error. If it is a number it is 695 * converted to a string before being returned. 696 */ 697 protected String getWordOrNumber() throws IOException { 698 int tok = st.nextToken(); 699 700 if (tok != StreamTokenizer.TT_WORD && tok != StreamTokenizer.TT_NUMBER) 701 parseError("expecting a word or number, " + gotWhat(tok)); 702 703 if (tok == StreamTokenizer.TT_NUMBER) { 704 // If st.nval is an integer, as it is stored into a double, 705 // toString() will transform it by automatically adding ".0", we 706 // prevent this. The tokenizer does not allow to read integers. 707 708 if ((st.nval - ((int) st.nval)) == 0) 709 return Integer.toString((int) st.nval); 710 else 711 return Double.toString(st.nval); 712 } else { 713 return st.sval; 714 } 715 } 716 717 /** 718 * Read a string or number or generate a parse error. If it is a number it 719 * is converted to a string before being returned. 720 */ 721 protected String getStringOrNumber() throws IOException { 722 int tok = st.nextToken(); 723 724 if (tok != QUOTE_CHAR && tok != StreamTokenizer.TT_NUMBER) 725 parseError("expecting a string constant or a number, " 726 + gotWhat(tok)); 727 728 if (tok == StreamTokenizer.TT_NUMBER) { 729 if ((st.nval - ((int) st.nval)) == 0) 730 return Integer.toString((int) st.nval); 731 else 732 return Double.toString(st.nval); 733 } else { 734 return st.sval; 735 } 736 } 737 738 /** 739 * Read a string or number or pushback and return null. If it is a number it 740 * is converted to a string before being returned. 741 */ 742 protected String getStringOrWordOrNumberOrPushback() throws IOException { 743 int tok = st.nextToken(); 744 745 if (tok == StreamTokenizer.TT_EOL || tok == StreamTokenizer.TT_EOF) { 746 pushBack(); 747 return null; 748 } 749 750 if (tok == StreamTokenizer.TT_NUMBER) { 751 if ((st.nval - ((int) st.nval)) == 0) 752 return Integer.toString((int) st.nval); 753 else 754 return Double.toString(st.nval); 755 } else if (tok == StreamTokenizer.TT_WORD || tok == QUOTE_CHAR) { 756 return st.sval; 757 } else { 758 pushBack(); 759 return null; 760 } 761 } 762 763 /** 764 * Read a string or number or generate a parse error. If it is a number it 765 * is converted to a string before being returned. 766 */ 767 protected String getStringOrWordOrNumber() throws IOException { 768 int tok = st.nextToken(); 769 770 if (tok == StreamTokenizer.TT_EOL || tok == StreamTokenizer.TT_EOF) 771 parseError("expecting word, string or number, " + gotWhat(tok)); 772 773 if (tok == StreamTokenizer.TT_NUMBER) { 774 if ((st.nval - ((int) st.nval)) == 0) 775 return Integer.toString((int) st.nval); 776 else 777 return Double.toString(st.nval); 778 } else { 779 return st.sval; 780 } 781 } 782 783 /** 784 * Read a string or number or generate a parse error. The returned value is 785 * converted to a Number of a String depending on its type. 786 */ 787 protected Object getStringOrWordOrNumberO() throws IOException { 788 int tok = st.nextToken(); 789 790 if (tok == StreamTokenizer.TT_EOL || tok == StreamTokenizer.TT_EOF) 791 parseError("expecting word, string or number, " + gotWhat(tok)); 792 793 if (tok == StreamTokenizer.TT_NUMBER) { 794 return st.nval; 795 } else { 796 return st.sval; 797 } 798 } 799 800 /** 801 * Read a string or number or generate a parse error. The returned value is 802 * converted to a Number of a String depending on its type. 803 */ 804 protected Object getStringOrWordOrSymbolOrNumberO() throws IOException { 805 int tok = st.nextToken(); 806 807 if (tok == StreamTokenizer.TT_EOL || tok == StreamTokenizer.TT_EOF) 808 parseError("expecting word, string or number, " + gotWhat(tok)); 809 810 if (tok == StreamTokenizer.TT_NUMBER) { 811 return st.nval; 812 } else if (tok == StreamTokenizer.TT_WORD) { 813 return st.sval; 814 } else 815 return Character.toString((char) tok); 816 } 817 818 /** 819 * Read a word or string or generate a parse error. 820 */ 821 protected String getWordOrString() throws IOException { 822 int tok = st.nextToken(); 823 824 if (tok == StreamTokenizer.TT_WORD || tok == QUOTE_CHAR) 825 return st.sval; 826 827 parseError("expecting a word or string, " + gotWhat(tok)); 828 return null; 829 } 830 831 /** 832 * Read a word or symbol or generate a parse error. 833 */ 834 protected String getWordOrSymbol() throws IOException { 835 int tok = st.nextToken(); 836 837 if (tok == StreamTokenizer.TT_NUMBER || tok == QUOTE_CHAR 838 || tok == StreamTokenizer.TT_EOF) 839 parseError("expecting a word or symbol, " + gotWhat(tok)); 840 841 if (tok == StreamTokenizer.TT_WORD) 842 return st.sval; 843 else 844 return Character.toString((char) tok); 845 } 846 847 /** 848 * Read a word or symbol or push back the read thing so that it is readable 849 * anew. In the second case, null is returned. 850 */ 851 protected String getWordOrSymbolOrPushback() throws IOException { 852 int tok = st.nextToken(); 853 854 if (tok == StreamTokenizer.TT_NUMBER || tok == QUOTE_CHAR 855 || tok == StreamTokenizer.TT_EOF) { 856 pushBack(); 857 return null; 858 } 859 860 if (tok == StreamTokenizer.TT_WORD) 861 return st.sval; 862 else 863 return Character.toString((char) tok); 864 } 865 866 /** 867 * Read a word or symbol or string or generate a parse error. 868 */ 869 protected String getWordOrSymbolOrString() throws IOException { 870 int tok = st.nextToken(); 871 872 if (tok == StreamTokenizer.TT_NUMBER || tok == StreamTokenizer.TT_EOF) 873 parseError("expecting a word, symbol or string, " + gotWhat(tok)); 874 875 if (tok == QUOTE_CHAR) 876 return st.sval; 877 878 if (tok == StreamTokenizer.TT_WORD) 879 return st.sval; 880 else 881 return Character.toString((char) tok); 882 } 883 884 /** 885 * Read a word or symbol or string or number or generate a parse error. 886 */ 887 protected String getAllExceptedEof() throws IOException { 888 int tok = st.nextToken(); 889 890 if (tok == StreamTokenizer.TT_EOF) 891 parseError("expecting all excepted EOF, " + gotWhat(tok)); 892 893 if (tok == StreamTokenizer.TT_NUMBER || tok == StreamTokenizer.TT_EOF) { 894 if ((st.nval - ((int) st.nval)) == 0) 895 return Integer.toString((int) st.nval); 896 else 897 return Double.toString(st.nval); 898 } 899 900 if (tok == QUOTE_CHAR) 901 return st.sval; 902 903 if (tok == StreamTokenizer.TT_WORD) 904 return st.sval; 905 else 906 return Character.toString((char) tok); 907 } 908 909 /** 910 * Read a word, a symbol or EOF, or generate a parse error. If this is EOF, 911 * the string "EOF" is returned. 912 */ 913 protected String getWordOrSymbolOrEof() throws IOException { 914 int tok = st.nextToken(); 915 916 if (tok == StreamTokenizer.TT_NUMBER || tok == QUOTE_CHAR) 917 parseError("expecting a word or symbol, " + gotWhat(tok)); 918 919 if (tok == StreamTokenizer.TT_WORD) 920 return st.sval; 921 else if (tok == StreamTokenizer.TT_EOF) 922 return "EOF"; 923 else 924 return Character.toString((char) tok); 925 } 926 927 /** 928 * Read a word or symbol or string or EOL/EOF or generate a parse error. If 929 * EOL is read the "EOL" string is returned. If EOF is read the "EOF" string 930 * is returned. 931 * 932 * @return A string. 933 */ 934 protected String getWordOrSymbolOrStringOrEolOrEof() throws IOException { 935 int tok = st.nextToken(); 936 937 if (tok == StreamTokenizer.TT_NUMBER) 938 parseError("expecting a word, symbol or string, " + gotWhat(tok)); 939 940 if (tok == QUOTE_CHAR) 941 return st.sval; 942 943 if (tok == StreamTokenizer.TT_WORD) 944 return st.sval; 945 946 if (tok == StreamTokenizer.TT_EOF) 947 return "EOF"; 948 949 if (tok == StreamTokenizer.TT_EOL) 950 return "EOL"; 951 952 return Character.toString((char) tok); 953 } 954 955 /** 956 * Read a word or number or string or EOL/EOF or generate a parse error. If 957 * EOL is read the "EOL" string is returned. If EOF is read the "EOF" string 958 * is returned. If a number is returned, it is converted to a string as 959 * follows: if it is an integer, only the integer part is converted to a 960 * string without dot or comma and no leading zeros. If it is a float the 961 * fractional part is also converted and the dot is used as separator. 962 * 963 * @return A string. 964 */ 965 protected String getWordOrNumberOrStringOrEolOrEof() throws IOException { 966 int tok = st.nextToken(); 967 968 if (tok == StreamTokenizer.TT_NUMBER) { 969 if (st.nval - ((int) st.nval) != 0) 970 return Double.toString(st.nval); 971 972 return Integer.toString((int) st.nval); 973 } 974 975 if (tok == QUOTE_CHAR) 976 return st.sval; 977 978 if (tok == StreamTokenizer.TT_WORD) 979 return st.sval; 980 981 if (tok == StreamTokenizer.TT_EOF) 982 return "EOF"; 983 984 if (tok == StreamTokenizer.TT_EOL) 985 return "EOL"; 986 987 parseError("expecting a word, a number, a string, EOL or EOF, " 988 + gotWhat(tok)); 989 return null; // Never happen, parseError throws unconditionally an 990 // exception. 991 } 992 993 /** 994 * Read a word or string or EOL/EOF or generate a parse error. If EOL is 995 * read the "EOL" string is returned. If EOF is read the "EOF" string is 996 * returned. 997 * 998 * @return A string. 999 */ 1000 protected String getWordOrStringOrEolOrEof() throws IOException { 1001 int tok = st.nextToken(); 1002 1003 if (tok == StreamTokenizer.TT_WORD) 1004 return st.sval; 1005 1006 if (tok == QUOTE_CHAR) 1007 return st.sval; 1008 1009 if (tok == StreamTokenizer.TT_EOL) 1010 return "EOL"; 1011 1012 if (tok == StreamTokenizer.TT_EOF) 1013 return "EOF"; 1014 1015 parseError("expecting a word, a string, EOL or EOF, " + gotWhat(tok)); 1016 return null; // Never happen, parseError throws unconditionally an 1017 // exception. 1018 } 1019 1020 // Order: Word | String | Symbol | Number | Eol | Eof 1021 1022 /** 1023 * Read a word or number or string or EOL/EOF or generate a parse error. If 1024 * EOL is read the "EOL" string is returned. If EOF is read the "EOF" string 1025 * is returned. If a number is returned, it is converted to a string as 1026 * follows: if it is an integer, only the integer part is converted to a 1027 * string without dot or comma and no leading zeros. If it is a float the 1028 * fractional part is also converted and the dot is used as separator. 1029 * 1030 * @return A string. 1031 */ 1032 protected String getWordOrSymbolOrNumberOrStringOrEolOrEof() 1033 throws IOException { 1034 int tok = st.nextToken(); 1035 1036 if (tok == StreamTokenizer.TT_NUMBER) { 1037 if (st.nval - ((int) st.nval) != 0) 1038 return Double.toString(st.nval); 1039 1040 return Integer.toString((int) st.nval); 1041 } 1042 1043 if (tok == QUOTE_CHAR) 1044 return st.sval; 1045 1046 if (tok == StreamTokenizer.TT_WORD) 1047 return st.sval; 1048 1049 if (tok == StreamTokenizer.TT_EOF) 1050 return "EOF"; 1051 1052 if (tok == StreamTokenizer.TT_EOL) 1053 return "EOL"; 1054 1055 return Character.toString((char) tok); 1056 } 1057 1058 /** 1059 * Read a number or generate a parse error. 1060 */ 1061 protected double getNumber() throws IOException { 1062 int tok = st.nextToken(); 1063 1064 if (tok != StreamTokenizer.TT_NUMBER) 1065 parseError("expecting a number, " + gotWhat(tok)); 1066 1067 return st.nval; 1068 } 1069 1070 /** 1071 * Read a number (possibly with an exponent) or generate a parse error. 1072 */ 1073 protected double getNumberExp() throws IOException { 1074 int tok = st.nextToken(); 1075 1076 if (tok != StreamTokenizer.TT_NUMBER) 1077 parseError("expecting a number, " + gotWhat(tok)); 1078 1079 double nb = st.nval; 1080 1081 tok = st.nextToken(); 1082 1083 if (tok == StreamTokenizer.TT_WORD 1084 && (st.sval.startsWith("e-") || st.sval.startsWith("e+"))) { 1085 double exp = Double.parseDouble(st.sval.substring(2)); 1086 return Math.pow(nb, exp); 1087 } else { 1088 st.pushBack(); 1089 } 1090 1091 return nb; 1092 } 1093 1094 /** 1095 * Return a string containing "got " then the content of the current 1096 * <code>token</code>. 1097 */ 1098 protected String gotWhat(int token) { 1099 switch (token) { 1100 case StreamTokenizer.TT_NUMBER: 1101 return "got number `" + st.nval + "'"; 1102 case StreamTokenizer.TT_WORD: 1103 return "got word `" + st.sval + "'"; 1104 case StreamTokenizer.TT_EOF: 1105 return "got EOF"; 1106 default: 1107 if (token == QUOTE_CHAR) 1108 return "got string constant `" + st.sval + "'"; 1109 else 1110 return "unknown symbol `" + token + "' (" + ((char) token) 1111 + ")"; 1112 } 1113 } 1114 1115 /** 1116 * Generate a parse error. 1117 */ 1118 protected void parseError(String message) throws IOException { 1119 throw new IOException("parse error: " + filename + ": " + st.lineno() 1120 + ": " + message); 1121 } 1122 1123 // Access 1124 1125 /** 1126 * True if the <code>string</code> represents a truth statement ("1", 1127 * "true", "yes", "on"). 1128 */ 1129 protected boolean isTrue(String string) { 1130 string = string.toLowerCase(); 1131 1132 if (string.equals("1")) 1133 return true; 1134 if (string.equals("true")) 1135 return true; 1136 if (string.equals("yes")) 1137 return true; 1138 if (string.equals("on")) 1139 return true; 1140 1141 return false; 1142 } 1143 1144 /** 1145 * True if the <code>string</code> represents a false statement ("0", 1146 * "false", "no", "off"). 1147 */ 1148 protected boolean isFalse(String string) { 1149 string = string.toLowerCase(); 1150 1151 if (string.equals("0")) 1152 return true; 1153 if (string.equals("false")) 1154 return true; 1155 if (string.equals("no")) 1156 return true; 1157 if (string.equals("off")) 1158 return true; 1159 1160 return false; 1161 } 1162 1163 /** 1164 * Uses {@link #isTrue(String)} and {@link #isFalse(String)} to determine if 1165 * <code>value</code> is a truth value and return the corresponding boolean. 1166 * 1167 * @throws NumberFormatException 1168 * if the <code>value</code> is not a truth value. 1169 */ 1170 protected boolean getBoolean(String value) throws NumberFormatException { 1171 if (isTrue(value)) 1172 return true; 1173 if (isFalse(value)) 1174 return false; 1175 throw new NumberFormatException("not a truth value `" + value + "'"); 1176 } 1177 1178 /** 1179 * Try to transform <code>value</code> into a double. 1180 * 1181 * @throws NumberFormatException 1182 * if the <code>value</code> is not a double. 1183 */ 1184 protected double getReal(String value) throws NumberFormatException { 1185 return Double.parseDouble(value); 1186 } 1187 1188 /** 1189 * Try to transform <code>value</code> into a long. 1190 * 1191 * @throws NumberFormatException 1192 * if the <code>value</code> is not a long. 1193 */ 1194 protected long getInteger(String value) throws NumberFormatException { 1195 return Long.parseLong(value); 1196 } 1197 1198 /** 1199 * Get a number triplet with numbers separated by comas and return a new 1200 * point for it. For example "0,1,2". 1201 */ 1202 protected Point3 getPoint3(String value) throws NumberFormatException { 1203 int p0 = value.indexOf(','); 1204 int p1 = value.indexOf(',', p0 + 1); 1205 1206 if (p0 > 0 && p1 > 0) { 1207 String n0, n1, n2; 1208 float v0, v1, v2; 1209 1210 n0 = value.substring(0, p0); 1211 n1 = value.substring(p0 + 1, p1); 1212 n2 = value.substring(p1 + 1); 1213 1214 v0 = Float.parseFloat(n0); 1215 v1 = Float.parseFloat(n1); 1216 v2 = Float.parseFloat(n2); 1217 1218 return new Point3(v0, v1, v2); 1219 } 1220 1221 throw new NumberFormatException("value '" + value 1222 + "' not in a valid point3 format"); 1223 } 1224 1225 /* 1226 * Get a number triplet with numbers separated by comas and return new 1227 * bounds for it. For example "0,1,2". 1228 protected Bounds3 getBounds3(String value) throws NumberFormatException { 1229 int p0 = value.indexOf(','); 1230 int p1 = value.indexOf(',', p0 + 1); 1231 1232 if (p0 > 0 && p1 > 0) { 1233 String n0, n1, n2; 1234 float v0, v1, v2; 1235 1236 n0 = value.substring(0, p0); 1237 n1 = value.substring(p0 + 1, p1); 1238 n2 = value.substring(p1 + 1); 1239 1240 v0 = Float.parseFloat(n0); 1241 v1 = Float.parseFloat(n1); 1242 v2 = Float.parseFloat(n2); 1243 1244 return new Bounds3(v0, v1, v2); 1245 } 1246 1247 throw new NumberFormatException("value '" + value 1248 + "' not in a valid point3 format"); 1249 } 1250 */ 1251 1252 // Nested classes 1253 1254 /** 1255 * Currently processed file. 1256 * <p> 1257 * The graph reader base can process includes in files, and handles a stack 1258 * of files. 1259 * </p> 1260 * 1261 */ 1262 protected static class CurrentFile { 1263 /** 1264 * The file name. 1265 */ 1266 public String file; 1267 1268 /** 1269 * The stream tokenizer. 1270 */ 1271 public StreamTokenizer tok; 1272 1273 public Reader reader; 1274 1275 public CurrentFile(String f, StreamTokenizer t, Reader reader) { 1276 file = f; 1277 tok = t; 1278 this.reader=reader; 1279 } 1280 } 1281}