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.PrintWriter;
036import java.util.Locale;
037import java.util.regex.Pattern;
038
039/**
040 * Transform the input events into a GML graph.
041 * 
042 * <p>
043 * THIS CLASS IS REALLY NOT APPROPRIATE FOR GENERAL USE. Indeed the GML format
044 * is not dynamic and it is very difficult to export the correct attributes of
045 * nodes if the declaration of the attribute is far from the declaration of the
046 * node. The only way would be to store the graph in a buffer and output it at
047 * once when the file is closed.
048 * </p>
049 * 
050 * <p>
051 * Therefore this class outputs attributes of nodes and edges only if their
052 * addition directly follows the corresponding node or edge.
053 * </p>
054 */
055public class FileSinkGML extends FileSinkBase {
056        // Attributes
057
058        /** Alias on the output OutputStream. */
059        protected PrintWriter out;
060
061        protected String nodeToFinish = null;
062
063        protected String edgeToFinish = null;
064
065        // Construction
066
067        public FileSinkGML() {
068                // NOP
069        }
070
071        // File format events
072
073        @Override
074        protected void outputHeader() throws IOException {
075                out = (PrintWriter) output;
076
077                out.printf("graph [%n");
078        }
079
080        @Override
081        protected void outputEndOfFile() throws IOException {
082                ensureToFinish();
083                out.printf("]%n");
084        }
085
086        // Attribute events
087
088        public void graphAttributeAdded(String sourceId, long timeId,
089                        String attribute, Object value) {
090                ensureToFinish();
091
092                String val = valueToString(value);
093                attribute = keyToString(attribute);
094
095                if (val != null) {
096                        out.printf("\t%s %s%n", attribute, val);
097                }
098        }
099
100        public void graphAttributeChanged(String sourceId, long timeId,
101                        String attribute, Object oldValue, Object newValue) {
102                ensureToFinish();
103                // GML is not a dynamic file format ?
104        }
105
106        public void graphAttributeRemoved(String sourceId, long timeId,
107                        String attribute) {
108                ensureToFinish();
109                // GML is not a dynamic file format ?
110        }
111
112        public void nodeAttributeAdded(String sourceId, long timeId, String nodeId,
113                        String attribute, Object value) {
114                if (nodeToFinish != null && nodeToFinish.equals(nodeId)) {
115                        String val = valueToString(value);
116                        attribute = keyToString(attribute);
117
118                        if (val != null) {
119                                out.printf("\t\t%s %s%n", attribute, val);
120                        }
121                } else {
122                        ensureToFinish();
123                }
124        }
125
126        public void nodeAttributeChanged(String sourceId, long timeId,
127                        String nodeId, String attribute, Object oldValue, Object newValue) {
128                if (edgeToFinish != null)
129                        ensureToFinish();
130                // GML is not a dynamic file format ?
131        }
132
133        public void nodeAttributeRemoved(String sourceId, long timeId,
134                        String nodeId, String attribute) {
135                if (edgeToFinish != null)
136                        ensureToFinish();
137                // GML is not a dynamic file format ?
138        }
139
140        public void edgeAttributeAdded(String sourceId, long timeId, String edgeId,
141                        String attribute, Object value) {
142                if (edgeToFinish != null && edgeToFinish.equals(edgeId)) {
143                        String val = valueToString(value);
144                        attribute = keyToString(attribute);
145
146                        if (val != null) {
147                                out.printf("\t\t%s %s%n", attribute, val);
148                        }
149                } else {
150                        ensureToFinish();
151                }
152        }
153
154        public void edgeAttributeChanged(String sourceId, long timeId,
155                        String edgeId, String attribute, Object oldValue, Object newValue) {
156                if (nodeToFinish != null)
157                        ensureToFinish();
158                // GML is not a dynamic file format ?
159        }
160
161        public void edgeAttributeRemoved(String sourceId, long timeId,
162                        String edgeId, String attribute) {
163                if (nodeToFinish != null)
164                        ensureToFinish();
165                // GML is not a dynamic file format ?
166        }
167
168        // Element events
169
170        public void nodeAdded(String sourceId, long timeId, String nodeId) {
171                ensureToFinish();
172                out.printf("\tnode [%n");
173                out.printf("\t\tid \"%s\"%n", nodeId);
174                nodeToFinish = nodeId;
175        }
176
177        public void nodeRemoved(String sourceId, long timeId, String nodeId) {
178                ensureToFinish();
179        }
180
181        public void edgeAdded(String sourceId, long timeId, String edgeId,
182                        String fromNodeId, String toNodeId, boolean directed) {
183                ensureToFinish();
184                out.printf("\tedge [%n");
185                out.printf("\t\tid \"%s\"%n", edgeId);
186                out.printf("\t\tsource \"%s\"%n", fromNodeId);
187                out.printf("\t\ttarget \"%s\"%n", toNodeId);
188                edgeToFinish = edgeId;
189        }
190
191        public void edgeRemoved(String sourceId, long timeId, String edgeId) {
192                ensureToFinish();
193        }
194
195        public void graphCleared(String sourceId, long timeId) {
196                // Ah ah ah !!
197        }
198
199        public void stepBegins(String sourceId, long timeId, double step) {
200                // NOP
201        }
202
203        // Commands
204
205        Pattern forbiddenKeyChars = Pattern.compile(".*[^a-zA-Z0-9-_.].*");
206
207        protected String keyToString(String key) {
208                if (forbiddenKeyChars.matcher(key).matches())
209                        return "\"" + key.replace("\"", "\\\"") + "\"";
210
211                return key;
212        }
213
214        protected String valueToString(Object value) {
215                if (value == null)
216                        return null;
217
218                if (value instanceof Number) {
219                        double val = ((Number) value).doubleValue();
220                        if ((val - ((int) val)) == 0)
221                                return String.format(Locale.US, "%d", (int) val);
222                        else
223                                return String.format(Locale.US, "%f", val);
224                }
225
226                return String.format("\"%s\"", value.toString().replaceAll("\n|\r|\"", " "));
227        }
228
229        protected void ensureToFinish() {
230                assert ((nodeToFinish != null && edgeToFinish == null)
231                                || (nodeToFinish == null && edgeToFinish != null) || (nodeToFinish == null && edgeToFinish == null));
232
233                if (nodeToFinish != null || edgeToFinish != null) {
234                        out.printf("\t]%n");
235                        nodeToFinish = null;
236                        edgeToFinish = null;
237                }
238        }
239}