Implemented relation queries with regex.

Change-Id: I1dafc835f6034d90a350d9688acdcf54bac28124
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java
index 9228c78..1ea89d8 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java
@@ -10,6 +10,7 @@
 import org.junit.Test;
 
 import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.SpanFocusQuery;
 import de.ids_mannheim.korap.query.SpanNextQuery;
 import de.ids_mannheim.korap.query.SpanRelationQuery;
@@ -36,7 +37,7 @@
         ki.commit();
         SpanRelationQuery sq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("base", ">:xip/syntax-dep_rel")),
-                true);
+                true, RelationDirection.RIGHT);
         sq.setSourceClass((byte) 1);
 
         SpanFocusQuery sfq = new SpanFocusQuery(sq, (byte) 1);
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java
index 177dbc7..b5d3412 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java
@@ -11,6 +11,7 @@
 import org.junit.Test;
 
 import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.DistanceConstraint;
 import de.ids_mannheim.korap.query.SpanClassQuery;
 import de.ids_mannheim.korap.query.SpanDistanceQuery;
@@ -44,7 +45,8 @@
         SpanFocusQuery sfq1 = new SpanFocusQuery(snq1, (byte) 2);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:child-of")), true);
+                new SpanTermQuery(new Term("base", "<:child-of")), true, 
+                RelationDirection.LEFT);
 
         SpanElementQuery seq2 = new SpanElementQuery("base", "pp");
         SpanClassQuery scq3 = new SpanClassQuery(seq2, (byte) 3);
@@ -126,7 +128,7 @@
         // nn -> prp
         SpanRelationQuery srq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("tokens", ">:stanford/d:tag")),
-                true);
+                true, RelationDirection.RIGHT);
         SpanRelationMatchQuery rq = new SpanRelationMatchQuery(srq, sfq2, scq2,
                 true);
 
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java
index 505d2bf..4d2ba30 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java
@@ -5,11 +5,14 @@
 import java.io.IOException;
 
 import org.apache.lucene.index.Term;
+import org.apache.lucene.sandbox.queries.regex.RegexQuery;
+import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
 import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 import org.junit.Test;
 
 import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.SpanAttributeQuery;
 import de.ids_mannheim.korap.query.SpanClassQuery;
 import de.ids_mannheim.korap.query.SpanElementQuery;
@@ -19,6 +22,7 @@
 import de.ids_mannheim.korap.query.SpanSegmentQuery;
 import de.ids_mannheim.korap.query.SpanTermWithIdQuery;
 import de.ids_mannheim.korap.query.SpanWithAttributeQuery;
