Updated dominance with type and label serialization.

Change-Id: I685ae7f8723ee6c87531665a035cd53b43de257a
diff --git a/src/main/antlr/annis/AqlParser.g4 b/src/main/antlr/annis/AqlParser.g4
index a55abcc..f510979 100644
--- a/src/main/antlr/annis/AqlParser.g4
+++ b/src/main/antlr/annis/AqlParser.g4
@@ -58,15 +58,14 @@
 ;
 
 edgeType
-:	((foundry '/')? layer eqOperator)? textSpec
-;
+: ID;
 
 edgeAnno
 :	((foundry '/')? (layer COLON)? key eqOperator)? textSpec
 ;
 
 edgeSpec
-: BRACKET_OPEN (edgeAnno WS*)+ BRACKET_CLOSE
+: BRACKET_OPEN edgeAnno BRACKET_CLOSE
 ;
 
 refOrNode
@@ -80,22 +79,16 @@
 | PRECEDENCE (layer COMMA?)? rangeSpec #RangePrecedence
 ;
 
-// 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
+: DOMINANCE (edgeType)? (LEFT_CHILD | RIGHT_CHILD)? (anno=edgeSpec)? # DirectDominance
+| DOMINANCE (edgeType)? STAR # IndirectDominance
+| DOMINANCE (edgeType)? rangeSpec? # RangeDominance
 ;
 
-// EM: qName is edgeType
 pointing
-: 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
-//| POINTING qName (anno=edgeSpec)? COMMA? rangeSpec # RangePointing
+: POINTING qName (anno=edgeSpec)? # DirectPointing
+| POINTING qName (anno=edgeSpec)? STAR # IndirectPointing
+| POINTING qName (anno=edgeSpec)? COMMA? rangeSpec # RangePointing
 ;
 
 spanrelation
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 f076255..0573fd4 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
@@ -4,11 +4,14 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 
+import org.antlr.v4.parse.ANTLRParser.throwsSpec_return;
 import org.antlr.v4.runtime.ANTLRInputStream;
 import org.antlr.v4.runtime.BailErrorStrategy;
 import org.antlr.v4.runtime.CharStream;
@@ -20,11 +23,13 @@
 import org.slf4j.LoggerFactory;
 
 import de.ids_mannheim.korap.query.object.KoralFrame;
+import de.ids_mannheim.korap.query.object.KoralMatchOperator;
 import de.ids_mannheim.korap.query.object.KoralOperation;
 import de.ids_mannheim.korap.query.object.KoralTermGroupRelation;
 import de.ids_mannheim.korap.query.parse.annis.AqlLexer;
 import de.ids_mannheim.korap.query.parse.annis.AqlParser;
 import de.ids_mannheim.korap.query.serialize.util.Antlr4DescriptiveErrorListener;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
 import de.ids_mannheim.korap.query.serialize.util.KoralObjectGenerator;
 import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 
@@ -685,6 +690,7 @@
             else {
                 Map<String, Object> operatorGroup = parseOperatorNode(
                         node.getChild(i).getChild(0));
+                // EM: change group type to enum KoralOperation
                 String groupType;
                 try {
                     groupType = (String) operatorGroup.get("groupType");
@@ -698,22 +704,31 @@
                     group = KoralObjectGenerator
                             .makeGroup(KoralOperation.RELATION);
                     Map<String, Object> relation = new HashMap<String, Object>();
-                    putAllButGroupType(relation, operatorGroup);
+                    putAllBut(relation, operatorGroup, "groupType");
                     group.put("relType", relation);
                 }
                 else if (groupType.equals("sequence")) {
                     group = KoralObjectGenerator
                             .makeGroup(KoralOperation.SEQUENCE);
-                    putAllButGroupType(group, operatorGroup);
+                    putAllBut(group, operatorGroup, "groupType");
                 }
                 else if (groupType.equals("hierarchy")) {
                     group = KoralObjectGenerator
                             .makeGroup(KoralOperation.HIERARCHY);
-                    putAllButGroupType(group, operatorGroup);
+                    if (operatorGroup.containsKey("edgeValue")) {
+                        String edgeValue = (String) operatorGroup
+                                .get("edgeValue");
+                        inheritFoundryAndLayer(edgeValue, operand1, operand2);
+                    }
+                    if (operatorGroup.containsKey("boundary")) {
+                        checkBoundary((Map<String, Object>) operatorGroup
+                                .get("boundary"), operand1, operand2);
+                    }
+                    putAllBut(group, operatorGroup, "groupType", "edgeValue");
                 }
                 else if (groupType.equals("position")) {
                     group = new HashMap<String, Object>();
-                    putAllButGroupType(group, operatorGroup);
+                    putAllBut(group, operatorGroup, "groupType");
                 }
 
                 // Get operands list before possible re-assignment of 'group'
@@ -813,6 +828,104 @@
     }
 
 
