Improved error handling for antlr3 and antlr4 trees:
- Error listeners and message formatters
- Cosmas grammar incorporates setter method for error listener
- No more QueryExceptions, instead return empty query serialization along with error msg
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/AbstractSyntaxTree.java b/src/main/java/de/ids_mannheim/korap/query/serialize/AbstractSyntaxTree.java
index ffdceae..624e42c 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/AbstractSyntaxTree.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/AbstractSyntaxTree.java
@@ -62,6 +62,7 @@
 		requestMap.put("warnings", warnings);
 		requestMap.put("messages", messages);
 		requestMap.put("collection", collection);
+		requestMap.put("query", new LinkedHashMap<String, Object>());
 		requestMap.put("meta", new LinkedHashMap<String, Object>());
 	}
 	
@@ -89,6 +90,10 @@
 		List<Object> error = Arrays.asList(new Object[]{code, msg}); 
 		errors.add(error);
 	}
+
+	public void addError(List<Object> fullErrorMsg) {
+		errors.add(fullErrorMsg);
+	}
 	
 	public Map<String, Object> getRequestMap() {
 		return requestMap;
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java b/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java
index d5e473c..bb56d65 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/AqlTree.java
@@ -104,16 +104,9 @@
 			throw new NullPointerException("Parser has not been instantiated!"); 
 		}
 		log.info("Processing Annis query.");
-		log.info("AST is: "+tree.toStringTree(parser));
-		System.out.println("Processing Annis QL");
-		if (verbose) System.out.println(tree.toStringTree(parser));
-		processNode(tree);
-		log.info(requestMap.toString());
-		
-		for (String ref : nodeVariables.keySet()) {
-			if (nodeReferencesTotal.get(ref) == null) {
-				System.err.println(ref);
-			}
+		if (tree != null) {
+			log.debug("ANTLR parse tree: "+tree.toStringTree(parser));
+			processNode(tree);
 		}
 	}
 
@@ -733,19 +726,12 @@
 			Method startRule = AqlParser.class.getMethod("start"); 
 			tree = (ParserRuleContext) startRule.invoke(parser, (Object[])null);
 		}
-
 		// Some things went wrong ...
 		catch (Exception e) {
-			System.err.println("ERROR: "+errorListener.generateFullErrorMsg());
-			log.error(e.getMessage());
+			log.error("Could not parse query. Please make sure it is well-formed.");
+			log.error(errorListener.generateFullErrorMsg().toString());
+			addError(errorListener.generateFullErrorMsg());
 		}
-
-		if (tree == null) {
-			log.error("Could not parse query. Make sure it is correct ANNIS QL syntax.");
-			throw new QueryException("Could not parse query. Make sure it is correct ANNIS QL syntax.");
-		}
-
-		// Return the generated tree
 		return tree;
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/CosmasTree.java b/src/main/java/de/ids_mannheim/korap/query/serialize/CosmasTree.java
index 5d94bd2..9803cf0 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/CosmasTree.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/CosmasTree.java
@@ -2,7 +2,7 @@
 
 import de.ids_mannheim.korap.query.cosmas2.c2psLexer;
 import de.ids_mannheim.korap.query.cosmas2.c2psParser;
-import de.ids_mannheim.korap.query.serialize.util.CosmasCondition;
+import de.ids_mannheim.korap.query.serialize.util.Antlr3DescriptiveErrorListener;
 import de.ids_mannheim.korap.query.serialize.util.ResourceMapper;
 import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 import de.ids_mannheim.korap.util.QueryException;
@@ -16,7 +16,6 @@
 
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.Table;
-import com.google.common.collect.TreeBasedTable;
 
 import java.util.*;
 import java.util.regex.Matcher;
@@ -25,7 +24,7 @@
 /**
  * Map representation of CosmasII syntax tree as returned by ANTLR
  *
- * @author bingel
+ * @author Joachim Bingel (bingel@ids-mannheim.de)
  * @version 0.2
  */
 public class CosmasTree extends Antlr3AbstractSyntaxTree {
@@ -106,16 +105,12 @@
 	@Override
 	public void process(String query) throws QueryException {
 		Tree tree = null;
-		try {
-			tree = parseCosmasQuery(query);
-		} catch (RecognitionException e) {
-			throw new QueryException("Your query could not be processed. Please make sure it is well-formed.");
-		} catch (NullPointerException e) {
-			throw new QueryException("Your query could not be processed. Please make sure it is well-formed.");
-		}
+		tree = parseCosmasQuery(query);
 		log.info("Processing CosmasII query");
-		processNode(tree);
-		log.info(requestMap.toString());
+		if (tree != null) {
+			log.debug("ANTLR parse tree: "+tree.toStringTree());
+			processNode(tree);
+		}
 	}
 
 	@SuppressWarnings("unchecked")
@@ -1025,30 +1020,6 @@
 		putIntoSuperObject(object, 0);
 	}
 
-
-	private Tree parseCosmasQuery(String q) throws RecognitionException {
-		q = rewritePositionQuery(q);
-
-		Tree tree = null;
-		ANTLRStringStream ss = new ANTLRStringStream(q);
-		c2psLexer lex = new c2psLexer(ss);
-		org.antlr.runtime.CommonTokenStream tokens = new org.antlr.runtime.CommonTokenStream(lex);  //v3
-		parser = new c2psParser(tokens);
-		c2psParser.c2ps_query_return c2Return = ((c2psParser) parser).c2ps_query();  // statt t().
-		// AST Tree anzeigen:
-		tree = (Tree) c2Return.getTree();
-
-		String treestring = tree.toStringTree();
-		if (treestring.contains("<mismatched token") || treestring.contains("<error") || treestring.contains("<unexpected")) {
-			log.error("Invalid tree. Could not parse Cosmas query. Make sure it is well-formed.");
-			throw new RecognitionException();
-		}
-		if (verbose) {
-			System.out.println(tree.toStringTree());
-		}
-		return tree;
-	}
-
 	/**
 	 * Normalises position operators to equivalents using #BED  
 	 */
