FileSourceDGS1And2.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;

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;

/**
 * Class responsible for parsing files in the DGS format (old versions of the
 * 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>
 * 
 * @see OldFileSourceDGS
 * @see FileSource
 */
public class FileSourceDGS1And2 extends FileSourceBase {
	// Constants

	/**
	 * Types of attributes.
	 */
	protected enum AttributeType {
		NUMBER, VECTOR, STRING
	};

	/**
	 * Pair <name,type> defining an attribute.
	 */
	protected static class AttributeFormat {
		/**
		 * Name of the attribute.
		 */
		public String name;

		/**
		 * Type of the attribute.
		 */
		public AttributeType type;

		/**
		 * New format descriptor for an attribute.
		 * 
		 * @param name
		 *            The attribute name.
		 * @param type
		 *            The attribute type.
		 */
		public AttributeFormat(String name, AttributeType type) {
			this.name = name;
			this.type = type;
		}

		/**
		 * Attribute name.
		 * 
		 * @return The name.
		 */
		public String getName() {
			return name;
		}

		/**
		 * Attribute format.
		 * 
		 * @return The format.
		 */
		public AttributeType getType() {
			return type;
		}
	}

	// Attributes

	/**
	 * 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;

	/**
	 * Attribute count and type expected for each node add and modify command.
	 */
	protected ArrayList<AttributeFormat> nodesFormat = new ArrayList<AttributeFormat>();

	/**
	 * Attribute count and type expected for each edges add and modify command.
	 */
	protected ArrayList<AttributeFormat> edgesFormat = new ArrayList<AttributeFormat>();

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

	// Constructors

	/**
	 * New reader for the DGS graph file format versions 1 and 2.
	 */
	public FileSourceDGS1And2() {
		super(true /* EOL is significant */);
	}

	// Access

	// Command

	@Override
	public boolean nextEvents() throws IOException {
		String key = getWordOrSymbolOrStringOrEolOrEof();
		String tag = null;

		if (key.equals("ce")) {
			tag = getStringOrWordOrNumber();

			readAttributes(edgesFormat);

			for (String k : attributes.keySet()) {
				Object value = attributes.get(k);
				sendEdgeAttributeChanged(graphName, tag, k, null, value);
			}

			if (eatEolOrEof() == StreamTokenizer.TT_EOF)
				return false;
		} else if (key.equals("cn")) {
			tag = getStringOrWordOrNumber();

			readAttributes(nodesFormat);

			for (String k : attributes.keySet()) {
				Object value = attributes.get(k);
				sendNodeAttributeChanged(graphName, tag, k, null, value);
			}

			if (eatEolOrEof() == StreamTokenizer.TT_EOF)
				return false;
		} else if (key.equals("ae")) {
			tag = getStringOrWordOrNumber();
			String fromTag = getStringOrWordOrNumber();
			String toTag = getStringOrWordOrNumber();

			readAttributes(edgesFormat);

			sendEdgeAdded(graphName, tag, fromTag, toTag, false);

			if (attributes != null) {
				for (String k : attributes.keySet()) {
					Object value = attributes.get(k);
					sendEdgeAttributeAdded(graphName, tag, k, value);
				}
			}

			if (eatEolOrEof() == StreamTokenizer.TT_EOF)
				return false;
		} else if (key.equals("an")) {
			tag = getStringOrWordOrNumber();

			readAttributes(nodesFormat);
			sendNodeAdded(graphName, tag);

			if (attributes != null) {
				for (String k : attributes.keySet()) {
					Object value = attributes.get(k);
					sendNodeAttributeAdded(graphName, tag, k, value);
				}
			}

			if (eatEolOrEof() == StreamTokenizer.TT_EOF)
				return false;
		} else if (key.equals("de")) {
			tag = getStringOrWordOrNumber();

			sendEdgeRemoved(graphName, tag);

			if (eatEolOrEof() == StreamTokenizer.TT_EOF)
				return false;
		} else if (key.equals("dn")) {
			tag = getStringOrWordOrNumber();

			sendNodeRemoved(graphName, tag);

			if (eatEolOrEof() == StreamTokenizer.TT_EOF)
				return false;
		} else if (key.equals("st")) {
			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)
				return false;
		} else if (key == "#") {
			eatAllUntilEol();
		} else if (key == "EOL") {
			return true;
		} else if (key == "EOF") {
			return false;
		} else {
			parseError("found an unknown key in file '" + key
					+ "' (expecting an,ae,cn,ce,dn,de or st)");
		}

