Added FCS WithinQuery.

Change-Id: I9c3f8cde68986a8d2eddedd289d53f1d86c75728
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/Element.java b/src/main/java/de/ids_mannheim/korap/query/elements/Element.java
index a7da5b4..73f580a 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/Element.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/Element.java
@@ -2,6 +2,10 @@
 
 import java.util.Map;
 
+/**
+ * @author margaretha
+ * 
+ */
 public interface Element {
 
     public Map<String, Object> buildMap();
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralGroup.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralGroup.java
index 7d8a9a1..61a2e04 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/KoralGroup.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralGroup.java
@@ -7,6 +7,10 @@
 
 import de.ids_mannheim.korap.query.serialize.MapBuilder;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class KoralGroup implements Element {
 
     private static final KoralType type = KoralType.GROUP;
@@ -16,6 +20,7 @@
     private boolean inOrder = false;
     private List<Object> operands;
     private List<Distance> distances;
+    private List<Frame> frames;
 
     public KoralGroup (KoralOperation operation) {
         this.operation = operation;
@@ -45,6 +50,14 @@
         this.distances = distances;
     }
 
+    public List<Frame> getFrames() {
+        return frames;
+    }
+
+    public void setFrames(List<Frame> frames) {
+        this.frames = frames;
+    }
+
     @Override
     public Map<String, Object> buildMap() {
         Map<String, Object> map = new LinkedHashMap<String, Object>();
@@ -60,6 +73,14 @@
             map.put("distances", distanceList);
         }
 
+        if (getFrames() != null) {
+            List<String> frameList = new ArrayList<String>();
+            for (Frame f : getFrames()) {
+                frameList.add(f.toString());
+            }
+            map.put("frames", frameList);
+        }
+
         List<Map<String, Object>> operandList = new ArrayList<Map<String, Object>>();
         for (Object o : getOperands()) {
             operandList.add(MapBuilder.buildQueryMap(o));
@@ -68,6 +89,26 @@
         return map;
     }
 
+    public enum Frame {
+        SUCCEDS("succeds"), SUCCEDS_DIRECTLY("succeedsDirectly"), OVERLAPS_RIGHT(
+                "overlapsRight"), ALIGNS_RIGHT("alignsRight"), IS_WITHIN(
+                "isWithin"), STARTS_WITH("startsWith"), MATCHES("matches"), ALIGNS_LEFT(
+                "alignsLeft"), IS_AROUND("isAround"), ENDS_WITH("endsWith"), OVERLAPS_LEFT(
+                "overlapsLeft"), PRECEDES_DIRECTLY("precedesDirectly"), PRECEDES(
+                "precedes");
+
+        String value;
+
+        Frame (String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return "frames:" + value;
+        };
+    }
+
     public class Distance implements Element {
 
         private final KoralType type = KoralType.DISTANCE;
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralOperation.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralOperation.java
index 0e8b30a..21fa833 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/KoralOperation.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralOperation.java
@@ -1,20 +1,15 @@
 package de.ids_mannheim.korap.query.elements;
 
+/**
+ * @author margaretha
+ * 
+ */
 public enum KoralOperation {
-    SEQUENCE("operation:sequence"), POSITION("operation:position"), DISJUNCTION(
-            "operation:disjunction"), REPETITION("operation:repetition"), CLASS(
-            "operation:class"), MERGE("operation:merge"), RELATION(
-            "operation:relation");
-
-    String value;
-
-    KoralOperation (String value) {
-        this.value = value;
-    }
+    SEQUENCE, POSITION, DISJUNCTION, REPETITION, CLASS, MERGE, RELATION;
 
     @Override
     public String toString() {
-        return value;
+        return "operation:" + super.toString().toLowerCase();
     }
 
 }
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralRelation.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralRelation.java
index 9f0d3eb..f1bfa91 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/KoralRelation.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralRelation.java
@@ -1,17 +1,15 @@
 package de.ids_mannheim.korap.query.elements;
 
+/**
+ * @author margaretha
+ * 
+ */
 public enum KoralRelation {
 
-    AND("relation:and"), OR("relation:or");
-
-    String value;
-
-    KoralRelation (String value) {
-        this.value = value;
-    }
+    AND, OR;
 
     @Override
     public String toString() {
-        return value;
+        return "relation:" + super.toString().toLowerCase();
     }
 }
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralSpan.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralSpan.java
new file mode 100644
index 0000000..bec50a3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralSpan.java
@@ -0,0 +1,75 @@
+package de.ids_mannheim.korap.query.elements;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KoralSpan implements Element {
+
+    private static final KoralType koralType = KoralType.SPAN;
+
+    private String key;
+    private String foundry;
+    private String layer;
+    private String matchOperator;
+    private List<KoralTerm> attr;
+
+    public KoralSpan (String key, String foundry, String layer,
+            MatchOperator operator) {
+        this.key = key;
+        this.foundry = foundry;
+        this.layer = layer;
+        this.matchOperator = operator.toString();
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getFoundry() {
+        return foundry;
+    }
+
+    public void setFoundry(String foundry) {
+        this.foundry = foundry;
+    }
+
+    public String getLayer() {
+        return layer;
+    }
+
+    public void setLayer(String layer) {
+        this.layer = layer;
+    }
+
+    public String getMatchOperator() {
+        return matchOperator;
+    }
+
+    public void setMatchOperator(String matchOperator) {
+        this.matchOperator = matchOperator;
+    }
+
+    public List<KoralTerm> getAttr() {
+        return attr;
+    }
+
+    public void setAttr(List<KoralTerm> attr) {
+        this.attr = attr;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", koralType.toString());
+        map.put("key", getKey());
+        map.put("foundry", getFoundry());
+        map.put("layer", getLayer());
+        map.put("match", getMatchOperator());
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralTerm.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralTerm.java
index 8f1f1c5..1960799 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/KoralTerm.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralTerm.java
@@ -3,6 +3,10 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class KoralTerm implements Element {
 
     public enum KoralTermType {
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralTermGroup.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralTermGroup.java
index 94e6f3d..54515fd 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/KoralTermGroup.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralTermGroup.java
@@ -9,6 +9,10 @@
 import de.ids_mannheim.korap.query.serialize.MapBuilder;
 import eu.clarin.sru.server.fcs.parser.QueryNode;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class KoralTermGroup implements Element {
 
     private static final KoralType type = KoralType.TERMGROUP;
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralToken.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralToken.java
index 751655f..6f3f221 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/KoralToken.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralToken.java
@@ -3,6 +3,10 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class KoralToken implements Element {
 
     private final static KoralType type = KoralType.TOKEN;
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/KoralType.java b/src/main/java/de/ids_mannheim/korap/query/elements/KoralType.java
index 23c45a6..ed5c456 100644
--- a/src/main/java/de/ids_mannheim/korap/query/elements/KoralType.java
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/KoralType.java
@@ -1,5 +1,9 @@
 package de.ids_mannheim.korap.query.elements;
 
+/**
+ * @author margaretha
+ * 
+ */
 public enum KoralType {
     TERMGROUP("koral:termGroup"), TERM("koral:term"), TOKEN("koral:token"), SPAN(
             "koral:span"), GROUP("koral:group"), BOUNDARY("koral:boundary"), RELATION(
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/MatchOperator.java b/src/main/java/de/ids_mannheim/korap/query/elements/MatchOperator.java
new file mode 100644
index 0000000..2963744
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/MatchOperator.java
@@ -0,0 +1,16 @@
+package de.ids_mannheim.korap.query.elements;
+
+public enum MatchOperator {
+    EQUALS("eq"), NOT_EQUALS("ne");
+
+    String value;
+
+    MatchOperator (String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "match:" + value;
+    };
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/elements/Scope.java b/src/main/java/de/ids_mannheim/korap/query/elements/Scope.java
new file mode 100644
index 0000000..02cc60f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/elements/Scope.java
@@ -0,0 +1,15 @@
+package de.ids_mannheim.korap.query.elements;
+
+public enum Scope {
+    SENTENCE("s"), PARAGRAPH("p"), TEXT("t");
+    String value;
+
+    Scope (String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
index 1dc4a4b..82b75de 100644
--- a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
@@ -1,18 +1,15 @@
 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 org.hamcrest.core.IsNot;
-
+import de.ids_mannheim.korap.query.elements.KoralRelation;
 import de.ids_mannheim.korap.query.elements.KoralTerm;
 import de.ids_mannheim.korap.query.elements.KoralTerm.KoralTermType;
 import de.ids_mannheim.korap.query.elements.KoralTermGroup;
 import de.ids_mannheim.korap.query.elements.KoralToken;
-import de.ids_mannheim.korap.query.elements.KoralRelation;
-import de.ids_mannheim.korap.query.elements.KoralGroup.Distance;
+import de.ids_mannheim.korap.query.elements.MatchOperator;
 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;
@@ -23,6 +20,10 @@
 import eu.clarin.sru.server.fcs.parser.QueryNode;
 import eu.clarin.sru.server.fcs.parser.RegexFlag;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class ExpressionParser {
 
     private static final String FOUNDRY_CNX = "cnx";
@@ -173,12 +174,14 @@
 
     private void parseOperator(KoralTerm koralTerm, Operator operator,
             boolean isNot) {
-        String matchOperator = null;
+        MatchOperator matchOperator = null;
         if (operator == null || operator == Operator.EQUALS) {
-            matchOperator = isNot ? "match:ne" : "match:eq";
+            matchOperator = isNot ? MatchOperator.NOT_EQUALS
+                    : MatchOperator.EQUALS;
         }
         else if (operator == Operator.NOT_EQUALS) {
-            matchOperator = isNot ? "match:eq" : "match:ne";
+            matchOperator = isNot ? MatchOperator.EQUALS
+                    : MatchOperator.NOT_EQUALS;
         }
         else {
             processor
@@ -187,7 +190,7 @@
                                     + " is unsupported.");
             koralTerm.setInvalid(true);
         }
-        koralTerm.setOperator(matchOperator);
+        koralTerm.setOperator(matchOperator.toString());
     }
 
     private void parseRegexFlags(KoralTerm koralTerm, Set<RegexFlag> set) {
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 ba440a3..a1c981f 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
@@ -1,10 +1,15 @@
 package de.ids_mannheim.korap.query.parse.fcsql;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import de.ids_mannheim.korap.query.elements.KoralGroup;
 import de.ids_mannheim.korap.query.elements.KoralOperation;
+import de.ids_mannheim.korap.query.elements.KoralGroup.Frame;
+import de.ids_mannheim.korap.query.elements.KoralSpan;
+import de.ids_mannheim.korap.query.elements.MatchOperator;
+import de.ids_mannheim.korap.query.elements.Scope;
 import de.ids_mannheim.korap.query.serialize.FCSQLQueryProcessor;
 import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 import eu.clarin.sru.server.fcs.parser.QueryDisjunction;
@@ -12,7 +17,13 @@
 import eu.clarin.sru.server.fcs.parser.QueryNode;
 import eu.clarin.sru.server.fcs.parser.QuerySegment;
 import eu.clarin.sru.server.fcs.parser.QuerySequence;
+import eu.clarin.sru.server.fcs.parser.QueryWithWithin;
+import eu.clarin.sru.server.fcs.parser.SimpleWithin;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class FCSSRUQueryParser {
 
     private FCSQLQueryProcessor processor;
@@ -38,8 +49,12 @@
         else if (queryNode instanceof QueryDisjunction) {
             return parseGroupQuery(queryNode.getChildren(),
                     KoralOperation.DISJUNCTION);
-            // } else if (queryNode instanceof QueryWithWithin) {
-
+        }
+        else if (queryNode instanceof QueryWithWithin) {
+            return parseWithinQuery((QueryWithWithin) queryNode);
+        }
+        else if (queryNode instanceof SimpleWithin) {
+            return parseFrame((SimpleWithin) queryNode);
         }
         else {
             processor.addError(StatusCodes.QUERY_TOO_COMPLEX,
@@ -49,6 +64,38 @@
         }
     }
 
+    private KoralSpan parseFrame(SimpleWithin frame) {
+        String foundry = "base";
+        String layer = "s"; // structure
+
+        if (frame.getScope() == null) {
+            processor.addError(StatusCodes.MALFORMED_QUERY,
+                    "FCS diagnostic 10: Within context is missing.");
+        }
+        switch (frame.getScope()) {
+            case SENTENCE:
+                return new KoralSpan(Scope.SENTENCE.toString(), foundry, layer,
+                        MatchOperator.EQUALS);
+            default:
+                processor.addError(StatusCodes.QUERY_TOO_COMPLEX,
+                        "FCS diagnostic 11:" + frame.toString()
+                                + " is currently unsupported.");
+                return null;
+        }
+    }
+
+    private KoralGroup parseWithinQuery(QueryWithWithin queryNode) {
+        KoralGroup koralGroup = new KoralGroup(KoralOperation.POSITION);
+        koralGroup.setFrames(Arrays.asList(Frame.IS_AROUND));
+
+        List<Object> operands = new ArrayList<Object>();
+        operands.add(parseQueryNode(queryNode.getWithin()));
+        operands.add(parseQueryNode(queryNode.getChild(0)));
+        koralGroup.setOperands(operands);
+
+        return koralGroup;
+    }
+
     private KoralGroup parseGroupQuery(List<QueryNode> children,
             KoralOperation operation) {
         KoralGroup koralGroup = new KoralGroup(operation);
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 1e973d8..d2b9d7a 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
@@ -1,15 +1,7 @@
 package de.ids_mannheim.korap.query.serialize;
 
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 
-import de.ids_mannheim.korap.query.elements.KoralGroup;
-import de.ids_mannheim.korap.query.elements.KoralTerm;
-import de.ids_mannheim.korap.query.elements.KoralOperation;
-import de.ids_mannheim.korap.query.elements.KoralType;
-import de.ids_mannheim.korap.query.elements.KoralGroup.Distance;
 import de.ids_mannheim.korap.query.parse.fcsql.FCSSRUQueryParser;
 import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 import eu.clarin.sru.server.SRUQueryBase;
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java b/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java
index 9dd6009..531b389 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java
@@ -3,10 +3,15 @@
 import java.util.Map;
 
 import de.ids_mannheim.korap.query.elements.KoralGroup;
+import de.ids_mannheim.korap.query.elements.KoralSpan;
 import de.ids_mannheim.korap.query.elements.KoralTerm;
 import de.ids_mannheim.korap.query.elements.KoralTermGroup;
 import de.ids_mannheim.korap.query.elements.KoralToken;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class MapBuilder {
 
     public static Map<String, Object> buildQueryMap(Object o) {
@@ -27,6 +32,10 @@
                 KoralTermGroup termGroup = (KoralTermGroup) o;
                 return termGroup.buildMap();
             }
+            else if (o instanceof KoralSpan) {
+                KoralSpan span = (KoralSpan) o;
+                return span.buildMap();
+            }
         }
         return null;
     }
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 b9026cb..2afb7cb 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
@@ -11,6 +11,10 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+/**
+ * @author margaretha
+ * 
+ */
 public class FCSQLQueryProcessorTest {
 
     QuerySerializer qs = new QuerySerializer();
@@ -288,4 +292,17 @@
         runAndValidate(query, jsonLd);
     }
 
+    @Test
+    public void testWithinQuery() throws JsonProcessingException {
+        String query = "[pos=\"VVFIN\"] within s";
+        String jsonLd = "{@type:koral:group,"
+                + "operation:operation:position,"
+                + "frames:["
+                + "frames:isAround"
+                + "],"
+                + "operands:[{@type: koral:span,key:s,foundry:base,layer:s,match:match:eq},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:VVFIN,foundry:tt,layer:p,type:type:regex,match:match:eq}}"
+                + "]}";
+        runAndValidate(query, jsonLd);
+    }
 }