JSON deserialization for queries
diff --git a/src/main/java/de/ids_mannheim/korap/KorapQuery.java b/src/main/java/de/ids_mannheim/korap/KorapQuery.java
index 905199b..4044bfc 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapQuery.java
@@ -1,13 +1,16 @@
 package de.ids_mannheim.korap;
 
-import org.apache.lucene.search.spans.SpanQuery;
-
 import de.ids_mannheim.korap.query.wrap.*;
+import de.ids_mannheim.korap.util.QueryException;
+
+import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.util.automaton.RegExp;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.JsonNode;
 
 import java.util.*;
+import java.io.*;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -20,6 +23,7 @@
  */
 public class KorapQuery {
     private String field;
+    private ObjectMapper json;
 
     // Logger
     private final static Logger log = LoggerFactory.getLogger(KorapQuery.class);
@@ -30,48 +34,119 @@
      */
     public KorapQuery (String field) {
 	this.field = field;
+	this.json = new ObjectMapper();
     };
 
-    public SpanQueryWrapperInterface fromJSON (String json) {
-	// Todo:
-	return this.seg("s:test");
+    public SpanQueryWrapperInterface fromJSON (String jsonString) throws QueryException {
+	JsonNode json;
+	try {
+	    json = this.json.readValue(jsonString, JsonNode.class);
+	}
+	catch (IOException e) {
+	    throw new QueryException(e.getMessage());
+	};
+
+	if (!json.has("@type") && json.has("query"))
+	    json = json.get("query");
+
+	return this.fromJSON(json);
     };
 
     // http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/JsonNode.html
-    public SpanQueryWrapperInterface fromJSON (JsonNode json) {
+    // TODO: Exception messages are horrible!
+    public SpanQueryWrapperInterface fromJSON (JsonNode json) throws QueryException {
+
+	if (!json.has("@type")) {
+	    throw new QueryException("JSON-LD group has no @type attribute");
+	};
+
 	String type = json.get("@type").asText();
-	if (type.equals("korap:group")) {
+
+	switch (type) {
+
+	case "korap:group":
+	    SpanClassQueryWrapper classWrapper;
+
+	    if (!json.has("relation")) {
+		if (json.has("class")) {
+		    return new SpanClassQueryWrapper(
+			this.fromJSON(json.get("operands").get(0)),
+                        json.get("class").asInt(0)
+                    );
+		}
+		throw new QueryException("Group needs a relation or a class");
+	    };
+
 	    String relation = json.get("relation").asText();
 
+	    if (!json.has("operands"))
+		throw new QueryException("Operation needs operands");
+
 	    // Alternation
-	    if (relation.equals("or")) {
+	    switch (relation) {
+
+	    case "or":
+
 		SpanAlterQueryWrapper ssaq = new SpanAlterQueryWrapper(this.field);
 		for (JsonNode operand : json.get("operands")) {
 		    ssaq.or(this.fromJSON(operand));
 		};
+		if (json.has("class")) {
+		    return new SpanClassQueryWrapper(ssaq, json.get("class").asInt(0));
+		};
 		return ssaq;
-	    }
-	    else {
-		System.err.println("Unknown element");
+
+	    case "position":
+		if (!json.has("position"))
+		    throw new QueryException("Operation needs position specification");
+
+		// temporary
+		if (json.get("position").asText().equals("contains") || json.get("position").asText().equals("within")) {
+		    return new SpanWithinQueryWrapper(
+			this.fromJSON(json.get("operands").get(0)),
+			this.fromJSON(json.get("operands").get(1))
+                    );
+		};
+		throw new QueryException("Unknown position type "+json.get("position").asText());
+
+	    case "shrink":
+		int number = 0;
+		// temporary
+		if (json.has("shrink"))
+		    number = json.get("shrink").asInt();
+
+		return new SpanMatchModifyQueryWrapper(this.fromJSON(json.get("operands").get(0)), number);
 	    };
-	}
-	else if (type.equals("korap:token")) {
-	    SpanSegmentQueryWrapper ssqw = new SpanSegmentQueryWrapper(this.field);
+	    throw new QueryException("Unknown group relation");
+
+	case "korap:token":
 	    JsonNode value = json.get("@value");
+	    SpanSegmentQueryWrapper ssegqw = new SpanSegmentQueryWrapper(this.field);
 	    type = value.get("@type").asText();
 	    if (type.equals("korap:term")) {
-		if (value.get("relation").asText().equals("=")) {
-		    ssqw.with(value.get("@value").asText());
+		switch (value.get("relation").asText()) {
+		case "=":
+		    ssegqw.with(value.get("@value").asText());
+		    return ssegqw;
+		case "!=":
+		    throw new QueryException("Term relation != not yet supported");
 		};
-	    }
-	    else {
-		System.err.println("Unknown type");
+		throw new QueryException("Unknown term relation");
 	    };
+	    throw new QueryException("Unknown token type");
 
-	    return ssqw;
-	}
 
-	return this.seg("s:test");
+	case "korap:sequence":
+	    if (!json.has("operands"))
+		throw new QueryException("SpanSequenceQuery needs operands");
+
+	    SpanSequenceQueryWrapper sseqqw = new SpanSequenceQueryWrapper(this.field);
+	    for (JsonNode operand : json.get("operands")) {
+		sseqqw.append(this.fromJSON(operand));
+	    };
+	    return sseqqw;
+	};
+	throw new QueryException("Unknown serialized query type: " + type);
     };
 
 
@@ -236,8 +311,6 @@
 	return new SpanWithinQueryWrapper(element, embedded);
     };
 
-
-
     // Class
     public SpanClassQueryWrapper _ (byte number, SpanQueryWrapperInterface element) {
 	return new SpanClassQueryWrapper(element, number);
diff --git a/src/main/java/de/ids_mannheim/korap/KorapSearch.java b/src/main/java/de/ids_mannheim/korap/KorapSearch.java
index d5e0c51..d0be889 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapSearch.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapSearch.java
@@ -7,6 +7,7 @@
 import de.ids_mannheim.korap.KorapCollection;
 import de.ids_mannheim.korap.KorapIndex;
 import de.ids_mannheim.korap.KorapResult;
+import de.ids_mannheim.korap.util.QueryException;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -85,6 +86,8 @@
 	    this.query = new KorapQuery("tokens").fromJSON(rootNode.get("query")).toQuery();
 	}
 	catch (IOException e) {
+	}
+	catch (QueryException e) {
 	};
     };
 
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestKorapQueryJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestKorapQueryJSON.java
new file mode 100644
index 0000000..9917184
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/query/TestKorapQueryJSON.java
@@ -0,0 +1,131 @@
+import java.util.*;
+import java.io.*;
+
+import org.apache.lucene.search.spans.SpanQuery;
+import de.ids_mannheim.korap.query.wrap.SpanQueryWrapperInterface;
+
+import de.ids_mannheim.korap.KorapQuery;
+import de.ids_mannheim.korap.util.QueryException;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.Ignore;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TestKorapQueryJSON {
+
+    @Ignore
+    public void queryJSONBsp1 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp1.json").getFile());
+
+	// ([base=foo]|[base=bar])[base=foobar]
+	assertEquals(sqwi.toQuery().toString(), "");
+    };
+
+    @Test
+    public void queryJSONBsp1b () {
+
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp1b.json").getFile());
+
+	// [base=foo]|([base=foo][base=bar]) meta author=Goethe&year=1815
+	assertEquals(sqwi.toQuery().toString(), "spanOr([tokens:base:foo, spanNext(tokens:base:foo, tokens:base:bar)])");
+    };
+
+
+    @Test
+    public void queryJSONBsp2 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp2.json").getFile());
+
+	// ([base=foo]|[base=bar])[base=foobar]
+	assertEquals(sqwi.toQuery().toString(), "spanNext(spanOr([tokens:base:foo, tokens:base:bar]), tokens:base:foobar)");
+    };
+
+    @Test
+    public void queryJSONBsp3 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp3.json").getFile());
+
+	// shrink({[base=Mann]})
+	assertEquals(sqwi.toQuery().toString(), "shrink(0: {0: tokens:base:Mann})");
+    };
+
+    @Test
+    public void queryJSONBsp4 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp4.json").getFile());
+
+	// shrink({[base=foo]}[orth=bar])
+	assertEquals(sqwi.toQuery().toString(), "shrink(0: spanNext({0: tokens:base:foo}, tokens:orth:bar))");
+    };
+
+    @Test
+    public void queryJSONBsp5 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp5.json").getFile());
+
+	// shrink(1:[base=Der]{1:[base=Mann]}) 
+	assertEquals(sqwi.toQuery().toString(), "shrink(1: spanNext(tokens:base:Der, {1: tokens:base:Mann}))");
+    };
+
+    @Test
+    public void queryJSONBsp6 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp6.json").getFile());
+
+	// [base=katze]
+	assertEquals(sqwi.toQuery().toString(), "tokens:base:Katze");
+    };
+
+    @Ignore
+    public void queryJSONBsp7 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp7.json").getFile());
+
+	// [!base=Katze]
+	assertEquals(sqwi.toQuery().toString(), "");
+    };
+
+    @Ignore
+    public void queryJSONBsp8 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp8.json").getFile());
+
+	// [!base=Katze]
+	assertEquals(sqwi.toQuery().toString(), "");
+    };
+
+    /*
+    @Test
+    public void queryJSONBsp9 () {
+	SpanQueryWrapperInterface sqwi = jsonQuery(getClass().getResource("/queries/bsp9.json").getFile());
+
+	// [base=Katze&orth=Katzen]
+	assertEquals(sqwi.toQuery().toString(), "");
+    };
+    */
+
+    public static String getString (String path) {
+	StringBuilder contentBuilder = new StringBuilder();
+	try {
+	    BufferedReader in = new BufferedReader(new FileReader(path));
+	    String str;
+	    while ((str = in.readLine()) != null) {
+		contentBuilder.append(str);
+	    };
+	    in.close();
+	} catch (IOException e) {
+	    fail(e.getMessage());
+	}
+	return contentBuilder.toString();
+    };
+
+    public static SpanQueryWrapperInterface jsonQuery (String jsonFile) {
+	SpanQueryWrapperInterface sqwi;
+	
+	try {
+	    String json = getString(jsonFile);
+	    sqwi = new KorapQuery("tokens").fromJSON(json);
+	}
+	catch (QueryException e) {
+	    fail(e.getMessage());
+	    sqwi = new KorapQuery("tokens").seg("???");
+	};
+	return sqwi;
+    };
+};
\ No newline at end of file
diff --git a/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java b/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java
index 705bf33..0d62975 100644
--- a/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java
+++ b/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java
@@ -90,29 +90,4 @@
 	assertEquals(6, kr.totalResults());
 	assertEquals(kr.getMatch(0).getSnippetBrackets(), "... dem [Buchstaben] A ...");
     };
