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}