1 // Copyright 2011 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.search
.query
;
5 import org
.antlr
.runtime
.tree
.Tree
;
8 * The walking of the query tree. This class takes care of visiting
9 * a tree resulting from parsing a query. As it traverses the tree
10 * it calls appropriate methods of the visitor, set at the construction
11 * time. The class uses a depth-first search, visiting all children
12 * of a node, before visiting the node. The visit is done by calling
13 * an appropriate method of the visitor. Typical code should match
14 * the following pattern:
16 * class MyVisitor implements QueryTreeVisitor {
19 * class MyContext extends QueryTreeContext<MyContext> {
22 * protected MyContext newChildContext() {
23 * return new MyContext();
27 * MyContext context = new MyContext();
28 * QueryTreeWalker<MyContext> walker = new QueryTreeWalker<MyContext>(new MyVisitor());
29 * Tree root = parser.query(queryStr);
30 * walker.walk(root, context);
31 * // retrieve whatever information you need from context
34 * @param <T> the context used by the visitor
36 public class QueryTreeWalker
<T
extends QueryTreeContext
<T
>> {
38 private final QueryTreeVisitor
<T
> visitor
;
41 * Creates a new query walker that calls the given {@code visitor}.
43 * @param visitor the visitor to be called by this walker
45 public QueryTreeWalker(QueryTreeVisitor
<T
> visitor
) {
46 this.visitor
= visitor
;
50 * @param tree the tree to be walked
51 * @param context the context in which the tree is walked
53 public void walk(Tree tree
, T context
) throws QueryTreeException
{
54 walkInternal(flatten(tree
, null), context
);
58 * Walks the tree and updates the context. This is a depth-first search,
59 * always exploring children of the {@code tree} in the order of their
60 * index 0 ... n - 1, where n is the number of children. Once all children
61 * of the {@code node} are visited, the appropriate visitor's method
64 * @param tree the tree to be walked
65 * @param context the context of the visit
67 private void walkInternal(Tree tree
, T context
) {
68 switch (tree
.getType()) {
69 case QueryLexer
.CONJUNCTION
:
70 walkChildren(tree
, context
);
71 visitor
.visitConjunction(tree
, context
);
72 postBooleanExpression(context
);
74 case QueryLexer
.DISJUNCTION
:
75 walkChildren(tree
, context
);
76 visitor
.visitDisjunction(tree
, context
);
77 postBooleanExpression(context
);
79 case QueryLexer
.SEQUENCE
:
80 walkChildren(tree
, context
);
81 visitor
.visitSequence(tree
, context
);
82 postBooleanExpression(context
);
84 case QueryLexer
.NEGATION
:
85 walkChildren(tree
, context
);
86 visitor
.visitNegation(tree
, context
);
87 postBooleanExpression(context
);
90 walkChildren(tree
, context
);
91 visitor
.visitContains(tree
, context
);
92 postBooleanExpression(context
);
95 walkChildren(tree
, context
);
96 visitor
.visitEqual(tree
, context
);
97 postBooleanExpression(context
);
100 walkChildren(tree
, context
);
101 visitor
.visitLessThan(tree
, context
);
102 postBooleanExpression(context
);
105 walkChildren(tree
, context
);
106 visitor
.visitLessOrEqual(tree
, context
);
107 postBooleanExpression(context
);
110 walkChildren(tree
, context
);
111 visitor
.visitGreaterThan(tree
, context
);
112 postBooleanExpression(context
);
115 walkChildren(tree
, context
);
116 visitor
.visitGreaterOrEqual(tree
, context
);
117 postBooleanExpression(context
);
119 case QueryLexer
.FUZZY
:
120 walkInternal(tree
.getChild(0), context
);
121 visitor
.visitFuzzy(tree
, context
);
123 case QueryLexer
.LITERAL
:
124 walkInternal(tree
.getChild(0), context
);
125 visitor
.visitLiteral(tree
, context
);
127 case QueryLexer
.VALUE
:
128 visitor
.visitValue(tree
, context
);
130 case QueryLexer
.FUNCTION
:
131 walkChildren(tree
.getChild(1), context
);
132 visitor
.visitFunction(tree
, context
);
134 case QueryLexer
.GLOBAL
:
135 visitor
.visitGlobal(tree
, context
);
138 visitor
.visitOther(tree
, context
);
142 protected void postBooleanExpression(T context
) {
143 context
.setReturnType(QueryTreeContext
.Type
.BOOL
);
144 context
.setKind(QueryTreeContext
.Kind
.EXPRESSION
);
147 private void walkChildren(Tree parent
, T context
) {
148 for (int i
= 0; i
< parent
.getChildCount(); ++i
) {
149 walkInternal(parent
.getChild(i
), context
.addChild());
153 public static Tree
simplify(Tree tree
) {
154 for (int i
= 0; i
< tree
.getChildCount(); ++i
) {
155 Tree child
= tree
.getChild(i
);
156 Tree optimized
= simplify(child
);
157 if (child
!= optimized
) {
158 tree
.setChild(i
, optimized
);
161 switch (tree
.getType()) {
162 case QueryLexer
.CONJUNCTION
:
163 case QueryLexer
.DISJUNCTION
:
164 case QueryLexer
.SEQUENCE
:
165 if (tree
.getChildCount() == 1) {
166 return tree
.getChild(0);
174 * Flattens the tree by pushing down the field name. For example, if
175 * the tree looks like this:
181 * TEXT field GLOBAL (N)
183 * Then we will output tree that looks like this:
191 * Here <code>(N)</code> is an arbitrary node. We also drop EQ if it
192 * is in front of conjunction or disjunction. We do not drop it for
193 * other comparators, as we want parsing to fail for foo < (1 2).
195 private static Tree
flatten(Tree tree
, Tree restriction
) throws QueryTreeException
{
196 if (tree
.getType() == QueryLexer
.VALUE
) {
199 if (tree
.getType() == QueryLexer
.HAS
|| tree
.getType() == QueryLexer
.EQ
) {
200 Tree lhs
= tree
.getChild(0);
201 if (lhs
.getType() == QueryLexer
.VALUE
) {
202 String myField
= lhs
.getChild(1).getText();
203 if (restriction
== null) {
206 String otherField
= restriction
.getChild(1).getText();
207 if (!myField
.equals(otherField
)) {
208 throw new QueryTreeException(
209 String
.format("Restriction on %s and %s", otherField
, myField
),
210 lhs
.getChild(1).getCharPositionInLine());
214 Tree rhs
= tree
.getChild(1);
215 Tree flattened
= flatten(rhs
, restriction
);
216 if (flattened
.getType() == QueryLexer
.HAS
217 || flattened
.getType() == QueryLexer
.EQ
218 || flattened
.getType() == QueryLexer
.CONJUNCTION
219 || flattened
.getType() == QueryLexer
.DISJUNCTION
220 || flattened
.getType() == QueryLexer
.SEQUENCE
) {
223 if (flattened
!= rhs
) {
224 tree
.setChild(1, flattened
);
226 if (restriction
!= lhs
) {
227 tree
.setChild(0, restriction
);
231 for (int i
= 0; i
< tree
.getChildCount(); ++i
) {
232 Tree original
= tree
.getChild(i
);
233 Tree flattened
= flatten(tree
.getChild(i
), restriction
);
234 if (original
!= flattened
) {
235 tree
.setChild(i
, flattened
);