		return true;
	}

	/**
	 * tries to read all the events between 2 steps
	 */
	public boolean nextStep() throws IOException {
		String key = "";
		String tag = null;

		while (!key.equals("st") && !key.equals("EOF")) {
			key = getWordOrSymbolOrStringOrEolOrEof();

			if (key.equals("ce")) {
				tag = getStringOrWordOrNumber();

				readAttributes(edgesFormat);

				for (String k : attributes.keySet()) {
					Object value = attributes.get(k);
					sendEdgeAttributeChanged(graphName, tag, k, null, value);
				}

				if (eatEolOrEof() == StreamTokenizer.TT_EOF)
					return false;
			} else if (key.equals("cn")) {
				tag = getStringOrWordOrNumber();

				readAttributes(nodesFormat);

				for (String k : attributes.keySet()) {
					Object value = attributes.get(k);
					sendNodeAttributeChanged(graphName, tag, k, null, value);
				}

				if (eatEolOrEof() == StreamTokenizer.TT_EOF)
					return false;
			} else if (key.equals("ae")) {
				tag = getStringOrWordOrNumber();
				String fromTag = getStringOrWordOrNumber();
				String toTag = getStringOrWordOrNumber();

				readAttributes(edgesFormat);
				sendEdgeAdded(graphName, tag, fromTag, toTag, false);

				if (attributes != null) {
					for (String k : attributes.keySet()) {
						Object value = attributes.get(k);
						sendNodeAttributeAdded(graphName, tag, k, value);
					}
				}

				if (eatEolOrEof() == StreamTokenizer.TT_EOF)
					return false;
			} else if (key.equals("an")) {
				tag = getStringOrWordOrNumber();

				readAttributes(nodesFormat);
				sendNodeAdded(graphName, tag);

				if (attributes != null) {
					for (String k : attributes.keySet()) {
						Object value = attributes.get(k);
						sendNodeAttributeAdded(graphName, tag, k, value);
					}
				}

				if (eatEolOrEof() == StreamTokenizer.TT_EOF)
					return false;
			} else if (key.equals("de")) {
				tag = getStringOrWordOrNumber();

				sendEdgeRemoved(graphName, tag);

				if (eatEolOrEof() == StreamTokenizer.TT_EOF)
					return false;
			} else if (key.equals("dn")) {
				tag = getStringOrWordOrNumber();

				sendNodeRemoved(graphName, tag);

				if (eatEolOrEof() == StreamTokenizer.TT_EOF)
					return false;
			} else if (key.equals("st")) {
				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)
					return false;
			} else if (key == "#") {
				eatAllUntilEol();
			} else if (key == "EOL") {
				// NOP
			} else if (key == "EOF") {
				return false;
			} else {
				parseError("found an unknown key in file '" + key
						+ "' (expecting an,ae,cn,ce,dn,de or st)");
			}
		}

		return true;
	}

	protected void readAttributes(ArrayList<AttributeFormat> formats)
			throws IOException {
		attributes.clear();

		if (formats.size() > 0) {
			for (AttributeFormat format : formats) {
				if (format.type == AttributeType.NUMBER) {
					readNumberAttribute(format.name);
				} else if (format.type == AttributeType.VECTOR) {
					readVectorAttribute(format.name);
				} else if (format.type == AttributeType.STRING) {
					readStringAttribute(format.name);
				}
			}
		}
	}

	protected void readNumberAttribute(String name) throws IOException {
		int tok = st.nextToken();

		if (isNull(tok)) {
			attributes.put(name, new Double(0));
		} else {
			st.pushBack();

			double n = getNumber();

			attributes.put(name, new Double(n));
		}
	}

	protected void readVectorAttribute(String name) throws IOException {
		int tok = st.nextToken();

		if (isNull(tok)) {
			attributes.put(name, new ArrayList<Double>());
		} else {

			boolean loop = true;

			ArrayList<Double> vector = new ArrayList<Double>();

			while (loop) {
				if (tok != StreamTokenizer.TT_NUMBER)
					parseError("expecting a number, " + gotWhat(tok));

				vector.add(st.nval);

				tok = st.nextToken();

				if (tok != ',') {
					loop = false;
					st.pushBack();
				} else {
					tok = st.nextToken();
				}
			}

			attributes.put(name, vector);
		}
	}

	protected void readStringAttribute(String name) throws IOException {
		String s = getStringOrWordOrNumber();

		attributes.put(name, s);
	}

	protected boolean isNull(int tok) {
		if (tok == StreamTokenizer.TT_WORD)
			return (st.sval.equals("null"));

		return false;
	}

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

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

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

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

	protected void init() throws IOException {
		st.parseNumbers();

		String magic = eatOneOfTwoWords("DGS001", "DGS002");

		if (magic.equals("DGS001"))
			version = 1;
		else
			version = 2;

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

		if (graphName != null) {
			attributes.clear();
			attributes.put("label", graphName);
			sendGraphAttributeAdded(graphName, "label", graphName);
		} else {
			graphName = "DGS_";
		}

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

		readAttributeFormat();
	}

	protected void readAttributeFormat() throws IOException {
		int tok = st.nextToken();

		if (tok == StreamTokenizer.TT_WORD && st.sval.equals("nodes")) {
			parseAttributeFormat(nodesFormat);
			tok = st.nextToken();
		}

		if (tok == StreamTokenizer.TT_WORD && st.sval.equals("edges")) {
			parseAttributeFormat(edgesFormat);
		} else {
			st.pushBack();
		}
	}

	protected void parseAttributeFormat(ArrayList<AttributeFormat> format)
			throws IOException {
		int tok = st.nextToken();

		while (tok != StreamTokenizer.TT_EOL) {
			if (tok == StreamTokenizer.TT_WORD) {
				String name = st.sval;

				eatSymbol(':');

				tok = st.nextToken();

				if (tok == StreamTokenizer.TT_WORD) {
					String type = st.sval.toLowerCase();

					if (type.equals("number") || type.equals("n")) {
						format.add(new AttributeFormat(name,
								AttributeType.NUMBER));
					} else if (type.equals("string") || type.equals("s")) {
						format.add(new AttributeFormat(name,
								AttributeType.STRING));
					} else if (type.equals("vector") || type.equals("v")) {
						format.add(new AttributeFormat(name,
								AttributeType.VECTOR));
					} else {
						parseError("unknown attribute type `"
								+ type
								+ "' (only `number', `vector' and `string' are accepted)");
					}
				} else {
					parseError("expecting an attribute type, got `"
							+ gotWhat(tok) + "'");
				}
			} else {
				parseError("expecting an attribute name, got `" + gotWhat(tok)
						+ "'");
			}

			tok = st.nextToken();
		}
	}

	@Override
	protected void continueParsingInInclude() throws IOException {
	}

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

		try {
			is = new GZIPInputStream(new FileInputStream(file));
		} catch (IOException e) {
			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.wordChars('_', '_');
		tok.ordinaryChar('1');
		tok.ordinaryChar('2');
		tok.ordinaryChar('3');
		tok.ordinaryChar('4');
		tok.ordinaryChar('5');
		tok.ordinaryChar('6');
		tok.ordinaryChar('7');
		tok.ordinaryChar('8');
		tok.ordinaryChar('9');
		tok.ordinaryChar('0');
		tok.ordinaryChar('.');
		tok.ordinaryChar('-');
		tok.wordChars('1', '1');
		tok.wordChars('2', '2');
		tok.wordChars('3', '3');
		tok.wordChars('4', '4');
		tok.wordChars('5', '5');
		tok.wordChars('6', '6');
		tok.wordChars('7', '7');
		tok.wordChars('8', '8');
		tok.wordChars('9', '9');
		tok.wordChars('0', '0');
		tok.wordChars('.', '.');
		tok.wordChars('-', '-');
		// tok.parseNumbers();
	}
}