Query optimization for subspanquery with an empty or negative subquery.
diff --git a/src/main/java/de/ids_mannheim/korap/KorapQuery.java b/src/main/java/de/ids_mannheim/korap/KorapQuery.java
index 78d050b..71f9338 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapQuery.java
@@ -10,7 +10,20 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import de.ids_mannheim.korap.query.SpanWithinQuery;
-import de.ids_mannheim.korap.query.wrap.*;
+import de.ids_mannheim.korap.query.wrap.SpanAlterQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanAttributeQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanClassQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanElementQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanMatchModifyQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanRegexQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanRepetitionQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSegmentQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSequenceQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSimpleQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSubspanQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanWildcardQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanWithinQueryWrapper;
 import de.ids_mannheim.korap.response.Notifications;
 import de.ids_mannheim.korap.util.QueryException;
 
@@ -247,9 +260,10 @@
 
                 if (DEBUG) log.trace("Wrap span reference {},{}", startOffset, length);
 
-                return new SpanSubspanQueryWrapper(
-                    this.fromJson(operands.get(0)), startOffset, length
-                );
+                SpanQueryWrapper sqw = this.fromJson(operands.get(0));
+				SpanSubspanQueryWrapper ssqw = new SpanSubspanQueryWrapper(
+						sqw, startOffset, length);
+				return ssqw;
             };
 
             if (DEBUG) log.trace("Wrap class reference {}", number);
@@ -593,8 +607,8 @@
             SpanQueryWrapper sqw = this.fromJson(operands.get(0));
 
             // Problematic
-            if (sqw.maybeExtension())
-                return sqw.setClassNumber(number);
+			// if (sqw.maybeExtension())
+			// return sqw.setClassNumber(number);
 
             return new SpanClassQueryWrapper(sqw, number);
         };
diff --git a/src/main/java/de/ids_mannheim/korap/query/SpanSubspanQuery.java b/src/main/java/de/ids_mannheim/korap/query/SpanSubspanQuery.java
index ecb1bd9..d16042e 100644
--- a/src/main/java/de/ids_mannheim/korap/query/SpanSubspanQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/query/SpanSubspanQuery.java
@@ -75,9 +75,9 @@
         StringBuilder sb = new StringBuilder();
         sb.append("subspan(");
         sb.append(this.firstClause.toString());
-        sb.append(",");
+		sb.append(", ");
         sb.append(this.startOffset);
-        sb.append(",");
+		sb.append(", ");
         sb.append(this.length);
         sb.append(")");
         return sb.toString();
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 2f59079..fd1ed56 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
@@ -3,11 +3,8 @@
 import org.apache.lucene.search.spans.SpanQuery;
 
 import de.ids_mannheim.korap.query.SpanClassQuery;
-import de.ids_mannheim.korap.query.wrap.SpanQueryWrapper;
 import de.ids_mannheim.korap.util.QueryException;
 
-import java.util.*;
-
 
 // TODO: If this.subquery.isNegative(), it may be an Expansion!
 // SpanExpansionQuery(x, y.negative, min, max. direction???, classNumber, true)
@@ -20,6 +17,8 @@
         this.number   = number;
         if (number != (byte) 0)
             this.hasClass = true;
