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.gml;
033
034import java.io.IOException;
035import java.util.HashMap;
036
037import org.graphstream.graph.implementations.AbstractElement.AttributeChangeEvent;
038import org.graphstream.stream.SourceBase.ElementType;
039import org.graphstream.stream.file.FileSourceGML;
040
041public class GMLContext {
042        FileSourceGML gml;
043        //GMLParser parser;
044        String sourceId;
045        boolean directed;
046        protected KeyValues nextStep = null;
047        boolean inGraph = false;
048
049        GMLContext(FileSourceGML gml) {
050                this.gml = gml;
051                this.sourceId = String.format("<GML stream %d>",
052                                System.currentTimeMillis());
053        }
054
055        void handleKeyValues(KeyValues kv) throws IOException {
056                if (nextStep != null) {
057                        insertKeyValues(nextStep);
058                        nextStep = null;
059                }
060
061                try {
062                        if (kv != null) {
063                                insertKeyValues(kv);
064                        }
065                } catch (IOException e) {
066                        throw new IOException(e);
067                }
068        }
069
070        void setNextStep(KeyValues kv) {
071                nextStep = kv;
072        }
073
074        public void setDirected(boolean on) {
075                directed = on;
076        }
077        
078        void setIsInGraph(boolean on) {
079                inGraph = on;
080        }
081
082        public void addNodeOrEdge(String element, KeyValues kv) {
083                System.err.printf("adding %s %n", element);
084        }
085
086        protected void insertKeyValues(KeyValues kv) throws IOException {
087                if (kv.key != null) {
088                        if (inGraph) {
089                                if (kv.key.equals("node") || kv.key.equals("add-node")) {
090                                        handleAddNode(kv);
091                                } else if (kv.key.equals("edge") || kv.key.equals("add-edge")) {
092                                        handleAddEdge(kv);
093                                } else if (kv.key.equals("del-node") || kv.key.equals("-node")) {
094                                        handleDelNode(kv);
095                                } else if (kv.key.equals("del-edge") || kv.key.equals("-edge")) {
096                                        handleDelEdge(kv);
097                                } else if (kv.key.equals("change-node")
098                                                || kv.key.equals("+node")) {
099                                        handleChangeNode(kv);
100                                } else if (kv.key.equals("change-edge")
101                                                || kv.key.equals("+edge")) {
102                                        handleChangeEdge(kv);
103                                } else if (kv.key.equals("step")) {
104                                        handleStep(kv);
105                                } else if (kv.key.equals("directed")) {
106                                        setDirected(getBoolean(kv.get("directed")));
107                                } else {
108                                        if (kv.key.startsWith("-")) {
109                                                gml.sendAttributeChangedEvent(sourceId, sourceId,
110                                                                ElementType.GRAPH, kv.key.substring(1),
111                                                                AttributeChangeEvent.REMOVE, null, null);
112                                        } else {
113                                                gml.sendAttributeChangedEvent(sourceId, sourceId,
114                                                                ElementType.GRAPH, kv.key,
115                                                                AttributeChangeEvent.ADD, null,
116                                                                compositeAttribute(kv));
117                                        }
118                                }
119                        } else {
120                                // XXX Should we consider these events pertain to the graph ?
121                                // XXX
122
123                                if (kv.key.startsWith("-")) {
124                                        gml.sendAttributeChangedEvent(sourceId, sourceId,
125                                                        ElementType.GRAPH, kv.key.substring(1),
126                                                        AttributeChangeEvent.REMOVE, null, null);
127                                } else {
128                                        gml.sendAttributeChangedEvent(sourceId, sourceId,
129                                                        ElementType.GRAPH, kv.key,
130                                                        AttributeChangeEvent.ADD, null,
131                                                        compositeAttribute(kv));
132                                }
133                        }
134                }
135        }
136
137        protected Object compositeAttribute(KeyValues kv) {
138                if (kv.size() < 2) {
139                        return kv.get(kv.key);
140                } else {
141                        return kv;
142                }
143        }
144
145        protected void handleAddNode(KeyValues kv) throws IOException {
146                Object thing = kv.get("node");
147                if (thing == null)
148                        thing = kv.get("add-node");
149                if (thing == null)
150                        kv.error("expecting a node or add-node token here");
151
152                if (thing instanceof String) {
153                        String id = (String) thing;
154                        gml.sendNodeAdded(sourceId, id);
155                } else if (thing instanceof KeyValues) {
156                        KeyValues node = (KeyValues) thing;
157                        String id = node.reqStringOrNumber("id");
158
159                        gml.sendNodeAdded(sourceId, id);
160                        handleNodeAttributes(id, node);
161                } else {
162                        kv.error("unknown token type");
163                }
164        }
165
166        protected long edgeid = 0;
167
168        protected void handleAddEdge(KeyValues kv) throws IOException {
169                Object thing = kv.get("edge");
170                if (thing == null)
171                        thing = kv.get("add-edge");
172                if (thing == null)
173                        kv.error("expecting a edge or add-edge token here");
174                if (!(thing instanceof KeyValues))
175                        kv.error("expecting a set of values for the new edge");
176
177                KeyValues edge = (KeyValues) thing;
178                String id = edge.optString("id");
179
180                String src = edge.reqStringOrNumber("source");
181                String trg = edge.reqStringOrNumber("target");
182
183                if (id == null)
184                        id = String.format("%s_%s_%d", src, trg, edgeid++);
185
186                String dir = edge.optString("directed");
187                
188                boolean directed = this.directed;
189
190                if (dir != null) {
191                        directed = getBoolean(dir);
192                }
193
194                gml.sendEdgeAdded(sourceId, id, src, trg, directed);
195
196                handleEdgeAttributes(id, edge);
197        }
198
199        protected void handleDelNode(KeyValues kv) throws IOException {
200                Object thing = kv.get("del-node");
201                if (thing == null)
202                        thing = kv.get("-node");
203                if (thing == null)
204                        kv.error("expecting a del-node or -node token here");
205
206                if (thing instanceof String) {
207                        String id = (String) thing;
208                        gml.sendNodeRemoved(sourceId, id);
209                } else if (thing instanceof KeyValues) {
210                        KeyValues node = (KeyValues) thing;
211                        String id = node.reqString("id");
212                        gml.sendNodeRemoved(sourceId, id);
213                } else {
214                        kv.error("unknown token type");
215                }
216        }
217
218        protected void handleDelEdge(KeyValues kv) throws IOException {
219                Object thing = kv.get("del-edge");
220                if (thing == null)
221                        thing = kv.get("-edge");
222                if (thing == null)
223                        kv.error("expecting a del-edge or -edge token here");
224
225                if (thing instanceof String) {
226                        String id = (String) thing;
227                        gml.sendEdgeRemoved(sourceId, id);
228                } else if (thing instanceof KeyValues) {
229                        KeyValues edge = (KeyValues) thing;
230                        String id = edge.reqString("id");
231                        gml.sendEdgeRemoved(sourceId, id);
232                } else {
233                        kv.error("unknown token type");
234                }
235        }
236
237        protected void handleChangeNode(KeyValues kv) throws IOException {
238                Object thing = kv.get("change-node");
239                if (thing == null)
240                        thing = kv.get("+node");
241                if (thing == null)
242                        kv.error("expecting a change-node or +node token here");
243                if (!(thing instanceof KeyValues))
244                        kv.error("expecting a set of values");
245
246                KeyValues node = (KeyValues) thing;
247                String id = node.reqString("id");
248
249                handleNodeAttributes(id, node);
250        }
251
252        protected void handleChangeEdge(KeyValues kv) throws IOException {
253                Object thing = kv.get("change-edge");
254                if (thing == null)
255                        thing = kv.get("+edge");
256                if (thing == null)
257                        kv.error("expecting a change-edge or +edge token here");
258                if (!(thing instanceof KeyValues))
259                        kv.error("expecting a set of values");
260
261                KeyValues edge = (KeyValues) thing;
262                String id = edge.reqString("id");
263
264                handleEdgeAttributes(id, edge);
265        }
266
267        protected void handleNodeAttributes(String id, KeyValues node) {
268                for (String key : node.keySet()) {
269                        if (key.startsWith("-")) {
270                                if (key.equals("-label"))
271                                        key = "-ui.label";
272
273                                gml.sendAttributeChangedEvent(sourceId, id, ElementType.NODE,
274                                                key.substring(1), AttributeChangeEvent.REMOVE, null,
275                                                null);
276                        } else {
277                                if (key.equals("graphics")
278                                                && node.get("graphics") instanceof KeyValues) {
279                                        Graphics graphics = optNodeStyle((KeyValues) node
280                                                        .get("graphics"));
281
282                                        if (graphics != null) {
283                                                if (graphics.position != null) {
284                                                        gml.sendAttributeChangedEvent(sourceId, id,
285                                                                        ElementType.NODE, "xyz",
286                                                                        AttributeChangeEvent.ADD, null,
287                                                                        graphics.getPosition());
288                                                }
289                                                if (graphics.style != null) {
290                                                        gml.sendAttributeChangedEvent(sourceId, id,
291                                                                        ElementType.NODE, "ui.style",
292                                                                        AttributeChangeEvent.ADD, null,
293                                                                        graphics.style);
294                                                }
295                                        }
296                                } else {
297                                        String k = key;
298
299                                        if (key.equals("label"))
300                                                k = "ui.label";
301
302                                        gml.sendAttributeChangedEvent(sourceId, id,
303                                                        ElementType.NODE, k, AttributeChangeEvent.ADD,
304                                                        null, node.get(key));
305                                }
306                        }
307                }
308        }
309
310        protected void handleEdgeAttributes(String id, KeyValues edge) {
311                for (String key : edge.keySet()) {
312                        if (key.startsWith("-")) {
313                                if (key.equals("-label"))
314                                        key = "-ui.label";
315
316                                gml.sendAttributeChangedEvent(sourceId, id, ElementType.EDGE,
317                                                key.substring(1), AttributeChangeEvent.REMOVE, null,
318                                                null);
319                        } else {
320                                if (key.equals("graphics")
321                                                && edge.get("graphics") instanceof KeyValues) {
322                                        Graphics graphics = optEdgeStyle((KeyValues) edge
323                                                        .get("graphics"));
324
325                                        if (graphics != null) {
326                                                if (graphics.style != null) {
327                                                        gml.sendAttributeChangedEvent(sourceId, id,
328                                                                        ElementType.EDGE, "ui.style",
329                                                                        AttributeChangeEvent.ADD, null,
330                                                                        graphics.style);
331                                                }
332                                        }
333                                } else {
334                                        String k = key;
335
336                                        if (key.equals("label"))
337                                                k = "ui.label";
338
339                                        gml.sendAttributeChangedEvent(sourceId, id,
340                                                        ElementType.EDGE, k, AttributeChangeEvent.ADD,
341                                                        null, edge.get(key));
342                                }
343                        }
344                }
345        }
346
347        protected void handleStep(KeyValues kv) throws IOException {
348                gml.sendStepBegins(sourceId, kv.reqNumber("step"));
349        }
350
351        protected Graphics optNodeStyle(KeyValues kv) {
352                Graphics graphics = null;
353
354                if (kv != null) {
355                        StringBuffer style = new StringBuffer();
356                        String w = null, h = null, d = null;
357                        graphics = new Graphics();
358
359                        if (kv.get("x") != null) {
360                                graphics.setX(asDouble((String) kv.get("x")));
361                        }
362                        if (kv.get("y") != null) {
363                                graphics.setY(asDouble((String) kv.get("y")));
364                        }
365                        if (kv.get("z") != null) {
366                                graphics.setZ(asDouble((String) kv.get("z")));
367                        }
368                        if (kv.get("w") != null) {
369                                w = (String) kv.get("w");
370                        }
371                        if (kv.get("h") != null) {
372                                h = (String) kv.get("h");
373                        }
374                        if (kv.get("d") != null) {
375                                d = (String) kv.get("d");
376                        }
377                        if (w != null || h != null || d != null) {
378                                int ww = w != null ? (int) asDouble(w) : 0;
379                                int hh = h != null ? (int) asDouble(h) : 0;
380                                int dd = d != null ? (int) asDouble(d) : 0;
381                                style.append(String.format("size: %dpx, %dpx, %dpx; ", ww, hh,
382                                                dd));
383                        }
384                        if (kv.get("type") != null) {
385                                style.append(String.format("shape: %s; ",
386                                                asNodeShape((String) kv.get("type"))));
387                        }
388
389                        commonGraphicsAttributes(kv, style);
390                        graphics.style = style.toString();
391                }
392
393                return graphics;
394        }
395
396        protected Graphics optEdgeStyle(KeyValues kv) {
397                Graphics graphics = null;
398
399                if (kv != null) {
400                        StringBuffer style = new StringBuffer();
401                        String w = null;
402                        graphics = new Graphics();
403
404                        if (kv.get("width") != null) {
405                                w = (String) kv.get("width");
406                        } else if (kv.get("w") != null) {
407                                w = (String) kv.get("w");
408                        }
409                        if (w != null) {
410                                double ww = w != null ? asDouble(w) : 0.0;
411                                style.append(String.format("size: %fpx;", ww));
412                        }
413                        if (kv.get("type") != null) {
414                                style.append(String.format("shape: %s; ",
415                                                asEdgeShape((String) kv.get("type"))));
416                        }
417
418                        commonGraphicsAttributes(kv, style);
419                        graphics.style = style.toString();
420                }
421
422                return graphics;
423        }
424
425        protected void commonGraphicsAttributes(KeyValues kv, StringBuffer style) {
426                if (kv.get("fill") != null) {
427                        style.append(String.format("fill-color: %s; ", kv.get("fill")));
428                }
429                if (kv.get("outline") != null) {
430                        style.append(String.format("stroke-color: %s; ", kv.get("outline")));
431                }
432                if (kv.get("outline_width") != null) {
433                        style.append(String.format("stroke-width: %spx; ",
434                                        kv.get("outline_width")));
435                }
436                if ((kv.get("outline") != null) || (kv.get("outline_width") != null)) {
437                        style.append("stroke-mode: plain; ");
438                }
439                if (kv.get("anchor") != null) {
440                        style.append(String.format("text-alginment: %s; ",
441                                        asTextAlignment((String) kv.get("anchor"))));
442                }
443                if (kv.get("image") != null) {
444                        style.append(String.format("icon-mode: at-left; icon: %s; ",
445                                        (String) kv.get("image")));
446                }
447                if (kv.get("arrow") != null) {
448                        style.append(String.format("arrow-shape: %s; ",
449                                        asArrowShape((String) kv.get("arrow"))));
450                }
451                if (kv.get("font") != null) {
452                        style.append(String.format("font: %s; ", (String) kv.get("font")));
453                }
454        }
455
456        protected double asDouble(String value) {
457                try {
458                        return Double.parseDouble(value);
459                } catch (NumberFormatException e) {
460                        return 0.0;
461                }
462        }
463
464        protected String asNodeShape(String type) {
465                if (type.equals("ellipse") || type.equals("oval")) {
466                        return "circle";
467                } else if (type.equals("rectangle") || type.equals("box")) {
468                        return "box";
469                } else if (type.equals("rounded-box")) {
470                        return "rounded-box";
471                } else if (type.equals("cross")) {
472                        return "cross";
473                } else if (type.equals("freeplane")) {
474                        return "freeplane";
475                } else if (type.equals("losange") || type.equals("diamond")) {
476                        return "diamond";
477                } else {
478                        return "circle";
479                }
480        }
481
482        protected String asEdgeShape(String type) {
483                if (type.equals("line")) {
484                        return "line";
485                } else if (type.equals("cubic-curve")) {
486                        return "cubic-curve";
487                } else if (type.equals("angle")) {
488                        return "angle";
489                } else if (type.equals("blob")) {
490                        return "blob";
491                } else if (type.equals("freeplane")) {
492                        return "freeplane";
493                } else {
494                        return "line";
495                }
496        }
497
498        protected String asTextAlignment(String anchor) {
499                if (anchor.equals("c")) {
500                        return "center";
501                } else if (anchor.equals("n")) {
502                        return "above";
503                } else if (anchor.equals("ne")) {
504                        return "at-right";
505                } else if (anchor.equals("e")) {
506                        return "at-right";
507                } else if (anchor.equals("se")) {
508                        return "at-right";
509                } else if (anchor.equals("s")) {
510                        return "under";
511                } else if (anchor.equals("sw")) {
512                        return "at-left";
513                } else if (anchor.equals("w")) {
514                        return "at-left";
515                } else if (anchor.equals("nw")) {
516                        return "at-left";
517                } else {
518                        return "center";
519                }
520        }
521
522        protected String asArrowShape(String arrow) {
523                if (arrow.equals("none")) {
524                        return "none";
525                } else if (arrow.equals("last")) {
526                        return "arrow";
527                } else {
528                        return "none";
529                }
530        }
531
532        protected boolean getBoolean(Object bool) {
533                if(bool instanceof String) {
534                        return (bool.equals("1") || bool.equals("true") || bool.equals("yes")
535                                || bool.equals("y"));
536                } else if(bool instanceof Number) {
537                        return (((Number)bool).doubleValue() != 0);
538                }
539                return false;
540        }
541}
542
543class Graphics {
544        public double[] position = null;
545        public String style = null;
546
547        public void setX(double value) {
548                if (position == null)
549                        position = new double[3];
550
551                position[0] = value;
552        }
553
554        public void setY(double value) {
555                if (position == null)
556                        position = new double[3];
557
558                position[1] = value;
559        }
560
561        public void setZ(double value) {
562                if (position == null)
563                        position = new double[3];
564
565                position[2] = value;
566        }
567
568        public Object[] getPosition() {
569                Object p[] = new Object[3];
570                p[0] = (Double) position[0];
571                p[1] = (Double) position[1];
572                p[2] = (Double) position[2];
573                return p;
574        }
575}
576
577class KeyValues extends HashMap<String, Object> {
578        private static final long serialVersionUID = 5920553787913520204L;
579
580        public String key;
581        public int line;
582        public int column;
583
584        public void print() {
585                System.err.printf("%s:%n", key);
586                for (String k : keySet()) {
587                        System.err.printf("    %s: %s%n", k, get(k));
588                }
589        }
590
591        public String optString(String key) throws IOException {
592                Object o = get(key);
593
594                if (o == null)
595                        return null;
596
597                if (o instanceof Number)
598                        o = o.toString();
599                
600                if (!(o instanceof String))
601                        throw new IOException(
602                                        String.format(
603                                                        "%d:%d: expecting a string or number value for tag %s, got a list of values",
604                                                        line, column, key));
605
606                remove(key);
607                return (String) o;
608        }
609
610        protected String reqString(String key) throws IOException {
611                Object o = get(key);
612
613                if (o == null)
614                        throw new IOException(String.format(
615                                        "%d:%d: expecting a tag %s but none found", line, column,
616                                        key));
617
618                if (!(o instanceof String))
619                        throw new IOException(
620                                        String.format(
621                                                        "%d:%d: expecting a string or number value for tag %s, got a list of values",
622                                                        line, column, key));
623
624                remove(key);
625
626                return (String) o;
627        }
628
629        protected String reqStringOrNumber(String key) throws IOException {
630                Object o = get(key);
631
632                if (o == null)
633                        throw new IOException(String.format(
634                                        "%d:%d: expecting a tag %s but none found", line, column,
635                                        key));
636
637                if (!(o instanceof String) && !(o instanceof Number))
638                        throw new IOException(
639                                        String.format(
640                                                        "%d:%d: expecting a string or number value for tag %s, got a list of values",
641                                                        line, column, key));
642
643                remove(key);
644
645                if(o instanceof Number) {
646                        o = o.toString();
647                }
648                
649                return (String) o;
650        }
651
652        protected double reqNumber(String key) throws IOException {
653                Object o = get(key);
654                double v = 0.0;
655
656                if (o == null)
657                        throw new IOException(String.format(
658                                        "%d:%d: expecting a tag %s but none found", line, column,
659                                        key));
660
661                if (!(o instanceof String))
662                        throw new IOException(
663                                        String.format(
664                                                        "%d:%d expecting a string or number value for tag %s, got a list of values",
665                                                        line, column, key));
666
667                try {
668                        remove(key);
669                        v = Double.parseDouble((String) o);
670                } catch (NumberFormatException e) {
671                        throw new IOException(String.format(
672                                        "%d:%d: expecting a number value for tag %s, got a string",
673                                        line, column, key));
674                }
675
676                return v;
677        }
678
679        protected KeyValues optKeyValues(String key) throws IOException {
680                Object o = get(key);
681
682                if (o == null)
683                        return null;
684
685                if (!(o instanceof KeyValues))
686                        throw new IOException(
687                                        String.format(
688                                                        "%d:%d: expecting a list of values for tag %s, got a string or number",
689                                                        line, column, key));
690
691                remove(key);
692
693                return (KeyValues) o;
694        }
695
696        protected KeyValues reqKeyValues(String key) throws IOException {
697                Object o = get(key);
698
699                if (o == null)
700                        throw new IOException(String.format(
701                                        "%d:%d: expecting a tag %s but none found", line, column,
702                                        key));
703
704                if (!(o instanceof KeyValues))
705                        throw new IOException(
706                                        String.format(
707                                                        "%d:%d: expecting a list of values for tag %s, got a string or number",
708                                                        line, column, key));
709
710                remove(key);
711
712                return (KeyValues) o;
713        }
714
715        protected void error(String message) throws IOException {
716                throw new IOException(String.format("%d:%d: %s", line, column, message));
717        }
718}