Updated Annis pointing relation implementations.

Change-Id: I440c7d1b264b51a636b3a366411481514c05cc69
diff --git a/src/main/antlr/annis/AqlParser.g4 b/src/main/antlr/annis/AqlParser.g4
index 6ef84fe..a55abcc 100644
--- a/src/main/antlr/annis/AqlParser.g4
+++ b/src/main/antlr/annis/AqlParser.g4
@@ -57,6 +57,10 @@
 :	(foundry '/')? layer
 ;
 
+edgeType
+:	((foundry '/')? layer eqOperator)? textSpec
+;
+
 edgeAnno
 :	((foundry '/')? (layer COLON)? key eqOperator)? textSpec
 ;
@@ -76,18 +80,18 @@
 | PRECEDENCE (layer COMMA?)? rangeSpec #RangePrecedence
 ;
 
-// EM: it seems that dominance does not have qName
+// EM: dominance may have a type. Is qName appropriate for the dominance type?
 dominance
 : DOMINANCE (qName)? (LEFT_CHILD | RIGHT_CHILD)? (anno=edgeSpec)? # DirectDominance
 | DOMINANCE (qName)? STAR # IndirectDominance
 | DOMINANCE (qName)? rangeSpec? # RangeDominance
 ;
 
-// EM: qName is edgeAnno
+// EM: qName is edgeType
 pointing
-: POINTING (qName | edgeAnno) (anno=edgeSpec)? # DirectPointing
-| POINTING (qName | edgeAnno) (anno=edgeSpec)? STAR # IndirectPointing
-| POINTING (qName | edgeAnno) (anno=edgeSpec)? COMMA? rangeSpec # RangePointing
+: POINTING (qName | edgeType) (anno=edgeSpec)? # DirectPointing
+| POINTING (qName | edgeType) (anno=edgeSpec)? STAR # IndirectPointing
+| POINTING (qName | edgeType) (anno=edgeSpec)? COMMA? rangeSpec # RangePointing
 
 //: POINTING qName (anno=edgeSpec)? # DirectPointing
 //| POINTING qName (anno=edgeSpec)? STAR # IndirectPointing
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessor.java
index c193300..f076255 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessor.java
@@ -188,8 +188,8 @@
             if (!queuedRelations.isEmpty()) {
                 ParseTree queued = queuedRelations.pop();
                 if (verbose)
-                    System.out.println("Taking off queue (last rel): "
-                            + queued.getText());
+                    System.out.println(
+                            "Taking off queue (last rel): " + queued.getText());
                 if (checkOperandsProcessedPreviously(queued)) {
                     processNode(queued);
                 }
@@ -197,8 +197,7 @@
                     addError(StatusCodes.UNBOUND_ANNIS_RELATION,
                             "The relation " + queued.getText()
                                     + " is not bound to any other relations.");
-                    requestMap
-                            .put("query", new HashMap<String, Object>());
+                    requestMap.put("query", new HashMap<String, Object>());
                 }
             }
         }
@@ -235,8 +234,8 @@
                 ParseTree queued = queuedRelations.getFirst();
                 if (checkOperandsProcessedPreviously(queued)) {
                     if (verbose)
-                        System.out.println("Taking off queue: "
-                                + queued.getText());
+                        System.out.println(
+                                "Taking off queue: " + queued.getText());
                     queuedRelations.removeFirst();
                     processNode(queued);
                 }
