Expert query filter, initial
diff --git a/pom.xml b/pom.xml
index 3f532b8..a4af39c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,30 @@
         <outputDirectory>${basedir}/bin</outputDirectory>
         <plugins>
             <plugin>
+				<!--
+				    This plugin will help to build the ANTLR4 grammar on the fly.
+				    The recipe is based on
+				    http://stackoverflow.com/questions/15310628/
+				           customize-maven-to-automatically-create-antlr4-grammar-java-files-on-build
+				-->
+				<groupId>org.antlr</groupId>
+				<artifactId>antlr4-maven-plugin</artifactId>
+				<version>4.0</version>
+				<configuration>
+				  <sourceDirectory>${basedir}/src/main/antlr</sourceDirectory>
+				  <outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/serialize/util</outputDirectory>
+			      <libDirectory>${basedir}/src/main/antlr</libDirectory>
+				</configuration>
+				<executions>
+				  <execution>
+				    <goals>
+				      <goal>antlr4</goal>
+				    </goals>
+				    <phase>generate-sources</phase>
+				  </execution>
+				</executions>
+	        </plugin>
+            <plugin>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>2.3.2</version>
                 <configuration>
diff --git a/src/main/antlr/CollectionQuery.g4 b/src/main/antlr/CollectionQuery.g4
new file mode 100644
index 0000000..67491ea
--- /dev/null
+++ b/src/main/antlr/CollectionQuery.g4
@@ -0,0 +1,75 @@
+grammar CollectionQuery;
+
+@header {package de.ids_mannheim.korap.query.serialize.util;}
+
+/*
+ -- author: jbingel
+ -- date: 14-05-11
+*/
+
+/*
+ * LEXER SECTION
+ */
+
+WS 					: ( ' ' | '\t' | '\r' | '\n' )+ -> skip ;
+fragment FOCC       : '{' WS* ( [0-9]* WS* ',' WS* [0-9]+ | [0-9]+ WS* ','? ) WS* '}';
+fragment NO_RE      : ~[ \t\/];
+fragment ALPHABET   : ~('\t' | ' ' | '/' | '*' | '?' | '+' | '{' | '}' | '[' | ']'
+                    | '(' | ')' | '|' | '"' | ',' | ':' | '\'' | '\\' | '!' | '=' | '~' | '&' | '^' | '<' | '>' );
+NUMBER              : [0-9]+;
+
+NL                  : [\r\n] -> skip;
+ws                  : WS+;
+
+WORD                : ALPHABET+;
+LRB					: '(';
+RRB					: ')';
+LT					: '<';
+GT					: '>';
+LEQ					: '<=';
+GEQ					: '>=';
+EQ					: '=';
+NE					: '!=';
+AND					: '&';
+OR					: '|';
+
+/*
+ * PARSER SECTION
+ */
+
+conj
+:	AND | OR;
+
+operator
+:	EQ | NE | LT | GT | LEQ | GEQ;
+
+expr
+:	field operator value
+|	value operator field operator value
+;
+	
+field
+:	WORD;
+	
+value
+:	WORD | NUMBER;
+	
+andGroup
+: 	(expr AND)* (LRB orGroup RRB)? (AND expr)*
+|	(expr AND)+ expr
+;
+
+orGroup
+: 	(expr OR)* (LRB andGroup RRB)? (OR expr)* 
+|	(expr OR)+ expr
+;
+
+exprGroup
+:	andGroup
+|	orGroup
+;
+
+start
+:	expr
+|	exprGroup
+;
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/ExpertFilter.java b/src/main/java/de/ids_mannheim/korap/query/serialize/ExpertFilter.java
new file mode 100644
index 0000000..3526657
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/ExpertFilter.java
@@ -0,0 +1,267 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import de.ids_mannheim.korap.query.serialize.util.CollectionQueryParser;
+import de.ids_mannheim.korap.query.serialize.util.CollectionQueryLexer;
+import de.ids_mannheim.korap.util.QueryException;
+
+import java.lang.reflect.Method;
+import java.util.*;
+
+import org.antlr.v4.runtime.ANTLRInputStream;
+import org.antlr.v4.runtime.BailErrorStrategy;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.Lexer;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ParseTree;
+
+/**
+ * @author hanl
+ * @date 06/12/2013
+ */
+public class ExpertFilter extends Antlr4AbstractSyntaxTree {
+
+	private Parser parser;
+	private boolean verbose = false;
+	private List<ParseTree> visited = new ArrayList<ParseTree>();
+	/**
+	 * Top-level map representing the whole request.
+	 */
+	LinkedHashMap<String,Object> requestMap = new LinkedHashMap<String,Object>();
+	/**
+	 * Keeps track of active object.
+	 */
+	LinkedList<LinkedHashMap<String,Object>> objectStack = new LinkedList<LinkedHashMap<String,Object>>();
+	/**
+	 * Keeps track of open node categories
+	 */
+	LinkedList<String> openNodeCats = new LinkedList<String>();
+	/**
+	 * Keeps track of how many objects there are to pop after every recursion of {@link #processNode(ParseTree)}
+	 */
+	LinkedList<Integer> objectsToPop = new LinkedList<Integer>();
+	Integer stackedObjects = 0;
+
+
+    public ExpertFilter() {
+    }
+    
+	@Override
+	public void process(String query) throws QueryException {
+		ParseTree tree = parseCollectionQuery(query);
+		if (this.parser != null) {
+			super.parser = this.parser;
+		} else {
+			throw new NullPointerException("Parser has not been instantiated!"); 
+		}
+		
+		System.out.println("Processing collection query");
+		if (verbose) System.out.println(tree.toStringTree(parser));
+		processNode(tree);
+	}
+
+	private void processNode(ParseTree node) {
+		// Top-down processing
+		String nodeCat = getNodeCat(node);
+		openNodeCats.push(nodeCat);
+		
+		stackedObjects = 0;
+		
+		if (verbose) {
+			System.err.println(" "+objectStack);
+			System.out.println(openNodeCats);
+		}
+
+		/*
+		 ****************************************************************
+		 **************************************************************** 
+		 * 			Processing individual node categories  				*
+		 ****************************************************************
+		 ****************************************************************
+		 */
+		
+		if (nodeCat.equals("andGroup")) {
+			LinkedHashMap<String, Object> exprGroup = makeTermGroup("and");
+			objectStack.push(exprGroup);
+			stackedObjects++;
+			putIntoSuperObject(exprGroup,1);
+		}
+		
+		if (nodeCat.equals("orGroup")) {
+			LinkedHashMap<String, Object> exprGroup = makeTermGroup("or");
+			objectStack.push(exprGroup);
+			stackedObjects++;
+			putIntoSuperObject(exprGroup,1);
+		}
+		
+		if (nodeCat.equals("expr")) {
+			ParseTree fieldNode = getFirstChildWithCat(node, "field");
+			String field = fieldNode.getChild(0).toStringTree(parser);
+			List<ParseTree> operatorNodes = getChildrenWithCat(node, "operator");
+			List<ParseTree> valueNodes = getChildrenWithCat(node, "value");
+			
+			if (valueNodes.size()==1) {
+				LinkedHashMap<String, Object> term = makeTerm();
+				term.put("attribute", field);
+				term.put("key", valueNodes.get(0).getChild(0).toStringTree(parser));
+				String match = operatorNodes.get(0).getChild(0).toStringTree(parser);
+				term.put("match", "match:"+interpretMatch(match));
+				putIntoSuperObject(term);
+			} else { // (valueNodes.size()==2)
+				LinkedHashMap<String, Object> termGroup = makeTermGroup("and");
+				ArrayList<Object> termGroupOperands = (ArrayList<Object>) termGroup.get("operands");
+				
+				LinkedHashMap<String, Object> term1 = makeTerm();
+				term1.put("attribute", field);
+				term1.put("key", valueNodes.get(0).getChild(0).toStringTree(parser));
+				String match1 = operatorNodes.get(0).getChild(0).toStringTree(parser);
+				term1.put("match", "match:"+invertInequation(interpretMatch(match1)));
+				termGroupOperands.add(term1);
+				
+				LinkedHashMap<String, Object> term2 = makeTerm();
+				term2.put("attribute", field);
+				term2.put("key", valueNodes.get(1).getChild(0).toStringTree(parser));
+				String match2 = operatorNodes.get(1).getChild(0).toStringTree(parser);
+				term2.put("match", "match:"+interpretMatch(match2));
+				termGroupOperands.add(term2);
+				
+				putIntoSuperObject(termGroup);
+			}
+			
+		}
+		
+		
+		
+		objectsToPop.push(stackedObjects);
+		
+		/*
+		 ****************************************************************
+		 **************************************************************** 
+		 *  recursion until 'request' node (root of tree) is processed  *
+		 ****************************************************************
+		 ****************************************************************
+		 */
+		for (int i=0; i<node.getChildCount(); i++) {
+			ParseTree child = node.getChild(i);
+			processNode(child);
+		}
+
+		/*
+		 **************************************************************
+		 * Stuff that happens after processing the children of a node *
+		 **************************************************************
+		 */
+		if (!objectsToPop.isEmpty()) {
+			for (int i=0; i<objectsToPop.pop(); i++) {
+				objectStack.pop();
+			}
+		}
+		openNodeCats.pop();
+		
+		
+	}
+
+	
+	
+	private String interpretMatch(String match) {
+		String out = null;
+		       if (match.equals("<")) {
+		    	   out = "lt";
+		} else if (match.equals(">")) {
+			out = "gt";
+		} else if (match.equals("<=")) {
+			out = "leq";
+		} else if (match.equals(">=")) {
+			out = "geq";
+		} else if (match.equals("=")) {
+			out = "eq";
+		} else if (match.equals("!=")) {
+			out = "ne";
+		}
+		return out;
+	}
+
+	private String invertInequation(String op) {
+		String inv = null;
+			   if (op.equals("lt")) {
+			inv = "gt";
+		} else if (op.equals("leq")) {
+			inv = "geq";
+		} else if (op.equals("gt")) {
+			inv = "lt";
+		} else if (op.equals("geq")) {
+			inv = "leq";
+		}
+		return inv;
+	}
+
+	private void putIntoSuperObject(LinkedHashMap<String, Object> object) {
+		putIntoSuperObject(object, 0);
+	}
+	
+	@SuppressWarnings({ "unchecked" })
+	private void putIntoSuperObject(LinkedHashMap<String, Object> object, int objStackPosition) {
+		if (objectStack.size()>objStackPosition) {
+			ArrayList<Object> topObjectOperands = (ArrayList<Object>) objectStack.get(objStackPosition).get("operands");
+			topObjectOperands.add(0, object);
+			
+		} else {
+			requestMap.put("query", object);
+		}
+	}
+    
+	private ParserRuleContext parseCollectionQuery (String p) throws QueryException {
+		Lexer collectionQueryLexer = new CollectionQueryLexer((CharStream)null);
+	    ParserRuleContext tree = null;
+	    // Like p. 111
+	    try {
+	    	
+	      // Tokenize input data
+	      ANTLRInputStream input = new ANTLRInputStream(p);
+	      collectionQueryLexer.setInputStream(input);
+	      CommonTokenStream tokens = new CommonTokenStream(collectionQueryLexer);
+	      parser = new CollectionQueryParser(tokens);
+	      
+	      // Don't throw out erroneous stuff
+	      parser.setErrorHandler(new BailErrorStrategy());
+	      parser.removeErrorListeners();
+	      // Get starting rule from parser
+	      Method startRule = CollectionQueryParser.class.getMethod("start");
+	      tree = (ParserRuleContext) startRule.invoke(parser, (Object[])null);
+	      
+	    }
+	    // Some things went wrong ...
+	    catch (Exception e) {
+	      System.err.println( e.getMessage() );
+	    }
+	    if (tree == null) {
+	    	throw new QueryException("Could not parse expert filter query. Make sure it is correct syntax.");
+	    }
+	    // Return the generated tree
+	    return tree;
+	  }
+    
+    public static void main(String[] args) {
+    	String query = "foo=bar&c=d";
+    	query = "(1990<year<2010&genre=Sport)|textClass=politk";
+    	query = "(textClass=wissenschaft & textClass=politik) | textClass=ausland";
+    	query = "1990<year<2010 & genre=Sport";
+    	ExpertFilter filter = new ExpertFilter();
+//    	filter.verbose = true;
+    	try {
+			filter.process(query);
+		} catch (QueryException e) {
+			e.printStackTrace();
+		}
+    	System.out.println(filter.getRequestMap());
+    	
+    }
+
+	@Override
+	public Map<String, Object> getRequestMap() {
+		return requestMap;
+	}
+
+
+}