OldFileSourceDGS.java

/*
 * Copyright 2006 - 2013
 *     Stefan Balev     <stefan.balev@graphstream-project.org>
 *     Julien Baudry    <julien.baudry@graphstream-project.org>
 *     Antoine Dutot    <antoine.dutot@graphstream-project.org>
 *     Yoann Pign��      <yoann.pigne@graphstream-project.org>
 *     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
 * 
 * This file is part of GraphStream <http://graphstream-project.org>.
 * 
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 * 
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */
package org.graphstream.stream.file.dgs;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.zip.GZIPInputStream;

import org.graphstream.stream.file.FileSource;
import org.graphstream.stream.file.FileSourceBase;

/**
 * Class responsible for parsing files in the DGS format.
 * 
 * <p>
 * The DGS file format is especially designed for storing dynamic graph
 * definitions into a file. More information about the DGS file format will be
 * found on the GraphStream web site: <a
 * href="http://graphstream-project.org/">http://graphstream-project.org/</a>
 * </p>
 * 
 * The usual file name extension used for this format is ".dgs".
 * 
 * @see FileSource
 */
public class OldFileSourceDGS extends FileSourceBase {
	// Attribute

	/**
	 * Format version.
	 */
	protected int version;

	/**
	 * Name of the graph.
	 */
	protected String graphName;

	/**
	 * Number of step given in the header.
	 */
	protected int stepCountAnnounced;

	/**
	 * Number of events given in the header.
	 */
	protected int eventCountAnnounced;

	/**
	 * Real number of step at current time.
	 */
	protected int stepCount;

	/**
	 * Real number of events at current time.
	 */
	protected int eventCount;

	/**
	 * An attribute set used everywhere.
	 */
	protected HashMap<String, Object> attributes = new HashMap<String, Object>();

	/**
	 * True as soon as the end of file is reached.
	 */
	protected boolean finished;

	// Construction

	/**
	 * New reader for the DGS graph file format version 3.
	 */
	public OldFileSourceDGS() {
		super(true /* EOL is significant */);
	}

	// Command -- Parsing

	@Override
	public boolean nextEvents() throws IOException {
		if (finished)
			return false;

		return next(false, false);
	}

	public boolean nextStep() throws IOException {
		if (finished)
			return false;

		return next(true, false);
	}

	/**
	 * Read either one event or several.
	 * 
	 * @param readSteps
	 *            If true, read several events (usually starting with a step
	 *            event, but it may be preceded by other events), until another
	 *            step is encountered.
	 * @param stop
	 *            If true stop at the next step encountered (and push it back so
	 *            that is is readable at the next call to this method).
	 * @return True if it remains things to read.
	 */
	protected boolean next(boolean readSteps, boolean stop) throws IOException {
		String key = null;
		boolean loop = readSteps;

		// Sorted in probability of appearance ...

		do {
			key = getWordOrSymbolOrStringOrEolOrEof();

			if (key.equals("ce")) {
				readCE();
			} else if (key.equals("cn")) {
				readCN();
			} else if (key.equals("ae")) {
				readAE();
			} else if (key.equals("an")) {
				readAN();
			} else if (key.equals("de")) {
				readDE();
			} else if (key.equals("dn")) {
				readDN();
			} else if (key.equals("cg")) {
				readCG();
			} else if (key.equals("st")) {
				if (readSteps) {
					if (stop) {
						loop = false;
						pushBack();
					} else {
						stop = true;
						readST();
					}
				} else {
					readST();
				}
			} else if (key.equals("#")) {
				eatAllUntilEol();
				return next(readSteps, stop);
			} else if (key.equals("EOL")) {
				// Probably an empty line.
				// NOP
				return next(readSteps, stop);
			} else if (key.equals("EOF")) {
				finished = true;
				return false;
			} else {
				parseError("unknown token '" + key + "'");
			}
		} while (loop);

		return true;
	}

