Added optionality for quantified queries
diff --git a/CHANGES b/CHANGES
index 8e14cd9..0bc6f4c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+0.31.8 2014-07-23
+        - [feature] Added optionality to querys for quantifiers (diewald)
+
 0.31.7 2014-07-18
         - [feature] Added warnings to responses (diewald)
 
diff --git a/pom.xml b/pom.xml
index cc4ae02..45aa13f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
 -->
   <groupId>KorAP-modules</groupId>
   <artifactId>KorAP-lucene-index</artifactId>
-  <version>0.31.7</version>
+  <version>0.31.8</version>
   <packaging>jar</packaging>
 
   <name>KorAP-lucene-index</name>
diff --git a/src/main/java/de/ids_mannheim/korap/KorapFilter.java b/src/main/java/de/ids_mannheim/korap/KorapFilter.java
index c4541d6..d97dd6e 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapFilter.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapFilter.java
@@ -38,6 +38,41 @@
 pubDate
 pubPlace
 
+Query: (corpusID=BRZ13 | corpusID=WPD) & textClass=wissenschaft
+
+{
+    "@type": "korap:filter",
+    "filter": {
+        "@type": "korap:docGroup",
+        "relation": "relation:and",
+        "operands": [
+            {
+                "@type": "korap:docGroup",
+                "relation": "relation:or",
+                "operands": [
+                    {
+                        "@type": "korap:doc",
+                        "key": "corpusID",
+                        "value": "BRZ13",
+                        "match": "match:eq"
+                    },
+                    {
+                        "@type": "korap:doc",
+                        "key": "corpusID",
+                        "value": "WPD",
+                        "match": "match:eq"
+                    }
+                ]
+            },
+            {
+                "@type": "korap:doc",
+                "key": "textClass",
+                "value": "wissenschaft",
+                "match": "match:eq"
+            }
+        ]
+    }
+}
 */
 
 public class KorapFilter {
diff --git a/src/main/java/de/ids_mannheim/korap/KorapQuery.java b/src/main/java/de/ids_mannheim/korap/KorapQuery.java
index d05d4d2..e258f57 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapQuery.java
@@ -24,6 +24,13 @@
   negation terms (and negation subqueries), like
   [base=Der]([base=alte]|[base=junge])[base=Mann & p!=ADJA]![base=war | base=lag]
   Search for all documents containing "s:Der" and ("s:alte" or "s:junge") and "s:Mann"
+
+  TODO: korap:reference doesn't work as expected:
+  - Check with
+    - focus(2:{1:[orth=der]{3:{2:[orth=Baum]}}})
+    - focus(3:startswith(<s>,{3:[tt/p=ART]{1:{2:[tt/p=ADJA]{3,4}}[tt/p=NN]}}))
+    - focus(3:endswith(<s>,{3:[tt/p=ART]{1:{2:[tt/p=ADJA]{3,4}}[tt/p=NN]}}))
+
 */
 
 /**
@@ -279,8 +286,10 @@
 		    throw new QueryException("The maximum repetition value has to " +
 					     "be greater or equal to the minimum repetition value");
 
-		if (min == 0)
-		    throw new QueryException("Minimum value of zero is not supported yet");
+		if (min == 0) {
+		    throw new QueryException(
+		        "Zero minimum of repetition is not supported yet");
+		};
 
 		return new SpanRepetitionQueryWrapper(
 		    this.fromJSON(operands.get(0)), min, max
@@ -320,13 +329,13 @@
 
 	case "korap:token":
 	    if (!json.has("wrap"))
-		throw new QueryException("Tokens need a wrap attribute");
+		throw new QueryException("Empty Tokens are not supported yet");
 
 	    return this._segFromJSON(json.get("wrap"));
 
 	case "korap:span":
 	    if (!json.has("key"))
-		throw new QueryException("A span need at least a key definition");
+		throw new QueryException("A span needs at least a key definition");
 
 	    return this._termFromJSON(json);
 	};
@@ -722,5 +731,4 @@
 
 
     // split
-
 };
diff --git a/src/main/java/de/ids_mannheim/korap/KorapSearch.java b/src/main/java/de/ids_mannheim/korap/KorapSearch.java
index f1b637f..f0a1012 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapSearch.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapSearch.java
@@ -65,7 +65,7 @@
 	    };
 
 	    if (this.request.has("warning"))
-		this.warning = this.request.get("warning").asText();
+		this.addWarning(this.request.get("warning").asText());
 	    
 	    // "meta" virtual collections
 	    if (this.request.has("collections"))
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAlterQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAlterQueryWrapper.java
index fd7c65d..5d5c4dc 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAlterQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAlterQueryWrapper.java
@@ -17,12 +17,19 @@
     private SpanQuery query;
     private List<SpanQuery> alternatives;
     private boolean isNull = true;
+    private boolean isOptional = false;
 
     public SpanAlterQueryWrapper (String field) {
 	this.field = field;
 	this.alternatives = new ArrayList<>();
     };
 
+    public SpanAlterQueryWrapper (String field, SpanQuery query) {
+	this.field = field;
+	this.alternatives = new ArrayList<>();
+	this.alternatives.add(query);
+    };
+
     public SpanAlterQueryWrapper (String field, String ... terms) {
 	this.field = field;
 	this.alternatives = new ArrayList<>();
@@ -33,7 +40,11 @@
     };
 
     public SpanAlterQueryWrapper or (String term) {
-	this.alternatives.add(new SpanTermQuery(new Term(this.field, term)));
+	return this.or(new SpanTermQuery(new Term(this.field, term)));
+    };
+
+    public SpanAlterQueryWrapper or (SpanQuery query) {
+	this.alternatives.add(query);
 	this.isNull = false;
 	return this;
     };
@@ -41,6 +52,12 @@
     public SpanAlterQueryWrapper or (SpanQueryWrapperInterface term) {
 	if (term.isNull())
 	    return this;
+
+	// If one operand is optional, the whole group can be optional
+	// a | b* | c
+	if (term.isOptional())
+	    this.isOptional = true;
+
 	this.alternatives.add( term.toQuery() );
 	this.isNull = false;
 	return this;
@@ -75,7 +92,7 @@
     };
 
     public boolean isOptional () {
-	return false;
+	return this.isOptional;
     };
 
     public boolean isNull () {
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanClassQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanClassQueryWrapper.java
index 5a6c1a4..50671f6 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanClassQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanClassQueryWrapper.java
@@ -43,7 +43,7 @@
     };
 
     public boolean isOptional () {
-	return false;
+	return this.subquery.isOptional();
     };
 
     public boolean isNull () {
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanMatchModifyQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanMatchModifyQueryWrapper.java
index 85926e2..4476f76 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanMatchModifyQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanMatchModifyQueryWrapper.java
@@ -39,7 +39,7 @@
     };
 
     public boolean isOptional () {
-	return false;
+	return this.subquery.isOptional();
     };
 
     public boolean isNull () {
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRepetitionQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRepetitionQueryWrapper.java
index 68ffb5b..d4b8c54 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRepetitionQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRepetitionQueryWrapper.java
@@ -10,7 +10,7 @@
     private SpanQueryWrapperInterface subquery;
     private int min = 1;
     private int max = 1;
-    private boolean optional = false;
+    private boolean isOptional = false;
     private boolean isNull = false;
 
     public SpanRepetitionQueryWrapper (SpanQueryWrapperInterface subquery, int exact) {
@@ -18,6 +18,7 @@
 
 	if (exact < 1 || this.subquery.isNull()) {
 	    this.isNull = true;
+	    this.isOptional = true;
 	    return;
 	};
 	
@@ -34,7 +35,8 @@
 	};
 	
 	if (min == 0) {
-	    this.optional = true;
+	    this.isOptional = true;
+	    min = 1;
 	    if (max == 0)
 		this.isNull = true;
 	};
@@ -45,11 +47,13 @@
     public SpanQuery toQuery () {
 	if (this.isNull)
 	    return (SpanQuery) null;
+	if (this.min == 1 && this.max == 1)
+	    return this.subquery.toQuery();
 	return new SpanRepetitionQuery(this.subquery.toQuery(), this.min, this.max, true);
     };
 
     public boolean isOptional () {
-	return this.optional;
+	return this.isOptional;
     };
 
     public boolean isNull () {
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSequenceQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSequenceQueryWrapper.java
index e3b755d..f758a8b 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSequenceQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSequenceQueryWrapper.java
@@ -8,6 +8,7 @@
 import de.ids_mannheim.korap.query.SpanMultipleDistanceQuery;
 
 import de.ids_mannheim.korap.query.wrap.SpanSegmentQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanAlterQueryWrapper;
 import de.ids_mannheim.korap.query.wrap.SpanRegexQueryWrapper;
 import de.ids_mannheim.korap.query.wrap.SpanWildcardQueryWrapper;
 
@@ -23,8 +24,12 @@
     private String field;
     private ArrayList<SpanQuery> segments;
     private ArrayList<DistanceConstraint> constraints;
-    private boolean isInOrder = true;
-    private boolean isNull = true;
+    private boolean
+	isInOrder = true,
+	isNull = true,
+	isOptional = true,
+	lastIsOptional = false,
+	firstIsOptional = false;
 
     public SpanSequenceQueryWrapper (String field) {
 	this.field = field;
@@ -35,6 +40,7 @@
 	this(field);
 	for (int i = 0; i < terms.length; i++) {
 	    this.segments.add((SpanQuery) new SpanTermQuery(new Term(field, terms[i])));
+	    this.isOptional = false;
 	};
 	this.isNull = false;
     };
@@ -42,6 +48,7 @@
     public SpanSequenceQueryWrapper (String field, SpanQuery sq) {
 	this(field);
 	this.segments.add((SpanQuery) sq);
+	this.isOptional = false;
 	this.isNull = false;
     };
 
@@ -50,6 +57,14 @@
 	if (!sswq.isNull()) {
 	    this.segments.add((SpanQuery) sswq.toQuery());
 	    this.isNull = false;
+	    if (sswq.isOptional()) {
+		this.isOptional = true;
+		this.lastIsOptional = true;
+		this.firstIsOptional = true;
+	    }
+	    else {
+		this.isOptional = false;
+	    };
 	};
     };
 
@@ -58,6 +73,7 @@
 	if (!re.isNull()) {
 	    this.segments.add((SpanQuery) re.toQuery());
 	    this.isNull = false;
+	    this.isOptional = false;
 	};
     };
 
@@ -65,6 +81,7 @@
 	this(field);
 	if (!wc.isNull()) {
 	    this.segments.add((SpanQuery) wc.toQuery());
+	    this.isOptional = false;
 	    this.isNull = false;
 	};
     };
@@ -78,14 +95,74 @@
     };
 
     public SpanSequenceQueryWrapper append (String term) {
-	this.segments.add((SpanQuery) new SpanTermQuery(new Term(field, term)));
+	return this.append((SpanQuery) new SpanTermQuery(new Term(field, term)));
+    };
+    
+    public SpanSequenceQueryWrapper append (SpanQuery query) {
 	this.isNull = false;
+	this.isOptional = false;
+
+	// Check if there has to be alternation magic in action
+	if (this.lastIsOptional) {
+	    SpanAlterQueryWrapper saqw = new SpanAlterQueryWrapper(field, query);
+	    SpanSequenceQueryWrapper ssqw = new SpanSequenceQueryWrapper(field, query);
+	    // Remove last element of the list and prepend it
+	    ssqw.prepend(this.segments.remove(this.segments.size() - 1));
+	    saqw.or(ssqw);
+
+	    // Update boundary optionality
+	    if (this.firstIsOptional && this.segments.size() == 0)
+		this.firstIsOptional = false;
+	    this.lastIsOptional = false;
+
+	    this.segments.add((SpanQuery) saqw.toQuery());
+	}
+	else {
+	    this.segments.add(query);
+	};
+	
 	return this;
     };
 
     public SpanSequenceQueryWrapper append (SpanQueryWrapperInterface ssq) {
 	if (!ssq.isNull()) {
-	    this.segments.add((SpanQuery) ssq.toQuery());
+	    SpanQuery appendQuery = ssq.toQuery();
+	    if (!ssq.isOptional()) {
+		return this.append((SpanQuery) ssq.toQuery());
+	    };
+	    
+	    // Situation is a?b?
+	    if (this.lastIsOptional) {
+		// Remove last element of the list and prepend it
+		SpanQuery lastQuery = this.segments.remove(this.segments.size() - 1);
+		SpanAlterQueryWrapper saqw = new SpanAlterQueryWrapper(field, lastQuery);
+		SpanSequenceQueryWrapper ssqw = new SpanSequenceQueryWrapper(field, appendQuery);
+		ssqw.prepend(lastQuery);
+		saqw.or(appendQuery);
+		saqw.or(ssqw);
+		this.segments.add((SpanQuery) saqw.toQuery());
+	    }
+	    
+	    // Situation is ab?
+	    else if (this.segments.size() != 0) {
+		// Remove last element of the list and prepend it
+		SpanQuery lastQuery = this.segments.remove(this.segments.size() - 1);
+		SpanAlterQueryWrapper saqw = new SpanAlterQueryWrapper(field, lastQuery);
+		SpanSequenceQueryWrapper ssqw = new SpanSequenceQueryWrapper(field, appendQuery);
+		ssqw.prepend(lastQuery);
+		saqw.or(ssqw);
+		this.segments.add((SpanQuery) saqw.toQuery());
+	    }
+	    
+	    // Situation is b?
+	    else {
+		this.segments.add(appendQuery);
+
+		// Update boundary optionality
+		this.firstIsOptional = true;
+		this.isOptional = true;
+		this.lastIsOptional = true;
+	    };
 	    this.isNull = false;
 	};
 	return this;
@@ -93,46 +170,64 @@
 
     public SpanSequenceQueryWrapper append (SpanRegexQueryWrapper srqw) {
 	if (!srqw.isNull()) {
-	    this.segments.add((SpanQuery) srqw.toQuery());
-	    this.isNull = false;
+	    return this.append((SpanQuery) srqw.toQuery());
 	};
 	return this;
     };
     
     public SpanSequenceQueryWrapper append (SpanWildcardQueryWrapper swqw) {
 	if (!swqw.isNull()) {
-	    this.segments.add((SpanQuery) swqw.toQuery());
-	    this.isNull = false;
+	    return this.append((SpanQuery) swqw.toQuery());
 	};
 	return this;
     };
 
     public SpanSequenceQueryWrapper prepend (String term) {
-	this.segments.add(0, (SpanQuery) new SpanTermQuery(new Term(field, term)));
+	return this.prepend(new SpanTermQuery(new Term(field, term)));
+    };
+
+    public SpanSequenceQueryWrapper prepend (SpanQuery query) {
 	this.isNull = false;
+	this.isOptional = false;
+	
+	// Check if there has to be alternation magic in action
+	if (this.firstIsOptional) {
+	    SpanAlterQueryWrapper saqw = new SpanAlterQueryWrapper(field, query);
+	    SpanSequenceQueryWrapper ssqw = new SpanSequenceQueryWrapper(field, query);
+	    // Remove last element of the list and prepend it
+	    ssqw.append(this.segments.remove(0));
+	    saqw.or(ssqw);
+
+	    // Update boundary optionality
+	    if (this.lastIsOptional && this.segments.size() == 0)
+		this.lastIsOptional = false;
+	    this.firstIsOptional = false;
+
+	    this.segments.add(0, (SpanQuery) saqw.toQuery());
+	}
+	else {
+	    this.segments.add(0,query);
+	};
 	return this;
     };
 
     public SpanSequenceQueryWrapper prepend (SpanSegmentQueryWrapper ssq) {
 	if (!ssq.isNull()) {
-	    this.segments.add(0, (SpanQuery) ssq.toQuery());
-	    this.isNull = false;
+	    return this.prepend(ssq.toQuery());
 	};
 	return this;
     };
 
     public SpanSequenceQueryWrapper prepend (SpanRegexQueryWrapper re) {
 	if (!re.isNull()) {
-	    this.segments.add(0, (SpanQuery) re.toQuery());
-	    this.isNull = false;
+	    return this.prepend(re.toQuery());
 	};
 	return this;
     };
 
     public SpanSequenceQueryWrapper prepend (SpanWildcardQueryWrapper swqw) {
 	if (!swqw.isNull()) {
-	    this.segments.add(0, (SpanQuery) swqw.toQuery());
-	    this.isNull = false;
+	    return this.prepend(swqw.toQuery());
 	};
 	return this;
     };
@@ -254,7 +349,7 @@
     };
 
     public boolean isOptional () {
-	return false;
+	return this.isOptional;
     };
 
     public boolean isNull () {
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithinQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithinQueryWrapper.java
index fe6f9c2..68b07fd 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithinQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithinQueryWrapper.java
@@ -10,13 +10,19 @@
 
 import org.apache.lucene.search.spans.SpanQuery;
 
+/*
+  Document: Optionality of operands will be ignored - while the optionality of the wrap is herited!
+
+  Idea:
+  - Maybe inherit the optionality when it is in an element and rewrite the query to an alternation if the wrap is
+*/
 
 
 public class SpanWithinQueryWrapper implements SpanQueryWrapperInterface {
     private SpanQueryWrapperInterface element;
     private SpanQueryWrapperInterface wrap;
     private byte flag;
-    private boolean isNull = true;;
+    private boolean isNull = true;
 
     public SpanWithinQueryWrapper (SpanQueryWrapperInterface element, SpanQueryWrapperInterface wrap) {
 	this.element = element;
@@ -30,6 +36,7 @@
 	this.element = element;
 	this.wrap = wrap;
 	this.flag = flag;
+
 	if (!element.isNull() && !wrap.isNull())
 	    this.isNull = false;
     };