@@ -1071,4 +1042,40 @@
 		}
 		return rewrittenQuery;
 	}
+
+	private Tree parseCosmasQuery(String query) {
+		query = rewritePositionQuery(query);
+		Tree tree = null;
+		Antlr3DescriptiveErrorListener errorListener = new Antlr3DescriptiveErrorListener(query);
+		try {
+			ANTLRStringStream ss = new ANTLRStringStream(query);
+			c2psLexer lex = new c2psLexer(ss);
+			org.antlr.runtime.CommonTokenStream tokens = new org.antlr.runtime.CommonTokenStream(lex);  //v3
+			parser = new c2psParser(tokens);
+	
+			((c2psParser) parser).setErrorReporter(errorListener); // Use the custom error reporter
+			c2psParser.c2ps_query_return c2Return = ((c2psParser) parser).c2ps_query();  // statt t().
+			// AST Tree anzeigen:
+			tree = (Tree) c2Return.getTree();
+
+		} catch (RecognitionException e) {
+			log.error("Could not parse query. Please make sure it is well-formed.");
+			addError(StatusCodes.MALFORMED_QUERY, "Could not parse query. Please make sure it is well-formed.");
+		}
+		String treestring = tree.toStringTree();
+		
+		boolean erroneous = false;
+		if (parser.failed() || parser.getNumberOfSyntaxErrors() > 0) {
+			erroneous = true;
+			tree = null;
+		}
+
+		if (erroneous || treestring.contains("<mismatched token") || 
+				treestring.contains("<error") || treestring.contains("<unexpected")) {
+			log.error("Could not parse query. Please make sure it is well-formed.");
+			log.error(errorListener.generateFullErrorMsg().toString());
+			addError(errorListener.generateFullErrorMsg());
+		}
+		return tree;
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java
index 3117220..ff525a0 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java
@@ -35,7 +35,6 @@
 	 */
 	public PoliqarpPlusTree(String query) throws QueryException {
 		process(query);
-		System.out.println(">>> " + requestMap + " <<<");
 		log.info(">>> " + requestMap.get("query") + " <<<");
 	}
 
@@ -45,7 +44,10 @@
 		tree = parsePoliqarpQuery(query);
 		super.parser = this.parser;
 		log.info("Processing PoliqarpPlus");
-		processNode(tree);
+		if (tree != null) {
+			log.debug("ANTLR parse tree: "+tree.toStringTree(parser));
+			processNode(tree);
+		}
 	}
 
 	/**
@@ -671,14 +673,14 @@
 	}
 
 
-	private ParserRuleContext parsePoliqarpQuery(String p) throws QueryException {
+	private ParserRuleContext parsePoliqarpQuery(String query) throws QueryException {
 		Lexer lexer = new PoliqarpPlusLexer((CharStream) null);
 		ParserRuleContext tree = null;
 		Antlr4DescriptiveErrorListener errorListener = new Antlr4DescriptiveErrorListener(query);
 		// Like p. 111
 		try {
 			// Tokenize input data
-			ANTLRInputStream input = new ANTLRInputStream(p);
+			ANTLRInputStream input = new ANTLRInputStream(query);
 			lexer.setInputStream(input);
 			CommonTokenStream tokens = new CommonTokenStream(lexer);
 			parser = new PoliqarpPlusParser(tokens);
@@ -693,21 +695,13 @@
 			// Get starting rule from parser
 			Method startRule = PoliqarpPlusParser.class.getMethod("request");
 			tree = (ParserRuleContext) startRule.invoke(parser, (Object[]) null);
-			log.debug(tree.toStringTree(parser));
 		}
 		// Some things went wrong ...
 		catch (Exception e) {
-			log.error("Could not parse query. Please make sure it is well-formed.");;
-			log.error("Underlying error is: "+e.getMessage());
-			System.err.println(e.getMessage());
+			log.error("Could not parse query. Please make sure it is well-formed.");
+			log.error(errorListener.generateFullErrorMsg().toString());
+			addError(errorListener.generateFullErrorMsg());
 		}
-
-		if (tree == null) {
-			throw new QueryException("The query you specified could not be processed. Please make sure it is well-formed.");
-		}
-		// Return the generated tree
-		log.info("ANTLR parse tree: "+tree.toStringTree(parser));
-		System.out.println("ANTLR parse tree: "+tree.toStringTree(parser));
 		return tree;
 	}
 }
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 a34f7c2..635b2c8 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
@@ -3,7 +3,7 @@
 import com.fasterxml.jackson.core.JsonGenerationException;
 import com.fasterxml.jackson.databind.JsonMappingException;
 
-import de.ids_mannheim.korap.query.poliqarp.PoliqarpPlusParser;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
 import de.ids_mannheim.korap.util.QueryException;
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.utils.KorAPLogger;
@@ -94,7 +94,7 @@
      *
      * @param outFile       The file to which the serialization is written
      * @param query         The query string
-     * @param queryLanguage The query language. As of 13/11/20, this must be either 'poliqarp' or 'poliqarpplus'. Some extra maven stuff needs to done to support CosmasII ('cosmas') [that maven stuff would be to tell maven how to build the cosmas grammar and where to find the classes]
+     * @param queryLanguage The query language. As of 17 Dec 2014, this must be one of 'poliqarpplus', 'cosmas2', 'annis' or 'cql'. 
      * @throws IOException
      * @throws QueryException
      */
