/** * Copyright (c) 2009-2011, The HATS Consortium. All rights reserved. * This file is licensed under the terms of the Modified BSD License. */ package abs.backend.java.lib.net; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import abs.backend.java.lib.net.GMLParser.Pattern; import abs.backend.java.lib.net.NetworkGraph.AttributeStore; import abs.backend.java.lib.net.NetworkGraph.GraphNode; /** * A parser for the GML language * see {@link https://secure.wikimedia.org/wikipedia/en/wiki/Graph_Modelling_Language} * * <h2>Example</h2> * <pre> * graph [ * node [ * id = 1 * ] * * node [ * id = 2 * ] * * edge [ * source = 1 * target = 2 * ] * * * ] * </pre> * * @author jschaefer * */ public class GMLParser { private NetworkGraph graph; private Reader reader; private StringBuilder readText = new StringBuilder(); public NetworkGraph parseString(String s) throws IOException { return parseReader(new StringReader(s)); } public NetworkGraph parseStream(InputStream s) throws IOException { return parseReader(new InputStreamReader(s)); } public NetworkGraph parseReader(Reader r) throws IOException { reader = new BufferedReader(r); readText.delete(0, readText.length()); return parseNetwork(); } private NetworkGraph parseNetwork() throws IOException { graph = new NetworkGraph(); scan(Seq(ZeroOrMore(WS))); scan("graph"); skipWhitespaces(); scan("["); skipWhitespaces(); scan(ZeroOrMore(new ElementParser("node"))); scan("]"); return graph; } interface Pattern { MatchResult matchNext(char c); void reset(); } enum MatchResult { FAILED, OK, MORE_EXPECTED, OK_LAST, FINISHED } static abstract class StatelessPattern implements Pattern { @Override public void reset() { } } public static final Pattern WS = new WS(); public static Pattern Opt(Pattern p) { return new OptPattern(p); } public static Pattern Seq(Pattern... p) { return new Seq(p); } public static Pattern ZeroOrMore(Pattern p) { return new ZeroOrMorePattern(p); } static class WS extends StatelessPattern { @Override public MatchResult matchNext(char c) { return Character.isWhitespace(c) ? MatchResult.OK_LAST : MatchResult.FAILED; } } static class CompositePattern implements Pattern { protected final Pattern pattern; public CompositePattern(Pattern p) { this.pattern = p; } @Override public MatchResult matchNext(char c) { return pattern.matchNext(c); } @Override public void reset() { pattern.reset(); } public Pattern getPattern() { return pattern; } } static class OptPattern extends CompositePattern { boolean tested = false; public OptPattern(Pattern p) { super(p); } @Override public MatchResult matchNext(char c) { if (tested) return MatchResult.FAILED; MatchResult r = pattern.matchNext(c); if (r != MatchResult.FAILED) return r; else return MatchResult.FINISHED; } @Override public void reset() { super.reset(); tested = false; } } static class Seq implements Pattern { private final Pattern[] patterns; private int curPattern; public Seq(Pattern... p) { this.patterns = p; } @Override public MatchResult matchNext(char c) { if (curPattern >= patterns.length) return MatchResult.FINISHED; Pattern p = patterns[curPattern]; MatchResult r = p.matchNext(c); switch (r) { case FINISHED: curPattern++; return matchNext(c); case OK_LAST: curPattern++; if (curPattern == patterns.length) { return MatchResult.OK_LAST; } return MatchResult.OK; default: return r; } } @Override public void reset() { curPattern = 0; for (Pattern p : patterns) { p.reset(); } } } static class ZeroOrMorePattern extends CompositePattern { public ZeroOrMorePattern(Pattern p) { super(p); } @Override public MatchResult matchNext(char c) { MatchResult r = pattern.matchNext(c); switch (r) { case FINISHED: case OK_LAST: pattern.reset(); return MatchResult.OK; case FAILED: return MatchResult.FINISHED; default: return r; } } } Pattern Str(String s) { return new StringPattern(s); } static class StringPattern implements Pattern { public final String value; private int pos; public StringPattern(String v) { value = v; } @Override public MatchResult matchNext(char c) { int oldPos = pos; pos++; if (oldPos >= value.length()) return MatchResult.FAILED; if (value.charAt(oldPos) == c) { if (oldPos == value.length() - 1) return MatchResult.OK_LAST; else return MatchResult.MORE_EXPECTED; } else { return MatchResult.FAILED; } } @Override public void reset() { pos = 0; } } private AttributeStore parseAttributes() { return new AttributeStore(); } static abstract class Parser extends CompositePattern { public Parser(Pattern p) { super(p); } @Override public MatchResult matchNext(char c) { MatchResult r = super.matchNext(c); switch (r) { case FINISHED: case OK_LAST: onMatch(); } return r; } public abstract void onMatch(); } class ElementParser extends Parser { private String name; public ElementParser(String name) { super(Seq(ZeroOrMore(WS), Str(name), ZeroOrMore(WS), Str("["), ZeroOrMore(WS), Str("]"))); this.name = name; } @Override public void onMatch() { //System.out.println(name+" found"); } } class ScanException extends RuntimeException { public ScanException(String string) { super(string); } } private boolean scanZeroOrMore(Pattern p) throws IOException { return scan(reader, new ZeroOrMorePattern(p)); } private boolean scanOpt(Pattern p) throws IOException { return scan(new OptPattern(p)); } private boolean scanOpt(String word) throws IOException { return scanOpt(new StringPattern(word)); } private boolean scan(String word) throws IOException { return scan(new StringPattern(word)); } private boolean scan(Pattern pattern) throws IOException { return scan(reader,pattern); } private boolean scan(Reader reader, Pattern pattern) throws IOException { MatchResult res = null; do { int i = readText.length(); reader.mark(Short.MAX_VALUE); int ic = reader.read(); if (ic == -1) { if (res == MatchResult.MORE_EXPECTED) throw new ScanException("End of stream reached before pattern could match"); else return true; } char c = (char) ic; readText.append(c); System.out.println(readText); res = pattern.matchNext(c); if (res == MatchResult.FAILED) throw new ScanException("Expected pattern "+pattern+" did not match"); if (res == MatchResult.FINISHED) { reader.reset(); readText.delete(i, readText.length()); return true; } } while (res != MatchResult.OK_LAST); return true; } private void skipWhitespaces() throws IOException { scanZeroOrMore(WS); } public static void main(String[] args) throws IOException { GMLParser p = new GMLParser(); System.out.println(p.parseString("graph[]")); System.out.println(p.parseString(" graph [ ] ")); System.out.println(p.parseString(" graph [ ] ")); System.out.println(p.parseString(" graph [ node [ ] ] ")); System.out.println(p.parseString(" graph [ node [ ] node[] ]")); } }