@@ -326,8 +325,8 @@
                 "n_ary_linguistic_term")) {
             for (ParseTree refOrNode : getChildrenWithCat(lingTermNode,
                     "refOrNode")) {
-                String refOrNodeString = refOrNode.getChild(0).toStringTree(
-                        parser);
+                String refOrNodeString = refOrNode.getChild(0)
+                        .toStringTree(parser);
                 if (refOrNodeString.startsWith("#")) {
                     String ref = refOrNode.getChild(0).toStringTree(parser)
                             .substring(1);
@@ -367,8 +366,8 @@
             // objects must still be available for later reference, handle this 
             // here. Direct declaration relation is present when grandparent is
             // n_ary_linguistic_term node.
-            if (getNodeCat(variableExprNode.getParent().getParent()).equals(
-                    "n_ary_linguistic_term")) {
+            if (getNodeCat(variableExprNode.getParent().getParent())
+                    .equals("n_ary_linguistic_term")) {
                 if (nodeReferencesTotal.containsKey(ref)) {
                     nodeReferencesTotal.put(ref,
                             nodeReferencesTotal.get(ref) + 1);
@@ -413,8 +412,7 @@
         else if (firstChildNodeCat.equals("tok")) {
             object = KoralObjectGenerator.makeToken();
             if (node.getChildCount() > 1) { // empty tokens do not wrap a term
-                Map<String, Object> term = KoralObjectGenerator
-                        .makeTerm();
+                Map<String, Object> term = KoralObjectGenerator.makeTerm();
                 term.put("layer", "orth");
                 object.put("wrap", term);
             }
@@ -425,13 +423,11 @@
             // (e.g. cnx/cat=NP vs mate/pos=NN)
             // TODO generalize the list below -> look up layers associated with
             // tokens rather than spans somewhere
-            Map<String, Object> qNameParse = parseQNameNode(node
-                    .getChild(0));
+            Map<String, Object> qNameParse = parseQNameNode(node.getChild(0));
             if (Arrays.asList(new String[] { "p", "lemma", "m", "orth" })
                     .contains(qNameParse.get("layer"))) {
                 object = KoralObjectGenerator.makeToken();
-                Map<String, Object> term = KoralObjectGenerator
-                        .makeTerm();
+                Map<String, Object> term = KoralObjectGenerator.makeTerm();
                 object.put("wrap", term);
                 term.putAll(qNameParse);
             }
@@ -442,8 +438,7 @@
         }
         else if (firstChildNodeCat.equals("textSpec")) {
             object = KoralObjectGenerator.makeToken();
-            Map<String, Object> term = KoralObjectGenerator
-                    .makeTerm();
+            Map<String, Object> term = KoralObjectGenerator.makeTerm();
             object.put("wrap", term);
             term.put("layer", "orth");
             term.putAll(parseTextSpec(node.getChild(0)));
@@ -454,17 +449,13 @@
                 HashMap<String, Object> term = (HashMap<String, Object>) object
                         .get("wrap");
                 term.putAll(parseTextSpec(node.getChild(2)));
-                term.put(
-                        "match",
-                        parseMatchOperator(getFirstChildWithCat(node,
-                                "eqOperator")));
+                term.put("match", parseMatchOperator(
+                        getFirstChildWithCat(node, "eqOperator")));
             }
             else {
                 object.putAll(parseTextSpec(node.getChild(2)));
-                object.put(
-                        "match",
-                        parseMatchOperator(getFirstChildWithCat(node,
-                                "eqOperator")));
+                object.put("match", parseMatchOperator(
+                        getFirstChildWithCat(node, "eqOperator")));
             }
         }
 
@@ -642,8 +633,7 @@
                         .makeGroup(KoralOperation.RELATION);
                 Map<String, Object> relation = KoralObjectGenerator
                         .makeRelation();
-                Map<String, Object> term = KoralObjectGenerator
-                        .makeTerm();
+                Map<String, Object> term = KoralObjectGenerator.makeTerm();
                 term.put("layer", "c");
                 relation.put("wrap", term);
                 // commonancestor is an indirect commonparent relation
@@ -661,15 +651,14 @@
                 // for lowest level, add the underspecified node as first 
                 // operand and wrap it in a class group
                 if (i == 1) {
-                    innerOperands
-                            .add(KoralObjectGenerator.wrapInClass(
-                                    KoralObjectGenerator.makeSpan(),
-                                    classCounter + 128));
+                    innerOperands.add(KoralObjectGenerator.wrapInClass(
+                            KoralObjectGenerator.makeSpan(),
+                            classCounter + 128));
                     // add the first operand and wrap the whole group in a 
                     // focusing reference 
                     innerOperands.add(operand1);
-                    innerGroup = KoralObjectGenerator.wrapInReference(
-                            innerGroup, classCounter + 128);
+                    innerGroup = KoralObjectGenerator
+                            .wrapInReference(innerGroup, classCounter + 128);
                     outerOperands.add(innerGroup);
                 }
                 else {
@@ -694,8 +683,8 @@
                 // attributes defined in KoralQ. and can be handled more easily
             }
             else {
-                Map<String, Object> operatorGroup = parseOperatorNode(node
-                        .getChild(i).getChild(0));
+                Map<String, Object> operatorGroup = parseOperatorNode(
+                        node.getChild(i).getChild(0));
                 String groupType;
                 try {
                     groupType = (String) operatorGroup.get("groupType");
@@ -704,19 +693,22 @@
                     groupType = "relation";
                 }
                 if (groupType.equals("relation")
-//                        || groupType.equals("treeRelation")
-                    ) {
-                    group = KoralObjectGenerator.makeGroup(KoralOperation.RELATION);
+                //                        || groupType.equals("treeRelation")
+                ) {
+                    group = KoralObjectGenerator
+                            .makeGroup(KoralOperation.RELATION);
                     Map<String, Object> relation = new HashMap<String, Object>();
                     putAllButGroupType(relation, operatorGroup);
                     group.put("relType", relation);
                 }
                 else if (groupType.equals("sequence")) {
-                    group = KoralObjectGenerator.makeGroup(KoralOperation.SEQUENCE);
+                    group = KoralObjectGenerator
+                            .makeGroup(KoralOperation.SEQUENCE);
                     putAllButGroupType(group, operatorGroup);
                 }
                 else if (groupType.equals("hierarchy")) {
-                    group = KoralObjectGenerator.makeGroup(KoralOperation.HIERARCHY);
+                    group = KoralObjectGenerator
+                            .makeGroup(KoralOperation.HIERARCHY);
                     putAllButGroupType(group, operatorGroup);
                 }
                 else if (groupType.equals("position")) {
@@ -728,13 +720,13 @@
                 // (see following 'if')
                 operands = (ArrayList<Object>) group.get("operands");
 
-                ParseTree leftChildSpec = getFirstChildWithCat(node.getChild(i)
-                        .getChild(0), "@l");
-                ParseTree rightChildSpec = getFirstChildWithCat(node
-                        .getChild(i).getChild(0), "@r");
+                ParseTree leftChildSpec = getFirstChildWithCat(
+                        node.getChild(i).getChild(0), "@l");
+                ParseTree rightChildSpec = getFirstChildWithCat(
+                        node.getChild(i).getChild(0), "@r");
                 if (leftChildSpec != null || rightChildSpec != null) {
-                    KoralFrame frame = (leftChildSpec != null) ? KoralFrame.STARTS_WITH
-                            : KoralFrame.ENDS_WITH;
+                    KoralFrame frame = (leftChildSpec != null)
+                            ? KoralFrame.STARTS_WITH : KoralFrame.ENDS_WITH;
                     ArrayList<KoralFrame> frames = new ArrayList<KoralFrame>();
                     frames.add(frame);
                     Map<String, Object> positionGroup = KoralObjectGenerator
@@ -849,46 +841,45 @@
 
 
     @SuppressWarnings("unchecked")
-    private Map<String, Object> parseOperatorNode (
-            ParseTree operatorNode) {
+    private Map<String, Object> parseOperatorNode (ParseTree operatorNode) {
         Map<String, Object> relation = null;
         String operator = getNodeCat(operatorNode);
         // DOMINANCE
         if (operator.equals("dominance")) {
-//            relation = KoralObjectGenerator.makeRelation();
+            //            relation = KoralObjectGenerator.makeRelation();
             relation = new HashMap<String, Object>();
             relation.put("groupType", "hierarchy");
-//            ParseTree qName = getFirstChildWithCat(operatorNode, "qName");
+            //            ParseTree qName = getFirstChildWithCat(operatorNode, "qName");
             ParseTree edgeSpecNode = getFirstChildWithCat(operatorNode,
                     "edgeSpec");
             ParseTree star = getFirstChildWithCat(operatorNode, "*");
             ParseTree rangeSpec = getFirstChildWithCat(operatorNode,
                     "rangeSpec");
-            
-//            term.put("layer", "c");
-//            if (qName != null)
-//                term = parseQNameNode(qName);
+
+            //            term.put("layer", "c");
+            //            if (qName != null)
+            //                term = parseQNameNode(qName);
             if (edgeSpecNode != null) {
-                Map<String, Object> term = KoralObjectGenerator
-                        .makeTerm();
+                Map<String, Object> term = KoralObjectGenerator.makeTerm();
                 Map<String, Object> edgeSpec = parseEdgeSpec(edgeSpecNode);
                 String edgeSpecType = (String) edgeSpec.get("@type");
                 if (edgeSpecType.equals("koral:termGroup")) {
                     ((ArrayList<Object>) edgeSpec.get("operands")).add(term);
-//                    term = edgeSpec;
+                    //                    term = edgeSpec;
                 }
-//                else {
-//                    term = KoralObjectGenerator.makeTermGroup(KoralTermGroupRelation.AND);
-//                    ArrayList<Object> termGroupOperands = (ArrayList<Object>) term
-//                            .get("operands");
-//                    termGroupOperands.add(edgeSpec);
-//                    Map<String, Object> constTerm = KoralObjectGenerator
-//                            .makeTerm();
-//                    constTerm.put("layer", "c");
-//                    termGroupOperands.add(constTerm);
-//                }
+                //                else {
+                //                    term = KoralObjectGenerator.makeTermGroup(KoralTermGroupRelation.AND);
+                //                    ArrayList<Object> termGroupOperands = (ArrayList<Object>) term
+                //                            .get("operands");
+                //                    termGroupOperands.add(edgeSpec);
+                //                    Map<String, Object> constTerm = KoralObjectGenerator
+                //                            .makeTerm();
+                //                    constTerm.put("layer", "c");
+                //                    termGroupOperands.add(constTerm);
+                //                }
                 term = edgeSpec;
-                Map<String, Object> relType = KoralObjectGenerator.makeRelation();
+                Map<String, Object> relType = KoralObjectGenerator
+                        .makeRelation();
                 relType.put("wrap", term);
                 relation.put("relType", relType);
             }
@@ -902,28 +893,49 @@
             relation = KoralObjectGenerator.makeRelation();
             relation.put("groupType", "relation");
             ParseTree qName = getFirstChildWithCat(operatorNode, "qName");
-            ParseTree edgeSpec = getFirstChildWithCat(operatorNode, "edgeAnno");
+            ParseTree edgeType = getFirstChildWithCat(operatorNode, "edgeType");
+            ParseTree edgeSpec = getFirstChildWithCat(operatorNode, "edgeSpec");
             ParseTree star = getFirstChildWithCat(operatorNode, "*");
             ParseTree rangeSpec = getFirstChildWithCat(operatorNode,
                     "rangeSpec");
-            Map<String, Object> term = KoralObjectGenerator
-                    .makeTerm();
-            if (qName != null){
-                term.putAll(parseQNameNode(qName));
-                relation.put("wrap", term);
+            Map<String, Object> type = KoralObjectGenerator.makeTerm();
+            if (qName != null) {
+                type.putAll(parseQNameNode(qName));
             }
-            if (edgeSpec != null){
-                term.putAll(parseEdgeSpec(edgeSpec));
-                Map<String, Object> relType = KoralObjectGenerator.makeRelation();
-                relType.put("wrap", term);
-                relation.put("relType", relType);
+            else if (edgeType != null) {
+                type.putAll(parseEdgeAnno(edgeType, "key"));
             }
+
+            Map<String, Object> label = KoralObjectGenerator.makeTerm();
+            if (edgeSpec != null) {
+                label.putAll(parseEdgeSpec(edgeSpec));
+                relation.put("wrap", label);
+            }
+
+            if (qName != null || edgeType != null) {
+                if (edgeSpec == null) {
+                    relation.put("wrap", type);
+                }
+                else {
+                    Map<String, Object> termgroup = KoralObjectGenerator
+                            .makeTermGroup(KoralTermGroupRelation.AND);
+                    ArrayList<Object> termGroupOperands = (ArrayList<Object>) termgroup
+                            .get("operands");
+                    termGroupOperands.add(type);
+                    termGroupOperands.add(label);
+                    relation.put("wrap", termgroup);
+                }
+            }
+            else if (!label.isEmpty()) {
+                relation.put("wrap", label);
+            }
+
             if (star != null)
                 relation.put("boundary",
                         KoralObjectGenerator.makeBoundary(0, null));
             if (rangeSpec != null)
                 relation.put("boundary", boundaryFromRangeSpec(rangeSpec));
-            
+
         }
         else if (operator.equals("precedence")) {
             relation = new HashMap<String, Object>();
@@ -1021,9 +1033,10 @@
         }
     }
 
-    private Map<String, Object> parseEdgeAnno (ParseTree edgeAnnoSpec) {
-        Map<String, Object> edgeAnno = KoralObjectGenerator
-                .makeTerm();
+
+    private Map<String, Object> parseEdgeAnno (ParseTree edgeAnnoSpec,
+            String name) {
+        Map<String, Object> edgeAnno = KoralObjectGenerator.makeTerm();
         ParseTree textSpecNode = getFirstChildWithCat(edgeAnnoSpec, "textSpec");
         ParseTree keyNode = getFirstChildWithCat(edgeAnnoSpec, "key");
         ParseTree layerNode = getFirstChildWithCat(edgeAnnoSpec, "layer");
@@ -1031,28 +1044,32 @@
         ParseTree matchOperatorNode = getFirstChildWithCat(edgeAnnoSpec,
                 "eqOperator");
         if (foundryNode != null)
-            edgeAnno.put("foundry", foundryNode.getChild(0)
-                    .toStringTree(parser));
+            edgeAnno.put("foundry",
+                    foundryNode.getChild(0).toStringTree(parser));
         if (layerNode != null)
             edgeAnno.put("layer", layerNode.getChild(0).toStringTree(parser));
         if (keyNode != null)
             edgeAnno.put("key", keyNode.getChild(0).toStringTree(parser));
-        edgeAnno.putAll(parseTextSpec(textSpecNode, "value"));
+        edgeAnno.putAll(parseTextSpec(textSpecNode, name));
         edgeAnno.put("match", parseMatchOperator(matchOperatorNode));
         return edgeAnno;
     }
 
 
-    private Map<String, Object> boundaryFromRangeSpec (
-            ParseTree rangeSpec) {
+    private Map<String, Object> parseEdgeAnno (ParseTree edgeAnnoSpec) {
+        return parseEdgeAnno(edgeAnnoSpec, "value");
+    }
+
+
+    private Map<String, Object> boundaryFromRangeSpec (ParseTree rangeSpec) {
         return boundaryFromRangeSpec(rangeSpec, true);
     }
 
 
-    private Map<String, Object> boundaryFromRangeSpec (
-            ParseTree rangeSpec, boolean expandToMax) {
-        Integer min = Integer.parseInt(rangeSpec.getChild(0).toStringTree(
-                parser));
+    private Map<String, Object> boundaryFromRangeSpec (ParseTree rangeSpec,
+            boolean expandToMax) {
+        Integer min = Integer
+                .parseInt(rangeSpec.getChild(0).toStringTree(parser));
         Integer max = min;
         if (expandToMax)
             max = null;
@@ -1080,10 +1097,12 @@
         return KoralObjectGenerator.makeDistance("w", min, max);
     }
 
+
     private Map<String, Object> parseTextSpec (ParseTree node) {
         return parseTextSpec(node, "key");
     }
-    
+
+
     private Map<String, Object> parseTextSpec (ParseTree node, String name) {
         Map<String, Object> term = new HashMap<String, Object>();
         if (hasChild(node, "regex")) {
@@ -1184,8 +1203,8 @@
             parser.addErrorListener(errorListener);
             // Get starting rule from parser
             Method startRule = AqlParser.class.getMethod("start");
-            tree = (ParserRuleContext) startRule
-                    .invoke(parser, (Object[]) null);
+            tree = (ParserRuleContext) startRule.invoke(parser,
+                    (Object[]) null);
         }
         // Some things went wrong ...
         catch (Exception e) {
diff --git a/src/test/java/de/ids_mannheim/korap/query/test/annis/DominanceTests.java b/src/test/java/de/ids_mannheim/korap/query/test/annis/DominanceTests.java
index 11d0a3b..5a85703 100644
--- a/src/test/java/de/ids_mannheim/korap/query/test/annis/DominanceTests.java
+++ b/src/test/java/de/ids_mannheim/korap/query/test/annis/DominanceTests.java
@@ -147,7 +147,7 @@
         query = "node & node & #1 >2,4 #2";

         qs.setQuery(query, "annis");

         res = mapper.readTree(qs.toJSON());

-        System.out.println(res.asText());

+

         assertEquals("koral:group", res.at("/query/@type").asText());

         assertEquals("operation:hierarchy",

                 res.at("/query/operation").asText());

@@ -166,7 +166,7 @@
 

 

     @Test

-    public void testDominanceWithType ()

+    public void testDominanceWithLabel ()

             throws JsonProcessingException, IOException {

         query = "\"Mann\" & node & #2 >[func=\"SBJ\"] #1";

         //coordinates the func=SB term and requires a "c"-layer term (consituency relation/dominance)

@@ -184,7 +184,7 @@
 

 

     @Test

-    public void testDominanceWithLayerAndType ()

+    public void testDominanceWithLayerAndLabel ()

             throws JsonProcessingException, IOException {

         query = "\"Mann\" & node & #2 >[c:func=\"SBJ\"] #1";

         qs.setQuery(query, "annis");

@@ -201,7 +201,7 @@
 

 

     @Test

-    public void testDominanceWithMultipleTypes ()

+    public void testDominanceWithMultipleLabels ()

             throws JsonProcessingException, IOException {

         query = "corenlp/c=\"VP\" & corenlp/c=\"NP\" & #1 >[corenlp/c:func=\"PP\" corenlp/c:func=\"PN\"] #2";

         qs.setQuery(query, "annis");

diff --git a/src/test/java/de/ids_mannheim/korap/query/test/annis/RelationTests.java b/src/test/java/de/ids_mannheim/korap/query/test/annis/RelationTests.java
index 544f94b..fb243d9 100644
--- a/src/test/java/de/ids_mannheim/korap/query/test/annis/RelationTests.java
+++ b/src/test/java/de/ids_mannheim/korap/query/test/annis/RelationTests.java
@@ -1,5 +1,8 @@
 package de.ids_mannheim.korap.query.test.annis;

 

+import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertTrue;

+

 import java.io.IOException;

 

 import org.junit.Test;

@@ -17,32 +20,159 @@
     private ObjectMapper mapper = new ObjectMapper();

     private JsonNode res;

 

+

     @Test

     public void testTypedRelationWithArbritraryNodes ()

             throws JsonProcessingException, IOException {

         query = "node & node & #1 ->malt/d=\"PP\" #2";

         qs.setQuery(query, "annis");

         res = mapper.readTree(qs.toJSON());

-        System.out.println(res.asText());

+        //        System.out.println(res.asText());

+

+        assertEquals("koral:group", res.at("/query/@type").asText());

+        assertEquals("operation:relation", res.at("/query/operation").asText());

+        assertEquals("koral:span", res.at("/query/operands/0/@type").asText());

+        assertEquals("koral:span", res.at("/query/operands/1/@type").asText());

+

+        assertEquals("koral:relation", res.at("/query/relType/@type").asText());

+        assertEquals("koral:term",

+                res.at("/query/relType/wrap/@type").asText());

+        assertEquals("malt", res.at("/query/relType/wrap/foundry").asText());

+        assertEquals("d", res.at("/query/relType/wrap/layer").asText());

+        assertEquals("match:eq", res.at("/query/relType/wrap/match").asText());

+        assertEquals("PP", res.at("/query/relType/wrap/key").asText());

+        assertTrue(res.at("/query/relType/wrap/value").isMissingNode());

     }

-    

+

+

+    @Test

+    public void testFoundryLayerTypedRelationWithArbritraryNodes ()

+            throws JsonProcessingException, IOException {

+        query = "node & node & #1 ->malt/d #2";

+        qs.setQuery(query, "annis");

+        res = mapper.readTree(qs.toJSON());

+

+        assertEquals("koral:group", res.at("/query/@type").asText());

+        assertEquals("operation:relation", res.at("/query/operation").asText());

+

+        assertEquals("koral:relation", res.at("/query/relType/@type").asText());

+        assertEquals("koral:term",

+                res.at("/query/relType/wrap/@type").asText());

+        assertEquals("malt", res.at("/query/relType/wrap/foundry").asText());

+        assertEquals("d", res.at("/query/relType/wrap/layer").asText());

+        assertTrue(res.at("/query/relType/wrap/key").isMissingNode());

+        assertTrue(res.at("/query/relType/wrap/value").isMissingNode());

+    }

+

+

     @Test

     public void testTypedRelation ()

             throws JsonProcessingException, IOException {

         query = "corenlp/c=\"VP\" & corenlp/c=\"NP\" & #1 ->malt/d=\"PP\" #2";

         qs.setQuery(query, "annis");

         res = mapper.readTree(qs.toJSON());

+

+        assertEquals("koral:group", res.at("/query/@type").asText());

+        assertEquals("operation:relation", res.at("/query/operation").asText());

+        assertEquals("koral:span", res.at("/query/operands/0/@type").asText());

+        assertEquals("VP", res.at("/query/operands/0/key").asText());

+        assertEquals("c", res.at("/query/operands/0/layer").asText());

+        assertEquals("koral:span", res.at("/query/operands/1/@type").asText());

+        assertEquals("NP", res.at("/query/operands/1/key").asText());

+        assertEquals("c", res.at("/query/operands/1/layer").asText());

+

+        assertEquals("koral:relation", res.at("/query/relType/@type").asText());

+        assertEquals("koral:term",

+                res.at("/query/relType/wrap/@type").asText());

+        assertEquals("malt", res.at("/query/relType/wrap/foundry").asText());

+        assertEquals("d", res.at("/query/relType/wrap/layer").asText());

+        assertEquals("match:eq", res.at("/query/relType/wrap/match").asText());

+        assertEquals("PP", res.at("/query/relType/wrap/key").asText());

+        assertTrue(res.at("/query/relType/wrap/value").isMissingNode());

     }

 

+    @Test

+    public void testIndirectTypedRelation ()

+            throws JsonProcessingException, IOException {

+        query = "corenlp/c=\"VP\" & corenlp/c=\"NP\" & #1 ->malt/d=\"PP\" * #2";

+        qs.setQuery(query, "annis");

+        res = mapper.readTree(qs.toJSON());

+        

+        assertEquals("koral:relation", res.at("/query/relType/@type").asText());

+        assertEquals("koral:term",

+                res.at("/query/relType/wrap/@type").asText());

+        assertEquals("malt", res.at("/query/relType/wrap/foundry").asText());

+        assertEquals("d", res.at("/query/relType/wrap/layer").asText());

+        assertEquals("match:eq", res.at("/query/relType/wrap/match").asText());

+        assertEquals("PP", res.at("/query/relType/wrap/key").asText());

+        assertTrue(res.at("/query/relType/wrap/value").isMissingNode());

+        

+        assertEquals("koral:boundary", res.at("/query/relType/boundary/@type").asText());

+        assertEquals(0, res.at("/query/relType/boundary/min").asInt());

+        assertTrue(res.at("/query/relType/boundary/max").isMissingNode());

+    }

+    

+    @Test

+    public void testSpecificIndirectTypedRelation ()

+            throws JsonProcessingException, IOException {

+        query = "corenlp/c=\"VP\" & corenlp/c=\"NP\" & #1 ->malt/d=\"PP\" 2,4 #2";

+        qs.setQuery(query, "annis");

+        res = mapper.readTree(qs.toJSON());

+        

+        assertEquals("koral:boundary", res.at("/query/relType/boundary/@type").asText());

+        assertEquals(2, res.at("/query/relType/boundary/min").asInt());

+        assertEquals(4, res.at("/query/relType/boundary/max").asInt());

+    }

 

     @Test

     public void testTypedRelationWithLabel ()

             throws JsonProcessingException, IOException {

-        query = "corenlp/c=\"VP\" & corenlp/c=\"NP\" & #1 ->malt/d=\"PP\"[func=\"SB\"] #2";

+        query = "corenlp/c=\"VP\" & corenlp/c=\"NP\" & "

+                + "#1 ->malt/d=\"PP\"[func=\"SBJ\"] #2";

         qs.setQuery(query, "annis");

         res = mapper.readTree(qs.toJSON());

+        

+        assertEquals("koral:relation", res.at("/query/relType/@type").asText());

+        assertEquals("koral:termGroup",

+                res.at("/query/relType/wrap/@type").asText());

+        assertEquals("relation:and",

+                res.at("/query/relType/wrap/relation").asText());

+        assertEquals("malt", res.at("/query/relType/wrap/operands/0/foundry").asText());

+        assertEquals("d", res.at("/query/relType/wrap/operands/0/layer").asText());

+        assertEquals("PP", res.at("/query/relType/wrap/operands/0/key").asText());

+        assertTrue(res.at("/query/relType/wrap/operands/0/value").isMissingNode());

+        

+        assertEquals("match:eq", res.at("/query/relType/wrap/operands/1/match").asText());

+        assertTrue(res.at("/query/relType/wrap/operands/1/layer").isMissingNode());

+        assertEquals("func", res.at("/query/relType/wrap/operands/1/key").asText());

+        assertEquals("SBJ", res.at("/query/relType/wrap/operands/1/value").asText());

     }

 

+    // fix me

+    @Test

+    public void testTypedRelationWithMultipleLabels ()

+            throws JsonProcessingException, IOException {

+        query = "corenlp/c=\"VP\" & corenlp/c=\"NP\" & "

+                + "#1 ->malt/d=\"PP\"[func=\"SBJ\" func!=\"ADV\"] #2";

+        qs.setQuery(query, "annis");

+        res = mapper.readTree(qs.toJSON());

+        

+//        assertEquals("koral:relation", res.at("/query/relType/@type").asText());

+//        assertEquals("koral:termGroup",

+//                res.at("/query/relType/wrap/@type").asText());

+//        assertEquals("relation:and",

+//                res.at("/query/relType/wrap/relation").asText());

+//        assertEquals("malt", res.at("/query/relType/wrap/operands/0/foundry").asText());

+//        assertEquals("d", res.at("/query/relType/wrap/operands/0/layer").asText());

+//        assertEquals("PP", res.at("/query/relType/wrap/operands/0/key").asText());

+//        assertTrue(res.at("/query/relType/wrap/operands/0/value").isMissingNode());

+//        

+//        assertEquals("match:eq", res.at("/query/relType/wrap/operands/1/match").asText());

+//        assertTrue(res.at("/query/relType/wrap/operands/1/layer").isMissingNode());

+//        assertEquals("func", res.at("/query/relType/wrap/operands/1/key").asText());

+//        assertEquals("SBJ", res.at("/query/relType/wrap/operands/1/value").asText());

+    }

+    

     //  

     //  @Test

     //  public void testPointingRelations() throws Exception {