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