	protected void readCE() throws IOException {
		String tag = getStringOrWordOrNumber();

		readAttributes(attributes);

		for (String key : attributes.keySet()) {
			Object value = attributes.get(key);

			if (value == null)
				sendEdgeAttributeRemoved(graphName, tag, key);
			else
				sendEdgeAttributeChanged(graphName, tag, key, null, value);
		}

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readCN() throws IOException {
		String tag = getStringOrWordOrNumber();

		readAttributes(attributes);

		for (String key : attributes.keySet()) {
			Object value = attributes.get(key);

			if (value == null)
				sendNodeAttributeRemoved(graphName, tag, key);
			else
				sendNodeAttributeChanged(graphName, tag, key, null, value);
		}

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readCG() throws IOException {
		readAttributes(attributes);

		for (String key : attributes.keySet()) {
			Object value = attributes.get(key);

			if (value == null)
				sendGraphAttributeRemoved(graphName, key);
			else
				sendGraphAttributeChanged(graphName, key, null, value);
		}

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readAE() throws IOException {
		int dir = 0;
		boolean directed = false;
		String dirc = null;
		String tag = null;
		String fromTag = null;
		String toTag = null;

		tag = getStringOrWordOrNumber();
		fromTag = getStringOrWordOrNumber();
		dirc = getWordOrSymbolOrNumberOrStringOrEolOrEof();

		if (dirc.equals(">")) {
			directed = true;
			dir = 1;
		} else if (dirc.equals("<")) {
			directed = true;
			dir = 2;
		} else {
			pushBack();
		}

		toTag = getStringOrWordOrNumber();

		if (dir == 2) {
			String tmp = toTag;
			toTag = fromTag;
			fromTag = tmp;
		}

		readAttributes(attributes);
		sendEdgeAdded(graphName, tag, fromTag, toTag, directed);

		for (String key : attributes.keySet()) {
			Object value = attributes.get(key);
			sendEdgeAttributeAdded(graphName, tag, key, value);
		}

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readAN() throws IOException {
		String tag = getStringOrWordOrNumber();

		readAttributes(attributes);

		sendNodeAdded(graphName, tag);

		for (String key : attributes.keySet()) {
			Object value = attributes.get(key);
			sendNodeAttributeAdded(graphName, tag, key, value);
		}

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readDE() throws IOException {
		String tag = getStringOrWordOrNumber();

		sendEdgeRemoved(graphName, tag);

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readDN() throws IOException {
		String tag = getStringOrWordOrNumber();

		sendNodeRemoved(graphName, tag);

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readST() throws IOException {
		String w = getWordOrNumber();

		try {
			double time = Double.parseDouble(w);

			sendStepBegins(graphName, time);
		} catch (NumberFormatException e) {
			parseError("expecting a number after `st', got `" + w + "'");
		}

		if (eatEolOrEof() == StreamTokenizer.TT_EOF)
			pushBack();
	}

	protected void readAttributes(HashMap<String, Object> attributes)
			throws IOException {
		boolean del = false;
		String key = getWordOrSymbolOrStringOrEolOrEof();

		attributes.clear();

		if (key.equals("-")) {
			key = getWordOrSymbolOrStringOrEolOrEof();
			del = true;
		}

		if (key.equals("+"))
			key = getWordOrSymbolOrStringOrEolOrEof();

		while (!key.equals("EOF") && !key.equals("EOL") && !key.equals("]")) {
			if (del)
				attributes.put(key, null);
			else
				attributes.put(key, readAttributeValue(key));

			key = getWordOrSymbolOrStringOrEolOrEof();

			if (key.equals("-")) {
				key = getWordOrStringOrEolOrEof();
				del = true;
			}

			if (key.equals("+")) {
				key = getWordOrStringOrEolOrEof();
				del = false;
			}
		}

		pushBack();
	}

	/**
	 * Read an attribute. The "key" (attribute name) is already read.
	 * 
	 * @param key
	 *            The attribute name, already read.
	 */
	protected Object readAttributeValue(String key) throws IOException {
		ArrayList<Object> vector = null;
		Object value = null;
		Object value2 = null;
		String next = null;

		if (key != null)
			eatSymbols(":=");

		value = getStringOrWordOrSymbolOrNumberO();

		if (value.equals("[")) {
			HashMap<String, Object> map = new HashMap<String, Object>();

			readAttributes(map);
			;
			eatSymbol(']');

			value = map;
		} else if (value.equals("{")) {
			vector = readAttributeArray(key);
			eatSymbol('}');
		} else {
			pushBack();

			value = getStringOrWordOrNumberO();

			if (key != null) {
				next = getWordOrSymbolOrNumberOrStringOrEolOrEof();

				while (next.equals(",")) {
					if (vector == null) {
						vector = new ArrayList<Object>();
						vector.add(value);
					}

					value2 = getStringOrWordOrNumberO();
					next = getWordOrSymbolOrNumberOrStringOrEolOrEof();

					vector.add(value2);
				}

				pushBack();
			}
		}

		if (vector != null)
			return vector.toArray();
		else
			return value;
	}

	/**
	 * Read a list of values.
	 * 
	 * @param key
	 *            attribute key
	 * @return a vector
	 * @throws IOException
	 */
	protected ArrayList<Object> readAttributeArray(String key)
			throws IOException {
		ArrayList<Object> list = new ArrayList<Object>();

		Object value;
		String next;

		do {
			value = readAttributeValue(null);
			next = getWordOrSymbolOrNumberOrStringOrEolOrEof();

			list.add(value);
		} while (next.equals(","));

		pushBack();

		return list;
	}

	// Command -- Basic parsing

	@Override
	public void begin(String filename) throws IOException {
		super.begin(filename);
		begin();
	}

	@Override
	public void begin(URL url) throws IOException {
		super.begin(url);
		begin();
	}

	@Override
	public void begin(InputStream stream) throws IOException {
		super.begin(stream);
		begin();
	}

	@Override
	public void begin(Reader reader) throws IOException {
		super.begin(reader);
		begin();
	}

	protected void begin() throws IOException {
		st.parseNumbers();
		eatWords("DGS003", "DGS004");

		version = 3;

		eatEol();
		graphName = getWordOrString();
		stepCountAnnounced = (int) getNumber();// Integer.parseInt( getWord() );
		eventCountAnnounced = (int) getNumber();// Integer.parseInt( getWord()
												// );
		eatEol();

		if (graphName != null)
			sendGraphAttributeAdded(graphName, "label", graphName);
		else
			graphName = "DGS_";

		graphName = String.format("%s_%d", graphName,
				System.currentTimeMillis() + ((long) Math.random() * 10));
	}

	@Override
	protected void continueParsingInInclude() throws IOException {
	}

	@Override
	protected Reader createReaderFrom(String file) throws FileNotFoundException {
		InputStream is = null;

		is = new FileInputStream(file);

		if (is.markSupported())
			is.mark(128);

		try {
			is = new GZIPInputStream(is);
		} catch (IOException e1) {
			//
			// This is not a gzip input.
			// But gzip has eat some bytes so we reset the stream
			// or close and open it again.
			//
			if (is.markSupported()) {
				try {
					is.reset();
				} catch (IOException e2) {
					//
					// Dirty but we hope do not get there
					//
					e2.printStackTrace();
				}
			} else {
				try {
					is.close();
				} catch (IOException e2) {
					//
					// Dirty but we hope do not get there
					//
					e2.printStackTrace();
				}
				
				is = new FileInputStream(file);
			}
		}

		return new BufferedReader(new InputStreamReader(is));
	}

	@Override
	protected Reader createReaderFrom(InputStream stream) {
		return new BufferedReader(new InputStreamReader(stream));
	}

	@Override
	protected void configureTokenizer(StreamTokenizer tok) throws IOException {
		if (COMMENT_CHAR > 0)
			tok.commentChar(COMMENT_CHAR);
		// tok.quoteChar( QUOTE_CHAR );
		tok.eolIsSignificant(eol_is_significant);
		tok.parseNumbers();
		tok.wordChars('_', '_');
	}
}