-
-    @Test
-    public void searchJSON () throws IOException {
-
-	String jsonFile = getClass().getResource("/queries/bsp1.json").getFile();
-
-	KorapSearch ks = new KorapSearch(getString(jsonFile));
-
-	// assertEquals(ks.getQuery().toString(), "");
-    };
-
-    public static String getString (String path) {
-	StringBuilder contentBuilder = new StringBuilder();
-	try {
-	    BufferedReader in = new BufferedReader(new FileReader(path));
-	    String str;
-	    while ((str = in.readLine()) != null) {
-		contentBuilder.append(str);
-	    };
-	    in.close();
-	} catch (IOException e) {
-	}
-	return contentBuilder.toString();
-    };
-
 };
diff --git a/src/test/resources/queries/readme.txt b/src/test/resources/queries/readme.txt
index 5cfd658..36745ce 100644
--- a/src/test/resources/queries/readme.txt
+++ b/src/test/resources/queries/readme.txt
@@ -1,4 +1,4 @@
-bsp1.json: [base=foo]|([base=foo][base=bar]) meta author=Goethe&year=1815
+bsp1.json: [base=foo]|([base=foo][base=bar])* meta author=Goethe&year=1815
 bsp2.json: ([base=foo]|[base=bar])[base=foobar]
 bsp3.json: shrink({[base=Mann]})
 bsp4.json: shrink({[base=foo]}[orth=bar])