+import de.ids_mannheim.korap.response.Match;
 import de.ids_mannheim.korap.response.Result;
 
 /*
@@ -114,6 +118,30 @@
                         + ":xip/syntax-dep_rel$<b>33<i>6<i>7<i>6<i>9<s>2<s>1<s>0]");
         return fd;
     }
+    
+    public static FieldDocument createFieldDoc3 () {
+        FieldDocument fd = new FieldDocument();
+        fd.addString("ID", "doc-1");
+        fd.addTV("base", "ceccecdeed",
+                "[(0-1)s:c$<s>2|<>:p$<b>64<i>0<i>3<i>3<b>0<s>1|_0$<i>0<i>1|"
+                        + ">:xip/dep_rel$<b>35<i>0<i>1<i>1<i>2<i>3<i>6<i>9<s>1<s>1<s>2|"
+                        + ">:xip/dep_rel$<b>33<i>1<i>2<i>6<i>9<s>2<s>1<s>0|"
+                        + "@:func=subj$<b>18<s>2]"
+                        + "[(1-2)s:e|_1$<i>1<i>2|<>:p$<b>64<i>1<i>3<i>3<b>0<s>1]"
+                        + "[(2-3)s:c|_2$<i>2<i>3]"
+                        + "[(3-4)s:c|s:b|_3$<i>3<i>4]"
+                        + "[(4-5)s:e|s:d|_4$<i>4<i>5]"
+                        + "[(5-6)s:c|_5$<i>5<i>6]"
+                        + "[(6-7)s:d$<s>2|<>:p$<b>64<i>6<i>9<i>9<b>0<s>1|_6$<i>6<i>7|"
+                        + ">:xip/dep_rel$<b>34<i>3<i>4<i>9<i>9<s>1<s>1<s>0|"
+                        + "<:xip/dep_rel$<b>35<i>3<i>4<i>4<i>5<i>9<i>1<i>3<s>1<s>1<s>2|"
+                        + "<:xip/dep_rel$<b>34<i>5<i>6<i>9<i>1<s>1<s>2<s>0|"
+                        + "@:func=obj$<b>18<s>2]" + "[(7-8)s:e|_7$<i>7<i>8]"
+                        + "[(8-9)s:e|s:b|_8$<i>8<i>9]"
+                        + "[(9-10)s:d$<s>1|_9$<i>9<i>10|<"
+                        + ":xip/dep_rel$<b>33<i>6<i>7<i>6<i>9<s>2<s>1<s>0]");
+        return fd;
+    }
 
 
     public static FieldDocument createFieldDoc2 () {
@@ -215,6 +243,29 @@
 //        }
 //
 //    }
+    @Test
+    public void testRelationWithRegex () throws IOException {
+        ki.addDoc(createFieldDoc0());
+        ki.addDoc(createFieldDoc3());
+        ki.commit();
+
+        SpanQuery sq; 
+        sq = new SpanRelationQuery(
+                new SpanMultiTermQueryWrapper<RegexQuery>(
+                        new RegexQuery(new Term("base", ">:xip/.*"))),
+                true, RelationDirection.RIGHT);
+        kr = ki.search(sq, (short) 10);
+        
+        assertEquals((long) 7, kr.getTotalResults());
+        
+        sq = new SpanRelationQuery(
+                new SpanMultiTermQueryWrapper<RegexQuery>(
+                        new RegexQuery(new Term("base", "<:xip/.*"))),
+                true, RelationDirection.LEFT);
+        kr = ki.search(sq, (short) 10);
+        
+        assertEquals((long) 7, kr.getTotalResults());
+    }
     
     /**
      * Relations: token to token, token to span, span to span
@@ -227,7 +278,7 @@
 
         SpanRelationQuery sq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("base", ">:xip/syntax-dep_rel")),
-                true);
+                true, RelationDirection.RIGHT);
         kr = ki.search(sq, (short) 10);
 
         assertEquals((long) 7, kr.getTotalResults());
@@ -269,7 +320,7 @@
 
         SpanRelationQuery sq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("base", "<:xip/syntax-dep_rel")),
-                true);
+                true, RelationDirection.LEFT);
         kr = ki.search(sq, (short) 10);
 
         assertEquals((long) 7, kr.getTotalResults());
@@ -306,7 +357,8 @@
 
         // child-of relations
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         kr = ki.search(srq, (short) 20);
 
         assertEquals((long) 13, kr.getTotalResults());
@@ -334,7 +386,8 @@
         ki.commit();
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
 
         SpanFocusQuery fq = new SpanFocusQuery(srq, srq.getTempClassNumbers());
         fq.setMatchTemporaryClass(true);
@@ -382,7 +435,8 @@
         SpanClassQuery scq1 = new SpanClassQuery(seq1, (byte) 1);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
 
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, scq1, true);
         SpanFocusQuery rv = new SpanFocusQuery(rm, (byte) 1);
@@ -406,7 +460,8 @@
 
         // return all parents that are NP
         srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:child-of")), true);
+                new SpanTermQuery(new Term("base", "<:child-of")), 
+                true, RelationDirection.LEFT);
         rm = new SpanRelationMatchQuery(srq, scq1, true);
         rv = new SpanFocusQuery(rm, (byte) 1);
         kr = ki.search(rv, (short) 10);
@@ -439,7 +494,8 @@
 
         // target of a dependency relation
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:dep")), true);
+                new SpanTermQuery(new Term("base", "<:dep")), true, 
+                RelationDirection.LEFT);
         kr = ki.search(srq, (short) 10);
         assertEquals((long) 6, kr.getTotalResults());
 
@@ -482,7 +538,8 @@
         SpanClassQuery scq1 = new SpanClassQuery(seq1, (byte) 1);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), true, 
+                RelationDirection.RIGHT);
         srq.setTargetClass((byte) 2);
 
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, scq1, true);
@@ -528,7 +585,8 @@
         SpanTermWithIdQuery tq = new SpanTermWithIdQuery(
                 new Term("base", "pos:NN"), true);
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:dep")), true);
+                new SpanTermQuery(new Term("base", "<:dep")), true, 
+                RelationDirection.LEFT);
         srq.setSourceClass((byte) 1);
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, tq, true);
         SpanQuery rv = new SpanFocusQuery(rm, (byte) 1);
@@ -544,7 +602,8 @@
 
         // return target of dep relations from pos:NN
         srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:dep")), true);
+                new SpanTermQuery(new Term("base", ">:dep")), true, 
+                RelationDirection.RIGHT);
         srq.setTargetClass((byte) 1);
         rm = new SpanRelationMatchQuery(srq, tq, true);
         rv = new SpanFocusQuery(rm, (byte) 1);
@@ -578,7 +637,8 @@
         SpanElementQuery seq1 = new SpanElementQuery("base", "np");
         SpanClassQuery scq1 = new SpanClassQuery(seq1, (byte) 1);
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:child-of")), true);
+                new SpanTermQuery(new Term("base", "<:child-of")), 
+                true, RelationDirection.LEFT);
         srq.setSourceClass((byte) 2);
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, scq1, true);
         SpanFocusQuery rv = new SpanFocusQuery(rm, (byte) 2);
@@ -620,7 +680,8 @@
         SpanClassQuery scq2 = new SpanClassQuery(seq, (byte) 2);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
 
@@ -661,7 +722,8 @@
         assertEquals(6, kr.getMatch(1).getEndPos());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
 
@@ -752,7 +814,8 @@
         assertEquals((long) 3, kr.getTotalResults());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
         kr = ki.search(srq, (short) 20);
@@ -834,7 +897,8 @@
         assertEquals((long) 3, kr.getTotalResults());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
         kr = ki.search(srq, (short) 20);
@@ -908,7 +972,8 @@
         assertEquals((long) 3, kr.getTotalResults());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
         kr = ki.search(srq, (short) 20);
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
index 2423ae6..67acf96 100644
--- a/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
@@ -105,12 +105,13 @@
                 "focus(#[1,2]spanSegment(spanRelation(tokens:>:mate/d:HEAD), <tokens:c:s />))",
                 sq.toString());
     }
-    
+
     @Test
     public void testMatchRelationSourceToken () throws QueryException {
         //
         String filepath = getClass()
-                .getResource("/queries/relation/match-source-token.json").getFile();
+                .getResource("/queries/relation/match-source-token.json")
+                .getFile();
         SpanQueryWrapper sqwi = getJSONQuery(filepath);
         SpanQuery sq = sqwi.toQuery();
         assertEquals(
@@ -118,7 +119,7 @@
                 sq.toString());
     }
 
-    
+
     @Test
     public void testMatchRelationTarget () throws QueryException {
         //
@@ -240,8 +241,21 @@
                 sq.toString());
     }
 
+    @Test
+    public void testTypedRelationWithAnnotationRegex () throws QueryException {
+        String filepath = getClass()
+                .getResource(
+                        "/queries/relation/typed-relation-with-key-regex.json")
+                .getFile();
+        SpanQueryWrapper sqwi = getJSONQuery(filepath);
+        SpanQuery sq = sqwi.toQuery();
+        assertEquals("focus(#[1,2]spanSegment(<tokens:corenlp/c:NP />, "
+                + "focus(#2: spanSegment(spanRelation(SpanMultiTermQueryWrapper(tokens:/>:malt/d:.*/)), "
+                + "<tokens:corenlp/c:VP />))))", sq.toString());
+    }
 
     // EM: should relation term allow empty key?
