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.FileReader;
035import java.io.IOException;
036import java.io.InputStream;
037import java.io.InputStreamReader;
038import java.io.Reader;
039import java.net.URL;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.Iterator;
043import java.util.LinkedList;
044import java.util.Stack;
045
046import javax.xml.stream.FactoryConfigurationError;
047import javax.xml.stream.Location;
048import javax.xml.stream.XMLEventReader;
049import javax.xml.stream.XMLInputFactory;
050import javax.xml.stream.XMLStreamConstants;
051import javax.xml.stream.XMLStreamException;
052import javax.xml.stream.events.Attribute;
053import javax.xml.stream.events.XMLEvent;
054
055import org.graphstream.stream.SourceBase;
056
057/**
058 * GraphML is a comprehensive and easy-to-use file format for graphs. It
059 * consists of a language core to describe the structural properties of a graph
060 * and a flexible extension mechanism to add application-specific data. Its main
061 * features include support of
062 * <ul>
063 * <li>directed, undirected, and mixed graphs,</li>
064 * <li>hypergraphs,</li>
065 * <li>hierarchical graphs,</li>
066 * <li>graphical representations,</li>
067 * <li>references to external data,</li>
068 * <li>application-specific attribute data, and</li>
069 * <li>light-weight parsers.</li>
070 * </ul>
071 * 
072 * Unlike many other file formats for graphs, GraphML does not use a custom
073 * syntax. Instead, it is based on XML and hence ideally suited as a common
074 * denominator for all kinds of services generating, archiving, or processing
075 * graphs.
076 * 
077 * <a href="http://graphml.graphdrawing.org/index.html">Source</a>
078 */
079public class FileSourceGraphML extends SourceBase implements FileSource,
080                XMLStreamConstants {
081
082        protected static enum Balise {
083                GRAPHML, GRAPH, NODE, EDGE, HYPEREDGE, DESC, DATA, LOCATOR, PORT, KEY, DEFAULT
084        }
085
086        protected static enum GraphAttribute {
087                ID, EDGEDEFAULT
088        }
089
090        protected static enum LocatorAttribute {
091                XMLNS_XLINK, XLINK_HREF, XLINK_TYPE
092        }
093
094        protected static enum NodeAttribute {
095                ID
096        }
097
098        protected static enum EdgeAttribute {
099                ID, SOURCE, SOURCEPORT, TARGET, TARGETPORT, DIRECTED
100        }
101
102        protected static enum DataAttribute {
103                KEY, ID
104        }
105
106        protected static enum PortAttribute {
107                NAME
108        }
109
110        protected static enum EndPointAttribute {
111                ID, NODE, PORT, TYPE
112        }
113
114        protected static enum EndPointType {
115                IN, OUT, UNDIR
116        }
117
118        protected static enum HyperEdgeAttribute {
119                ID
120        }
121
122        protected static enum KeyAttribute {
123                ID, FOR, ATTR_NAME, ATTR_TYPE
124        }
125
126        protected static enum KeyDomain {
127                GRAPHML, GRAPH, NODE, EDGE, HYPEREDGE, PORT, ENDPOINT, ALL
128        }
129
130        protected static enum KeyAttrType {
131                BOOLEAN, INT, LONG, FLOAT, DOUBLE, STRING
132        }
133
134        protected static class Key {
135                KeyDomain domain;
136                String name;
137                KeyAttrType type;
138                String def = null;
139
140                Key() {
141                        domain = KeyDomain.ALL;
142                        name = null;
143                        type = KeyAttrType.STRING;
144                }
145
146                Object getKeyValue(String value) {
147                        if (value == null)
148                                return null;
149
150                        switch (type) {
151                        case STRING:
152                                return value;
153                        case INT:
154                                return Integer.valueOf(value);
155                        case LONG:
156                                return Long.valueOf(value);
157                        case FLOAT:
158                                return Float.valueOf(value);
159                        case DOUBLE:
160                                return Double.valueOf(value);
161                        case BOOLEAN:
162                                return Boolean.valueOf(value);
163                        }
164
165                        return value;
166                }
167
168                Object getDefaultValue() {
169                        return getKeyValue(def);
170                }
171        }
172
173        protected static class Data {
174                Key key;
175                String id;
176                String value;
177        }
178
179        protected static class Locator {
180                String href;
181                String xlink;
182                String type;
183
184                Locator() {
185                        xlink = "http://www.w3.org/TR/2000/PR-xlink-20001220/";
186                        type = "simple";
187                        href = null;
188                }
189        }
190
191        protected static class Port {
192                String name;
193                String desc;
194
195                LinkedList<Data> datas;
196                LinkedList<Port> ports;
197
198                Port() {
199                        name = null;
200                        desc = null;
201
202                        datas = new LinkedList<Data>();
203                        ports = new LinkedList<Port>();
204                }
205        }
206
207        protected static class EndPoint {
208                String id;
209                String node;
210                String port;
211                String desc;
212                EndPointType type;
213
214                EndPoint() {
215                        id = null;
216                        node = null;
217                        port = null;
218                        desc = null;
219                        type = EndPointType.UNDIR;
220                }
221        }
222
223        protected XMLEventReader reader;
224        protected HashMap<String, Key> keys;
225        protected LinkedList<Data> datas;
226        protected Stack<XMLEvent> events;
227        protected Stack<String> graphId;
228        protected int graphCounter;
229
230        /**
231         * Build a new source to parse an xml stream in GraphML format.
232         */
233        public FileSourceGraphML() {
234                events = new Stack<XMLEvent>();
235                keys = new HashMap<String, Key>();
236                datas = new LinkedList<Data>();
237                graphId = new Stack<String>();
238                graphCounter = 0;
239                sourceId = String.format("<GraphML stream %x>", System.nanoTime());
240        }
241
242        /*
243         * (non-Javadoc)
244         * 
245         * @see org.graphstream.stream.file.FileSource#readAll(java.lang.String)
246         */
247        public void readAll(String fileName) throws IOException {
248                readAll(new FileReader(fileName));
249        }
250
251        /*
252         * (non-Javadoc)
253         * 
254         * @see org.graphstream.stream.file.FileSource#readAll(java.net.URL)
255         */
256        public void readAll(URL url) throws IOException {
257                readAll(url.openStream());
258        }
259
260        /*
261         * (non-Javadoc)
262         * 
263         * @see org.graphstream.stream.file.FileSource#readAll(java.io.InputStream)
264         */
265        public void readAll(InputStream stream) throws IOException {
266                readAll(new InputStreamReader(stream));
267        }
268
269        /*
270         * (non-Javadoc)
271         * 
272         * @see org.graphstream.stream.file.FileSource#readAll(java.io.Reader)
273         */
274        public void readAll(Reader reader) throws IOException {
275                begin(reader);
276                while (nextEvents())
277                        ;
278                end();
279        }
280
281        /*
282         * (non-Javadoc)
283         * 
284         * @see org.graphstream.stream.file.FileSource#begin(java.lang.String)
285         */
286        public void begin(String fileName) throws IOException {
287                begin(new FileReader(fileName));
288        }
289
290        /*
291         * (non-Javadoc)
292         * 
293         * @see org.graphstream.stream.file.FileSource#begin(java.net.URL)
294         */
295        public void begin(URL url) throws IOException {
296                begin(url.openStream());
297        }
298
299        /*
300         * (non-Javadoc)
301         * 
302         * @see org.graphstream.stream.file.FileSource#begin(java.io.InputStream)
303         */
304        public void begin(InputStream stream) throws IOException {
305                begin(new InputStreamReader(stream));
306        }
307
308        /*
309         * (non-Javadoc)
310         * 
311         * @see org.graphstream.stream.file.FileSource#begin(java.io.Reader)
312         */
313        public void begin(Reader reader) throws IOException {
314                openStream(reader);
315        }
316
317        /*
318         * (non-Javadoc)
319         * 
320         * @see org.graphstream.stream.file.FileSource#nextEvents()
321         */
322        public boolean nextEvents() throws IOException {
323                try {
324                        __graphml();
325                } catch (XMLStreamException ex) {
326                        throw new IOException(ex);
327                }
328
329                return false;
330        }
331
332        /*
333         * (non-Javadoc)
334         * 
335         * @see org.graphstream.stream.file.FileSource#nextStep()
336         */
337        public boolean nextStep() throws IOException {
338                return nextEvents();
339        }
340
341        /*
342         * (non-Javadoc)
343         * 
344         * @see org.graphstream.stream.file.FileSource#end()
345         */
346        public void end() throws IOException {
347                closeStream();
348        }
349
350        protected XMLEvent getNextEvent() throws IOException, XMLStreamException {
351                skipWhiteSpaces();
352
353                if (events.size() > 0)
354                        return events.pop();
355
356                return reader.nextEvent();
357        }
358
359        protected void pushback(XMLEvent e) {
360                events.push(e);
361        }
362
363        private XMLStreamException newParseError(XMLEvent e, String msg,
364                        Object... args) {
365                return new XMLStreamException(String.format(msg, args), e.getLocation());
366        }
367
368        private boolean isEvent(XMLEvent e, int type, String name) {
369                boolean valid = e.getEventType() == type;
370
371                if (valid) {
372                        switch (type) {
373                        case START_ELEMENT:
374                                valid = e.asStartElement().getName().getLocalPart()
375                                                .equals(name);
376                                break;
377                        case END_ELEMENT:
378                                valid = e.asEndElement().getName().getLocalPart().equals(name);
379                                break;
380                        case ATTRIBUTE:
381                                valid = ((Attribute) e).getName().getLocalPart().equals(name);
382                                break;
383                        case CHARACTERS:
384                        case NAMESPACE:
385                        case PROCESSING_INSTRUCTION:
386                        case COMMENT:
387                        case START_DOCUMENT:
388                        case END_DOCUMENT:
389                        case DTD:
390                        }
391                }
392
393                return valid;
394        }
395
396        private void checkValid(XMLEvent e, int type, String name)
397                        throws XMLStreamException {
398                boolean valid = isEvent(e, type, name);
399
400                if (!valid)
401                        throw newParseError(e, "expecting %s, got %s", gotWhat(type, name),
402                                        gotWhat(e));
403        }
404
405        private String gotWhat(XMLEvent e) {
406                String v = null;
407
408                switch (e.getEventType()) {
409                case START_ELEMENT:
410                        v = e.asStartElement().getName().getLocalPart();
411                        break;
412                case END_ELEMENT:
413                        v = e.asEndElement().getName().getLocalPart();
414                        break;
415                case ATTRIBUTE:
416                        v = ((Attribute) e).getName().getLocalPart();
417                        break;
418                }
419
420                return gotWhat(e.getEventType(), v);
421        }
422
423        private String gotWhat(int type, String v) {
424                switch (type) {
425                case START_ELEMENT:
426                        return String.format("'<%s>'", v);
427                case END_ELEMENT:
428                        return String.format("'</%s>'", v);
429                case ATTRIBUTE:
430                        return String.format("attribute '%s'", v);
431                case NAMESPACE:
432                        return "namespace";
433                case PROCESSING_INSTRUCTION:
434                        return "processing instruction";
435                case COMMENT:
436                        return "comment";
437                case START_DOCUMENT:
438                        return "document start";
439                case END_DOCUMENT:
440                        return "document end";
441                case DTD:
442                        return "dtd";
443                case CHARACTERS:
444                        return "characters";
445                default:
446                        return "UNKNOWN";
447                }
448        }
449
450        private Object getValue(Data data) {
451                switch (data.key.type) {
452                case BOOLEAN:
453                        return Boolean.parseBoolean(data.value);
454                case INT:
455                        return Integer.parseInt(data.value);
456                case LONG:
457                        return Long.parseLong(data.value);
458                case FLOAT:
459                        return Float.parseFloat(data.value);
460                case DOUBLE:
461                        return Double.parseDouble(data.value);
462                case STRING:
463                        return data.value;
464                }
465
466                return data.value;
467        }
468
469        private Object getDefaultValue(Key key) {
470                switch (key.type) {
471                case BOOLEAN:
472                        return Boolean.TRUE;
473                case INT:
474                        if (key.def != null)
475                                return Integer.valueOf(key.def);
476
477                        return Integer.valueOf(0);
478                case LONG:
479                        if (key.def != null)
480                                return Long.valueOf(key.def);
481
482                        return Long.valueOf(0);
483                case FLOAT:
484                        if (key.def != null)
485                                return Float.valueOf(key.def);
486
487                        return Float.valueOf(0.0f);
488                case DOUBLE:
489                        if (key.def != null)
490                                return Double.valueOf(key.def);
491
492                        return Double.valueOf(0.0);
493                case STRING:
494                        if (key.def != null)
495                                return key.def;
496
497                        return "";
498                }
499
500                return key.def != null ? key.def : Boolean.TRUE;
501        }
502
503        private void skipWhiteSpaces() throws IOException, XMLStreamException {
504                XMLEvent e;
505
506                do {
507                        if (events.size() > 0)
508                                e = events.pop();
509                        else
510                                e = reader.nextEvent();
511                } while (isEvent(e, XMLEvent.CHARACTERS, null)
512                                && e.asCharacters().getData().matches("^\\s*$"));
513
514                pushback(e);
515        }
516
517        protected void openStream(Reader stream) throws IOException {
518                if (reader != null)
519                        closeStream();
520
521                try {
522                        XMLEvent e;
523
524                        reader = XMLInputFactory.newInstance().createXMLEventReader(stream);
525
526                        e = getNextEvent();
527                        checkValid(e, XMLEvent.START_DOCUMENT, null);
528
529                } catch (XMLStreamException e) {
530                        throw new IOException(e);
531                } catch (FactoryConfigurationError e) {
532                        throw new IOException(e);
533                }
534        }
535
536        protected void closeStream() throws IOException {
537                try {
538                        reader.close();
539                } catch (XMLStreamException e) {
540                        throw new IOException(e);
541                } finally {
542                        reader = null;
543                }
544        }
545
546        protected String toConstantName(Attribute a) {
547                return toConstantName(a.getName().getLocalPart());
548        }
549
550        protected String toConstantName(String value) {
551                return value.toUpperCase().replaceAll("\\W", "_");
552        }
553
554        /**
555         * <pre>
556         * <!ELEMENT graphml  ((desc)?,(key)*,((data)|(graph))*)>
557         * </pre>
558         * 
559         * @throws IOException
560         * @throws XMLStreamException
561         */
562        private void __graphml() throws IOException, XMLStreamException {
563                XMLEvent e;
564
565                e = getNextEvent();
566                checkValid(e, XMLEvent.START_ELEMENT, "graphml");
567
568                e = getNextEvent();
569
570                if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
571                        pushback(e);
572                        __desc();
573
574                        e = getNextEvent();
575                }
576
577                while (isEvent(e, XMLEvent.START_ELEMENT, "key")) {
578                        pushback(e);
579                        __key();
580
581                        e = getNextEvent();
582                }
583
584                while (isEvent(e, XMLEvent.START_ELEMENT, "data")
585                                || isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
586                        pushback(e);
587
588                        if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
589                                __data();
590                        } else {
591                                __graph();
592                        }
593
594                        e = getNextEvent();
595                }
596
597                checkValid(e, XMLEvent.END_ELEMENT, "graphml");
598        }
599
600        private String __characters() throws IOException, XMLStreamException {
601                XMLEvent e;
602                StringBuilder buffer = new StringBuilder();
603
604                e = getNextEvent();
605
606                while (e.getEventType() == XMLEvent.CHARACTERS) {
607                        buffer.append(e.asCharacters());
608                        e = getNextEvent();
609                }
610
611                pushback(e);
612
613                return buffer.toString();
614        }
615
616        /**
617         * <pre>
618         * <!ELEMENT desc (#PCDATA)>
619         * </pre>
620         * 
621         * @return
622         * @throws IOException
623         * @throws XMLStreamException
624         */
625        private String __desc() throws IOException, XMLStreamException {
626                XMLEvent e;
627                String desc;
628
629                e = getNextEvent();
630                checkValid(e, XMLEvent.START_ELEMENT, "desc");
631
632                desc = __characters();
633
634                e = getNextEvent();
635                checkValid(e, XMLEvent.END_ELEMENT, "desc");
636
637                return desc;
638        }
639
640        /**
641         * <pre>
642         * <!ELEMENT locator EMPTY>
643         * <!ATTLIST locator 
644         *           xmlns:xlink   CDATA    #FIXED    "http://www.w3.org/TR/2000/PR-xlink-20001220/"
645         *           xlink:href    CDATA    #REQUIRED
646         *           xlink:type    (simple) #FIXED    "simple"
647         * >
648         * </pre>
649         * 
650         * @return
651         * @throws IOException
652         * @throws XMLStreamException
653         */
654        private Locator __locator() throws IOException, XMLStreamException {
655                XMLEvent e;
656
657                e = getNextEvent();
658                checkValid(e, XMLEvent.START_ELEMENT, "locator");
659
660                @SuppressWarnings("unchecked")
661                Iterator<? extends Attribute> attributes = e.asStartElement()
662                                .getAttributes();
663
664                Locator loc = new Locator();
665
666                while (attributes.hasNext()) {
667                        Attribute a = attributes.next();
668
669                        try {
670                                LocatorAttribute attribute = LocatorAttribute
671                                                .valueOf(toConstantName(a));
672
673                                switch (attribute) {
674                                case XMLNS_XLINK:
675                                        loc.xlink = a.getValue();
676                                        break;
677                                case XLINK_HREF:
678                                        loc.href = a.getValue();
679                                        break;
680                                case XLINK_TYPE:
681                                        loc.type = a.getValue();
682                                        break;
683                                }
684                        } catch (IllegalArgumentException ex) {
685                                throw newParseError(e, "invalid locator attribute '%s'", a
686                                                .getName().getLocalPart());
687                        }
688                }
689
690                e = getNextEvent();
691                checkValid(e, XMLEvent.END_ELEMENT, "locator");
692
693                if (loc.href == null)
694                        throw newParseError(e, "locator requires an href");
695
696                return loc;
697        }
698
699        /**
700         * <pre>
701         * <!ELEMENT key (#PCDATA)>
702         * <!ATTLIST key 
703         *           id  ID                                            #REQUIRED
704         *           for (graphml|graph|node|edge|hyperedge|port|endpoint|all) "all"
705         * >
706         * </pre>
707         * 
708         * @throws IOException
709         * @throws XMLStreamException
710         */
711        private void __key() throws IOException, XMLStreamException {
712                XMLEvent e;
713
714                e = getNextEvent();
715                checkValid(e, XMLEvent.START_ELEMENT, "key");
716
717                @SuppressWarnings("unchecked")
718                Iterator<? extends Attribute> attributes = e.asStartElement()
719                                .getAttributes();
720
721                String id = null;
722                KeyDomain domain = KeyDomain.ALL;
723                KeyAttrType type = KeyAttrType.STRING;
724                String name = null;
725                String def = null;
726
727                while (attributes.hasNext()) {
728                        Attribute a = attributes.next();
729
730                        try {
731                                KeyAttribute attribute = KeyAttribute
732                                                .valueOf(toConstantName(a));
733
734                                switch (attribute) {
735                                case ID:
736                                        id = a.getValue();
737
738                                        break;
739                                case FOR:
740                                        try {
741                                                domain = KeyDomain
742                                                                .valueOf(toConstantName(a.getValue()));
743                                        } catch (IllegalArgumentException ex) {
744                                                throw newParseError(e, "invalid key domain '%s'",
745                                                                a.getValue());
746                                        }
747
748                                        break;
749                                case ATTR_TYPE:
750                                        try {
751                                                type = KeyAttrType
752                                                                .valueOf(toConstantName(a.getValue()));
753                                        } catch (IllegalArgumentException ex) {
754                                                throw newParseError(e, "invalid key type '%s'",
755                                                                a.getValue());
756                                        }
757
758                                        break;
759                                case ATTR_NAME:
760                                        name = a.getValue();
761
762                                        break;
763                                }
764                        } catch (IllegalArgumentException ex) {
765                                throw newParseError(e, "invalid key attribute '%s'", a
766                                                .getName().getLocalPart());
767                        }
768                }
769
770                e = getNextEvent();
771
772                if (isEvent(e, XMLEvent.START_ELEMENT, "default")) {
773                        def = __characters();
774
775                        e = getNextEvent();
776                        checkValid(e, XMLEvent.END_ELEMENT, "default");
777
778                        e = getNextEvent();
779                }
780
781                checkValid(e, XMLEvent.END_ELEMENT, "key");
782
783                if (id == null)
784                        throw newParseError(e, "key requires an id");
785
786                if (name == null)
787                        name = id;
788
789                System.out.printf("add key \"%s\"\n", id);
790
791                Key k = new Key();
792                k.name = name;
793                k.domain = domain;
794                k.type = type;
795                k.def = def;
796
797                keys.put(id, k);
798        }
799
800        /**
801         * <pre>
802         * <!ELEMENT port ((desc)?,((data)|(port))*)>
803         * <!ATTLIST port
804         *           name    NMTOKEN  #REQUIRED
805         * >
806         * </pre>
807         * 
808         * @return
809         * @throws IOException
810         * @throws XMLStreamException
811         */
812        private Port __port() throws IOException, XMLStreamException {
813                XMLEvent e;
814
815                e = getNextEvent();
816                checkValid(e, XMLEvent.START_ELEMENT, "port");
817
818                Port port = new Port();
819                @SuppressWarnings("unchecked")
820                Iterator<? extends Attribute> attributes = e.asStartElement()
821                                .getAttributes();
822                while (attributes.hasNext()) {
823                        Attribute a = attributes.next();
824
825                        try {
826                                PortAttribute attribute = PortAttribute
827                                                .valueOf(toConstantName(a));
828
829                                switch (attribute) {
830                                case NAME:
831                                        port.name = a.getValue();
832                                        break;
833                                }
834                        } catch (IllegalArgumentException ex) {
835                                throw newParseError(e, "invalid attribute '%s' for '<port>'", a
836                                                .getName().getLocalPart());
837                        }
838                }
839
840                if (port.name == null)
841                        throw newParseError(e,
842                                        "'<port>' element requires a 'name' attribute");
843
844                e = getNextEvent();
845                if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
846                        pushback(e);
847                        port.desc = __desc();
848                } else {
849                        while (isEvent(e, XMLEvent.START_ELEMENT, "data")
850                                        || isEvent(e, XMLEvent.START_ELEMENT, "port")) {
851                                if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
852                                        Data data;
853
854                                        pushback(e);
855                                        data = __data();
856
857                                        port.datas.add(data);
858                                } else {
859                                        Port portChild;
860
861                                        pushback(e);
862                                        portChild = __port();
863
864                                        port.ports.add(portChild);
865                                }
866
867                                e = getNextEvent();
868                        }
869                }
870
871                e = getNextEvent();
872                checkValid(e, XMLEvent.END_ELEMENT, "port");
873
874                return port;
875        }
876
877        /**
878         * <pre>
879         * <!ELEMENT endpoint ((desc)?)>
880         * <!ATTLIST endpoint 
881         *           id    ID             #IMPLIED
882         *           node  IDREF          #REQUIRED
883         *           port  NMTOKEN        #IMPLIED
884         *           type  (in|out|undir) "undir"
885         * >
886         * </pre>
887         * 
888         * @return
889         * @throws IOException
890         * @throws XMLStreamException
891         */
892        private EndPoint __endpoint() throws IOException, XMLStreamException {
893                XMLEvent e;
894
895                e = getNextEvent();
896                checkValid(e, XMLEvent.START_ELEMENT, "endpoint");
897
898                @SuppressWarnings("unchecked")
899                Iterator<? extends Attribute> attributes = e.asStartElement()
900                                .getAttributes();
901                EndPoint ep = new EndPoint();
902
903                while (attributes.hasNext()) {
904                        Attribute a = attributes.next();
905
906                        try {
907                                EndPointAttribute attribute = EndPointAttribute
908                                                .valueOf(toConstantName(a));
909
910                                switch (attribute) {
911                                case NODE:
912                                        ep.node = a.getValue();
913                                        break;
914                                case ID:
915                                        ep.id = a.getValue();
916                                        break;
917                                case PORT:
918                                        ep.port = a.getValue();
919                                        break;
920                                case TYPE:
921                                        try {
922                                                ep.type = EndPointType.valueOf(toConstantName(a
923                                                                .getValue()));
924                                        } catch (IllegalArgumentException ex) {
925                                                throw newParseError(e, "invalid end point type '%s'",
926                                                                a.getValue());
927                                        }
928
929                                        break;
930                                }
931                        } catch (IllegalArgumentException ex) {
932                                throw newParseError(e,
933                                                "invalid attribute '%s' for '<endpoint>'", a.getName()
934                                                                .getLocalPart());
935                        }
936                }
937
938                if (ep.node == null)
939                        throw newParseError(e,
940                                        "'<endpoint>' element requires a 'node' attribute");
941
942                e = getNextEvent();
943
944                if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
945                        pushback(e);
946                        ep.desc = __desc();
947                }
948
949                e = getNextEvent();
950                checkValid(e, XMLEvent.END_ELEMENT, "endpoint");
951
952                return ep;
953        }
954
955        /**
956         * <pre>
957         * <!ELEMENT data  (#PCDATA)>
958         * <!ATTLIST data 
959         *           key      IDREF        #REQUIRED
960         *           id       ID           #IMPLIED
961         * >
962         * </pre>
963         * 
964         * @return
965         * @throws IOException
966         * @throws XMLStreamException
967         */
968        private Data __data() throws IOException, XMLStreamException {
969                XMLEvent e;
970                StringBuilder buffer = new StringBuilder();
971
972                e = getNextEvent();
973                checkValid(e, XMLEvent.START_ELEMENT, "data");
974
975                @SuppressWarnings("unchecked")
976                Iterator<? extends Attribute> attributes = e.asStartElement()
977                                .getAttributes();
978                String key = null, id = null;
979
980                while (attributes.hasNext()) {
981                        Attribute a = attributes.next();
982
983                        try {
984                                DataAttribute attribute = DataAttribute
985                                                .valueOf(toConstantName(a));
986
987                                switch (attribute) {
988                                case KEY:
989                                        key = a.getValue();
990                                        break;
991                                case ID:
992                                        id = a.getValue();
993                                        break;
994                                }
995                        } catch (IllegalArgumentException ex) {
996                                throw newParseError(e, "invalid attribute '%s' for '<data>'", a
997                                                .getName().getLocalPart());
998                        }
999                }
1000
1001                if (key == null)
1002                        throw newParseError(e,
1003                                        "'<data>' element must have a 'key' attribute");
1004
1005                e = getNextEvent();
1006
1007                while (e.getEventType() == XMLEvent.CHARACTERS) {
1008                        buffer.append(e.asCharacters());
1009                        e = getNextEvent();
1010                }
1011
1012                checkValid(e, XMLEvent.END_ELEMENT, "data");
1013
1014                if (keys.containsKey(key))
1015                        newParseError(e, "unknown key '%s'", key);
1016
1017                Data d = new Data();
1018
1019                d.key = keys.get(key);
1020                d.id = id;
1021                d.value = buffer.toString();
1022
1023                return d;
1024        }
1025
1026        /**
1027         * <pre>
1028         * <!ELEMENT graph    ((desc)?,((((data)|(node)|(edge)|(hyperedge))*)|(locator)))>
1029         * <!ATTLIST graph    
1030         *     id          ID                    #IMPLIED
1031         *     edgedefault (directed|undirected) #REQUIRED
1032         * >
1033         * </pre>
1034         * 
1035         * @throws IOException
1036         * @throws XMLStreamException
1037         */
1038        private void __graph() throws IOException, XMLStreamException {
1039                XMLEvent e;
1040
1041                e = getNextEvent();
1042                checkValid(e, XMLEvent.START_ELEMENT, "graph");
1043
1044                @SuppressWarnings("unchecked")
1045                Iterator<? extends Attribute> attributes = e.asStartElement()
1046                                .getAttributes();
1047
1048                String id = null;
1049                String desc = null;
1050                boolean directed = false;
1051                boolean directedSet = false;
1052
1053                while (attributes.hasNext()) {
1054                        Attribute a = attributes.next();
1055
1056                        try {
1057                                GraphAttribute attribute = GraphAttribute
1058                                                .valueOf(toConstantName(a));
1059
1060                                switch (attribute) {
1061                                case ID:
1062                                        id = a.getValue();
1063                                        break;
1064                                case EDGEDEFAULT:
1065                                        if (a.getValue().equals("directed"))
1066                                                directed = true;
1067                                        else if (a.getValue().equals("undirected"))
1068                                                directed = false;
1069                                        else
1070                                                throw newParseError(e,
1071                                                                "invalid 'edgefault' value '%s'", a.getValue());
1072
1073                                        directedSet = true;
1074
1075                                        break;
1076                                }
1077                        } catch (IllegalArgumentException ex) {
1078                                throw newParseError(e, "invalid node attribute '%s'", a
1079                                                .getName().getLocalPart());
1080                        }
1081                }
1082
1083                if (!directedSet)
1084                        throw newParseError(e, "graph requires attribute 'edgedefault'");
1085
1086                String gid = "";
1087
1088                if (graphId.size() > 0)
1089                        gid = graphId.peek() + ":";
1090
1091                if (id != null)
1092                        gid += id;
1093                else
1094                        gid += Integer.toString(graphCounter++);
1095
1096                graphId.push(gid);
1097
1098                e = getNextEvent();
1099
1100                if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
1101                        pushback(e);
1102                        desc = __desc();
1103
1104                        sendGraphAttributeAdded(sourceId, "desc", desc);
1105
1106                        e = getNextEvent();
1107                }
1108
1109                if (isEvent(e, XMLEvent.START_ELEMENT, "locator")) {
1110                        pushback(e);
1111                        __locator();
1112                        // TODO
1113                        e = getNextEvent();
1114                } else {
1115                        while (isEvent(e, XMLEvent.START_ELEMENT, "data")
1116                                        || isEvent(e, XMLEvent.START_ELEMENT, "node")
1117                                        || isEvent(e, XMLEvent.START_ELEMENT, "edge")
1118                                        || isEvent(e, XMLEvent.START_ELEMENT, "hyperedge")) {
1119                                pushback(e);
1120
1121                                if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
1122                                        datas.add(__data());
1123                                } else if (isEvent(e, XMLEvent.START_ELEMENT, "node")) {
1124                                        __node();
1125                                } else if (isEvent(e, XMLEvent.START_ELEMENT, "edge")) {
1126                                        __edge(directed);
1127                                } else {
1128                                        __hyperedge();
1129                                }
1130
1131                                e = getNextEvent();
1132                        }
1133                }
1134
1135                graphId.pop();
1136                checkValid(e, XMLEvent.END_ELEMENT, "graph");
1137        }
1138
1139        /**
1140         * <pre>
1141         * <!ELEMENT node   (desc?,(((data|port)*,graph?)|locator))>
1142         * <!ATTLIST node   
1143         *               id        ID      #REQUIRED
1144         * >
1145         * </pre>
1146         * 
1147         * @throws IOException
1148         * @throws XMLStreamException
1149         */
1150        private void __node() throws IOException, XMLStreamException {
1151                XMLEvent e;
1152
1153                e = getNextEvent();
1154                checkValid(e, XMLEvent.START_ELEMENT, "node");
1155
1156                @SuppressWarnings("unchecked")
1157                Iterator<? extends Attribute> attributes = e.asStartElement()
1158                                .getAttributes();
1159
1160                String id = null;
1161                HashSet<Key> sentAttributes = new HashSet<Key>();
1162
1163                while (attributes.hasNext()) {
1164                        Attribute a = attributes.next();
1165
1166                        try {
1167                                NodeAttribute attribute = NodeAttribute
1168                                                .valueOf(toConstantName(a));
1169
1170                                switch (attribute) {
1171                                case ID:
1172                                        id = a.getValue();
1173                                        break;
1174                                }
1175                        } catch (IllegalArgumentException ex) {
1176                                throw newParseError(e, "invalid node attribute '%s'", a
1177                                                .getName().getLocalPart());
1178                        }
1179                }
1180
1181                if (id == null)
1182                        throw newParseError(e, "node requires an id");
1183
1184                sendNodeAdded(sourceId, id);
1185
1186                e = getNextEvent();
1187
1188                if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
1189                        String desc;
1190
1191                        pushback(e);
1192                        desc = __desc();
1193
1194                        sendNodeAttributeAdded(sourceId, id, "desc", desc);
1195                } else if (isEvent(e, XMLEvent.START_ELEMENT, "locator")) {
1196                        // TODO
1197                        pushback(e);
1198                        __locator();
1199                } else {
1200                        while (isEvent(e, XMLEvent.START_ELEMENT, "data")
1201                                        || isEvent(e, XMLEvent.START_ELEMENT, "port")) {
1202                                if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
1203                                        Data data;
1204
1205                                        pushback(e);
1206                                        data = __data();
1207
1208                                        sendNodeAttributeAdded(sourceId, id, data.key.name,
1209                                                        getValue(data));
1210
1211                                        sentAttributes.add(data.key);
1212                                } else {
1213                                        pushback(e);
1214                                        __port();
1215                                }
1216
1217                                e = getNextEvent();
1218                        }
1219                }
1220
1221                for (Key k : keys.values()) {
1222                        if ((k.domain == KeyDomain.NODE || k.domain == KeyDomain.ALL)
1223                                        && !sentAttributes.contains(k))
1224                                sendNodeAttributeAdded(sourceId, id, k.name, getDefaultValue(k));
1225                }
1226
1227                if (isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
1228                        Location loc = e.getLocation();
1229
1230                        System.err.printf(
1231                                        "[WARNING] %d:%d graph inside node is not implemented",
1232                                        loc.getLineNumber(), loc.getColumnNumber());
1233
1234                        pushback(e);
1235                        __graph();
1236
1237                        e = getNextEvent();
1238                }
1239
1240                checkValid(e, XMLEvent.END_ELEMENT, "node");
1241        }
1242
1243        /**
1244         * <pre>
1245         * <!ELEMENT edge ((desc)?,(data)*,(graph)?)>
1246         * <!ATTLIST edge 
1247         *           id         ID           #IMPLIED
1248         *           source     IDREF        #REQUIRED
1249         *           sourceport NMTOKEN      #IMPLIED
1250         *           target     IDREF        #REQUIRED
1251         *           targetport NMTOKEN      #IMPLIED
1252         *           directed   (true|false) #IMPLIED
1253         * >
1254         * </pre>
1255         * 
1256         * @param edgedefault
1257         * @throws IOException
1258         * @throws XMLStreamException
1259         */
1260        private void __edge(boolean edgedefault) throws IOException,
1261                        XMLStreamException {
1262                XMLEvent e;
1263
1264                e = getNextEvent();
1265                checkValid(e, XMLEvent.START_ELEMENT, "edge");
1266
1267                @SuppressWarnings("unchecked")
1268                Iterator<? extends Attribute> attributes = e.asStartElement()
1269                                .getAttributes();
1270
1271                HashSet<Key> sentAttributes = new HashSet<Key>();
1272                String id = null;
1273                boolean directed = edgedefault;
1274                String source = null;
1275                String target = null;
1276
1277                while (attributes.hasNext()) {
1278                        Attribute a = attributes.next();
1279
1280                        try {
1281                                EdgeAttribute attribute = EdgeAttribute
1282                                                .valueOf(toConstantName(a));
1283
1284                                switch (attribute) {
1285                                case ID:
1286                                        id = a.getValue();
1287                                        break;
1288                                case DIRECTED:
1289                                        directed = Boolean.parseBoolean(a.getValue());
1290                                        break;
1291                                case SOURCE:
1292                                        source = a.getValue();
1293                                        break;
1294                                case TARGET:
1295                                        target = a.getValue();
1296                                        break;
1297                                case SOURCEPORT:
1298                                case TARGETPORT:
1299                                        throw newParseError(e,
1300                                                        "sourceport and targetport not implemented");
1301                                }
1302                        } catch (IllegalArgumentException ex) {
1303                                throw newParseError(e, "invalid graph attribute '%s'", a
1304                                                .getName().getLocalPart());
1305                        }
1306                }
1307
1308                if (id == null)
1309                        throw newParseError(e, "edge must have an id");
1310
1311                if (source == null || target == null)
1312                        throw newParseError(e, "edge must have a source and a target");
1313
1314                sendEdgeAdded(sourceId, id, source, target, directed);
1315
1316                e = getNextEvent();
1317
1318                if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
1319                        String desc;
1320
1321                        pushback(e);
1322                        desc = __desc();
1323
1324                        sendEdgeAttributeAdded(sourceId, id, "desc", desc);
1325                } else {
1326                        while (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
1327                                Data data;
1328
1329                                pushback(e);
1330                                data = __data();
1331
1332                                sendEdgeAttributeAdded(sourceId, id, data.key.name,
1333                                                getValue(data));
1334
1335                                sentAttributes.add(data.key);
1336
1337                                e = getNextEvent();
1338                        }
1339                }
1340
1341                for (Key k : keys.values()) {
1342                        if ((k.domain == KeyDomain.EDGE || k.domain == KeyDomain.ALL)
1343                                        && !sentAttributes.contains(k))
1344                                sendEdgeAttributeAdded(sourceId, id, k.name, getDefaultValue(k));
1345                }
1346
1347                if (isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
1348                        Location loc = e.getLocation();
1349
1350                        System.err.printf(
1351                                        "[WARNING] %d:%d graph inside node is not implemented",
1352                                        loc.getLineNumber(), loc.getColumnNumber());
1353
1354                        pushback(e);
1355                        __graph();
1356
1357                        e = getNextEvent();
1358                }
1359
1360                checkValid(e, XMLEvent.END_ELEMENT, "edge");
1361        }
1362
1363        /**
1364         * <pre>
1365         * <!ELEMENT hyperedge  ((desc)?,((data)|(endpoint))*,(graph)?)>
1366         * <!ATTLIST hyperedge 
1367         *           id     ID      #IMPLIED
1368         * >
1369         * </pre>
1370         * 
1371         * @throws IOException
1372         * @throws XMLStreamException
1373         */
1374        private void __hyperedge() throws IOException, XMLStreamException {
1375                XMLEvent e;
1376
1377                e = getNextEvent();
1378                checkValid(e, XMLEvent.START_ELEMENT, "hyperedge");
1379
1380                Location loc = e.getLocation();
1381
1382                System.err.printf(
1383                                "[WARNING] %d:%d hyperedge feature is not implemented",
1384                                loc.getLineNumber(), loc.getColumnNumber());
1385
1386                String id = null;
1387
1388                @SuppressWarnings("unchecked")
1389                Iterator<? extends Attribute> attributes = e.asStartElement()
1390                                .getAttributes();
1391
1392                while (attributes.hasNext()) {
1393                        Attribute a = attributes.next();
1394
1395                        try {
1396                                HyperEdgeAttribute attribute = HyperEdgeAttribute
1397                                                .valueOf(toConstantName(a));
1398
1399                                switch (attribute) {
1400                                case ID:
1401                                        id = a.getValue();
1402                                        break;
1403                                }
1404                        } catch (IllegalArgumentException ex) {
1405                                throw newParseError(e,
1406                                                "invalid attribute '%s' for '<endpoint>'", a.getName()
1407                                                                .getLocalPart());
1408                        }
1409                }
1410
1411                if (id == null)
1412                        throw newParseError(e,
1413                                        "'<hyperedge>' element requires a 'node' attribute");
1414
1415                e = getNextEvent();
1416
1417                if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
1418                        pushback(e);
1419                        __desc();
1420                } else {
1421                        while (isEvent(e, XMLEvent.START_ELEMENT, "data")
1422                                        || isEvent(e, XMLEvent.START_ELEMENT, "endpoint")) {
1423                                if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
1424                                        pushback(e);
1425                                        __data();
1426                                } else {
1427                                        pushback(e);
1428                                        __endpoint();
1429                                }
1430
1431                                e = getNextEvent();
1432                        }
1433                }
1434
1435                if (isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
1436                        loc = e.getLocation();
1437
1438                        System.err.printf(
1439                                        "[WARNING] %d:%d graph inside node is not implemented",
1440                                        loc.getLineNumber(), loc.getColumnNumber());
1441
1442                        pushback(e);
1443                        __graph();
1444
1445                        e = getNextEvent();
1446                }
1447
1448                e = getNextEvent();
1449                checkValid(e, XMLEvent.END_ELEMENT, "hyperedge");
1450        }
1451}