+    private void checkBoundary (Map<String, Object> boundary,
+            Map<String, Object> operand1, Map<String, Object> operand2) {
+
+        boolean isDirect = true;
+        if (boundary.containsKey("min")) {
+            if ((int) boundary.get("min") != 1) {
+                isDirect = false;
+            }
+        }
+        if (isDirect && boundary.containsKey("max")) {
+            if ((int) boundary.get("max") != 1) {
+                isDirect = false;
+            }
+        }
+
+        if (!isDirect) {
+
+            String layer1 = searchMap(operand1, "layer");
+            String layer2 = searchMap(operand2, "layer");
+
+            if (layer1 != null && layer2 != null && !layer1.equals(layer2)) {
+                addError(StatusCodes.INCOMPATIBLE_OPERATOR_AND_OPERAND,
+                        "Indirect dominance between operands of different layers is not possible.");
+                return;
+            }
+
+            String foundry1 = searchMap(operand1, "foundry");
+            String foundry2 = searchMap(operand2, "foundry");
+
+            if (foundry1 != null && foundry2 != null
+                    && !foundry1.equals(foundry2)) {
+                addError(StatusCodes.INCOMPATIBLE_OPERATOR_AND_OPERAND,
+                        "Indirect dominance between operands of different foundries is not possible.");
+            }
+        }
+    }
+
+
+    private String searchMap (Map<String, Object> operand, String key) {
+
+        String type = (String) operand.get("@type");
+        Map<String, Object> child;
+        if (type.equals("koral:token") && operand.containsKey("wrap")) {
+            return searchMap((Map<String, Object>) operand.get("wrap"), key);
+        }
+        else if (type.equals("koral:span")) {
+            // EM: legacy, should be deprecated later
+            if (operand.containsKey(key)) {
+                return (String) operand.get(key);
+            }
+            else if (operand.containsKey("wrap")) {
+                return searchMap((Map<String, Object>) operand.get("wrap"),
+                        key);
+            }
+        }
+        else if (type.equals("koral:term")) {
+            if (operand.containsKey(key)) {
+                return (String) operand.get(key);
+            }
+        }
+        else {
+            addError(StatusCodes.MALFORMED_QUERY,
+                    "Cannot determine the " + key + ".");
+        }
+
+        return null;
+    }
+
+
+    private void inheritFoundryAndLayer (String edgeValue,
+            Map<String, Object> operand1, Map<String, Object> operand2) {
+
+        Map<String, Object> attribute = KoralObjectGenerator.makeTerm();
+        attribute.put("key", edgeValue);
+        attribute.put("match", KoralMatchOperator.EQUALS.toString());
+        operand1.put("attr", attribute);
+
+        if (operand1.containsKey("layer")) {
+            attribute.put("layer", operand1.get("layer"));
+            if (operand1.containsKey("foundry")) {
+                attribute.put("foundry", operand1.get("foundry"));
+            }
+        }
+        else if (operand2.containsKey("layer")) {
+            attribute.put("layer", operand2.get("layer"));
+            if (operand2.containsKey("foundry")) {
+                attribute.put("foundry", operand2.get("foundry"));
+            }
+        }
+        else if (operand1.containsKey("foundry")) {
+            attribute.put("foundry", operand1.get("foundry"));
+        }
+        else if (operand2.containsKey("foundry")) {
+            attribute.put("foundry", operand2.get("foundry"));
+        }
+    }
+
+
     /**
      * Parses a unary_linguistic_operator node. Possible operators
      * are:
@@ -849,39 +962,18 @@
             //            relation = KoralObjectGenerator.makeRelation();
             relation = new HashMap<String, Object>();
             relation.put("groupType", "hierarchy");
-            //            ParseTree qName = getFirstChildWithCat(operatorNode, "qName");
+            // Ignored
+            // ParseTree edgeType = getFirstChildWithCat(operatorNode, "edgeType");
             ParseTree edgeSpecNode = getFirstChildWithCat(operatorNode,
                     "edgeSpec");
             ParseTree star = getFirstChildWithCat(operatorNode, "*");
             ParseTree rangeSpec = getFirstChildWithCat(operatorNode,
                     "rangeSpec");
 
-            //            term.put("layer", "c");
-            //            if (qName != null)
             //                term = parseQNameNode(qName);
             if (edgeSpecNode != null) {
-                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;
-                }
-                //                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();
-                relType.put("wrap", term);
-                relation.put("relType", relType);
+                relation.put("edgeValue", edgeSpec.get("value"));
             }
             if (star != null)
                 relation.put("boundary",
@@ -1019,18 +1111,23 @@
     @SuppressWarnings("unchecked")
     private Map<String, Object> parseEdgeSpec (ParseTree edgeSpec) {
         List<ParseTree> annos = getChildrenWithCat(edgeSpec, "edgeAnno");
-        if (annos.size() == 1)
-            return parseEdgeAnno(annos.get(0));
-        else {
-            Map<String, Object> termGroup = KoralObjectGenerator
-                    .makeTermGroup(KoralTermGroupRelation.AND);
-            ArrayList<Object> operands = (ArrayList<Object>) termGroup
-                    .get("operands");
-            for (ParseTree anno : annos) {
-                operands.add(parseEdgeAnno(anno));
-            }
-            return termGroup;
+        if (annos.size() != 1) {
+            addWarning(StatusCodes.MALFORMED_QUERY,
+                    "Multiple annotations are not allowed. "
+                            + "Processed only the first annotation.");
         }
+        return parseEdgeAnno(annos.get(0));
+
+        //        else {
+        //            Map<String, Object> termGroup = KoralObjectGenerator
+        //                    .makeTermGroup(KoralTermGroupRelation.AND);
+        //            ArrayList<Object> operands = (ArrayList<Object>) termGroup
+        //                    .get("operands");
+        //            for (ParseTree anno : annos) {
+        //                operands.add(parseEdgeAnno(anno));
+        //            }
+        //            return termGroup;
+        //        }
     }
 
 
@@ -1173,13 +1270,23 @@
     }
 
 
-    private void putAllButGroupType (Map<String, Object> container,
-            Map<String, Object> input) {
-        for (String key : input.keySet()) {
-            if (!key.equals("groupType")) {
-                container.put(key, input.get(key));
-            }
+    private void putAllBut (Map<String, Object> container,
+            Map<String, Object> input, String ... keys) {
+
+        Set<String> names = input.keySet();
+        for (String k : keys) {
+            names.remove(k);
         }
+
+        for (String n : names) {
+            container.put(n, input.get(n));
+        }
+
+        //        for (String key : input.keySet()) {
+        //            if (!key.equals("groupType")) {
+        //                container.put(key, input.get(key));
+        //            }
+        //        }
     }