@@ -120,7 +120,7 @@
             throws QueryException {
 
         if (query == null || query.isEmpty())
-            throw new QueryException(406, "No Content!");
+            throw new QueryException(StatusCodes.NO_QUERY, "No Content!");
 
         try {
             if (ql.equalsIgnoreCase("poliqarp")) {
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java b/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java
index 61a97e2..2f5c237 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java
@@ -1,12 +1,19 @@
 package de.ids_mannheim.korap.query.serialize;
 
 import de.ids_mannheim.korap.util.QueryException;
+
 import org.apache.commons.lang.StringUtils;
 
+import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Stack;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -15,366 +22,375 @@
  * @date 10/12/2013
  */
 public class QueryUtils {
-//
-//    /**
-//     * Returns the category (or 'label') of the root of a ParseTree (ANTLR 4).
-//     *
-//     * @param node
-//     * @return
-//     */
-//    public static String getNodeCat(ParseTree node) {
-//        String nodeCat = node.toStringTree(parser);
-//        Pattern p = Pattern.compile("\\((.*?)\\s"); // from opening parenthesis to 1st whitespace
-//        Matcher m = p.matcher(node.toStringTree(parser));
-//        if (m.find()) {
-//            nodeCat = m.group(1);
-//        }
-//        return nodeCat;
-//    }
-//
-//    /**
-//     * Returns the category (or 'label') of the root of a ParseTree (ANTLR 3).
-//     *
-//     * @param node
-//     * @return
-//     */
-//    public static String getNodeCat(Tree node) {
-//        String nodeCat = node.toStringTree();
-//        Pattern p = Pattern.compile("\\((.*?)\\s"); // from opening parenthesis to 1st whitespace
-//        Matcher m = p.matcher(node.toStringTree());
-//        if (m.find()) {
-//            nodeCat = m.group(1);
-//        }
-//        return nodeCat;
-//    }
-//
-//
-//    /**
-//     * Tests whether a certain node has a child by a certain name
-//     *
-//     * @param node     The parent node.
-//     * @param childCat The category of the potential child.
-//     * @return true iff one or more children belong to the specified category
-//     */
-//    public static boolean hasChild(Tree node, String childCat) {
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//            if (getNodeCat(node.getChild(i)).equals(childCat)) {
-//                return true;
-//            }
-//        }
-//        return false;
-//    }
-//    
-//    /**
-//     * Tests whether a certain node has a child by a certain name
-//     *
-//     * @param node     The parent node.
-//     * @param childCat The category of the potential child.
-//     * @return true iff one or more children belong to the specified category
-//     */
-//    public static boolean hasChild(ParseTree node, String childCat) {
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//            if (getNodeCat(node.getChild(i)).equals(childCat)) {
-//                return true;
-//            }
-//        }
-//        return false;
-//    }
-//
-//    public static boolean hasDescendant(ParseTree node, String childCat) {
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//            ParseTree child = node.getChild(i);
-//            if (getNodeCat(child).equals(childCat)) {
-//                return true;
-//            }
-//            if (hasDescendant(child, childCat)) {
-//                return true;
-//            }
-//        }
-//        return false;
-//    }
-//
-//    public static List<Tree> getChildrenWithCat(Tree node, String nodeCat) {
-//        ArrayList<Tree> children = new ArrayList<Tree>();
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
-//                children.add(node.getChild(i));
-//            }
-//        }
-//        return children;
-//    }
-//
-//    public static List<ParseTree> getChildrenWithCat(ParseTree node, String nodeCat) {
-//        ArrayList<ParseTree> children = new ArrayList<ParseTree>();
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
-//                children.add(node.getChild(i));
-//            }
-//        }
-//        return children;
-//    }
-//
-//    public static List<ParseTree> getChildren(ParseTree node) {
-//        ArrayList<ParseTree> children = new ArrayList<ParseTree>();
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//                children.add(node.getChild(i));
-//        }
-//        return children;
-//    }
-//    
-//    public static Tree getFirstChildWithCat(Tree node, String nodeCat) {
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
-//                return node.getChild(i);
-//            }
-//        }
-//        return null;
-//    }
-//
-//    public static ParseTree getFirstChildWithCat(ParseTree node, String nodeCat) {
-//        for (int i = 0; i < node.getChildCount(); i++) {
-//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
-//                return node.getChild(i);
-//            }
-//        }
-//        return null;
-//    }
-//    
-//    /**
-//     * Checks whether a node only serves as a container for another node (e.g. in (cq_segment ( cg_seg_occ ...)), the cq_segment node does not contain
-//     * any information and only contains the cq_seg_occ node.  
-//     * @param node The node to check
-//     * @return true iff the node is a container only.
-//     */
-//    public static boolean isContainerOnly(ParseTree node) {
-//    	String[] validNodeNamesArray = "cq_segment sq_segment element empty_segments".split(" ");
-//    	List<String> validNodeNames = Arrays.asList(validNodeNamesArray);
-//    	List<ParseTree> children = getChildren(node);
-//    	for (ParseTree child : children) {
-//    		if (validNodeNames.contains(getNodeCat(child))) {
-//    			return false;
-//    		}
-//    	}
-//    	return true;
-//    }
+	//
+	//    /**
+	//     * Returns the category (or 'label') of the root of a ParseTree (ANTLR 4).
+	//     *
+	//     * @param node
+	//     * @return
+	//     */
+	//    public static String getNodeCat(ParseTree node) {
+	//        String nodeCat = node.toStringTree(parser);
+	//        Pattern p = Pattern.compile("\\((.*?)\\s"); // from opening parenthesis to 1st whitespace
+	//        Matcher m = p.matcher(node.toStringTree(parser));
+	//        if (m.find()) {
+	//            nodeCat = m.group(1);
+	//        }
+	//        return nodeCat;
+	//    }
+	//
+	//    /**
+	//     * Returns the category (or 'label') of the root of a ParseTree (ANTLR 3).
+	//     *
+	//     * @param node
+	//     * @return
+	//     */
+	//    public static String getNodeCat(Tree node) {
+	//        String nodeCat = node.toStringTree();
+	//        Pattern p = Pattern.compile("\\((.*?)\\s"); // from opening parenthesis to 1st whitespace
+	//        Matcher m = p.matcher(node.toStringTree());
+	//        if (m.find()) {
+	//            nodeCat = m.group(1);
+	//        }
+	//        return nodeCat;
+	//    }
+	//
+	//
+	//    /**
+	//     * Tests whether a certain node has a child by a certain name
+	//     *
+	//     * @param node     The parent node.
+	//     * @param childCat The category of the potential child.
+	//     * @return true iff one or more children belong to the specified category
+	//     */
+	//    public static boolean hasChild(Tree node, String childCat) {
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//            if (getNodeCat(node.getChild(i)).equals(childCat)) {
+	//                return true;
+	//            }
+	//        }
+	//        return false;
+	//    }
+	//    
+	//    /**
+	//     * Tests whether a certain node has a child by a certain name
+	//     *
+	//     * @param node     The parent node.
+	//     * @param childCat The category of the potential child.
+	//     * @return true iff one or more children belong to the specified category
+	//     */
+	//    public static boolean hasChild(ParseTree node, String childCat) {
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//            if (getNodeCat(node.getChild(i)).equals(childCat)) {
+	//                return true;
+	//            }
+	//        }
+	//        return false;
+	//    }
+	//
+	//    public static boolean hasDescendant(ParseTree node, String childCat) {
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//            ParseTree child = node.getChild(i);
+	//            if (getNodeCat(child).equals(childCat)) {
+	//                return true;
+	//            }
+	//            if (hasDescendant(child, childCat)) {
+	//                return true;
+	//            }
+	//        }
+	//        return false;
+	//    }
+	//
+	//    public static List<Tree> getChildrenWithCat(Tree node, String nodeCat) {
+	//        ArrayList<Tree> children = new ArrayList<Tree>();
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
+	//                children.add(node.getChild(i));
+	//            }
+	//        }
+	//        return children;
+	//    }
+	//
+	//    public static List<ParseTree> getChildrenWithCat(ParseTree node, String nodeCat) {
+	//        ArrayList<ParseTree> children = new ArrayList<ParseTree>();
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
+	//                children.add(node.getChild(i));
+	//            }
+	//        }
+	//        return children;
+	//    }
+	//
+	//    public static List<ParseTree> getChildren(ParseTree node) {
+	//        ArrayList<ParseTree> children = new ArrayList<ParseTree>();
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//                children.add(node.getChild(i));
+	//        }
+	//        return children;
+	//    }
+	//    
+	//    public static Tree getFirstChildWithCat(Tree node, String nodeCat) {
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
+	//                return node.getChild(i);
+	//            }
+	//        }
+	//        return null;
+	//    }
+	//
+	//    public static ParseTree getFirstChildWithCat(ParseTree node, String nodeCat) {
+	//        for (int i = 0; i < node.getChildCount(); i++) {
+	//            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
+	//                return node.getChild(i);
+	//            }
+	//        }
+	//        return null;
+	//    }
+	//    
+	//    /**
+	//     * Checks whether a node only serves as a container for another node (e.g. in (cq_segment ( cg_seg_occ ...)), the cq_segment node does not contain
+	//     * any information and only contains the cq_seg_occ node.  
+	//     * @param node The node to check
+	//     * @return true iff the node is a container only.
+	//     */
+	//    public static boolean isContainerOnly(ParseTree node) {
+	//    	String[] validNodeNamesArray = "cq_segment sq_segment element empty_segments".split(" ");
+	//    	List<String> validNodeNames = Arrays.asList(validNodeNamesArray);
+	//    	List<ParseTree> children = getChildren(node);
+	//    	for (ParseTree child : children) {
+	//    		if (validNodeNames.contains(getNodeCat(child))) {
+	//    			return false;
+	//    		}
+	//    	}
+	//    	return true;
+	//    }
 
-    public static void checkUnbalancedPars(String q) throws QueryException {
-        int openingPars = StringUtils.countMatches(q, "(");
-        int closingPars = StringUtils.countMatches(q, ")");
-        int openingBrkts = StringUtils.countMatches(q, "[");
-        int closingBrkts = StringUtils.countMatches(q, "]");
-        int openingBrcs = StringUtils.countMatches(q, "{");
-        int closingBrcs = StringUtils.countMatches(q, "}");
-        if (openingPars != closingPars) throw new QueryException(
-                "Your query string contains an unbalanced number of parantheses.");
-        if (openingBrkts != closingBrkts) throw new QueryException(
-                "Your query string contains an unbalanced number of brackets.");
-        if (openingBrcs != closingBrcs) throw new QueryException(
-                "Your query string contains an unbalanced number of braces.");
-    }
+	public static SimpleEntry<String, Integer> checkUnbalancedPars(String q) {
+		Map<Character, Character> brackets = new HashMap<Character, Character>();
+		brackets.put('[', ']');
+		brackets.put('{', '}');
+		brackets.put('(', ')');
+		Set<Character> allChars = new HashSet<Character>();
+		allChars.addAll(brackets.keySet());
+		allChars.addAll(brackets.values());
+		int lastOpenBracket = 0;
+		
+		final Stack<Character> stack = new Stack<Character>();
+		for (int i = 0; i < q.length(); i++) {
+			if (!allChars.contains(q.charAt(i))) continue;
+			if (brackets.containsKey(q.charAt(i))) {
+				stack.push(q.charAt(i));
+				lastOpenBracket = i;
+			} else if (stack.empty() || (q.charAt(i) != brackets.get(stack.pop()))) {
+				return new SimpleEntry<String, Integer>("Parantheses/brackets unbalanced.",i);
+			} 
+		}
+		if (!stack.empty()) return new SimpleEntry<String, Integer>("Parantheses/brackets unbalanced.", lastOpenBracket);
+		return null;
+	}
 
-    public static List<String> parseMorph(String stringTree) {
+	public static List<String> parseMorph(String stringTree) {
 
-        ArrayList<String> morph = new ArrayList<String>();
-        return morph;
-    }
+		ArrayList<String> morph = new ArrayList<String>();
+		return morph;
+	}
 
 
-    public static String buildCypherQuery(String cypher, String ctypel, String ctyper,
-                                          int cl, int cr, int page, int limit) {
-        //todo: implies that there is only one type allowed!
-        String sctypel = "", sctyper = "";
-        switch (ctypel) {
-            case "C":
-                sctypel = "chars";
-                break;
-            case "T":
-                sctypel = "tokens";
-                break;
-        }
-        switch (ctyper) {
-            case "C":
-                sctyper = "chars";
-                break;
-            case "T":
-                sctyper = "tokens";
-                break;
-        }
+	public static String buildCypherQuery(String cypher, String ctypel, String ctyper,
+			int cl, int cr, int page, int limit) {
+		//todo: implies that there is only one type allowed!
+		String sctypel = "", sctyper = "";
+		switch (ctypel) {
+		case "C":
+			sctypel = "chars";
+			break;
+		case "T":
+			sctypel = "tokens";
+			break;
+		}
+		switch (ctyper) {
+		case "C":
+			sctyper = "chars";
+			break;
+		case "T":
+			sctyper = "tokens";
+			break;
+		}
 
-        StringBuffer buffer = new StringBuffer();
-        buffer.append("<query><cypher><![CDATA[");
-        buffer.append(cypher);
-        buffer.append("]]></cypher>");
-        buffer.append("<wordAliasPrefix>wtok_</wordAliasPrefix>");
-        buffer.append("<contextColumn>sent</contextColumn>");
-        buffer.append("<contextIdColumn>sid</contextIdColumn>");
-        buffer.append("<textColumn>txt</textColumn>");
-        buffer.append("<startIndex>");
-        buffer.append(page);
-        buffer.append("</startIndex>");
-        buffer.append("<itemsPerPage>");
-        buffer.append(limit);
-        buffer.append("</itemsPerPage>");
-        buffer.append("<context>");
-        buffer.append("<left>");
-        buffer.append("<" + sctypel + ">");
-        buffer.append(cl);
-        buffer.append("</" + sctypel + ">");
-        buffer.append("</left>");
-        buffer.append("<right>");
-        buffer.append("<" + sctyper + ">");
-        buffer.append(cr);
-        buffer.append("</" + sctyper + ">");
-        buffer.append("</right>");
-        buffer.append("</context>");
-        buffer.append("</query>");
-        return buffer.toString();
-    }
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("<query><cypher><![CDATA[");
+		buffer.append(cypher);
+		buffer.append("]]></cypher>");
+		buffer.append("<wordAliasPrefix>wtok_</wordAliasPrefix>");
+		buffer.append("<contextColumn>sent</contextColumn>");
+		buffer.append("<contextIdColumn>sid</contextIdColumn>");
+		buffer.append("<textColumn>txt</textColumn>");
+		buffer.append("<startIndex>");
+		buffer.append(page);
+		buffer.append("</startIndex>");
+		buffer.append("<itemsPerPage>");
+		buffer.append(limit);
+		buffer.append("</itemsPerPage>");
+		buffer.append("<context>");
+		buffer.append("<left>");
+		buffer.append("<" + sctypel + ">");
+		buffer.append(cl);
+		buffer.append("</" + sctypel + ">");
+		buffer.append("</left>");
+		buffer.append("<right>");
+		buffer.append("<" + sctyper + ">");
+		buffer.append(cr);
+		buffer.append("</" + sctyper + ">");
+		buffer.append("</right>");
+		buffer.append("</context>");
+		buffer.append("</query>");
+		return buffer.toString();
+	}
 
-    public static String buildDotQuery(long sid, String graphdb_id) {
-        StringBuffer b = new StringBuffer();
-        b.append("<query>");
-        b.append("<sentenceId>");
-        b.append(sid);
-        b.append("</sentenceId>");
-        b.append("<gdbId>");
-        b.append(graphdb_id);
-        b.append("</gdbId>");
-        b.append("<hls>");
-        b.append("<hl>");
-        b.append(40857);
-        b.append("</hl>");
-        b.append("<hl>");
-        b.append(40856);
-        b.append("</hl>");
-        b.append("</hls>");
-        b.append("</query>");
+	public static String buildDotQuery(long sid, String graphdb_id) {
+		StringBuffer b = new StringBuffer();
+		b.append("<query>");
+		b.append("<sentenceId>");
+		b.append(sid);
+		b.append("</sentenceId>");
+		b.append("<gdbId>");
+		b.append(graphdb_id);
+		b.append("</gdbId>");
+		b.append("<hls>");
+		b.append("<hl>");
+		b.append(40857);
+		b.append("</hl>");
+		b.append("<hl>");
+		b.append(40856);
+		b.append("</hl>");
+		b.append("</hls>");
+		b.append("</query>");
 
-        return b.toString();
-    }
+		return b.toString();
+	}
 
-    public String buildaggreQuery(String query) {
-        StringBuffer b = new StringBuffer();
-        b.append("<query><cypher><![CDATA[");
-        b.append(query);
-        b.append("]]></cypher>");
-        b.append("<columns>");
-        b.append("<column agg='true' sum='false'>");
-        b.append("<cypherAlias>");
-        b.append("aggBy");
-        b.append("</cypherAlias>");
-        b.append("<displayName>");
-        b.append("Aggregate");
-        b.append("</displayName>");
-        b.append("</column>");
+	public String buildaggreQuery(String query) {
+		StringBuffer b = new StringBuffer();
+		b.append("<query><cypher><![CDATA[");
+		b.append(query);
+		b.append("]]></cypher>");
+		b.append("<columns>");
+		b.append("<column agg='true' sum='false'>");
+		b.append("<cypherAlias>");
+		b.append("aggBy");
+		b.append("</cypherAlias>");
+		b.append("<displayName>");
+		b.append("Aggregate");
+		b.append("</displayName>");
+		b.append("</column>");
 
-        b.append("<column agg='fals' sum='true'>");
-        b.append("<cypherAlias>");
-        b.append("cnt");
-        b.append("</cypherAlias>");
-        b.append("<displayName>");
-        b.append("Count");
-        b.append("</displayName>");
-        b.append("</column>");
-        b.append("</columns>");
+		b.append("<column agg='fals' sum='true'>");
+		b.append("<cypherAlias>");
+		b.append("cnt");
+		b.append("</cypherAlias>");
+		b.append("<displayName>");
+		b.append("Count");
+		b.append("</displayName>");
+		b.append("</column>");
+		b.append("</columns>");
 
-        b.append("</query>");
-        return b.toString();
-    }
+		b.append("</query>");
+		return b.toString();
+	}
 
-    @Deprecated
-    public static Map addParameters(Map request, int page, int num, String cli, String cri,
-                                    int cls, int crs, boolean cutoff) {
-        Map ctx = new LinkedHashMap();
-        List left = new ArrayList();
-        left.add(cli);
-        left.add(cls);
-        List right = new ArrayList();
-        right.add(cri);
-        right.add(crs);
-        ctx.put("left", left);
-        ctx.put("right", right);
+	@Deprecated
+	public static Map addParameters(Map request, int page, int num, String cli, String cri,
+			int cls, int crs, boolean cutoff) {
+		Map ctx = new LinkedHashMap();
+		List left = new ArrayList();
+		left.add(cli);
+		left.add(cls);
+		List right = new ArrayList();
+		right.add(cri);
+		right.add(crs);
+		ctx.put("left", left);
+		ctx.put("right", right);
 
-        request.put("startPage", page);
-        request.put("count", num);
-        request.put("context", ctx);
-        request.put("cutOff", cutoff);
+		request.put("startPage", page);
+		request.put("count", num);
+		request.put("context", ctx);
+		request.put("cutOff", cutoff);
 
-        return request;
-    }
+		return request;
+	}
 
-    public static void prepareContext(LinkedHashMap<String, Object> requestMap) {
-        LinkedHashMap<String, Object> context = new LinkedHashMap<String, Object>();
+	public static void prepareContext(LinkedHashMap<String, Object> requestMap) {
+		LinkedHashMap<String, Object> context = new LinkedHashMap<String, Object>();
 
-        LinkedHashMap<String, Object> classMap = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> operands = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> operation = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> frame = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> classRef = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> spanRef = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> classRefOp = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> min = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> max = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> exclude = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> distances = new LinkedHashMap<String, Object>();
-        LinkedHashMap<String, Object> inOrder = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> classMap = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> operands = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> operation = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> frame = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> classRef = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> spanRef = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> classRefOp = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> min = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> max = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> exclude = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> distances = new LinkedHashMap<String, Object>();
+		LinkedHashMap<String, Object> inOrder = new LinkedHashMap<String, Object>();
 
-        operation.put("@id", "group:operation/");
-        operation.put("@type", "@id");
+		operation.put("@id", "group:operation/");
+		operation.put("@type", "@id");
 
-        classMap.put("@id", "group:class");
-        classMap.put("@type", "xsd:integer");
+		classMap.put("@id", "group:class");
+		classMap.put("@type", "xsd:integer");
 
-        operands.put("@id", "group:operands");
-        operands.put("@container", "@list");
+		operands.put("@id", "group:operands");
+		operands.put("@container", "@list");
 
-        frame.put("@id", "group:frame/");
-        frame.put("@type", "@id");
+		frame.put("@id", "group:frame/");
+		frame.put("@type", "@id");
 
-        classRef.put("@id", "group:classRef");
-        classRef.put("@type", "xsd:integer");
+		classRef.put("@id", "group:classRef");
+		classRef.put("@type", "xsd:integer");
 
-        spanRef.put("@id", "group:spanRef");
-        spanRef.put("@type", "xsd:integer");
+		spanRef.put("@id", "group:spanRef");
+		spanRef.put("@type", "xsd:integer");
 
-        classRefOp.put("@id", "group:classRefOp");
-        classRefOp.put("@type", "@id");
+		classRefOp.put("@id", "group:classRefOp");
+		classRefOp.put("@type", "@id");
 
-        min.put("@id", "boundary:min");
-        min.put("@type", "xsd:integer");
+		min.put("@id", "boundary:min");
+		min.put("@type", "xsd:integer");
 
-        max.put("@id", "boundary:max");
-        max.put("@type", "xsd:integer");
+		max.put("@id", "boundary:max");
+		max.put("@type", "xsd:integer");
 
-        exclude.put("@id", "group:exclude");
-        exclude.put("@type", "xsd:boolean");
+		exclude.put("@id", "group:exclude");
+		exclude.put("@type", "xsd:boolean");
 
-        distances.put("@id", "group:distances");
-        distances.put("@container", "@list");
+		distances.put("@id", "group:distances");
+		distances.put("@container", "@list");
 
-        inOrder.put("@id", "group:inOrder");
-        inOrder.put("@type", "xsd:boolean");
+		inOrder.put("@id", "group:inOrder");
+		inOrder.put("@type", "xsd:boolean");
 
-        context.put("korap", "http://korap.ids-mannheim.de/ns/KorAP/json-ld/v0.1/");
-        context.put("boundary", "korap:boundary/");
-        context.put("group", "korap:group/");
-        context.put("operation", operation);
-        context.put("class", classMap);
-        context.put("operands", operands);
-        context.put("frame", frame);
-        context.put("classRef", classRef);
-        context.put("spanRef", spanRef);
-        context.put("classRefOp", classRefOp);
-        context.put("min", min);
-        context.put("max", max);
-        context.put("exclude", exclude);
-        context.put("distances", distances);
-        context.put("inOrder", inOrder);
+		context.put("korap", "http://korap.ids-mannheim.de/ns/KorAP/json-ld/v0.1/");
+		context.put("boundary", "korap:boundary/");
+		context.put("group", "korap:group/");
+		context.put("operation", operation);
+		context.put("class", classMap);
+		context.put("operands", operands);
+		context.put("frame", frame);
+		context.put("classRef", classRef);
+		context.put("spanRef", spanRef);
+		context.put("classRefOp", classRefOp);
+		context.put("min", min);
+		context.put("max", max);
+		context.put("exclude", exclude);
+		context.put("distances", distances);
+		context.put("inOrder", inOrder);
 
-        requestMap.put("@context", context);
-    }
+		requestMap.put("@context", context);
+	}
 
-    public static String escapeRegexSpecialChars(String key) {
+	public static String escapeRegexSpecialChars(String key) {
 		key.replace("\\", "\\\\");
 		Pattern p = Pattern.compile("\\.|\\^|\\$|\\||\\?|\\*|\\+|\\(|\\)|\\[|\\]|\\{|\\}");
 		Matcher m = p.matcher(key);
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/Antlr3DescriptiveErrorListener.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/Antlr3DescriptiveErrorListener.java
new file mode 100644
index 0000000..2a0fb63
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/Antlr3DescriptiveErrorListener.java
@@ -0,0 +1,85 @@
+package de.ids_mannheim.korap.query.serialize.util;
+
+import java.util.ArrayList;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.ids_mannheim.korap.query.cosmas2.IErrorReporter;
+import de.ids_mannheim.korap.query.serialize.QueryUtils;
+
+/**
+ * Custom descriptive error listener for Antlr3 grammars. Requires interface IErrorReporter to be present in 
+ * grammar destination (generated source directory).
+ * @author Joachim Bingel (bingel@ids-mannheim.de)
+ *
+ */
+public class Antlr3DescriptiveErrorListener implements IErrorReporter {
+
+	private String query;
+	private String offendingSymbol;
+	private String expected;
+	private int charPosition;
+
+	public Antlr3DescriptiveErrorListener(String query) {
+		this.query = query;
+	};
+
+	@Override
+	public void reportError(String error) {
+		String charPositionStr = null;
+		String offendingSymbol = null;
+		String expected = null;
+		Pattern p = Pattern.compile("line \\d+:(\\d+).* '(.+?)' expecting (.+)");
+		Matcher m = p.matcher(error);
+		if (m.find()) {
+			charPositionStr = m.group(1);
+			offendingSymbol = m.group(2);
+			expected = m.group(3);
+		}
+		if (charPositionStr != null)
+			this.charPosition = Integer.parseInt(charPositionStr);
+		if (offendingSymbol != null)
+			this.offendingSymbol = offendingSymbol;
+		if (expected != null)
+			this.expected = expected;
+	}
+
+	public ArrayList<Object> generateFullErrorMsg() {
+		ArrayList<Object> errorSpecs = new ArrayList<Object>();
+		String msg = getDetailedErrorMessage(); 
+		errorSpecs.add(StatusCodes.MALFORMED_QUERY);
+		errorSpecs.add(msg);
+		errorSpecs.add(getCharPosition());
+		return errorSpecs;
+	}
+
+	private String getDetailedErrorMessage() {
+		// default message, in case no detailed info is available;
+		String msg = "Malformed query. Could not parse."; 
+		char offendingSymbol = query.charAt(0);
+		if (query.length() > charPosition) offendingSymbol = query.charAt(charPosition);
+		msg = "Failing to parse at symbol: '"+offendingSymbol+"'";
+		if (expected != null) {
+			if (expected.equals("EOF") || expected.equals("<EOF>")) {
+				msg += " Expected end of query.";
+			} else {
+				msg += " Expected '"+expected+"'";
+			}
+		}
+		// check for unbalanced parantheses
+		SimpleEntry<String, Integer> unbalanced = QueryUtils.checkUnbalancedPars(query); 
+		if (unbalanced != null) {
+			msg = unbalanced.getKey();
+			charPosition = unbalanced.getValue();
+		}
+
+		return msg;
+	}
+
+	public int getCharPosition() {
+		return charPosition;
+	}
+
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/Antlr4DescriptiveErrorListener.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/Antlr4DescriptiveErrorListener.java
new file mode 100644
index 0000000..5b7115e
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/Antlr4DescriptiveErrorListener.java
@@ -0,0 +1,72 @@
+package de.ids_mannheim.korap.query.serialize.util;
+
+import java.util.ArrayList;
+import java.util.AbstractMap.SimpleEntry;
+
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+
+import de.ids_mannheim.korap.query.serialize.QueryUtils;
+
+public class Antlr4DescriptiveErrorListener extends BaseErrorListener {
+    
+    String query;
+    String message;
+	int line;
+    int charPosition;
+        
+    public Antlr4DescriptiveErrorListener(String query) {
+    	this.query = query;
+    };
+
+    @Override
+    public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
+                            int line, int charPositionInLine,
+                            String msg, RecognitionException e)
+    {
+    	this.message = msg;
+    	this.line = line;
+    	this.charPosition = charPositionInLine;
+    }
+    
+    public String getMessage() {
+		return message;
+	}
+
+	public int getLine() {
+		return line;
+	}
+
+	public int getCharPosition() {
+		return charPosition;
+	}
+	
+	public ArrayList<Object> generateFullErrorMsg() {
+		ArrayList<Object> errorSpecs = new ArrayList<Object>();
+		String msg = getDetailedErrorMessage(); 
+		errorSpecs.add(StatusCodes.MALFORMED_QUERY);
+		errorSpecs.add(msg);
+		errorSpecs.add(getCharPosition());
+		return errorSpecs;
+	}
+
+	private String getDetailedErrorMessage() {
+		// default message, in case no detailed info is available;
+		String msg = "Malformed query. Could not parse."; 
+		char offendingSymbol = query.charAt(0);
+		if (query.length() > charPosition) offendingSymbol = query.charAt(charPosition);
+		msg = "Failing to parse at symbol: '"+offendingSymbol+"'";
+		// check for unbalanced parantheses
+		SimpleEntry<String, Integer> unbalanced = QueryUtils.checkUnbalancedPars(query); 
+		if (unbalanced != null) {
+			msg = unbalanced.getKey();
+			charPosition = unbalanced.getValue();
+		}
+		
+		return msg;
+	}
+	
+  
+
+}
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
new file mode 100644
index 0000000..6bcf7e9
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
@@ -0,0 +1,10 @@
+package de.ids_mannheim.korap.query.serialize.util;
+
+public class StatusCodes {
+	public final static int NO_QUERY = 301;
+	public final static int MALFORMED_QUERY = 302;
+	public final static int DEPRECATED_QUERY_ELEMENT = 303;
+	public final static int UNDEFINED_CLASS_REFERENCE = 304;
+	public final static int INCOMPATIBLE_OPERATOR_AND_OPERAND = 305;
+	public final static int UNKNOWN_QUERY_ELEMENT = 306;
+}
diff --git a/src/test/java/CosmasTreeTest.java b/src/test/java/CosmasTreeTest.java
index fe6370e..03ab986 100644
--- a/src/test/java/CosmasTreeTest.java
+++ b/src/test/java/CosmasTreeTest.java
@@ -595,6 +595,14 @@
 		assertEquals(true,							res.at("/query/operands/0/operands/0/frames/1").isMissingNode());
 		assertEquals(true,							res.at("/query/operands/0/operands/0/exclude").asBoolean());
 
+		query = "wegen #IN(FE,%,MIN) <s>";
+		qs.setQuery(query, "cosmas2");
+		res = mapper.readTree(qs.toJSON());
+		assertEquals(true,							res.at("/query/reset").isMissingNode());
+		assertEquals("classRefCheck:equals",		res.at("/query/operands/0/classRefCheck/0").asText());
+		assertEquals("frames:matches",				res.at("/query/operands/0/operands/0/frames/0").asText());
+		assertEquals(true,							res.at("/query/operands/0/operands/0/exclude").asBoolean());
+		
 		query = "wegen #IN(FE,ALL,%,MIN) <s>";
 		qs.setQuery(query, "cosmas2");
 		res = mapper.readTree(qs.toJSON());
@@ -602,7 +610,6 @@
 		assertEquals("classRefCheck:equals",		res.at("/query/operands/0/classRefCheck/0").asText());
 		assertEquals("frames:matches",				res.at("/query/operands/0/operands/0/frames/0").asText());
 		assertEquals(true,							res.at("/query/operands/0/operands/0/exclude").asBoolean());
-
 	}
 
 	@Test