+    // EM: or should this be interpreted as any key, i.e. [func=/.*/]
     @Test
     public void testTypedRelationWithoutKey () throws QueryException {
 
@@ -297,11 +311,11 @@
         SpanQuery sq = sqwi.toQuery();
         assertEquals(
                 "focus(#[1,2]spanSegment(tokens:tt/p:VVINF, "
-                + "focus(#2: spanSegment("
-                + "spanRelation(tokens:>:malt/d:KONJ), tokens:tt/p:KOUI))))",
+                        + "focus(#2: spanSegment("
+                        + "spanRelation(tokens:>:malt/d:KONJ), tokens:tt/p:KOUI))))",
                 sq.toString());
 
     }
-    
+
     // EM: handle empty koral:span
 }
diff --git a/src/test/resources/queries/relation/indirect-typed-relation-without-key.json b/src/test/resources/queries/relation/indirect-typed-relation-without-key.json
new file mode 100644
index 0000000..f641b8f
--- /dev/null
+++ b/src/test/resources/queries/relation/indirect-typed-relation-without-key.json
@@ -0,0 +1,41 @@
+{
+    "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
+    "query": {
+        "operation": "operation:relation",
+        "operands": [
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "VP"
+                },
+                "@type": "koral:span"
+            },
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "NP"
+                },
+                "@type": "koral:span"
+            }
+        ],
+        "@type": "koral:group",
+        "relType": {
+            "wrap": {
+                "@type": "koral:term",
+                "layer": "d",
+                "foundry": "malt"
+            },
+            "@type": "koral:relation",
+            "boundary": {
+                "min": 0,
+                "@type": "koral:boundary"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/indirect-typed-relation.json b/src/test/resources/queries/relation/indirect-typed-relation.json
new file mode 100644
index 0000000..17a92ed
--- /dev/null
+++ b/src/test/resources/queries/relation/indirect-typed-relation.json
@@ -0,0 +1,43 @@
+{
+    "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
+    "query": {
+        "operation": "operation:relation",
+        "operands": [
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "VP"
+                },
+                "@type": "koral:span"
+            },
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "NP"
+                },
+                "@type": "koral:span"
+            }
+        ],
+        "@type": "koral:group",
+        "relType": {
+            "wrap": {
+                "@type": "koral:term",
+                "layer": "d",
+                "foundry": "malt",
+                "match": "match:eq",
+                "key": "DET"
+            },
+            "@type": "koral:relation",
+            "boundary": {
+                "min": 0,
+                "@type": "koral:boundary"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/typed-relation-with-key-regex.json b/src/test/resources/queries/relation/typed-relation-with-key-regex.json
new file mode 100644
index 0000000..bee6c78
--- /dev/null
+++ b/src/test/resources/queries/relation/typed-relation-with-key-regex.json
@@ -0,0 +1,40 @@
+{
+    "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
+    "query": {
+        "operation": "operation:relation",
+        "operands": [
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "VP"
+                },
+                "@type": "koral:span"
+            },
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "NP"
+                },
+                "@type": "koral:span"
+            }
+        ],
+        "@type": "koral:group",
+        "relType": {
+        	"@type": "koral:relation",
+            "wrap": {
+                "@type": "koral:term",
+                "layer": "d",
+                "foundry": "malt",
+                "match": "match:eq",
+                "type": "type:regex",
+                "key": ".*"
+            }
+        }
+    }
+}
\ No newline at end of file