+		this.min = subquery.min;
+        this.max=subquery.max;
     };
 
     public SpanClassQueryWrapper (SpanQueryWrapper subquery, short number) {
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanQueryWrapper.java
index 0c50ae8..06f71aa 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanQueryWrapper.java
@@ -1,6 +1,7 @@
 package de.ids_mannheim.korap.query.wrap;
 
 import org.apache.lucene.search.spans.SpanQuery;
+
 import de.ids_mannheim.korap.util.QueryException;
 
 // TODO: Add warnings and errors - using KorapQuery
@@ -187,7 +188,7 @@
     public boolean maybeAnchor () {
         if (this.isNegative()) return false;
         if (this.isOptional()) return false;
-        if (this.isEmpty())    return false;
+		if (this.isEmpty()) { return false;}		
         return true;
     };
 
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 b13d6bc..d7f03f5 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
@@ -1,17 +1,21 @@
 package de.ids_mannheim.korap.query.wrap;
 
-import java.util.*;
-import de.ids_mannheim.korap.query.*;
-import de.ids_mannheim.korap.query.wrap.*;
-
-import de.ids_mannheim.korap.util.QueryException;
+import java.util.ArrayList;
 
 import org.apache.lucene.index.Term;
-import org.apache.lucene.search.spans.*;
-
+import org.apache.lucene.search.spans.SpanQuery;
+import org.apache.lucene.search.spans.SpanTermQuery;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.ids_mannheim.korap.query.DistanceConstraint;
+import de.ids_mannheim.korap.query.SpanDistanceQuery;
+import de.ids_mannheim.korap.query.SpanElementQuery;
+import de.ids_mannheim.korap.query.SpanExpansionQuery;
+import de.ids_mannheim.korap.query.SpanMultipleDistanceQuery;
+import de.ids_mannheim.korap.query.SpanNextQuery;
+import de.ids_mannheim.korap.util.QueryException;
+
 /*
   TODO: Make isNegative work!
   TODO: Make isEmpty work!
@@ -180,11 +184,16 @@
      * @param query A new {@link SpanQueryWrapper} to search for.
      * @return The {@link SpanSequenceQueryWrapper} object for chaining.
      */
-    public SpanSequenceQueryWrapper append (SpanQueryWrapper ssq) {
+	public SpanSequenceQueryWrapper append(SpanQueryWrapper ssq) {
 
         // The wrapper is null - ignore this in the sequence
         if (ssq.isNull())
             return this;
+		if (ssq.isEmpty) {
+			this.isEmpty = true;
+			this.min = ssq.min;
+			this.max = ssq.max;
+		}
 
         // As the spanQueryWrapper is not null,
         // the sequence can't be null as well
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSubspanQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSubspanQueryWrapper.java
index 25cf0d6..f4b9f39 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSubspanQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanSubspanQueryWrapper.java
@@ -9,122 +9,128 @@
 import de.ids_mannheim.korap.util.QueryException;
 
 /**
+ * Automatically handle the length parameter if it is less than 0, then no
+ * SpanSubspanQuery is created, but a SpanQuery for the subquery ?
+ * 
  * @author margaretha, diewald
  * 
  */
 public class SpanSubspanQueryWrapper extends SpanQueryWrapper {
 
-    private SpanQueryWrapper subquery;
-    private int startOffset, length;
+	private SpanQueryWrapper subquery;
+	private int startOffset, length;
 
-    private final static Logger log =
-        LoggerFactory.getLogger(SpanSubspanQueryWrapper.class);
+	private final static Logger log = LoggerFactory
+			.getLogger(SpanSubspanQueryWrapper.class);
 
-    // This advices the java compiler to ignore all loggings
-    public static final boolean DEBUG = false;
+	// This advices the java compiler to ignore all loggings
+	public static final boolean DEBUG = false;
 
-    public SpanSubspanQueryWrapper(SpanQueryWrapper sqw,
-                                   int startOffset,
-                                   int length) {
-        this.subquery = sqw;
-        if (sqw == null) {
-            this.isNull = true;
-            return;
-        }
-        else {
-            this.isNull = false;
-        };
+	public SpanSubspanQueryWrapper(SpanQueryWrapper sqw, int startOffset,
+			int length) throws QueryException {
+		if (length < 0) {
+			throw new QueryException(
+					"SpanSubspanQuery cannot have length less than 0.");
+		}
 
-        this.startOffset = startOffset;
-        this.length = length;
+		this.subquery = sqw;
+		if (subquery != null) {
+			this.isNull = false;
+		} else
+			return;
 
-        // The embedded class is empty,
-        // but probably in a valid range
-        // - optimize
-        // subspan([]{,5}, 2) -> subspan([]{2,5}, 2)
-        // subspan([]{2,}, 2,5) -> subspan([]{2,5}, 2,5)
-        if (subquery.isEmpty()) {
+		this.startOffset = startOffset;
+		this.length = length;
 
-            // Todo: Is there a possible way to deal with that?
-            if (startOffset < 0) {
-                this.isNull = true;
-                return;
-            };
+		if (subquery.isEmpty()) {
+			handleEmptySubquery();
+		} else if (subquery.isNegative) {
+			handleNegativeSubquery();
+		}
+	}
 
-            // e.g, subspan([]{0,6}, 8)
-            if (subquery.getMax() < startOffset) {
-                this.isNull = true;
-                return;
-            };
+	private void handleNegativeSubquery() {
+		this.isNegative = true;
+		if (startOffset < 0) {
+			int max = Math.abs(startOffset) + length;
+			subquery.setMax(max);
+			startOffset = max + startOffset;
+		} else {
+			subquery.setMax(startOffset + length);
+		}
+		subquery.setMin(startOffset);
+		subquery.isOptional = false;
 
-            // Readjust the minimum of the subquery
-            if (startOffset > 0) {
-                subquery.setMin(startOffset);
-                subquery.isOptional = false;
-            };
+		setMax(subquery.max);
+		setMin(subquery.min);
+	}
 
-            // Readjust the maximum,
-            // although the following case may be somehow disputable:
-            // subspan([]{2,8}, 2, 1) -> subspan([]{2,5},2,1)
-            if (length > 0) {
-                int newMax = subquery.getMin() + startOffset + length;
-                if (subquery.getMax() > newMax) {
-                    subquery.setMax(subquery.getMin() + length);
-                };
-            };
-        };
+	private void handleEmptySubquery() {
+		if (subquery instanceof SpanRepetitionQueryWrapper) {
+			this.isEmpty = true;
+		}
+		// subspan([]{,5}, 2) -> subspan([]{2,5}, 2)
+		// e.g. subspan([]{0,6}, 8)
+		if (startOffset >= subquery.getMax()) {
+			this.isNull = true;
+			return;
+		}
+		if (startOffset < 0) {
+			startOffset = subquery.getMax() + startOffset;
+		}
+		subquery.isOptional = false;
+		subquery.setMin(startOffset);
 
-        // Todo: What happens with negative queries?
-        // submatch([base!=tree],3)
-    }
+		// subspan([]{2,}, 2,5) -> subspan([]{2,5}, 2,5)
+		int endOffset = startOffset + length;
+		if (length == 0) {
+			length = subquery.getMax() - startOffset;
+		}
+		else if (subquery.getMax() > endOffset || subquery.getMax() == 0) {
+			subquery.setMax(endOffset);
+		}
+		else if (subquery.getMax() < endOffset) {
+			length = subquery.max - subquery.min;
+		}
 
-    @Override
-    public SpanQuery toQuery() throws QueryException {
+		// System.out.println(subquery.min + "," + subquery.max);
+		setMax(subquery.max);
+		setMin(subquery.min);
+	}
 
-        if (this.isNull() || subquery.isNull()) {
-            if (DEBUG)
-                log.warn("Subquery of SpanSubspanquery is null.");
-            return null;
-        };
+	@Override
+	public SpanQuery toQuery() throws QueryException {
 
-        if (startOffset == 0 && length == 0) {
-            if (DEBUG)
-                log.warn("Not SpanSubspanQuery. Creating only the subquery.");
-            return subquery.toQuery();
-        };
+		if (this.isNull()) {
+			// if (DEBUG) log.warn("Subquery of SpanSubspanquery is null.");
+			return null;
+		}
 
-        // The embedded subquery may be null
-        SpanQuery sq = subquery.toQuery();
-        if (sq == null) return null;
-        
-        if (sq instanceof SpanTermQuery) {
+		SpanQuery sq = subquery.toQuery();
+		if (sq == null)
+			return null;
+		if (sq instanceof SpanTermQuery) {
+			if ((startOffset == 0 || startOffset == -1) && 
+					(length == 1 || length == 0)) {
+				// if (DEBUG) log.warn("Not SpanSubspanQuery. " +
+				// "Creating only a SpanQuery for the subquery.");
+				return sq;
+			}
+			return null;
+		}
 
-            // No relevant subspan
-            if ((startOffset == 0 || startOffset == -1) &&
-                (length <= 1)) {
-                if (DEBUG)
-                    log.warn("Not SpanSubspanQuery. " +
-                             "Creating only the subquery.");
-                return sq;
-            };
+		return new SpanSubspanQuery(sq, startOffset, length, true);
+	}
 
-            // Subspanquery can't match (always out of scope)
-            return null;
-        }
+	@Override
+	public boolean isNegative() {
+		return this.subquery.isNegative();
+	};
 
-        return new SpanSubspanQuery(sq, startOffset, length,
-                true);
-    }
-
-    @Override
-    public boolean isNegative () {
-        return this.subquery.isNegative();
-    };
-
-    @Override
-    public boolean isOptional () {
-        if (startOffset > 0)
-            return false;
-        return this.subquery.isOptional();
-    };
+	@Override
+	public boolean isOptional() {
+		if (startOffset > 0)
+			return false;
+		return this.subquery.isOptional();
+	};
 }
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestSpanSequenceQueryJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestSpanSequenceQueryJSON.java
index fd3f239..e1378fe 100644
--- a/src/test/java/de/ids_mannheim/korap/query/TestSpanSequenceQueryJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanSequenceQueryJSON.java
@@ -1,19 +1,18 @@
 package de.ids_mannheim.korap.query;
 
-import java.util.*;
-import java.io.*;
+import static de.ids_mannheim.korap.TestSimple.getJSONQuery;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import static de.ids_mannheim.korap.TestSimple.*;
+import org.apache.lucene.search.spans.SpanQuery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import de.ids_mannheim.korap.query.wrap.SpanQueryWrapper;
 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;
-
 /**
  * @author diewald
  */
@@ -192,7 +191,8 @@
     public void queryJSONseqNegativeEndClass () throws QueryException {
 	SpanQueryWrapper sqwi = jsonQueryFile("negative-last-class.jsonld");
 	// [tt/p=NN]{2:[tt/p!=NN]}
-	assertEquals(sqwi.toQuery().toString(), "spanExpansion(tokens:tt/p:NN, !tokens:tt/p:NN{1, 1}, right, class:2)");
+	SpanQuery sq = sqwi.toQuery();
+	assertEquals(sq.toString(), "spanExpansion(tokens:tt/p:NN, !tokens:tt/p:NN{1, 1}, right, class:2)");
     };
 
     @Test
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestSpanSubspanQueryJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestSpanSubspanQueryJSON.java
index 1fd3093..444ec79 100644
--- a/src/test/java/de/ids_mannheim/korap/query/TestSpanSubspanQueryJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanSubspanQueryJSON.java
@@ -13,71 +13,206 @@
  * @author margaretha, diewald
  */
 public class TestSpanSubspanQueryJSON {
+	
+	@Test
+	public void testTermQuery() throws QueryException {
+		// subspan(tokens:tt/l:Haus, 0, 1)
+		String filepath = getClass().getResource(
+				"/queries/submatch/termquery.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("tokens:tt/l:Haus", sq.toString());
+	}
 
-    @Test
-    public void testCase1() throws QueryException {
-        String filepath = getClass().getResource("/queries/submatch/1.jsonld")
-                .getFile();
-        SpanQueryWrapper sqwi = getJSONQuery(filepath);
-        SpanQuery sq = sqwi.toQuery();
-        assertEquals(sq.toString(),
-                "subspan(spanContain(<tokens:s />, tokens:tt/l:Haus),1,4)");
-    }
+	@Test
+	public void testTermStartOffset() throws QueryException {
+		// subspan(tokens:tt/l:Haus, -1, 0)
+		String filepath = getClass().getResource(
+				"/queries/submatch/term-start-offset.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("tokens:tt/l:Haus", sq.toString());
+	}
 
-    @Test
-    public void testCase2() throws QueryException {
-        String filepath = getClass().getResource("/queries/submatch/2.jsonld")
-            .getFile();
-        SpanQueryWrapper sqwi = getJSONQuery(filepath);
-        SpanQuery sq = sqwi.toQuery();
-        assertEquals(sq.toString(), "subspan(<tokens:s />,1,4)");
-    }
+	@Test
+	public void testTermNull() throws QueryException {
+		// subspan(tokens:tt/l:Haus, 1, 1)
+		String filepath = getClass().getResource(
+				"/queries/submatch/term-null.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(null, sq);
+	}
 
-    @Test
-    public void testCase3() throws QueryException {
-        String filepath = getClass().getResource("/queries/submatch/3.jsonld")
-                .getFile();
-        SpanQueryWrapper sqwi = getJSONQuery(filepath);
-        SpanQuery sq = sqwi.toQuery();
-        assertEquals(sq.toString(), "subspan(<tokens:s />,1,0)");
-    }
+	@Test
+	public void testElementQuery() throws QueryException {
+		// submatch(1,4:<s>)
+		String filepath = getClass().getResource(
+				"/queries/submatch/simpleElement.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(
+				"subspan(spanContain(<tokens:s />, tokens:tt/l:Haus), 1, 4)",
+				sq.toString());
+	}
 
-    @Test
-    public void testCaseWrapped() throws QueryException {
-        String filepath = getClass().getResource("/queries/submatch/wrapped.jsonld")
-                .getFile();
-        SpanQueryWrapper sqwi = getJSONQuery(filepath);
-        SpanQuery sq = sqwi.toQuery();
-        assertEquals(sq.toString(), "focus(129: spanElementDistance({129: tokens:s:der},"+
-                     " {129: subspan(<tokens:s />,0,1)}, [(s[0:0], ordered, notExcluded)]))");
-    }
+	@Test
+	public void testNoLength() throws QueryException {
+		// submatch(1,:<s>)
+		String filepath = getClass().getResource(
+				"/queries/submatch/noLength.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("subspan(<tokens:s />, 1, 0)", sq.toString());
+	}
+	
+	@Test
+	public void testMinusStartOffset() throws QueryException {
+		// submatch(-1,4:<s>)
+		String filepath = getClass().getResource("/queries/submatch/minusStart.jsonld")
+				.getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("subspan(<tokens:s />, -1, 4)", sq.toString());
+	}
+
+	@Test
+	public void testEmptyMinusStartOffset() throws QueryException {
+		// subspan(spanExpansion(tokens:s:der, []{1, 8}, right),-1,4)
+		// need a spanExpansionQueryWrapper to adjust min, max
+		String filepath = getClass().getResource(
+				"/queries/submatch/empty-minusStart.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		// subspan(spanExpansion(tokens:s:der, []{7, 8}, right),7,1)
+		assertEquals(
+				"subspan(spanExpansion(tokens:s:der, []{1, 8}, right), 7, 1)",
+				sq.toString());
+	}
+
+	@Test
+	public void testEmptyMax() throws QueryException {
+		// need a spanExpansionQueryWrapper to adjust min, max
+		String filepath = getClass().getResource(
+				"/queries/submatch/empty-max.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(3, sqwi.getMax());
+		// subspan(spanExpansion(tokens:s:der, []{1, 3}, right),1,2)
+		assertEquals(
+				"subspan(spanExpansion(tokens:s:der, []{1, 8}, right), 1, 2)",
+				sq.toString());
+	}
+
+	@Test
+	public void testCaseEmptyWrapped() throws QueryException {
+		String filepath = getClass().getResource(
+				"/queries/submatch/wrapped.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(
+				"focus(129: spanElementDistance({129: tokens:s:der}, {129: subspan"
+						+ "(<tokens:s />, 0, 1)}, [(s[0:0], ordered, notExcluded)]))",
+				sq.toString());
+	}
+
+	@Test
+	public void testCaseEmptyEmbedded() throws QueryException {
+		// die subspan(der []{1,}, 2,3)
+		String filepath = getClass().getResource(
+				"/queries/submatch/embedded.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("spanNext({1: tokens:s:die}, {1: subspan(spanExpansion("
+				+ "tokens:s:der, []{1, 100}, right), 2, 3)})", sq.toString());
+	}
+
+	@Test
+	public void testCaseEmptyEmbeddedNull() throws QueryException {
+		// die subspan([],5,6)
+		// start offset is bigger than the original span
+		String filepath = getClass().getResource(
+				"/queries/submatch/embedded-null.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("tokens:s:die", sq.toString());
+	}
+
+	@Test
+	public void testCaseEmptyEmbeddedValid() throws QueryException {
+		// die subspan([]{0,5},2)
+		String filepath = getClass().getResource(
+				"/queries/submatch/embedded-valid-empty.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("spanExpansion(tokens:s:die, []{2, 5}, right)",
+				sq.toString());
+	}
+
+	@Test
+	public void testNegativeEmbeddedSequence() throws QueryException {
+		// submatch(1,1:das [base != Baum])
+		String filepath = getClass().getResource(
+				"/queries/submatch/embedded-negative-seq.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(
+				"subspan(spanExpansion(tokens:s:das, !tokens:l:Baum{1, 1}, right), 1, 1)",
+				sq.toString());
+	}
+
+	@Test
+	public void testNegativeSequence() throws QueryException {
+		// das submatch(0,1:[base != Baum])
+		String filepath = getClass().getResource(
+				"/queries/submatch/negative-seq.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(
+				"spanExpansion(tokens:s:das, !tokens:l:Baum{1, 1}, right)",
+				sq.toString());
+	}
+
+	@Test
+	public void testNegativeToken() throws QueryException {
+		// submatch(0,1:[base != Baum])
+		String filepath = getClass().getResource(
+				"/queries/submatch/negative-token.jsonld").getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals("tokens:l:Baum", sq.toString());
+	}
+
+	@Test
+	public void testEmbeddedNegativeRepetition() throws QueryException {
+		// submatch(1,1:das [base != Baum]{1,3})
+		// need a spanExpansionQueryWrapper to handle a null notquery and
+		// a repetition of a negative query
+		String filepath = getClass().getResource(
+				"/queries/submatch/embedded-negative-repetition.jsonld")
+				.getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(
+				"subspan(spanExpansion(tokens:s:das, !tokens:l:Baum{1, 3}, right), 1, 1)",
+				sq.toString());
+	}
+
+	@Test
+	public void testNegativeRepetition() throws QueryException {
+		// das submatch(1,1:[base != Baum]{1,3})
+		// need a spanExpansionQueryWrapper to handle a null notquery and
+		// a repetition of a negative query
+		String filepath = getClass().getResource(
+				"/queries/submatch/negative-repetition.jsonld")
+				.getFile();
+		SpanQueryWrapper sqwi = getJSONQuery(filepath);
+		SpanQuery sq = sqwi.toQuery();
+		assertEquals(
+				"spanExpansion(tokens:s:das, !tokens:l:Baum{1, 1}, right)",
+				sq.toString());
+	}
 
 
-    @Test
-    public void testCaseEmbedded() throws QueryException {
-        String filepath = getClass().getResource("/queries/submatch/embedded.jsonld")
-                .getFile();
-        SpanQueryWrapper sqwi = getJSONQuery(filepath);
-        SpanQuery sq = sqwi.toQuery();
-        assertEquals(sq.toString(), "spanNext({1: tokens:s:die},"+
-                     " {1: subspan(spanExpansion(tokens:s:der, []{1, 100}, right),2,3)})");
-    }
 
-    @Test
-    public void testCaseEmbeddedNull() throws QueryException {
-        String filepath = getClass().getResource("/queries/submatch/embedded-null.jsonld")
-                .getFile();
-        SpanQueryWrapper sqwi = getJSONQuery(filepath);
-        SpanQuery sq = sqwi.toQuery();
-        assertEquals(sq.toString(), "tokens:s:die");
-    }
-
-    @Test
-    public void testCaseEmbeddedValidEmpty() throws QueryException {
-        String filepath = getClass().getResource("/queries/submatch/embedded-valid-empty.jsonld")
-                .getFile();
-        SpanQueryWrapper sqwi = getJSONQuery(filepath);
-        SpanQuery sq = sqwi.toQuery();
-        assertEquals(sq.toString(), "??? (Known issue)");
-    }
 }
diff --git a/src/test/resources/queries/submatch/embedded-negative-repetition.jsonld b/src/test/resources/queries/submatch/embedded-negative-repetition.jsonld
new file mode 100644
index 0000000..29f676d
--- /dev/null
+++ b/src/test/resources/queries/submatch/embedded-negative-repetition.jsonld
@@ -0,0 +1,49 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+    "errors": [],
+    "warnings": [],
+    "messages": [],
+    "collection": {},
+    "query": {
+        "@type": "korap:reference",
+        "operation": "operation:focus",
+        "operands": [{
+            "@type": "korap:group",
+            "operation": "operation:sequence",
+            "operands": [
+                {
+                    "@type": "korap:token",
+                    "wrap": {
+                        "@type": "korap:term",
+                        "layer": "orth",
+                        "key": "das",
+                        "match": "match:eq"
+                    }
+                },
+                {
+                    "@type": "korap:group",
+                    "operation": "operation:repetition",
+                    "operands": [{
+                        "@type": "korap:token",
+                        "wrap": {
+                            "@type": "korap:term",
+                            "layer": "lemma",
+                            "key": "Baum",
+                            "match": "match:ne"
+                        }
+                    }],
+                    "boundary": {
+                        "@type": "korap:boundary",
+                        "min": 1,
+                        "max": 3
+                    }
+                }
+            ]
+        }],
+        "spanRef": [
+            1,
+            1
+        ]
+    },
+    "meta": {}
+}
diff --git a/src/test/resources/queries/submatch/embedded-negative-seq.jsonld b/src/test/resources/queries/submatch/embedded-negative-seq.jsonld
new file mode 100644
index 0000000..5f9b1a8
--- /dev/null
+++ b/src/test/resources/queries/submatch/embedded-negative-seq.jsonld
@@ -0,0 +1,40 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+    "errors": [],
+    "warnings": [],
+    "messages": [],
+    "collection": {},
+    "query": {
+        "@type": "korap:reference",
+        "operation": "operation:focus",
+        "operands": [{
+            "@type": "korap:group",
+            "operation": "operation:sequence",
+            "operands": [
+                {
+                    "@type": "korap:token",
+                    "wrap": {
+                        "@type": "korap:term",
+                        "layer": "orth",
+                        "key": "das",
+                        "match": "match:eq"
+                    }
+                },
+                {
+                    "@type": "korap:token",
+                    "wrap": {
+                        "@type": "korap:term",
+                        "layer": "lemma",
+                        "key": "Baum",
+                        "match": "match:ne"
+                    }
+                }
+            ]
+        }],
+        "spanRef": [
+            1,
+            1
+        ]
+    },
+    "meta": {}
+}
diff --git a/src/test/resources/queries/submatch/empty-max.jsonld b/src/test/resources/queries/submatch/empty-max.jsonld
new file mode 100644
index 0000000..dc4d321
--- /dev/null
+++ b/src/test/resources/queries/submatch/empty-max.jsonld
@@ -0,0 +1,56 @@
+{
+   "@context":"http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+   "errors":[
+
+   ],
+   "warnings":[
+
+   ],
+   "messages":[
+
+   ],
+   "collection":null,
+   "query":{
+      "@type" : "korap:reference",
+      "operands" : [
+	      {
+			"@type" : "korap:group",
+			"operands" : [
+			  {
+			    "@type" : "korap:token",
+			    "wrap" : {
+			      "@type" : "korap:term",
+			      "foundry" : "opennlp",
+			      "key" : "der",
+			      "layer" : "orth",
+			      "match" : "match:eq"
+			    }
+			  },
+			  {
+			    "@type" : "korap:group",
+			    "boundary" : {
+			      "@type" : "korap:boundary",
+			      "min" : 1,
+			      "max" : 8
+			    },
+			    "operands" : [
+			      {
+					"@type" : "korap:token"
+			      }
+			    ],
+			    "operation" : "operation:repetition"
+			  }
+			],
+			"operation" : "operation:sequence"
+	     }  
+      ],
+      "operation" : "operation:focus",
+      "spanRef" : [
+         1,
+         2
+      ]
+   },
+   "meta":{
+
+   }
+}
diff --git a/src/test/resources/queries/submatch/empty-minusStart.jsonld b/src/test/resources/queries/submatch/empty-minusStart.jsonld
new file mode 100644
index 0000000..3efaeba
--- /dev/null
+++ b/src/test/resources/queries/submatch/empty-minusStart.jsonld
@@ -0,0 +1,56 @@
+{
+   "@context":"http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+   "errors":[
+
+   ],
+   "warnings":[
+
+   ],
+   "messages":[
+
+   ],
+   "collection":null,
+   "query":{
+      "@type" : "korap:reference",
+      "operands" : [
+	      {
+			"@type" : "korap:group",
+			"operands" : [
+			  {
+			    "@type" : "korap:token",
+			    "wrap" : {
+			      "@type" : "korap:term",
+			      "foundry" : "opennlp",
+			      "key" : "der",
+			      "layer" : "orth",
+			      "match" : "match:eq"
+			    }
+			  },
+			  {
+			    "@type" : "korap:group",
+			    "boundary" : {
+			      "@type" : "korap:boundary",
+			      "min" : 1,
+			      "max" : 8
+			    },
+			    "operands" : [
+			      {
+					"@type" : "korap:token"
+			      }
+			    ],
+			    "operation" : "operation:repetition"
+			  }
+			],
+			"operation" : "operation:sequence"
+	     }  
+      ],
+      "operation" : "operation:focus",
+      "spanRef" : [
+         -1,
+         4
+      ]
+   },
+   "meta":{
+
+   }
+}
diff --git a/src/test/resources/queries/submatch/2.jsonld b/src/test/resources/queries/submatch/minusStart.jsonld
similarity index 96%
rename from src/test/resources/queries/submatch/2.jsonld
rename to src/test/resources/queries/submatch/minusStart.jsonld
index 45a3155..38c593a 100644
--- a/src/test/resources/queries/submatch/2.jsonld
+++ b/src/test/resources/queries/submatch/minusStart.jsonld
@@ -20,7 +20,7 @@
       ],
       "operation" : "operation:focus",
       "spanRef" : [
-         1,
+         -1,
          4
       ]
    },
diff --git a/src/test/resources/queries/submatch/negative-repetition.jsonld b/src/test/resources/queries/submatch/negative-repetition.jsonld
new file mode 100644
index 0000000..88cf217
--- /dev/null
+++ b/src/test/resources/queries/submatch/negative-repetition.jsonld
@@ -0,0 +1,49 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+    "errors": [],
+    "warnings": [],
+    "messages": [],
+    "collection": {},
+    "query": {
+        "@type": "korap:group",
+        "operation": "operation:sequence",
+        "operands": [
+            {
+                "@type": "korap:token",
+                "wrap": {
+                    "@type": "korap:term",
+                    "layer": "orth",
+                    "key": "das",
+                    "match": "match:eq"
+                }
+            },
+            {
+                "@type": "korap:reference",
+                "operation": "operation:focus",
+                "operands": [{
+                    "@type": "korap:group",
+                    "operation": "operation:repetition",
+                    "operands": [{
+                        "@type": "korap:token",
+                        "wrap": {
+                            "@type": "korap:term",
+                            "layer": "lemma",
+                            "key": "Baum",
+                            "match": "match:ne"
+                        }
+                    }],
+                    "boundary": {
+                        "@type": "korap:boundary",
+                        "min": 1,
+                        "max": 3
+                    }
+                }],
+                "spanRef": [
+                    1,
+                    1
+                ]
+            }
+        ]
+    },
+    "meta": {}
+}
diff --git a/src/test/resources/queries/submatch/negative-seq.jsonld b/src/test/resources/queries/submatch/negative-seq.jsonld
new file mode 100644
index 0000000..0d4a733
--- /dev/null
+++ b/src/test/resources/queries/submatch/negative-seq.jsonld
@@ -0,0 +1,40 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+    "errors": [],
+    "warnings": [],
+    "messages": [],
+    "collection": {},
+    "query": {
+        "@type": "korap:group",
+        "operation": "operation:sequence",
+        "operands": [
+            {
+                "@type": "korap:token",
+                "wrap": {
+                    "@type": "korap:term",
+                    "layer": "orth",
+                    "key": "das",
+                    "match": "match:eq"
+                }
+            },
+            {
+                "@type": "korap:reference",
+                "operation": "operation:focus",
+                "operands": [{
+                    "@type": "korap:token",
+                    "wrap": {
+                        "@type": "korap:term",
+                        "layer": "lemma",
+                        "key": "Baum",
+                        "match": "match:ne"
+                    }
+                }],
+                "spanRef": [
+                    0,
+                    1
+                ]
+            }
+        ]
+    },
+    "meta": {}
+}
diff --git a/src/test/resources/queries/submatch/negative-token.jsonld b/src/test/resources/queries/submatch/negative-token.jsonld
new file mode 100644
index 0000000..c4c0408
--- /dev/null
+++ b/src/test/resources/queries/submatch/negative-token.jsonld
@@ -0,0 +1,25 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+    "errors": [],
+    "warnings": [],
+    "messages": [],
+    "collection": {},
+    "query": {
+        "@type": "korap:reference",
+        "operation": "operation:focus",
+        "operands": [{
+            "@type": "korap:token",
+            "wrap": {
+                "@type": "korap:term",
+                "layer": "lemma",
+                "key": "Baum",
+                "match": "match:ne"
+            }
+        }],
+        "spanRef": [
+            0,
+            1
+        ]
+    },
+    "meta": {}
+}
diff --git a/src/test/resources/queries/submatch/3.jsonld b/src/test/resources/queries/submatch/noLength.jsonld
similarity index 100%
rename from src/test/resources/queries/submatch/3.jsonld
rename to src/test/resources/queries/submatch/noLength.jsonld
diff --git a/src/test/resources/queries/submatch/1.jsonld b/src/test/resources/queries/submatch/simpleElement.jsonld
similarity index 100%
rename from src/test/resources/queries/submatch/1.jsonld
rename to src/test/resources/queries/submatch/simpleElement.jsonld
diff --git a/src/test/resources/queries/submatch/term-null.jsonld b/src/test/resources/queries/submatch/term-null.jsonld
new file mode 100644
index 0000000..71b0b11
--- /dev/null
+++ b/src/test/resources/queries/submatch/term-null.jsonld
@@ -0,0 +1,36 @@
+{
+   "@context":"http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+   "errors":[
+
+   ],
+   "warnings":[
+
+   ],
+   "messages":[
+
+   ],
+   "collection":null,
+   "query":{
+      "@type" : "korap:reference",
+      "operands" : [
+         {
+          "@type" : "korap:token",
+          "wrap" : {
+             "@type" : "korap:term",
+             "foundry" : "tt",
+             "key" : "Haus",
+             "layer" : "lemma",
+             "match" : "match:eq"
+             }           
+         }
+      ],
+      "operation" : "operation:focus",
+      "spanRef" : [
+         1,
+         1
+      ]
+   },
+   "meta":{
+
+   }
+}
diff --git a/src/test/resources/queries/submatch/term-start-offset.jsonld b/src/test/resources/queries/submatch/term-start-offset.jsonld
new file mode 100644
index 0000000..84a6395
--- /dev/null
+++ b/src/test/resources/queries/submatch/term-start-offset.jsonld
@@ -0,0 +1,35 @@
+{
+   "@context":"http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+   "errors":[
+
+   ],
+   "warnings":[
+
+   ],
+   "messages":[
+
+   ],
+   "collection":null,
+   "query":{
+      "@type" : "korap:reference",
+      "operands" : [
+         {
+          "@type" : "korap:token",
+          "wrap" : {
+             "@type" : "korap:term",
+             "foundry" : "tt",
+             "key" : "Haus",
+             "layer" : "lemma",
+             "match" : "match:eq"
+             }           
+         }
+      ],
+      "operation" : "operation:focus",
+      "spanRef" : [
+         -1
+      ]
+   },
+   "meta":{
+
+   }
+}
diff --git a/src/test/resources/queries/submatch/termquery.jsonld b/src/test/resources/queries/submatch/termquery.jsonld
new file mode 100644
index 0000000..7a831b1
--- /dev/null
+++ b/src/test/resources/queries/submatch/termquery.jsonld
@@ -0,0 +1,36 @@
+{
+   "@context":"http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+   "errors":[
+
+   ],
+   "warnings":[
+
+   ],
+   "messages":[
+
+   ],
+   "collection":null,
+   "query":{
+      "@type" : "korap:reference",
+      "operands" : [
+         {
+          "@type" : "korap:token",
+          "wrap" : {
+             "@type" : "korap:term",
+             "foundry" : "tt",
+             "key" : "Haus",
+             "layer" : "lemma",
+             "match" : "match:eq"
+             }           
+         }
+      ],
+      "operation" : "operation:focus",
+      "spanRef" : [
+         0,
+         1
+      ]
+   },
+   "meta":{
+
+   }
+}