Added FCSAndQuery serialization.

Change-Id: I5a1bc12a5e308a6ae6a040f6492a0d479da7aceb
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
new file mode 100644
index 0000000..1ca4edd
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
@@ -0,0 +1,217 @@
+package de.ids_mannheim.korap.query.parse.fcsql;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import de.ids_mannheim.korap.query.parse.fcsql.KoralSequence.Distance;
+import de.ids_mannheim.korap.query.serialize.FCSQLQueryProcessor;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import eu.clarin.sru.server.fcs.parser.Expression;
+import eu.clarin.sru.server.fcs.parser.ExpressionAnd;
+import eu.clarin.sru.server.fcs.parser.Operator;
+import eu.clarin.sru.server.fcs.parser.QueryNode;
+import eu.clarin.sru.server.fcs.parser.QuerySegment;
+import eu.clarin.sru.server.fcs.parser.RegexFlag;
+
+public class FCSSRUQueryParser {
+
+    private static final String FOUNDRY_CNX = "cnx";
+    private static final String FOUNDRY_OPENNLP = "opennlp";
+    private static final String FOUNDRY_TT = "tt";
+    private static final String FOUNDRY_MATE = "mate";
+    private static final String FOUNDRY_XIP = "xip";
+
+    private List<String> supportedFoundries = Arrays
+            .asList(new String[] { FOUNDRY_CNX, FOUNDRY_OPENNLP, FOUNDRY_TT,
+                    FOUNDRY_MATE, FOUNDRY_XIP });
+
+    private FCSQLQueryProcessor processor;
+
+    public FCSSRUQueryParser (FCSQLQueryProcessor processor) {
+        this.processor = processor;
+    }
+
+    public Object parseQueryNode(QueryNode queryNode) {
+
+        if (queryNode instanceof QuerySegment) {
+            return parseQuerySegment((QuerySegment) queryNode);
+            // } else if (queryNode instanceof QueryGroup) {
+            //
+            // } else if (queryNode instanceof QuerySequence) {
+            //
+            // } else if (queryNode instanceof QueryDisjunction) {
+            //
+            // } else if (queryNode instanceof QueryWithWithin) {
+
+        }
+        else {
+            processor.addError(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11:" + queryNode.getNodeType().name()
+                            + " is currently unsupported.");
+            return null;
+        }
+    }
+
+    private Object parseQuerySegment(QuerySegment segment) {
+        if ((segment.getMinOccurs() == 1) && (segment.getMaxOccurs() == 1)) {
+            return parseExpression(segment.getExpression());
+        }
+        else {
+            processor.addError(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11: Query is too complex.");
+            return null;
+        }
+    }
+
+    private Object parseExpression(QueryNode queryNode) {
+        if (queryNode instanceof Expression) {
+            Expression expression = (Expression) queryNode;
+            return parseSimpleExpression(expression);
+        }
+        else if (queryNode instanceof ExpressionAnd) {
+            ExpressionAnd expressionAnd = (ExpressionAnd) queryNode;
+            return parseExpressionAnd(expressionAnd);
+        }
+        // else if (queryNode instanceof ExpressionGroup) {
+        //
+        // }
+        // else if (queryNode instanceof ExpressionNot) {
+        //
+        // }
+        // else if (queryNode instanceof ExpressionOr) {
+        //
+        // }
+        // else if (queryNode instanceof ExpressionWildcard) {
+        //
+        // }
+        else {
+            processor.addError(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11: Query is too complex.");
+            return null;
+        }
+    }
+
+    private Object parseExpressionAnd(ExpressionAnd expressionAnd) {
+        KoralSequence koralSequence = new KoralSequence();
+        List<Object> operands = new ArrayList<Object>();
+        for (QueryNode child : expressionAnd.getChildren()) {
+            operands.add(parseExpression(child));
+        }
+
+        List<Distance> distances = new ArrayList<Distance>();
+        Distance d = koralSequence.new Distance("s", 0, 0);
+        distances.add(d);
+
+        koralSequence.setOperands(operands);
+        koralSequence.setDistances(distances);
+        return koralSequence;
+    }
+
+    private Object parseSimpleExpression(Expression expression) {
+        KoralTerm koralTerm = new KoralTerm();
+        koralTerm.setQueryTerm(expression.getRegexValue());
+        parseLayerIdentifier(koralTerm, expression.getLayerIdentifier());
+        parseQualifier(koralTerm, expression.getLayerQualifier());
+        parseOperator(koralTerm, expression.getOperator());
+        parseRegexFlags(koralTerm, expression.getRegexFlags());
+        return koralTerm;
+    }
+
+    private void parseLayerIdentifier(KoralTerm koralTerm, String identifier) {
+        String layer = null;
+        if (identifier == null) {
+            processor.addError(StatusCodes.MALFORMED_QUERY,
+                    "FCS diagnostic 10: Layer identifier is missing.");
+            koralTerm.setInvalid(true);
+        }
+        else if (identifier.equals("text")) {
+            layer = "orth";
+        }
+        else if (identifier.equals("pos")) {
+            layer = "p";
+        }
+        else if (identifier.equals("lemma")) {
+            layer = "l";
+        }
+        else {
+            processor.addError(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Layer " + identifier
+                            + " is unsupported.");
+            koralTerm.setInvalid(true);
+        }
+
+        koralTerm.setLayer(layer);
+    }
+
+    private void parseQualifier(KoralTerm koralTerm, String qualifier) {
+        String layer = koralTerm.getLayer();
+        if (layer == null) {
+            koralTerm.setInvalid(true);
+            return;
+        }
+        // Set default foundry
+        if (qualifier == null) {
+            if (layer.equals("orth")) {
+                qualifier = FOUNDRY_OPENNLP;
+            }
+            else {
+                qualifier = FOUNDRY_TT;
+            }
+        }
+        else if (qualifier.equals(FOUNDRY_OPENNLP) && layer.equals("l")) {
+            processor
+                    .addError(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                            "SRU diagnostic 48: Layer lemma with qualifier opennlp is unsupported.");
+            koralTerm.setInvalid(true);
+        }
+        else if (!supportedFoundries.contains(qualifier)) {
+            processor.addError(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Qualifier " + qualifier
+                            + " is unsupported.");
+            koralTerm.setInvalid(true);
+        }
+
+        koralTerm.setFoundry(qualifier);
+    }
+
+    private void parseOperator(KoralTerm koralTerm, Operator operator) {
+        String matchOperator = null;
+        if (operator == null || operator == Operator.EQUALS) {
+            matchOperator = "match:eq";
+        }
+        else if (operator == Operator.NOT_EQUALS) {
+            matchOperator = "match:ne";
+        }
+        else {
+            processor
+                    .addError(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                            "SRU diagnostic 37:" + operator.name()
+                                    + " is unsupported.");
+            koralTerm.setInvalid(true);
+        }
+        koralTerm.setOperator(matchOperator);
+    }
+
+    private void parseRegexFlags(KoralTerm koralTerm, Set<RegexFlag> set) {
+        // default case sensitive
+        if (set != null) {
+            for (RegexFlag f : set) {
+                if (f == RegexFlag.CASE_SENSITVE) {
+                    koralTerm.setCaseSensitive(true);
+                }
+                else if (f == RegexFlag.CASE_INSENSITVE) {
+                    koralTerm.setCaseSensitive(false);
+                }
+                else {
+                    processor.addError(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                            "SRU diagnostic 48:" + f.name()
+                                    + " is unsupported.");
+                    koralTerm.setInvalid(true);
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/KoralSequence.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/KoralSequence.java
new file mode 100644
index 0000000..31bd159
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/KoralSequence.java
@@ -0,0 +1,71 @@
+package de.ids_mannheim.korap.query.parse.fcsql;
+
+import java.util.List;
+
+public class KoralSequence {
+
+    private boolean inOrder = false;
+    private List<Object> operands;
+    private List<Distance> distances;
+
+    public boolean isInOrder() {
+        return inOrder;
+    }
+
+    public void setInOrder(boolean inOrder) {
+        this.inOrder = inOrder;
+    }
+
+    public List<Object> getOperands() {
+        return operands;
+    }
+
+    public void setOperands(List<Object> operands) {
+        this.operands = operands;
+    }
+
+    public List<Distance> getDistances() {
+        return distances;
+    }
+
+    public void setDistances(List<Distance> distances) {
+        this.distances = distances;
+    }
+
+    public class Distance {
+        private String key;
+        private String min;
+        private String max;
+
+        public Distance (String key, int min, int max) {
+            this.key = key;
+            this.min = String.valueOf(min);
+            this.max = String.valueOf(max);
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public void setKey(String key) {
+            this.key = key;
+        }
+
+        public String getMin() {
+            return min;
+        }
+
+        public void setMin(String min) {
+            this.min = min;
+        }
+
+        public String getMax() {
+            return max;
+        }
+
+        public void setMax(String max) {
+            this.max = max;
+        }
+
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/KoralTerm.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/KoralTerm.java
new file mode 100644
index 0000000..1ae2e09
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/KoralTerm.java
@@ -0,0 +1,61 @@
+package de.ids_mannheim.korap.query.parse.fcsql;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import de.ids_mannheim.korap.query.serialize.FCSQLQueryProcessor;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import eu.clarin.sru.server.fcs.parser.Operator;
+import eu.clarin.sru.server.fcs.parser.RegexFlag;
+
+public class KoralTerm {
+
+	private String layer;
+	private String foundry;
+	private String operator;
+	private String queryTerm;
+	private boolean caseSensitive = true;
+	private boolean invalid = false;
+	
+	
+	public String getLayer() {
+		return layer;
+	}
+	public void setLayer(String layer) {
+		this.layer = layer;
+	}
+	public String getFoundry() {
+		return foundry;
+	}
+	public void setFoundry(String foundry) {
+		this.foundry = foundry;
+	}
+	public String getOperator() {
+		return operator;
+	}
+	public void setOperator(String operator) {
+		this.operator = operator;
+	}
+	public String getQueryTerm() {
+		return queryTerm;
+	}
+	public void setQueryTerm(String queryTerm) {
+		this.queryTerm = queryTerm;
+	}
+	public boolean isCaseSensitive() {
+		return caseSensitive;
+	}
+	public void setCaseSensitive(boolean isCaseSensitive) {
+		this.caseSensitive = isCaseSensitive;
+	}
+	public boolean isInvalid() {
+		return invalid;
+	}
+	public void setInvalid(boolean invalid) {
+		this.invalid = invalid;
+	}
+	
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java
index 7e0c199..0785237 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java
@@ -4,266 +4,168 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
+import de.ids_mannheim.korap.query.parse.fcsql.FCSSRUQueryParser;
+import de.ids_mannheim.korap.query.parse.fcsql.KoralSequence;
+import de.ids_mannheim.korap.query.parse.fcsql.KoralSequence.Distance;
+import de.ids_mannheim.korap.query.parse.fcsql.KoralTerm;
 import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 import eu.clarin.sru.server.SRUQueryBase;
-import eu.clarin.sru.server.SRUVersion;
 import eu.clarin.sru.server.fcs.Constants;
-import eu.clarin.sru.server.fcs.parser.Expression;
-import eu.clarin.sru.server.fcs.parser.Operator;
 import eu.clarin.sru.server.fcs.parser.QueryNode;
 import eu.clarin.sru.server.fcs.parser.QueryParser;
 import eu.clarin.sru.server.fcs.parser.QueryParserException;
-import eu.clarin.sru.server.fcs.parser.QuerySegment;
-import eu.clarin.sru.server.fcs.parser.RegexFlag;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class FCSQLQueryProcessor extends AbstractQueryProcessor {
 
-	public static final class FCSQuery extends SRUQueryBase<QueryNode> {
+    public static final class FCSSRUQuery extends SRUQueryBase<QueryNode> {
 
-		private FCSQuery(String rawQuery, QueryNode parsedQuery) {
-			super(rawQuery, parsedQuery);
-		}
+        private FCSSRUQuery (String rawQuery, QueryNode parsedQuery) {
+            super(rawQuery, parsedQuery);
+        }
 
-		@Override
-		public String getQueryType() {
-			return Constants.FCS_QUERY_TYPE_FCS;
-		}
-	}
+        @Override
+        public String getQueryType() {
+            return Constants.FCS_QUERY_TYPE_FCS;
+        }
+    }
 
-	public enum Foundry {
-		CNX, OPENNLP, TT, MATE, XIP;
-	}
+    private static final String VERSION_2_0 = "2.0";
+    private static final String OPERATION_OR = "operation:or";
+    private static final String OPERATION_SEQUENCE = "operation:sequence";
+    private static final String OPERATION_POSITION = "operation:position";
 
-	private static final String KORAP_CONTEXT = "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld";
-	private String version;
-	private List<Foundry> supportedFoundries;
-	private final QueryParser parser = new QueryParser();
+    private final QueryParser fcsParser = new QueryParser();
+    private String version;
 
-	public FCSQLQueryProcessor(String query, String version) {
-		if (version == null) {
-			addError(StatusCodes.MISSING_VERSION,
-					"SRU Diagnostic 7: Version number is missing.");
-		} else if (!version.equals(SRUVersion.VERSION_2_0)) {
-			addError(StatusCodes.MISSING_VERSION,
-					"SRU Diagnostic 5: Only supports SRU version 2.0.");
-		}
-		this.version = version;
+    public FCSQLQueryProcessor (String query, String version) {
+        super();
+        this.version = version;
+        process(query);
+    }
 
-		this.requestMap = new LinkedHashMap<>();
-		requestMap.put("@context", KORAP_CONTEXT);
+    @Override
+    public Map<String, Object> getRequestMap() {
+        return this.requestMap;
+    }
 
-		this.supportedFoundries = new ArrayList<Foundry>(5);
-		supportedFoundries.add(Foundry.CNX);
-		supportedFoundries.add(Foundry.OPENNLP);
-		supportedFoundries.add(Foundry.TT);
-		supportedFoundries.add(Foundry.MATE);
-		supportedFoundries.add(Foundry.XIP);
+    @Override
+    public void process(String query) {
+        if (isVersionValid()) {
+            FCSSRUQuery fcsSruQuery = parseQueryStringtoFCSQuery(query);
+            QueryNode fcsQueryNode = fcsSruQuery.getParsedQuery();
+            parseFCSQueryToKoralQuery(fcsQueryNode);
+        }
+    }
 
-		process(query);
-	}
+    private boolean isVersionValid() {
+        if (version == null || version.isEmpty()) {
+            addError(StatusCodes.MISSING_VERSION,
+                    "SRU diagnostic 7: Version number is missing.");
+            return false;
+        }
+        else if (!version.equals(VERSION_2_0)) {
+            addError(StatusCodes.MISSING_VERSION,
+                    "SRU diagnostic 5: Only supports SRU version 2.0.");
+            return false;
+        }
+        return true;
+    }
 
-	@Override
-	public Map<String, Object> getRequestMap() {
-		return this.requestMap;
-	}
+    private FCSSRUQuery parseQueryStringtoFCSQuery(String query) {
+        if ((query == null) || query.isEmpty())
+            addError(StatusCodes.NO_QUERY,
+                    "SRU diagnostic 1: No query has been passed.");
+        FCSSRUQuery fcsQuery = null;
+        try {
+            QueryNode parsedQuery = fcsParser.parse(query);
+            fcsQuery = new FCSSRUQuery(query, parsedQuery);
+        }
+        catch (QueryParserException e) {
+            addError(StatusCodes.UNKNOWN_QUERY_ERROR, "FCS diagnostic 10: +"
+                    + e.getMessage());
+        }
+        catch (Exception e) {
+            addError(StatusCodes.UNKNOWN_QUERY_ERROR, "FCS diagnostic 10: +"
+                    + "Unexpected error while parsing query.");
+        }
+        return fcsQuery;
+    }
 
-	@Override
-	public void process(String query) {
-		FCSQuery fcsQuery = parseQueryStringtoFCSQuery(query);
-		QueryNode fcsQueryNode = fcsQuery.getParsedQuery();
-		Map<String, Object> queryMap = parseFCSQuery(fcsQueryNode);
-		requestMap.put("query", queryMap);
-	}
+    private void parseFCSQueryToKoralQuery(QueryNode queryNode) {
+        FCSSRUQueryParser parser = new FCSSRUQueryParser(this);
+        Object o = parser.parseQueryNode(queryNode);
+        Map<String, Object> queryMap = buildQueryMap(o);
+        if (queryMap != null) requestMap.put("query", queryMap);
+    }
 
-	private FCSQuery parseQueryStringtoFCSQuery(String query) {
-		if ((query == null) || query.isEmpty())
-			addError(StatusCodes.MALFORMED_QUERY,
-					"SRU diagnostic 1: No query has been passed.");
-		FCSQuery fcsQuery = null;
-		try {
-			QueryNode parsedQuery = parser.parse(query);
-			fcsQuery = new FCSQuery(query, parsedQuery);
-		} catch (QueryParserException e) {
-			addError(StatusCodes.UNKNOWN_QUERY_ERROR, "FCS Diagnostic 10: +"
-					+ e.getMessage());
-		}
-		catch (Exception e) {
-			addError(StatusCodes.UNKNOWN_QUERY_ERROR, "FCS Diagnostic 10: +"
-					+ "Unexpected error while parsing query.");
-		}
-		return fcsQuery;
-	}
+    private Map<String, Object> buildQueryMap(Object o) {
+        if (o != null) {
+            if (o instanceof KoralTerm) {
+                KoralTerm koralTerm = (KoralTerm) o;
+                if (!koralTerm.isInvalid()) {
+                    return createTermMap(koralTerm);
+                }
+            }
+            else if (o instanceof KoralSequence) {
+                KoralSequence koralSequence = (KoralSequence) o;
+                return createSequenceMap(koralSequence);
+            }
+        }
+        return null;
+    }
 
-	private Map<String, Object> parseFCSQuery(QueryNode queryNode) {
-		Map<String, Object> queryMap = parseQueryNode(queryNode);
-		if (queryMap == null) {
-			addError(StatusCodes.UNKNOWN_QUERY_ERROR, "SRU diagnostic 47:"
-					+ " Failed parsing query for unknown reasons.");
-		}
-		return queryMap;
+    private Map<String, Object> createSequenceMap(KoralSequence koralSequence) {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", "koral:group");
+        map.put("operation", OPERATION_SEQUENCE);
+        map.put("inOrder", koralSequence.isInOrder());
 
-	}
+        if (koralSequence.getDistances() != null) {
+            List<Map<String, Object>> distanceList = new ArrayList<Map<String, Object>>();
+            for (Distance d : koralSequence.getDistances()) {
+                distanceList.add(createDistanceMap(d));
+            }
+            map.put("distances", distanceList);
+        }
 
-	private Map<String, Object> parseQueryNode(QueryNode queryNode) {
-		Map<String, Object> queryMap = null;
+        List<Map<String, Object>> operandList = new ArrayList<Map<String, Object>>();
+        for (Object o : koralSequence.getOperands()) {
+            operandList.add(buildQueryMap(o));
+        }
+        map.put("operands", operandList);
+        return map;
+    }
 
-		if (queryNode instanceof QuerySegment) {
-			queryMap = parseQuerySegment((QuerySegment) queryNode);
-//		} else if (queryNode instanceof QueryGroup) {
-//
-//		} else if (queryNode instanceof QuerySequence) {
-//
-//		} else if (queryNode instanceof QueryDisjunction) {
-//
-//		} else if (queryNode instanceof QueryWithWithin) {
+    private Map<String, Object> createDistanceMap(Distance distance) {
+        Map<String, Object> distanceMap = new LinkedHashMap<String, Object>();
+        distanceMap.put("@type", "koral:distance");
+        distanceMap.put("key", distance.getKey());
+        distanceMap.put("min", distance.getMin());
+        distanceMap.put("max", distance.getMax());
+        return distanceMap;
 
-		}else {
-			addError(StatusCodes.QUERY_TOO_COMPLEX, "FCS diagnostic 11:"
-					+ queryNode.getNodeType().name()
-					+ " is currently unsupported.");
-		}
+    }
 
-		return queryMap;
-	}
+    private Map<String, Object> createTermMap(KoralTerm fcsQuery) {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", "koral:term");
+        if (!fcsQuery.isCaseSensitive()) {
+            map.put("caseInsensitive", "true");
+        }
+        map.put("key", fcsQuery.getQueryTerm());
+        map.put("foundry", fcsQuery.getFoundry());
+        map.put("layer", fcsQuery.getLayer());
+        map.put("match", fcsQuery.getOperator());
 
-	private Map<String, Object> parseQuerySegment(QuerySegment segment) {
-		Map<String, Object> queryMap = null;
-
-		if ((segment.getMinOccurs() == 1) && (segment.getMaxOccurs() == 1)) {
-			queryMap = parseExpression(segment.getExpression());
-		} else {
-			addError(StatusCodes.QUERY_TOO_COMPLEX, "FCS diagnostic 11:"
-					+ "Query is too complex.");
-		}
-		return queryMap;
-	}
-
-	private Map<String, Object> parseExpression(QueryNode queryNode) {
-		Map<String, Object> queryMap = null;
-
-		if (queryNode instanceof Expression) {
-			Expression expression = (Expression) queryNode;
-			queryMap = parseLayer(expression);
-		}
-		// else if (queryNode instanceof ExpressionAnd) {
-		//
-		// }
-		// else if (queryNode instanceof ExpressionGroup) {
-		//
-		// }
-		// else if (queryNode instanceof ExpressionNot) {
-		//
-		// }
-		// else if (queryNode instanceof ExpressionOr) {
-		//
-		// }
-		// else if (queryNode instanceof ExpressionWildcard) {
-		//
-		// }
-		else {
-			addError(StatusCodes.QUERY_TOO_COMPLEX, "FCS diagnostic 11:"
-					+ "Query is too complex.");
-		}
-		return queryMap;
-	}
-
-	private Map<String, Object> parseLayer(Expression expression) {
-		String layer = parseLayerIdentifier(expression.getLayerIdentifier());
-		String foundry = parseQualifier(expression.getLayerQualifier(), layer);
-		String operator = parseOperator(expression.getOperator());
-		boolean isCaseSensitive = parseRegexFlags(expression.getRegexFlags());		
-		String term = expression.getRegexValue();
-		
-		return writeTerm(term, foundry, layer, operator, isCaseSensitive);
-	}
-	private String parseLayerIdentifier(String identifier) {
-		String layer = null;
-		if (identifier == null) {
-			// throw exception
-		} else if (identifier.equals("text")) {
-			layer = "orth";
-		} else if (identifier.equals("pos")) {
-			layer = "p";
-		} else if (identifier.equals("lemma")) {
-			layer = "l";
-		} else {
-			addError(StatusCodes.UNKNOWN_QUERY_ELEMENT, "SRU diagnostic 48:"
-					+ identifier + " is unsupported.");
-		}
-
-		return layer;
-	}
-	
-	private String parseQualifier(String qualifier, String layer) {
-		// Set default foundry
-		if (qualifier == null) {
-			if (layer.equals("orth")) {
-				qualifier = Foundry.OPENNLP.name().toLowerCase();
-			} else {
-				qualifier = Foundry.TT.name().toLowerCase();
-			}
-		} else if (qualifier.equals(Foundry.OPENNLP.name().toLowerCase())
-				&& layer.equals("lemma")) {
-			addError(StatusCodes.UNKNOWN_QUERY_ELEMENT, "SRU diagnostic 48:"
-					+ "Layer lemma with qualifier opennlp is unsupported.");
-		} else if (!supportedFoundries.contains(qualifier)) {
-			addError(StatusCodes.UNKNOWN_QUERY_ELEMENT, "SRU diagnostic 48:"
-					+ "Layer " + layer + " with qualifier" + qualifier
-					+ " is unsupported.");
-		}
-		return qualifier;
-	}
-	
-	private String parseOperator(Operator operator) {
-		String matchOperator = null;
-		if (operator == null || operator == Operator.EQUALS) {
-			matchOperator = "match:eq";
-		} else if (operator == Operator.NOT_EQUALS) {
-			matchOperator = "match:ne";
-		} else {
-			addError(StatusCodes.UNKNOWN_QUERY_ELEMENT, "SRU diagnostic 37:"
-					+ operator.name() + " is unsupported.");
-		}
-		return matchOperator;
-	}
-	
-	private boolean parseRegexFlags(Set<RegexFlag> set) {
-		// default case sensitive
-		boolean flag = true;
-		if (set != null) {
-			for (RegexFlag f : set) {
-				if (f == RegexFlag.CASE_SENSITVE) {
-					continue;
-				} else if (f == RegexFlag.CASE_INSENSITVE) {
-					flag = false;
-				} else {
-					addError(StatusCodes.UNKNOWN_QUERY_ELEMENT,
-							"SRU diagnostic 48:" + f.name()
-									+ " is unsupported.");
-				}
-			}
-		}
-		return flag;
-	}
-
-	private Map<String, Object> writeTerm(String term, String foundry,
-			String layer, String operator, boolean isCaseSensitive) {
-		Map<String, Object> map = new LinkedHashMap<String, Object>();
-		map.put("@type", "koral:term");
-		if (!isCaseSensitive) {
-			map.put("caseInsensitive", "true");
-		}
-		map.put("key", term);
-		map.put("foundry", foundry);
-		map.put("layer", layer);
-		map.put("match", operator);
-
-		Map<String, Object> tokenMap = new LinkedHashMap<String, Object>();
-		tokenMap.put("@type", "koral:token");
-		tokenMap.put("wrap", map);
-		return tokenMap;
-	}
+        Map<String, Object> tokenMap = new LinkedHashMap<String, Object>();
+        tokenMap.put("@type", "koral:token");
+        tokenMap.put("wrap", map);
+        return tokenMap;
+    }
 
 }
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 729381e..9b7da8b 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
@@ -2,89 +2,147 @@
 
 import static org.junit.Assert.assertEquals;
 
+import java.util.List;
 
 import org.junit.Test;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-public class FcsqlQueryProcessorTest {
-	ObjectMapper mapper = new ObjectMapper();
+public class FCSQLQueryProcessorTest {
 
-	private void runAndValidate(String query, String jsonLD)
-			throws JsonProcessingException {
-		FCSQLQueryProcessor tree = new FCSQLQueryProcessor(query, "2.0");
-		String serializedQuery = mapper.writeValueAsString(tree.getRequestMap()
-				.get("query"));
-		assertEquals(jsonLD.replace(" ", ""), serializedQuery.replace("\"", ""));
-	}
+    ObjectMapper mapper = new ObjectMapper();
 
-	@Test
-	public void testTermQuery() throws JsonProcessingException {
-		String query = "\"Sonne\"";
-		String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
-				+ "foundry:opennlp, layer:orth, match:match:eq}}";
-		runAndValidate(query, jsonLd);
-	}
+    private 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("\"", ""));
+    }
 
-	@Test
-	public void testTermQueryWithRegexFlag() throws JsonProcessingException {
-		String query = "\"Fliegen\" /c";
-		String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, caseInsensitive:true, "
-				+ "key:Fliegen, foundry:opennlp, layer:orth, match:match:eq}}";
-		runAndValidate(query, jsonLd);
-	}
+    private List<Object> getError(FCSQLQueryProcessor processor) {
+        List<Object> errors = (List<Object>) processor.requestMap.get("errors");
+        return (List<Object>) errors.get(0);
+    }
 
-	@Test
-	public void testTermQueryWithSpecificLayer() throws JsonProcessingException {
-		String query = "[text = \"Sonne\"]";
-		String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
-				+ "foundry:opennlp, layer:orth, match:match:eq}}";
-		runAndValidate(query, jsonLd);
+    @Test
+    public void testTermQuery() throws JsonProcessingException {
+        String query = "\"Sonne\"";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+                + "foundry:opennlp, layer:orth, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+    }
 
-		query = "[lemma = \"sein\"]";
-		jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
-				+ "foundry:tt, layer:l, match:match:eq}}";
-		runAndValidate(query, jsonLd);
+    @Test
+    public void testTermQueryWithRegexFlag() throws JsonProcessingException {
+        String query = "\"Fliegen\" /c";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, caseInsensitive:true, "
+                + "key:Fliegen, foundry:opennlp, layer:orth, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+    }
 
-		query = "[pos = \"NN\"]";
-		jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
-				+ "foundry:tt, layer:p, match:match:eq}}";
-		runAndValidate(query, jsonLd);
-	}
+    @Test
+    public void testTermQueryWithSpecificLayer() throws JsonProcessingException {
+        String query = "[text = \"Sonne\"]";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+                + "foundry:opennlp, layer:orth, match:match:eq}}";
+        runAndValidate(query, jsonLd);
 
-	@Test
-	public void testTermQueryWithQualifier() throws JsonProcessingException {
-		String query = "[mate:lemma = \"sein\"]";
-		String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
-				+ "foundry:mate, layer:l, match:match:eq}}";
-		runAndValidate(query, jsonLd);
+        query = "[lemma = \"sein\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
+                + "foundry:tt, layer:l, match:match:eq}}";
+        runAndValidate(query, jsonLd);
 
-		query = "[cnx:pos = \"N\"]";
-		jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
-				+ "foundry:cnx, layer:p, match:match:eq}}";
-		runAndValidate(query, jsonLd);
-	}
+        query = "[pos = \"NN\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
+                + "foundry:tt, layer:p, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+    }
 
-	@Test
-	public void testMatchOperation() throws JsonProcessingException {
-		String query = "[cnx:pos != \"N\"]";
-		String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
-				+ "foundry:cnx, layer:p, match:match:ne}}";
-		runAndValidate(query, jsonLd);
-	}
+    @Test
+    public void testTermQueryWithQualifier() throws JsonProcessingException {
+        String query = "[mate:lemma = \"sein\"]";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
+                + "foundry:mate, layer:l, match:match:eq}}";
+        runAndValidate(query, jsonLd);
 
-	// @Test
-	// public void testSequenceQuery() throws JsonProcessingException {
-	// String query = "\"blaue\" [pos = \"NN\"]";
-	// String jsonLd =
-	// "{@type:koral:group, operation:operation:sequence, operands:["
-	// +
-	// "{@type:koral:token, wrap:{@type:koral:term, key:blaue, foundry:opennlp, layer:orth, match:match:eq}},"
-	// +
-	// "{@type:koral:token, wrap:{@type:koral:term, key:NN, foundry:tt, layer:p, match:match:eq}}"
-	// + "]}";
-	// runAndValidate(query, jsonLd);
-	// }
+        query = "[cnx:pos = \"N\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+                + "foundry:cnx, layer:p, match:match:eq}}";
+        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));
+
+    }
+
+    @Test
+    public void testMatchOperation() throws JsonProcessingException {
+        String query = "[cnx:pos != \"N\"]";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+                + "foundry:cnx, layer:p, match:match:ne}}";
+        runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testSequenceQuery() throws JsonProcessingException {
+        String query = "\"blaue\" [pos = \"NN\"]";
+        String jsonLd = "{@type:koral:group, operation:operation:sequence, operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term, key:blaue, foundry:opennlp, layer:orth, match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term, key:NN, foundry:tt, layer:p, match:match:eq}}"
+                + "]}";
+        runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testAndQuery() throws JsonProcessingException {
+        String query = "[text=\"Sonne\" & text=\"scheint\"]";
+        String jsonLd = "{@type:koral:group, operation:operation:sequence, inOrder:false,"
+                + "distances:["
+                + "{@type:koral:distance, key:s, min:0, max:0}],"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, foundry: opennlp, layer:orth, match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term, key:scheint, foundry: opennlp, layer:orth,match:match:eq}"
+                + "}]}";
+        runAndValidate(query, jsonLd);
+
+        // sru parser doesnt work for the following queries:
+        // String query = "\"Sonne & scheint\"";
+        // String query = "\"Sonne&scheint\"";
+    }
+
+    @Test
+    public void testVersion() throws JsonProcessingException {
+        List<Object> error = getError(new FCSQLQueryProcessor("\"Sonne\"",
+                "1.0"));
+        assertEquals(309, error.get(0));
+        assertEquals("SRU diagnostic 5: Only supports SRU version 2.0.",
+                error.get(1));
+
+        error = getError(new FCSQLQueryProcessor("\"Sonne\"", null));
+        assertEquals(309, error.get(0));
+        assertEquals("SRU diagnostic 7: Version number is missing.",
+                error.get(1));
+    }
 
 }