Merge "Merge branch 'master' of ssh://korap.ids-mannheim.de:29418/KorAP/Koral"
diff --git a/README.md b/README.md
index 4711bea..e9ab53e 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@
 
     git clone https://github.com/korap/Koral [install-dir]
     cd [install-dir]
-    mvn test
+    mvn test -Dhttps.protocols=TLSv1.2
     mvn install
 
 There is also a command line version. After installation, simply run
@@ -106,10 +106,10 @@
    
 ## Prerequisites
 
-Java 7 (OpenJDK or Oracle JDK with [JCE] (http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html))
-[Git](http://git-scm.com/),
-At least [Maven 3.2.1](https://maven.apache.org/).
-Further dependencies are resolved by Maven.
+* Java 7 (OpenJDK or Oracle JDK with [JCE] (http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html))
+* [Git](http://git-scm.com/)
+* At least [Maven 3.2.1](https://maven.apache.org/)
+* Further dependencies are resolved by Maven.
 
 ## Authorship
 
diff --git a/pom.xml b/pom.xml
index 453f107..17b0666 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,9 +35,9 @@
 	</developers>
 
 	<properties>
-		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>		
 	</properties>
-
+	
 	<repositories>
 		<repository>
 			<id>id-maven-repo</id>
@@ -162,6 +162,50 @@
 					<target>1.7</target>
 				</configuration>
 			</plugin>
+			<!-- <plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>properties-maven-plugin</artifactId>
+				<version>1.0.0</version>
+				<executions>
+					<execution>
+						<goals>
+							<goal>set-system-properties</goal>
+						</goals>
+						<configuration>
+							<properties>
+								<property>
+									<name>https.protocols</name>
+									<value>TLSv1.2</value>
+								</property>
+							</properties>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin> -->
+			<!-- <plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+				<version>2.19.1</version>
+				<configuration>
+					<systemProperties>
+			            <property>
+			              <name>https.protocols</name>
+			              <value>TLSv1.2</value>
+			            </property>
+			          </systemProperties>
+					<argLine>-Djava.https.protocols=TLSv1.2</argLine>
+				</configuration>
+			</plugin>
+			<plugin>
+		        <groupId>org.apache.maven.plugins</groupId>
+		        <artifactId>maven-failsafe-plugin</artifactId>
+		        <version>2.19.1</version>
+		        <configuration>
+		          <systemPropertyVariables>
+		            <https.protocols>TLSv1.2</https.protocols>
+		          </systemPropertyVariables>
+		        </configuration>
+	      </plugin> -->
 			<!-- Formatter plugin for Eclipse based coding conventions http://maven-java-formatter-plugin.googlecode.com/svn/site/0.4/usage.html -->
 			<plugin>
 				<groupId>com.googlecode.maven-java-formatter-plugin</groupId>
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
index 92f535c..df1590f 100644
--- a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
@@ -66,7 +66,28 @@
                             + " is currently unsupported.");
         }
     }
+    
+    private KoralObject parseQuerySegment(QuerySegment segment)
+            throws KoralException {
+        int minOccurs = segment.getMinOccurs();
+        int maxOccurs = segment.getMaxOccurs();
 
+        if ((minOccurs == 1) && (maxOccurs == 1)) {
+            return expressionParser.parseExpression(segment.getExpression());
+        }
+        else {
+            KoralBoundary boundary = new KoralBoundary(minOccurs, maxOccurs);
+            List<KoralObject> operand = new ArrayList<KoralObject>(1);
+            operand.add(expressionParser.parseExpression(segment
+                    .getExpression()));
+
+            KoralGroup koralGroup = new KoralGroup(KoralOperation.REPETITION);
+            koralGroup.setBoundary(boundary);
+            koralGroup.setOperands(operand);
+            return koralGroup;
+        }
+    }
+    
     private KoralObject parseWithinQuery(QueryWithWithin queryNode)
             throws KoralException {
         KoralGroup koralGroup = new KoralGroup(KoralOperation.POSITION);
@@ -146,7 +167,6 @@
         }
 
         if (isEmptyTokenFound) {
-            //operands = updateOperands(operands);
             operands = createDistance(koralGroup,operands);
         }
 
@@ -169,8 +189,7 @@
     }
 
     private List<KoralObject> createDistance(KoralGroup koralGroup, List<KoralObject> operands){
-        boolean isLastOperandUpdated = false;
-        boolean isDistanceSet = false;
+        boolean isSubGroupAdded = false;
         List<KoralObject> newOperands = new ArrayList<KoralObject>(
                 operands.size());        
         newOperands.add(operands.get(0));        
@@ -178,68 +197,43 @@
         for (int i = 1; i < operandSize - 1; i++) {
             KoralObject operand = operands.get(i);
             if (operand instanceof KoralBoundary) {
-                if (isDistanceSet){
-                    
+                List<KoralDistance> distances = new ArrayList<KoralDistance>();
+                distances.add(new KoralDistance ((KoralBoundary) operand));  
+                
+                if (koralGroup.getDistances() != null){
+                    KoralObject lastOperand = newOperands.get(newOperands.size()-1);
+                    KoralGroup subGroup = createSubGroup(distances, lastOperand, operands.get(i+1));
+                    newOperands.remove(lastOperand);
+                    newOperands.add(subGroup);
+                    isSubGroupAdded = true;
+                    continue;
                 }
-                else{
-                    List<KoralDistance> distances = new ArrayList<KoralDistance>(1);
-                    distances.add(new KoralDistance((KoralBoundary) operand));
+                else{                    
                     koralGroup.setDistances(distances);
+                    koralGroup.setInOrder(true);
                 }
-                isLastOperandUpdated = true;
             }
-            isLastOperandUpdated = false;
+            else{
+                newOperands.add(operand);                
+            }
+            isSubGroupAdded = false;
         }
-        if (!isLastOperandUpdated){
+        
+        if (!isSubGroupAdded){
             newOperands.add(operands.get(operandSize-1));
         }
         return newOperands;
     }
     
-    private List<KoralObject> updateOperands(List<KoralObject> operands) {
-        boolean isLastOperandUpdated = false;
-        List<KoralObject> newOperands = new ArrayList<KoralObject>(
-                operands.size());        
-        newOperands.add(operands.get(0));        
-        int operandSize = operands.size();
-        for (int i = 1; i < operandSize - 1; i++) {
-            KoralObject operand = operands.get(i);
-            if (operand instanceof KoralBoundary) {
-                KoralGroup koralGroup = new KoralGroup(KoralOperation.SEQUENCE);
-                List<KoralDistance> distances = new ArrayList<KoralDistance>(1);
-                distances.add(new KoralDistance((KoralBoundary) operand));
-                koralGroup.setDistances(distances);
-                koralGroup.setOperands(Arrays.asList(newOperands.get(i - 1),
-                        operands.get(i + 1)));
-                newOperands.set(i-1,koralGroup);
-                isLastOperandUpdated = true;
-            }
-            isLastOperandUpdated = false;
-        }
-        if (!isLastOperandUpdated){
-            newOperands.add(operands.get(operandSize-1));
-        }
-        return newOperands;
-    }
-
-    private KoralObject parseQuerySegment(QuerySegment segment)
-            throws KoralException {
-        int minOccurs = segment.getMinOccurs();
-        int maxOccurs = segment.getMaxOccurs();
-
-        if ((minOccurs == 1) && (maxOccurs == 1)) {
-            return expressionParser.parseExpression(segment.getExpression());
-        }
-        else {
-            KoralBoundary boundary = new KoralBoundary(minOccurs, maxOccurs);
-            List<KoralObject> operand = new ArrayList<KoralObject>(1);
-            operand.add(expressionParser.parseExpression(segment
-                    .getExpression()));
-
-            KoralGroup koralGroup = new KoralGroup(KoralOperation.REPETITION);
-            koralGroup.setBoundary(boundary);
-            koralGroup.setOperands(operand);
-            return koralGroup;
-        }
+    private KoralGroup createSubGroup(List<KoralDistance> distances, 
+            KoralObject operand, KoralObject operand2) {
+        KoralGroup subGroup = new KoralGroup(KoralOperation.SEQUENCE);
+        subGroup.setDistances(distances);
+        subGroup.setInOrder(true);
+        List<KoralObject> operands = new ArrayList<KoralObject>();
+        operands.add(operand);
+        operands.add(operand2);
+        subGroup.setOperands(operands);
+        return subGroup;
     }
 }
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
index d4cb27e..28d371f 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
@@ -180,19 +180,29 @@
         return raw();
     }
 
-	private Map raw() {
+    private Map raw () {
         if (ast != null) {
-			Map<String, Object> requestMap = ast.getRequestMap();
+            Map<String, Object> requestMap = new HashMap<>(ast.getRequestMap());
             Map meta = (Map) requestMap.get("meta");
             Map collection = (Map) requestMap.get("collection");
             List errors = (List) requestMap.get("errors");
             List warnings = (List) requestMap.get("warnings");
             List messages = (List) requestMap.get("messages");
-			this.collection = mergeCollection(collection, this.collection);
-			requestMap.put("collection", this.collection);
+            collection = mergeCollection(collection, this.collection);
+            requestMap.put("collection", collection);
+            
+            if (meta == null)
+                meta = new HashMap();
+            if (errors == null)
+                errors = new LinkedList();
+            if (warnings == null)
+                warnings = new LinkedList();
+            if (messages == null)
+                messages = new LinkedList();
+
             if (this.meta != null) {
-				this.meta.putAll(meta);
-				requestMap.put("meta", this.meta);
+                meta.putAll(this.meta);
+                requestMap.put("meta", meta);
             }
             if (this.errors != null && !this.errors.isEmpty()) {
                 errors.addAll(this.errors);
@@ -206,37 +216,41 @@
                 messages.addAll(this.messages);
                 requestMap.put("messages", messages);
             }
-
             return cleanup(requestMap);
         }
         return new HashMap<>();
     }
 
-	private Map<String, Object> cleanup(Map<String, Object> requestMap) {
+    private Map<String, Object> cleanup (Map<String, Object> requestMap) {
         Iterator<Map.Entry<String, Object>> set = requestMap.entrySet()
                 .iterator();
         while (set.hasNext()) {
             Map.Entry<String, Object> entry = set.next();
-			if (entry.getValue() instanceof List
-					&& ((List) entry.getValue()).isEmpty())
+            if (entry.getValue() instanceof List
+                    && ((List) entry.getValue()).isEmpty())
                 set.remove();
-			else if (entry.getValue() instanceof Map
-					&& ((Map) entry.getValue()).isEmpty())
+            else if (entry.getValue() instanceof Map
+                    && ((Map) entry.getValue()).isEmpty())
                 set.remove();
-			else if (entry.getValue() instanceof String
-					&& ((String) entry.getValue()).isEmpty())
+            else if (entry.getValue() instanceof String
+                    && ((String) entry.getValue()).isEmpty())
                 set.remove();
         }
         return requestMap;
     }
 
-	private Map<String, Object> mergeCollection(
-			Map<String, Object> collection1, Map<String, Object> collection2) {
+	private Map<String, Object> mergeCollection (
+            Map<String, Object> collection1, Map<String, Object> collection2) {
         if (collection1 == null || collection1.isEmpty()) {
             return collection2;
-		} else if (collection2 == null || collection2.isEmpty()) {
+        }
+        else if (collection2 == null || collection2.isEmpty()) {
             return collection1;
-		} else {
+        }
+        else if (collection1.equals(collection2)) {
+            return collection1;
+        }
+        else {
             LinkedHashMap<String, Object> docGroup = KoralObjectGenerator
                     .makeDocGroup("and");
             ArrayList<Object> operands = (ArrayList<Object>) docGroup
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
index 2493802..47a174e 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
@@ -15,6 +15,10 @@
  */
 public class FCSQLComplexTest {
 
+    String query;
+    String jsonLd;
+    List<Object> error;
+
     // -------------------------------------------------------------------------
     // simple-query ::= '(' main_query ')' /* grouping */
     // | implicit-query
@@ -26,8 +30,8 @@
     // simple-query ::= '(' main_query ')' /* grouping */
     @Test
     public void testGroupQuery() throws IOException {
-        String query = "(\"blaue\"|\"grüne\")";
-        String jsonLd = "{@type:koral:group,"
+        query = "(\"blaue\"|\"grüne\")";
+        jsonLd = "{@type:koral:group,"
                 + "operation:operation:disjunction,"
                 + "operands:["
                 + "{@type:koral:token, wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
@@ -61,8 +65,8 @@
     // | simple-query "|" main-query /* or */
     @Test
     public void testOrQuery() throws IOException {
-        String query = "\"man\"|\"Mann\"";
-        String jsonLd = "{@type:koral:group,"
+        query = "\"man\"|\"Mann\"";
+        jsonLd = "{@type:koral:group,"
                 + "operation:operation:disjunction,"
                 + "operands:["
                 + "{@type:koral:token,wrap:{@type:koral:term,key:man,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
@@ -87,8 +91,8 @@
     // | simple-query main-query /* sequence */
     @Test
     public void testSequenceQuery() throws IOException {
-        String query = "\"blaue|grüne\" [pos = \"NN\"]";
-        String jsonLd = "{@type:koral:group, "
+        query = "\"blaue|grüne\" [pos = \"NN\"]";
+        jsonLd = "{@type:koral:group, "
                 + "operation:operation:sequence, "
                 + "operands:["
                 + "{@type:koral:token, wrap:{@type:koral:term, key:blaue|grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}},"
@@ -124,8 +128,8 @@
     @Test
     public void testQueryWithQuantifier() throws IOException {
         // repetition
-        String query = "\"die\"{2}";
-        String jsonLd = "{@type:koral:group,"
+        query = "\"die\"{2}";
+        jsonLd = "{@type:koral:group,"
                 + "operation:operation:repetition,"
                 + "operands:["
                 + "{@type:koral:token,wrap:{@type:koral:term,key:die,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}],"
@@ -165,8 +169,8 @@
     @Test
     public void testQueryWithEmptyToken() throws IOException {
         // expansion query
-        String query = "[]{2}\"Hund\"";
-        String jsonLd = "{@type:koral:group, "
+        query = "[]{2}\"Hund\"";
+        jsonLd = "{@type:koral:group, "
                 + "operation:operation:sequence, "
                 + "operands:["
                 + "{@type:koral:group,"
@@ -202,10 +206,10 @@
     }
 
     @Test
-    public void testQueryWithDistance() throws JsonProcessingException {
+    public void testQueryWithDistance() throws IOException {
         // distance query
-        String query = "\"Katze\" []{3} \"Hund\"";
-        String jsonLd = "{@type:koral:group,operation:operation:sequence,inOrder:false,"
+        query = "\"Katze\" []{3} \"Hund\"";
+        jsonLd = "{@type:koral:group,operation:operation:sequence,inOrder:true,"
                 + "distances:["
                 + "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:3,max:3}}"
                 + "],"
@@ -214,6 +218,38 @@
                 + "{@type:koral:token,wrap:{@type:koral:term,key:Hund,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
         FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
 
+        // sequences of wildcards
+        query = "\"Katze\" []{3}[] \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:4,max:4}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        query = "\"Katze\" []{2}[]{3}[] \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:6,max:6}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        // multiple occurrences of wildcards
+        query = "\"Katze\" []{3} \"Hund\" []{1,2} [cnx:pos=\"V\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "inOrder:true,"
+                + "distances:["
+                + "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:3,max:3}}"
+                + "],"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Katze,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:group,"
+                    + "operation:operation:sequence,"
+                    + "inOrder:true,"
+                    + "distances:["
+                    + "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:1,max:2}}"
+                    + "],"
+                    + "operands:["
+                    + "{@type:koral:token,wrap:{@type:koral:term,key:Hund,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                    + "{@type:koral:token,wrap:{@type:koral:term,key:V,foundry:cnx,layer:p,type:type:regex,match:match:eq}}]}" 
+                +"]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
     }
 
     // -------------------------------------------------------------------------
@@ -224,8 +260,8 @@
 
     @Test
     public void testWithinQuery() throws IOException {
-        String query = "[cnx:pos=\"VVFIN\"] within s";
-        String jsonLd = "{@type:koral:group,"
+        query = "[cnx:pos=\"VVFIN\"] within s";
+        jsonLd = "{@type:koral:group,"
                 + "operation:operation:position,"
                 + "operands:["
                 + "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}},"
@@ -247,19 +283,31 @@
                 .validateNode(query, "/query/operands/0", jsonLd);
 
         query = "[cnx:pos=\"VVFIN\"] within u";
-        List<Object> error = FCSQLQueryProcessorTest
-                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
         assertEquals(310, error.get(0));
         assertEquals(
                 "FCS diagnostic 11: Within scope UTTERANCE is currently unsupported.",
                 (String) error.get(1));
     }
+    
+    @Test
+    public void testWithinQueryWithEmptyTokens() throws IOException {        
+        query = "[] within s";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:position,"
+                + "operands:["
+                + "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}},"
+                + "{@type:koral:token}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
 
     @Test
     public void testWrongQuery() throws IOException {
-        String query = "!(mate:lemma=\"sein\" | mate:pos=\"PPOSS\")";
-        List<Object> error = FCSQLQueryProcessorTest
-                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        query = "!(mate:lemma=\"sein\" | mate:pos=\"PPOSS\")";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
         assertEquals(399, error.get(0));
         assertEquals(true,
                 error.get(1).toString().startsWith("FCS diagnostic 10"));
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
index 93a130c..9c030de 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
@@ -20,13 +20,16 @@
     static QuerySerializer qs = new QuerySerializer();
     static ObjectMapper mapper = new ObjectMapper();
     static JsonNode node;
+    String query;
+    String jsonLd;
+    List<Object> error;
 
-    public static void runAndValidate(String query, String jsonLD)
+    public static void runAndValidate(String query, String jsonLd)
             throws JsonProcessingException {
         FCSQLQueryProcessor processor = new FCSQLQueryProcessor(query, "2.0");
         String serializedQuery = mapper.writeValueAsString(processor
                 .getRequestMap().get("query"));
-        assertEquals(jsonLD.replace(" ", ""), serializedQuery.replace("\"", ""));
+        assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
     }
 
     public static void validateNode(String query, String path, String jsonLd)
@@ -44,7 +47,7 @@
 
     @Test
     public void testVersion() throws JsonProcessingException {
-        List<Object> error = getError(new FCSQLQueryProcessor("\"Sonne\"",
+        error = getError(new FCSQLQueryProcessor("\"Sonne\"",
                 "1.0"));
         assertEquals(309, error.get(0));
         assertEquals("SRU diagnostic 5: Only supports SRU version 2.0.",
@@ -59,16 +62,16 @@
     // regexp ::= quoted-string
     @Test
     public void testTermQuery() throws JsonProcessingException {
-        String query = "\"Sonne\"";
-        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+        query = "\"Sonne\"";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
                 + "foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
         runAndValidate(query, jsonLd);
     }
 
     @Test
     public void testRegex() throws JsonProcessingException {
-        String query = "[text=\"M(a|ä)nn(er)?\"]";
-        String jsonLd = "{@type:koral:token,wrap:{@type:koral:term,"
+        query = "[text=\"M(a|ä)nn(er)?\"]";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,"
                 + "key:M(a|ä)nn(er)?,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
         runAndValidate(query, jsonLd);
 
@@ -95,8 +98,8 @@
     // | regexp "/" regexp-flag+
     @Test
     public void testTermQueryWithRegexFlag() throws IOException {
-        String query = "\"Fliegen\" /c";
-        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, caseInsensitive:true, "
+        query = "\"Fliegen\" /c";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, caseInsensitive:true, "
                 + "key:Fliegen, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
         FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
 
@@ -111,7 +114,7 @@
         FCSQLQueryProcessorTest.validateNode(query, "/query/wrap", jsonLd);
 
         query = "\"Fliegen\" /l";
-        List<Object> error = FCSQLQueryProcessorTest
+        error = FCSQLQueryProcessorTest
                 .getError(new FCSQLQueryProcessor(query, "2.0"));
         assertEquals(306, error.get(0));
         String msg = (String) error.get(1);
@@ -130,8 +133,8 @@
     // | "!=" /* non-equals */
     @Test
     public void testOperator() throws IOException {
-        String query = "[cnx:pos != \"N\"]";
-        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+        query = "[cnx:pos != \"N\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
                 + "foundry:cnx, layer:p, type:type:regex, match:match:ne}}";
         runAndValidate(query, jsonLd);
     }
@@ -145,8 +148,8 @@
     // simple-attribute ::= identifier
     @Test
     public void testTermQueryWithSpecificLayer() throws JsonProcessingException {
-        String query = "[text = \"Sonne\"]";
-        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+        query = "[text = \"Sonne\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
                 + "foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
         FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
 
@@ -164,8 +167,8 @@
     // qualified-attribute ::= identifier ":" identifier
     @Test
     public void testTermQueryWithQualifier() throws JsonProcessingException {
-        String query = "[mate:lemma = \"sein\"]";
-        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
+        query = "[mate:lemma = \"sein\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
                 + "foundry:mate, layer:l, type:type:regex, match:match:eq}}";
         runAndValidate(query, jsonLd);
 
@@ -175,28 +178,6 @@
         runAndValidate(query, jsonLd);
     }
 
-    @Test
-    public void testTermQueryException() throws JsonProcessingException {
-        String query = "[opennlp:lemma = \"sein\"]";
-        List<Object> error = getError(new FCSQLQueryProcessor(query, "2.0"));
-        assertEquals(306, error.get(0));
-        assertEquals(
-                "SRU diagnostic 48: Layer lemma with qualifier opennlp is unsupported.",
-                error.get(1));
-
-        query = "[malt:lemma = \"sein\"]";
-        error = getError(new FCSQLQueryProcessor(query, "2.0"));
-        assertEquals(306, error.get(0));
-        assertEquals("SRU diagnostic 48: Qualifier malt is unsupported.",
-                error.get(1));
-
-        query = "[cnx:morph = \"heit\"]";
-        error = getError(new FCSQLQueryProcessor(query, "2.0"));
-        assertEquals(306, error.get(0));
-        assertEquals("SRU diagnostic 48: Layer morph is unsupported.",
-                error.get(1));
-
-    }
 
     // segment-query ::= "[" expression? "]"
     // -------------------------------------------------------------------------
@@ -208,8 +189,8 @@
     // | expression "|" expression /* or */
     @Test
     public void testExpressionOr() throws IOException {
-        String query = "[mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
-        String jsonLd = "{@type: koral:token,"
+        query = "[mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:token,"
                 + " wrap: { @type: koral:termGroup,"
                 + "relation: relation:or,"
                 + " operands:["
@@ -227,8 +208,8 @@
     // | expression "&" expression /* and */
     @Test
     public void testExpressionAnd() throws IOException {
-        String query = "[mate:lemma=\"sein\" & mate:pos=\"PPOSS\"]";
-        String jsonLd = "{@type: koral:token,"
+        query = "[mate:lemma=\"sein\" & mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:token,"
                 + " wrap: { @type: koral:termGroup,"
                 + "relation: relation:and,"
                 + " operands:["
@@ -247,8 +228,8 @@
 
     @Test
     public void testExpressionGroup() throws JsonProcessingException {
-        String query = "[(text=\"blau\"|pos=\"ADJ\")]";
-        String jsonLd = "{@type: koral:token,"
+        query = "[(text=\"blau\"|pos=\"ADJ\")]";
+        jsonLd = "{@type: koral:token,"
                 + "wrap: {@type: koral:termGroup,"
                 + "relation: relation:or,"
                 + "operands: ["
@@ -260,9 +241,9 @@
     // "!" expression /* not */
     @Test
     public void testExpressionNot() throws IOException {
-        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
                 + "foundry:tt, layer:p, type:type:regex, match:match:eq}}";
-        String query = "[!pos != \"NN\"]";
+        query = "[!pos != \"NN\"]";
         FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
         query = "[!!pos = \"NN\"]";
         FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
@@ -280,4 +261,42 @@
         FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
     }
 
+    @Test
+    public void testExceptions() throws JsonProcessingException {
+        // unsupported lemma und qualifier
+        query = "[opennlp:lemma = \"sein\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals(
+                "SRU diagnostic 48: Layer lemma with qualifier opennlp is unsupported.",
+                error.get(1));
+
+        query = "[tt:morph = \"sein\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals(
+                "SRU diagnostic 48: Layer morph is unsupported.",
+                error.get(1));
+        
+        // unsupported qualifier
+        query = "[malt:lemma = \"sein\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Qualifier malt is unsupported.",
+                error.get(1));
+
+        // unsupported layer
+        query = "[cnx:morph = \"heit\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Layer morph is unsupported.",
+                error.get(1));
+
+        // missing layer
+        query = "[cnx=\"V\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Layer cnx is unsupported.",
+                error.get(1));
+    }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java
index 1142ae9..2cb60e3 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java
@@ -658,7 +658,13 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:token", res.at("/query/@type").asText());
         assertEquals(true, res.at("/query/key").isMissingNode());
-
+        
+        query = "[]{3}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("{@type:koral:boundary,min:3,max:3}", 
+                res.at("/query/boundary").asText());
+        
         query = "contains(<s>, [])";
         qs.setQuery(query, "poliqarpplus");
         res = mapper.readTree(qs.toJSON());