- Annis: referencing direct declaration exprs
- QuerySerializer: empty map rather than null if no virtual collection specified
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 07afcd1..5972e47 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
@@ -28,7 +28,7 @@
 
 /**
  * Map representation of ANNIS QL syntax tree as returned by ANTLR
- * @author joachim
+ * @author Joachim Bingel (bingel@ids-mannheim.de)
  *
  */
 public class AnnisQueryProcessor extends Antlr4AbstractQueryProcessor {
@@ -48,7 +48,7 @@
     /**
      * Counter for variable definitions.
      */
-    Integer variableCounter = 1;
+    Integer variableCount = 1;
     /**
      * Marks the currently active token in order to know where to add flags (might already have been taken away from token stack).
      */
@@ -71,8 +71,7 @@
      * but are to be integrated into the AqlTree at a later point (namely as operands of the respective group). Therefore, store references to these
      * nodes here and exclude the operands from being written into the query map individually.   
      */
-    private List<ParseTree> globalLingTermNodes = new ArrayList<ParseTree>();
-    private int totalRelationCount;
+    private int totalRelationCount = 0;
     /**
      * Keeps a record of reference-class-mapping, i.e. which 'class' has been assigned to which #n reference. This is important when introducing korap:reference 
      * spans to refer back to previously established classes for entities.
@@ -92,6 +91,13 @@
      * any operands with the previous relation. Then wait until a relation with a shared operand has been processed.
      */
     private LinkedList<ParseTree> queuedRelations = new LinkedList<ParseTree>();
+    /**
+     * For some objects, it may be decided in the initial scan (processAndTopExpr()) that they
+     * need to be wrapped in a class operation when retrieved later. This map stores this information.
+     * More precisely, it stores for every node in the tree which class ID its derived Koral
+     * object will receive.
+     */
+    private LinkedHashMap<ParseTree, Integer> objectsToWrapInClass = new LinkedHashMap<ParseTree, Integer>();
 
     public AnnisQueryProcessor(String query) {
         KoralObjectGenerator.setQueryProcessor(this);
@@ -216,35 +222,49 @@
         // naturally as operands of the relations/groups introduced by the 
         // *node. For that purpose, this section mines all used references
         // and stores them in a list for later reference.
-        for (ParseTree exprNode : getChildrenWithCat(node,"expr")) {
-            // Pre-process any 'variableExpr' such that the variableReferences map can be filled
-            List<ParseTree> definitionNodes = new ArrayList<ParseTree>();
-            definitionNodes.addAll(getChildrenWithCat(exprNode, "variableExpr"));
-            for (ParseTree definitionNode : definitionNodes) {
-                processNode(definitionNode);
-            }
-            // Then, mine all relations between nodes
-            List<ParseTree> lingTermNodes = new ArrayList<ParseTree>();
-            lingTermNodes.addAll(getChildrenWithCat(exprNode, "n_ary_linguistic_term"));
-            globalLingTermNodes.addAll(lingTermNodes);
-            totalRelationCount  = globalLingTermNodes.size();
-            // Traverse refOrNode nodes under *ary_linguistic_term nodes and extract references
-            for (ParseTree lingTermNode : lingTermNodes) {
-                for (ParseTree refOrNode : getChildrenWithCat(lingTermNode, "refOrNode")) {
-                    String refOrNodeString = refOrNode.getChild(0).toStringTree(parser);
-                    if (refOrNodeString.startsWith("#")) {
-                        String ref = refOrNode.getChild(0).toStringTree(parser).substring(1);
-                        if (nodeReferencesTotal.containsKey(ref)) {
-                            nodeReferencesTotal.put(ref, nodeReferencesTotal.get(ref)+1);
-                        } else {
-                            nodeReferencesTotal.put(ref, 1);
-                            nodeReferencesProcessed.put(ref, 0);
-                        }
+        for (ParseTree lingTermNode : getDescendantsWithCat(node, "n_ary_linguistic_term")) {
+            for (ParseTree refOrNode : getChildrenWithCat(lingTermNode, "refOrNode")) {
+                String refOrNodeString = refOrNode.getChild(0).toStringTree(parser);
+                if (refOrNodeString.startsWith("#")) {
+                    String ref = refOrNode.getChild(0).toStringTree(parser).substring(1);
+                    if (nodeReferencesTotal.containsKey(ref)) {
+                        nodeReferencesTotal.put(ref, nodeReferencesTotal.get(ref)+1);
+                    } else {
+                        nodeReferencesTotal.put(ref, 1);
+                        nodeReferencesProcessed.put(ref, 0);   
                     }
                 }
             }
+            totalRelationCount++;
         }
-        if (verbose) System.err.println(nodeVariables);
+        // Then, mine all object definitions. 
+        for (ParseTree variableExprNode : getDescendantsWithCat(node, "variableExpr")) {
+            LinkedHashMap<String,Object> object = processVariableExpr(variableExprNode);
+            String ref = variableCount.toString();
+            // Check if this object definition is part of a "direct declaration relation", 
+            // i.e. a relation which declares its operands directly rather than using
+            // references to earlier declared objects. These 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 (nodeReferencesTotal.containsKey(ref)) {
+                    nodeReferencesTotal.put(ref, nodeReferencesTotal.get(ref)+1);
+                } else {
+                    nodeReferencesTotal.put(ref, 1);
+                }
+                // This is important for later relations wrapping the present relation.
+                // If the object isn't registered as processed, it won't be available
+                // for referencing.
+                nodeReferencesProcessed.put(ref, 1);  
+                // Register this node for latter wrapping in class.
+                if (nodeReferencesTotal.get(ref) > 1) {
+                    refClassMapping.put(ref, classCounter);
+                    objectsToWrapInClass.put(variableExprNode, classCounter++);    
+                }
+            }
+            nodeVariables.put(ref, object);
+            variableCount++;
+        }
     }
 
     private void processUnary_linguistic_term(ParseTree node) {
@@ -320,8 +340,10 @@
             if (getNodeCat(parentsFirstChild).endsWith("#")) {
                 nodeVariables.put(getNodeCat(parentsFirstChild).replaceAll("#", ""), object);
             }
-            nodeVariables.put(variableCounter.toString(), object);
-            variableCounter++;
+            if (objectsToWrapInClass.containsKey(node)) {
+                int classId = objectsToWrapInClass.get(node);
+                object = KoralObjectGenerator.wrapInClass(object, classId);
+            }
         }
         return object;
     }
@@ -332,7 +354,7 @@
      * a reference is created in its place. 
      * The operand will be wrapped in a class group if necessary.
      * @param operandNode
-     * @return A map object with the appropriate CQLF representation of the operand 
+     * @return A map object with the appropriate Koral representation of the operand 
      */
     private LinkedHashMap<String, Object> retrieveOperand(ParseTree operandNode) {
         LinkedHashMap<String, Object> operand = null;
@@ -373,6 +395,10 @@
         if (operandRef1.startsWith("#") && operandRef2.startsWith("#")) {
             operandRef1 = operandRef1.substring(1, operandRef1.length());
             operandRef2 = operandRef2.substring(1, operandRef2.length());
+            if (verbose) {
+                System.out.println(operandRef1);
+                System.out.println(nodeReferencesProcessed);   
+            }
             if (nodeReferencesProcessed.get(operandRef1) > 0 || 
                     nodeReferencesProcessed.get(operandRef2) > 0) {
                 return true;
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/Antlr4AbstractQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/Antlr4AbstractQueryProcessor.java
index aecc928..517338e 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/Antlr4AbstractQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/Antlr4AbstractQueryProcessor.java
@@ -78,6 +78,17 @@
         }
         return children;
     }
+    
+    protected List<ParseTree> getDescendantsWithCat(ParseTree node, String nodeCat) {
+        ArrayList<ParseTree> descendants = new ArrayList<ParseTree>();
+        for (ParseTree child : getChildren(node)) {
+            if (getNodeCat(child).equals(nodeCat)) {
+                descendants.add(child);
+            }
+            descendants.addAll(getDescendantsWithCat(child, nodeCat));
+        }
+        return descendants;
+    }
 
     protected ParseTree getFirstChildWithCat(ParseTree node, String nodeCat) {
         return getNthChildWithCat(node, nodeCat, 1);
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
index 5f3bf1e..b063f73 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
@@ -38,7 +38,7 @@
     public static String queryLanguageVersion;
 
     private AbstractQueryProcessor ast;
-    private Map<String, Object> collection;
+    private Map<String, Object> collection = new LinkedHashMap<String, Object>();
     private Map<String, Object> meta;
     private List<Object> errors;
     private List<Object> warnings;
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessorTest.java
index a8c996e..58a378f 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/AnnisQueryProcessorTest.java
@@ -663,14 +663,22 @@
         assertEquals("korap:group",         res.at("/query/@type").asText());
         assertEquals("operation:position",  res.at("/query/operation").asText());
         assertEquals("frames:startswith",   res.at("/query/frames/0").asText());
-        assertEquals("korap:group",         res.at("/query/operands/0/@type").asText());
-        assertEquals("operation:relation",  res.at("/query/operands/0/operation").asText());
-        // following can't be correct, would need class around and also reference around the relation to be accessed by the position
-        assertEquals("korap:span",          res.at("/query/operands/0/operands/0/@type").asText());
-        assertEquals("NP",                  res.at("/query/operands/0/operands/0/key").asText());
-        assertEquals("VP",                  res.at("/query/operands/0/operands/1/key").asText());
+        assertEquals("korap:reference",         res.at("/query/operands/0/@type").asText());
+        assertEquals("operation:focus",  res.at("/query/operands/0/operation").asText());
+        assertEquals(128,  res.at("/query/operands/0/classRef/0").asInt());
+        assertEquals("korap:group",         res.at("/query/operands/0/operands/0/@type").asText());
+        assertEquals("operation:relation",  res.at("/query/operands/0/operands/0/operation").asText());
+        assertEquals("operation:class",     res.at("/query/operands/0/operands/0/operands/0/operation").asText());
+        assertEquals(128,                   res.at("/query/operands/0/operands/0/operands/0/classOut").asInt());
+        assertEquals("korap:span",          res.at("/query/operands/0/operands/0/operands/0/operands/0/@type").asText());
+        assertEquals("NP",                  res.at("/query/operands/0/operands/0/operands/0/operands/0/key").asText());
+        assertEquals("operation:class",     res.at("/query/operands/0/operands/0/operands/1/operation").asText());
+        assertEquals(129,                   res.at("/query/operands/0/operands/0/operands/1/classOut").asInt());
+        assertEquals("VP",                  res.at("/query/operands/0/operands/0/operands/1/operands/0/key").asText());
         assertEquals("korap:reference",     res.at("/query/operands/1/@type").asText());
-
+        assertEquals("operation:focus",     res.at("/query/operands/1/operation").asText());
+        assertEquals(129,  res.at("/query/operands/1/classRef/0").asInt());
+        
         String eq2 =
                 "{@type=korap:group, operation=operation:position, frames=[frame:startswith], sharedClasses=[sharedClasses:includes], operands=[" +
                         "{@type=korap:group, operation=operation:relation, operands=[" +