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.IOException;
035import java.io.InputStream;
036import java.io.Reader;
037import java.net.URL;
038import java.util.HashSet;
039
040/**
041 * Reader for the "ncol" graph format.
042 * 
043 * <p>
044 * The ncol graph format is a simple format where each line
045 * describes an edge by giving two node names and an optional third
046 * parameters giving the edge weight. The nodes are created implicitly.
047 * </p>
048 * 
049 * <p>
050 * Also, the format does not specify any direction for edges. By default all
051 * edges are undirected. It is specified in the format that you will never
052 * have directed edges and that the lines:
053 * <pre>
054 *     node1Name node2Name
055 * </pre>
056 * and
057 * <pre>
058 *     node2Name node1Name
059 * </pre>
060 * Cannot both appear at the same time in a file.
061 * </p>
062 * 
063 * <p>
064 * This format only contains edges. To ensure the "add node" events are sent
065 * before an edge referencing two nodes is created via an "add edge" event, this
066 * reader has a hash set of already encountered nodes. The hash set allows to
067 * issue "add node" events only when a node is encountered for the first time.
068 * </p>
069 * 
070 * </p> This hash set consumes memory, but is the only way to ensure "add node"
071 * events are correctly issued. If this input is directly connected to a graph,
072 * as graphs can create non-existing nodes automatically, you can disable the
073 * hash set of nodes using the constructor
074 * {@link #FileSourceNCol(boolean)}, and giving "false" for the first
075 * argument. </p>
076 * 
077 * The usual file name extension for this format is ".ncol".
078 */
079public class FileSourceNCol extends FileSourceBase {
080        // Attribute
081
082        /**
083         * Allocator for edge identifiers.
084         */
085        protected int edgeid = 0;
086
087        /**
088         * Set of existing nodes (if nodes are declared).
089         */
090        protected HashSet<String> nodes;
091
092        protected String graphName = "NCOL_";
093
094        // Construction
095
096        /**
097         * New reader for the "ncol" format.
098         */
099        public FileSourceNCol() {
100                this(false);
101        }
102
103        /**
104         * New reader for the "ncol" format.
105         * 
106         * @param declareNodes
107         *            If true (default=true) this reader outputs nodeAdded events.
108         */
109        public FileSourceNCol(boolean declareNodes) {
110                nodes = declareNodes ? new HashSet<String>() : null;
111        }
112
113        // Commands
114
115        @Override
116        protected void continueParsingInInclude() throws IOException {
117                // Should not happen, NCol files cannot be nested.
118        }
119
120        @Override
121        public boolean nextEvents() throws IOException {
122                String id1 = getWordOrNumberOrStringOrEolOrEof();
123
124                if (id1.equals("EOL")) {
125                        // Empty line. Skip it.
126                } else if (id1.equals("EOF")) {
127                        return false;
128                } else {
129                        declareNode(id1);
130
131                        String id2 = getWordOrNumberOrStringOrEolOrEof();
132
133                        if(!id2.equals("EOL") && !id2.equals("EOF")) {
134                                // Loops are not accepted by the format.
135                                if (!id1.equals(id2)) {
136                                        // There may be a weight.
137                                        String weight = getWordOrNumberOrStringOrEolOrEof();
138                                        double w = 0.0;
139                                        
140                                        if(weight.equals("EOL") || weight.equals("EOF")) {
141                                                weight = null;
142                                                pushBack();
143                                        } else {
144                                                try {
145                                                        w = Double.parseDouble(weight);
146                                                } catch(Exception e) {
147                                                        throw new IOException(String.format("cannot transform weight %s into a number", weight));
148                                                }
149                                        }
150                                        
151                                        String edgeId = Integer.toString(edgeid++);
152
153                                        declareNode(id2);
154                                        sendEdgeAdded(graphName, edgeId, id1, id2, false);
155                                        
156                                        if(weight != null)
157                                                sendEdgeAttributeAdded(graphName, edgeId, "weight", (Double)w);
158                                }
159                        } else {
160                                throw new IOException("unexpected EOL or EOF");
161                        }
162                }
163
164                return true;
165        }
166
167        protected void declareNode(String id) {
168                if (nodes != null) {
169                        if (!nodes.contains(id)) {
170                                sendNodeAdded(graphName, id);
171                                nodes.add(id);
172                        }
173                }
174        }
175
176        @Override
177        public void begin(String filename) throws IOException {
178                super.begin(filename);
179                init();
180        }
181
182        @Override
183        public void begin(URL url) throws IOException {
184                super.begin(url);
185                init();
186        }
187
188        @Override
189        public void begin(InputStream stream) throws IOException {
190                super.begin(stream);
191                init();
192        }
193
194        @Override
195        public void begin(Reader reader) throws IOException {
196                super.begin(reader);
197                init();
198        }
199
200        protected void init() throws IOException {
201                st.eolIsSignificant(true);
202                st.commentChar('#');
203
204                graphName = String.format("%s_%d", graphName,
205                                System.currentTimeMillis() + ((long) Math.random() * 10));
206        }
207
208        public boolean nextStep() throws IOException {
209                return nextEvents();
210        }
211
212        @Override
213        public void end() throws IOException {
214                super.end();
215        }
216}