View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.filter;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.hadoop.hbase.classification.InterfaceAudience;
24  import org.apache.hadoop.hbase.classification.InterfaceStability;
25  import org.apache.hadoop.hbase.KeyValue;
26  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
27  import org.apache.hadoop.hbase.util.Bytes;
28  
29  import java.lang.reflect.InvocationTargetException;
30  import java.lang.reflect.Method;
31  import java.nio.ByteBuffer;
32  import java.nio.charset.CharacterCodingException;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.EmptyStackException;
36  import java.util.HashMap;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.Stack;
40  
41  /**
42   * This class allows a user to specify a filter via a string
43   * The string is parsed using the methods of this class and
44   * a filter object is constructed. This filter object is then wrapped
45   * in a scanner object which is then returned
46   * <p>
47   * This class addresses the HBASE-4168 JIRA. More documentation on this
48   * Filter Language can be found at: https://issues.apache.org/jira/browse/HBASE-4176
49   */
50  @InterfaceAudience.Public
51  @InterfaceStability.Stable
52  public class ParseFilter {
53    private static final Log LOG = LogFactory.getLog(ParseFilter.class);
54  
55    private static HashMap<ByteBuffer, Integer> operatorPrecedenceHashMap;
56    private static HashMap<String, String> filterHashMap;
57  
58    static {
59      // Registers all the filter supported by the Filter Language
60      filterHashMap = new HashMap<String, String>();
61      filterHashMap.put("KeyOnlyFilter", ParseConstants.FILTER_PACKAGE + "." +
62                        "KeyOnlyFilter");
63      filterHashMap.put("FirstKeyOnlyFilter", ParseConstants.FILTER_PACKAGE + "." +
64                        "FirstKeyOnlyFilter");
65      filterHashMap.put("PrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
66                        "PrefixFilter");
67      filterHashMap.put("ColumnPrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
68                        "ColumnPrefixFilter");
69      filterHashMap.put("MultipleColumnPrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
70                        "MultipleColumnPrefixFilter");
71      filterHashMap.put("ColumnCountGetFilter", ParseConstants.FILTER_PACKAGE + "." +
72                        "ColumnCountGetFilter");
73      filterHashMap.put("PageFilter", ParseConstants.FILTER_PACKAGE + "." +
74                        "PageFilter");
75      filterHashMap.put("ColumnPaginationFilter", ParseConstants.FILTER_PACKAGE + "." +
76                        "ColumnPaginationFilter");
77      filterHashMap.put("InclusiveStopFilter", ParseConstants.FILTER_PACKAGE + "." +
78                        "InclusiveStopFilter");
79      filterHashMap.put("TimestampsFilter", ParseConstants.FILTER_PACKAGE + "." +
80                        "TimestampsFilter");
81      filterHashMap.put("RowFilter", ParseConstants.FILTER_PACKAGE + "." +
82                        "RowFilter");
83      filterHashMap.put("FamilyFilter", ParseConstants.FILTER_PACKAGE + "." +
84                        "FamilyFilter");
85      filterHashMap.put("QualifierFilter", ParseConstants.FILTER_PACKAGE + "." +
86                        "QualifierFilter");
87      filterHashMap.put("ValueFilter", ParseConstants.FILTER_PACKAGE + "." +
88                        "ValueFilter");
89      filterHashMap.put("ColumnRangeFilter", ParseConstants.FILTER_PACKAGE + "." +
90                        "ColumnRangeFilter");
91      filterHashMap.put("SingleColumnValueFilter", ParseConstants.FILTER_PACKAGE + "." +
92                        "SingleColumnValueFilter");
93      filterHashMap.put("SingleColumnValueExcludeFilter", ParseConstants.FILTER_PACKAGE + "." +
94                        "SingleColumnValueExcludeFilter");
95      filterHashMap.put("DependentColumnFilter", ParseConstants.FILTER_PACKAGE + "." +
96                        "DependentColumnFilter");
97  
98      // Creates the operatorPrecedenceHashMap
99      operatorPrecedenceHashMap = new HashMap<ByteBuffer, Integer>();
100     operatorPrecedenceHashMap.put(ParseConstants.SKIP_BUFFER, 1);
101     operatorPrecedenceHashMap.put(ParseConstants.WHILE_BUFFER, 1);
102     operatorPrecedenceHashMap.put(ParseConstants.AND_BUFFER, 2);
103     operatorPrecedenceHashMap.put(ParseConstants.OR_BUFFER, 3);
104   }
105 
106   /**
107    * Parses the filterString and constructs a filter using it
108    * <p>
109    * @param filterString filter string given by the user
110    * @return filter object we constructed
111    */
112   public Filter parseFilterString (String filterString)
113     throws CharacterCodingException {
114     return parseFilterString(Bytes.toBytes(filterString));
115   }
116 
117   /**
118    * Parses the filterString and constructs a filter using it
119    * <p>
120    * @param filterStringAsByteArray filter string given by the user
121    * @return filter object we constructed
122    */
123   public Filter parseFilterString (byte [] filterStringAsByteArray)
124     throws CharacterCodingException {
125     // stack for the operators and parenthesis
126     Stack <ByteBuffer> operatorStack = new Stack<ByteBuffer>();
127     // stack for the filter objects
128     Stack <Filter> filterStack = new Stack<Filter>();
129 
130     Filter filter = null;
131     for (int i=0; i<filterStringAsByteArray.length; i++) {
132       if (filterStringAsByteArray[i] == ParseConstants.LPAREN) {
133         // LPAREN found
134         operatorStack.push(ParseConstants.LPAREN_BUFFER);
135       } else if (filterStringAsByteArray[i] == ParseConstants.WHITESPACE ||
136                  filterStringAsByteArray[i] == ParseConstants.TAB) {
137         // WHITESPACE or TAB found
138         continue;
139       } else if (checkForOr(filterStringAsByteArray, i)) {
140         // OR found
141         i += ParseConstants.OR_ARRAY.length - 1;
142         reduce(operatorStack, filterStack, ParseConstants.OR_BUFFER);
143         operatorStack.push(ParseConstants.OR_BUFFER);
144       } else if (checkForAnd(filterStringAsByteArray, i)) {
145         // AND found
146         i += ParseConstants.AND_ARRAY.length - 1;
147         reduce(operatorStack, filterStack, ParseConstants.AND_BUFFER);
148         operatorStack.push(ParseConstants.AND_BUFFER);
149       } else if (checkForSkip(filterStringAsByteArray, i)) {
150         // SKIP found
151         i += ParseConstants.SKIP_ARRAY.length - 1;
152         reduce(operatorStack, filterStack, ParseConstants.SKIP_BUFFER);
153         operatorStack.push(ParseConstants.SKIP_BUFFER);
154       } else if (checkForWhile(filterStringAsByteArray, i)) {
155         // WHILE found
156         i += ParseConstants.WHILE_ARRAY.length - 1;
157         reduce(operatorStack, filterStack, ParseConstants.WHILE_BUFFER);
158         operatorStack.push(ParseConstants.WHILE_BUFFER);
159       } else if (filterStringAsByteArray[i] == ParseConstants.RPAREN) {
160         // RPAREN found
161         if (operatorStack.empty()) {
162           throw new IllegalArgumentException("Mismatched parenthesis");
163         }
164         ByteBuffer argumentOnTopOfStack = operatorStack.peek();
165         while (!(argumentOnTopOfStack.equals(ParseConstants.LPAREN_BUFFER))) {
166           filterStack.push(popArguments(operatorStack, filterStack));
167           if (operatorStack.empty()) {
168             throw new IllegalArgumentException("Mismatched parenthesis");
169           }
170           argumentOnTopOfStack = operatorStack.pop();
171         }
172       } else {
173         // SimpleFilterExpression found
174         byte [] filterSimpleExpression = extractFilterSimpleExpression(filterStringAsByteArray, i);
175         i+= (filterSimpleExpression.length - 1);
176         filter = parseSimpleFilterExpression(filterSimpleExpression);
177         filterStack.push(filter);
178       }
179     }
180 
181     // Finished parsing filterString
182     while (!operatorStack.empty()) {
183       filterStack.push(popArguments(operatorStack, filterStack));
184     }
185     filter = filterStack.pop();
186     if (!filterStack.empty()) {
187       throw new IllegalArgumentException("Incorrect Filter String");
188     }
189     return filter;
190   }
191 
192 /**
193  * Extracts a simple filter expression from the filter string given by the user
194  * <p>
195  * A simpleFilterExpression is of the form: FilterName('arg', 'arg', 'arg')
196  * The user given filter string can have many simpleFilterExpressions combined
197  * using operators.
198  * <p>
199  * This function extracts a simpleFilterExpression from the
200  * larger filterString given the start offset of the simpler expression
201  * <p>
202  * @param filterStringAsByteArray filter string given by the user
203  * @param filterExpressionStartOffset start index of the simple filter expression
204  * @return byte array containing the simple filter expression
205  */
206   public byte [] extractFilterSimpleExpression (byte [] filterStringAsByteArray,
207                                                 int filterExpressionStartOffset)
208     throws CharacterCodingException {
209     int quoteCount = 0;
210     for (int i=filterExpressionStartOffset; i<filterStringAsByteArray.length; i++) {
211       if (filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE) {
212         if (isQuoteUnescaped(filterStringAsByteArray, i)) {
213           quoteCount ++;
214         } else {
215           // To skip the next quote that has been escaped
216           i++;
217         }
218       }
219       if (filterStringAsByteArray[i] == ParseConstants.RPAREN && (quoteCount %2 ) == 0) {
220         byte [] filterSimpleExpression = new byte [i - filterExpressionStartOffset + 1];
221         Bytes.putBytes(filterSimpleExpression, 0, filterStringAsByteArray,
222                        filterExpressionStartOffset, i-filterExpressionStartOffset + 1);
223         return filterSimpleExpression;
224       }
225     }
226     throw new IllegalArgumentException("Incorrect Filter String");
227   }
228 
229 /**
230  * Constructs a filter object given a simple filter expression
231  * <p>
232  * @param filterStringAsByteArray filter string given by the user
233  * @return filter object we constructed
234  */
235   public Filter parseSimpleFilterExpression (byte [] filterStringAsByteArray)
236     throws CharacterCodingException {
237 
238     String filterName = Bytes.toString(getFilterName(filterStringAsByteArray));
239     ArrayList<byte []> filterArguments = getFilterArguments(filterStringAsByteArray);
240     if (!filterHashMap.containsKey(filterName)) {
241       throw new IllegalArgumentException("Filter Name " + filterName + " not supported");
242     }
243     try {
244       filterName = filterHashMap.get(filterName);
245       Class<?> c = Class.forName(filterName);
246       Class<?>[] argTypes = new Class [] {ArrayList.class};
247       Method m = c.getDeclaredMethod("createFilterFromArguments", argTypes);
248       return (Filter) m.invoke(null,filterArguments);
249     } catch (ClassNotFoundException e) {
250       e.printStackTrace();
251     } catch (NoSuchMethodException e) {
252       e.printStackTrace();
253     } catch (IllegalAccessException e) {
254       e.printStackTrace();
255     } catch (InvocationTargetException e) {
256       e.printStackTrace();
257     }
258     throw new IllegalArgumentException("Incorrect filter string " +
259                                        new String(filterStringAsByteArray));
260   }
261 
262 /**
263  * Returns the filter name given a simple filter expression
264  * <p>
265  * @param filterStringAsByteArray a simple filter expression
266  * @return name of filter in the simple filter expression
267  */
268   public static byte [] getFilterName (byte [] filterStringAsByteArray) {
269     int filterNameStartIndex = 0;
270     int filterNameEndIndex = 0;
271 
272     for (int i=filterNameStartIndex; i<filterStringAsByteArray.length; i++) {
273       if (filterStringAsByteArray[i] == ParseConstants.LPAREN ||
274           filterStringAsByteArray[i] == ParseConstants.WHITESPACE) {
275         filterNameEndIndex = i;
276         break;
277       }
278     }
279 
280     if (filterNameEndIndex == 0) {
281       throw new IllegalArgumentException("Incorrect Filter Name");
282     }
283 
284     byte [] filterName = new byte[filterNameEndIndex - filterNameStartIndex];
285     Bytes.putBytes(filterName, 0, filterStringAsByteArray, 0,
286                    filterNameEndIndex - filterNameStartIndex);
287     return filterName;
288   }
289 
290 /**
291  * Returns the arguments of the filter from the filter string
292  * <p>
293  * @param filterStringAsByteArray filter string given by the user
294  * @return an ArrayList containing the arguments of the filter in the filter string
295  */
296   public static ArrayList<byte []> getFilterArguments (byte [] filterStringAsByteArray) {
297     int argumentListStartIndex = KeyValue.getDelimiter(filterStringAsByteArray, 0,
298                                                        filterStringAsByteArray.length,
299                                                        ParseConstants.LPAREN);
300     if (argumentListStartIndex == -1) {
301       throw new IllegalArgumentException("Incorrect argument list");
302     }
303 
304     int argumentStartIndex = 0;
305     int argumentEndIndex = 0;
306     ArrayList<byte []> filterArguments = new ArrayList<byte []>();
307 
308     for (int i = argumentListStartIndex + 1; i<filterStringAsByteArray.length; i++) {
309 
310       if (filterStringAsByteArray[i] == ParseConstants.WHITESPACE ||
311           filterStringAsByteArray[i] == ParseConstants.COMMA ||
312           filterStringAsByteArray[i] == ParseConstants.RPAREN) {
313         continue;
314       }
315 
316       // The argument is in single quotes - for example 'prefix'
317       if (filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE) {
318         argumentStartIndex = i;
319         for (int j = argumentStartIndex+1; j < filterStringAsByteArray.length; j++) {
320           if (filterStringAsByteArray[j] == ParseConstants.SINGLE_QUOTE) {
321             if (isQuoteUnescaped(filterStringAsByteArray,j)) {
322               argumentEndIndex = j;
323               i = j+1;
324               byte [] filterArgument = createUnescapdArgument(filterStringAsByteArray,
325                                                               argumentStartIndex, argumentEndIndex);
326               filterArguments.add(filterArgument);
327               break;
328             } else {
329               // To jump over the second escaped quote
330               j++;
331             }
332           } else if (j == filterStringAsByteArray.length - 1) {
333             throw new IllegalArgumentException("Incorrect argument list");
334           }
335         }
336       } else {
337         // The argument is an integer, boolean, comparison operator like <, >, != etc
338         argumentStartIndex = i;
339         for (int j = argumentStartIndex; j < filterStringAsByteArray.length; j++) {
340           if (filterStringAsByteArray[j] == ParseConstants.WHITESPACE ||
341               filterStringAsByteArray[j] == ParseConstants.COMMA ||
342               filterStringAsByteArray[j] == ParseConstants.RPAREN) {
343             argumentEndIndex = j - 1;
344             i = j;
345             byte [] filterArgument = new byte [argumentEndIndex - argumentStartIndex + 1];
346             Bytes.putBytes(filterArgument, 0, filterStringAsByteArray,
347                            argumentStartIndex, argumentEndIndex - argumentStartIndex + 1);
348             filterArguments.add(filterArgument);
349             break;
350           } else if (j == filterStringAsByteArray.length - 1) {
351             throw new IllegalArgumentException("Incorrect argument list");
352           }
353         }
354       }
355     }
356     return filterArguments;
357   }
358 
359 /**
360  * This function is called while parsing the filterString and an operator is parsed
361  * <p>
362  * @param operatorStack the stack containing the operators and parenthesis
363  * @param filterStack the stack containing the filters
364  * @param operator the operator found while parsing the filterString
365  */
366   public void reduce(Stack<ByteBuffer> operatorStack,
367                      Stack<Filter> filterStack,
368                      ByteBuffer operator) {
369     while (!operatorStack.empty() &&
370            !(ParseConstants.LPAREN_BUFFER.equals(operatorStack.peek())) &&
371            hasHigherPriority(operatorStack.peek(), operator)) {
372       filterStack.push(popArguments(operatorStack, filterStack));
373     }
374   }
375 
376   /**
377    * Pops an argument from the operator stack and the number of arguments required by the operator
378    * from the filterStack and evaluates them
379    * <p>
380    * @param operatorStack the stack containing the operators
381    * @param filterStack the stack containing the filters
382    * @return the evaluated filter
383    */
384   public static Filter popArguments (Stack<ByteBuffer> operatorStack, Stack <Filter> filterStack) {
385     ByteBuffer argumentOnTopOfStack = operatorStack.peek();
386 
387     if (argumentOnTopOfStack.equals(ParseConstants.OR_BUFFER)) {
388       // The top of the stack is an OR
389       try {
390         ArrayList<Filter> listOfFilters = new ArrayList<Filter>();
391         while (!operatorStack.empty() && operatorStack.peek().equals(ParseConstants.OR_BUFFER)) {
392           Filter filter = filterStack.pop();
393           listOfFilters.add(0, filter);
394           operatorStack.pop();
395         }
396         Filter filter = filterStack.pop();
397         listOfFilters.add(0, filter);
398         Filter orFilter = new FilterList(FilterList.Operator.MUST_PASS_ONE, listOfFilters);
399         return orFilter;
400       } catch (EmptyStackException e) {
401         throw new IllegalArgumentException("Incorrect input string - an OR needs two filters");
402       }
403 
404     } else if (argumentOnTopOfStack.equals(ParseConstants.AND_BUFFER)) {
405       // The top of the stack is an AND
406       try {
407         ArrayList<Filter> listOfFilters = new ArrayList<Filter>();
408         while (!operatorStack.empty() && operatorStack.peek().equals(ParseConstants.AND_BUFFER)) {
409           Filter filter = filterStack.pop();
410           listOfFilters.add(0, filter);
411           operatorStack.pop();
412         }
413         Filter filter = filterStack.pop();
414         listOfFilters.add(0, filter);
415         Filter andFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, listOfFilters);
416         return andFilter;
417       } catch (EmptyStackException e) {
418         throw new IllegalArgumentException("Incorrect input string - an AND needs two filters");
419       }
420 
421     } else if (argumentOnTopOfStack.equals(ParseConstants.SKIP_BUFFER)) {
422       // The top of the stack is a SKIP
423       try {
424         Filter wrappedFilter = filterStack.pop();
425         Filter skipFilter = new SkipFilter(wrappedFilter);
426         operatorStack.pop();
427         return skipFilter;
428       } catch (EmptyStackException e) {
429         throw new IllegalArgumentException("Incorrect input string - a SKIP wraps a filter");
430       }
431 
432     } else if (argumentOnTopOfStack.equals(ParseConstants.WHILE_BUFFER)) {
433       // The top of the stack is a WHILE
434       try {
435         Filter wrappedFilter = filterStack.pop();
436         Filter whileMatchFilter = new WhileMatchFilter(wrappedFilter);
437         operatorStack.pop();
438         return whileMatchFilter;
439       } catch (EmptyStackException e) {
440         throw new IllegalArgumentException("Incorrect input string - a WHILE wraps a filter");
441       }
442 
443     } else if (argumentOnTopOfStack.equals(ParseConstants.LPAREN_BUFFER)) {
444       // The top of the stack is a LPAREN
445       try {
446         Filter filter  = filterStack.pop();
447         operatorStack.pop();
448         return filter;
449       } catch (EmptyStackException e) {
450         throw new IllegalArgumentException("Incorrect Filter String");
451       }
452 
453     } else {
454       throw new IllegalArgumentException("Incorrect arguments on operatorStack");
455     }
456   }
457 
458 /**
459  * Returns which operator has higher precedence
460  * <p>
461  * If a has higher precedence than b, it returns true
462  * If they have the same precedence, it returns false
463  */
464   public boolean hasHigherPriority(ByteBuffer a, ByteBuffer b) {
465     if ((operatorPrecedenceHashMap.get(a) - operatorPrecedenceHashMap.get(b)) < 0) {
466       return true;
467     }
468     return false;
469   }
470 
471 /**
472  * Removes the single quote escaping a single quote - thus it returns an unescaped argument
473  * <p>
474  * @param filterStringAsByteArray filter string given by user
475  * @param argumentStartIndex start index of the argument
476  * @param argumentEndIndex end index of the argument
477  * @return returns an unescaped argument
478  */
479   public static byte [] createUnescapdArgument (byte [] filterStringAsByteArray,
480                                                 int argumentStartIndex, int argumentEndIndex) {
481     int unescapedArgumentLength = 2;
482     for (int i = argumentStartIndex + 1; i <= argumentEndIndex - 1; i++) {
483       unescapedArgumentLength ++;
484       if (filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE &&
485           i != (argumentEndIndex - 1) &&
486           filterStringAsByteArray[i+1] == ParseConstants.SINGLE_QUOTE) {
487         i++;
488         continue;
489       }
490     }
491 
492     byte [] unescapedArgument = new byte [unescapedArgumentLength];
493     int count = 1;
494     unescapedArgument[0] = '\'';
495     for (int i = argumentStartIndex + 1; i <= argumentEndIndex - 1; i++) {
496       if (filterStringAsByteArray [i] == ParseConstants.SINGLE_QUOTE &&
497           i != (argumentEndIndex - 1) &&
498           filterStringAsByteArray [i+1] == ParseConstants.SINGLE_QUOTE) {
499         unescapedArgument[count++] = filterStringAsByteArray [i+1];
500         i++;
501       }
502       else {
503         unescapedArgument[count++] = filterStringAsByteArray [i];
504       }
505     }
506     unescapedArgument[unescapedArgumentLength - 1] = '\'';
507     return unescapedArgument;
508   }
509 
510 /**
511  * Checks if the current index of filter string we are on is the beginning of the keyword 'OR'
512  * <p>
513  * @param filterStringAsByteArray filter string given by the user
514  * @param indexOfOr index at which an 'O' was read
515  * @return true if the keyword 'OR' is at the current index
516  */
517   public static boolean checkForOr (byte [] filterStringAsByteArray, int indexOfOr)
518     throws CharacterCodingException, ArrayIndexOutOfBoundsException {
519 
520     try {
521       if (filterStringAsByteArray[indexOfOr] == ParseConstants.O &&
522           filterStringAsByteArray[indexOfOr+1] == ParseConstants.R &&
523           (filterStringAsByteArray[indexOfOr-1] == ParseConstants.WHITESPACE ||
524            filterStringAsByteArray[indexOfOr-1] == ParseConstants.RPAREN) &&
525           (filterStringAsByteArray[indexOfOr+2] == ParseConstants.WHITESPACE ||
526            filterStringAsByteArray[indexOfOr+2] == ParseConstants.LPAREN)) {
527         return true;
528       } else {
529         return false;
530       }
531     } catch (ArrayIndexOutOfBoundsException e) {
532       return false;
533     }
534   }
535 
536 /**
537  * Checks if the current index of filter string we are on is the beginning of the keyword 'AND'
538  * <p>
539  * @param filterStringAsByteArray filter string given by the user
540  * @param indexOfAnd index at which an 'A' was read
541  * @return true if the keyword 'AND' is at the current index
542  */
543   public static boolean checkForAnd (byte [] filterStringAsByteArray, int indexOfAnd)
544     throws CharacterCodingException {
545 
546     try {
547       if (filterStringAsByteArray[indexOfAnd] == ParseConstants.A &&
548           filterStringAsByteArray[indexOfAnd+1] == ParseConstants.N &&
549           filterStringAsByteArray[indexOfAnd+2] == ParseConstants.D &&
550           (filterStringAsByteArray[indexOfAnd-1] == ParseConstants.WHITESPACE ||
551            filterStringAsByteArray[indexOfAnd-1] == ParseConstants.RPAREN) &&
552           (filterStringAsByteArray[indexOfAnd+3] == ParseConstants.WHITESPACE ||
553            filterStringAsByteArray[indexOfAnd+3] == ParseConstants.LPAREN)) {
554         return true;
555       } else {
556         return false;
557       }
558     } catch (ArrayIndexOutOfBoundsException e) {
559       return false;
560     }
561   }
562 
563 /**
564  * Checks if the current index of filter string we are on is the beginning of the keyword 'SKIP'
565  * <p>
566  * @param filterStringAsByteArray filter string given by the user
567  * @param indexOfSkip index at which an 'S' was read
568  * @return true if the keyword 'SKIP' is at the current index
569  */
570   public static boolean checkForSkip (byte [] filterStringAsByteArray, int indexOfSkip)
571     throws CharacterCodingException {
572 
573     try {
574       if (filterStringAsByteArray[indexOfSkip] == ParseConstants.S &&
575           filterStringAsByteArray[indexOfSkip+1] == ParseConstants.K &&
576           filterStringAsByteArray[indexOfSkip+2] == ParseConstants.I &&
577           filterStringAsByteArray[indexOfSkip+3] == ParseConstants.P &&
578           (indexOfSkip == 0 ||
579            filterStringAsByteArray[indexOfSkip-1] == ParseConstants.WHITESPACE ||
580            filterStringAsByteArray[indexOfSkip-1] == ParseConstants.RPAREN ||
581            filterStringAsByteArray[indexOfSkip-1] == ParseConstants.LPAREN) &&
582           (filterStringAsByteArray[indexOfSkip+4] == ParseConstants.WHITESPACE ||
583            filterStringAsByteArray[indexOfSkip+4] == ParseConstants.LPAREN)) {
584         return true;
585       } else {
586         return false;
587       }
588     } catch (ArrayIndexOutOfBoundsException e) {
589       return false;
590     }
591   }
592 
593 /**
594  * Checks if the current index of filter string we are on is the beginning of the keyword 'WHILE'
595  * <p>
596  * @param filterStringAsByteArray filter string given by the user
597  * @param indexOfWhile index at which an 'W' was read
598  * @return true if the keyword 'WHILE' is at the current index
599  */
600   public static boolean checkForWhile (byte [] filterStringAsByteArray, int indexOfWhile)
601     throws CharacterCodingException {
602 
603     try {
604       if (filterStringAsByteArray[indexOfWhile] == ParseConstants.W &&
605           filterStringAsByteArray[indexOfWhile+1] == ParseConstants.H &&
606           filterStringAsByteArray[indexOfWhile+2] == ParseConstants.I &&
607           filterStringAsByteArray[indexOfWhile+3] == ParseConstants.L &&
608           filterStringAsByteArray[indexOfWhile+4] == ParseConstants.E &&
609           (indexOfWhile == 0 || filterStringAsByteArray[indexOfWhile-1] == ParseConstants.WHITESPACE
610            || filterStringAsByteArray[indexOfWhile-1] == ParseConstants.RPAREN ||
611            filterStringAsByteArray[indexOfWhile-1] == ParseConstants.LPAREN) &&
612           (filterStringAsByteArray[indexOfWhile+5] == ParseConstants.WHITESPACE ||
613            filterStringAsByteArray[indexOfWhile+5] == ParseConstants.LPAREN)) {
614         return true;
615       } else {
616         return false;
617       }
618     } catch (ArrayIndexOutOfBoundsException e) {
619       return false;
620     }
621   }
622 
623 /**
624  * Returns a boolean indicating whether the quote was escaped or not
625  * <p>
626  * @param array byte array in which the quote was found
627  * @param quoteIndex index of the single quote
628  * @return returns true if the quote was unescaped
629  */
630   public static boolean isQuoteUnescaped (byte [] array, int quoteIndex) {
631     if (array == null) {
632       throw new IllegalArgumentException("isQuoteUnescaped called with a null array");
633     }
634 
635     if (quoteIndex == array.length - 1 || array[quoteIndex+1] != ParseConstants.SINGLE_QUOTE) {
636       return true;
637     }
638     else {
639       return false;
640     }
641   }
642 
643 /**
644  * Takes a quoted byte array and converts it into an unquoted byte array
645  * For example: given a byte array representing 'abc', it returns a
646  * byte array representing abc
647  * <p>
648  * @param quotedByteArray the quoted byte array
649  * @return Unquoted byte array
650  */
651   public static byte [] removeQuotesFromByteArray (byte [] quotedByteArray) {
652     if (quotedByteArray == null ||
653         quotedByteArray.length < 2 ||
654         quotedByteArray[0] != ParseConstants.SINGLE_QUOTE ||
655         quotedByteArray[quotedByteArray.length - 1] != ParseConstants.SINGLE_QUOTE) {
656       throw new IllegalArgumentException("removeQuotesFromByteArray needs a quoted byte array");
657     } else {
658       byte [] targetString = new byte [quotedByteArray.length - 2];
659       Bytes.putBytes(targetString, 0, quotedByteArray, 1, quotedByteArray.length - 2);
660       return targetString;
661     }
662   }
663 
664 /**
665  * Converts an int expressed in a byte array to an actual int
666  * <p>
667  * This doesn't use Bytes.toInt because that assumes
668  * that there will be {@link Bytes#SIZEOF_INT} bytes available.
669  * <p>
670  * @param numberAsByteArray the int value expressed as a byte array
671  * @return the int value
672  */
673   public static int convertByteArrayToInt (byte [] numberAsByteArray) {
674 
675     long tempResult = ParseFilter.convertByteArrayToLong(numberAsByteArray);
676 
677     if (tempResult > Integer.MAX_VALUE) {
678       throw new IllegalArgumentException("Integer Argument too large");
679     } else if (tempResult < Integer.MIN_VALUE) {
680       throw new IllegalArgumentException("Integer Argument too small");
681     }
682 
683     int result = (int) tempResult;
684     return result;
685   }
686 
687 /**
688  * Converts a long expressed in a byte array to an actual long
689  * <p>
690  * This doesn't use Bytes.toLong because that assumes
691  * that there will be {@link Bytes#SIZEOF_INT} bytes available.
692  * <p>
693  * @param numberAsByteArray the long value expressed as a byte array
694  * @return the long value
695  */
696   public static long convertByteArrayToLong (byte [] numberAsByteArray) {
697     if (numberAsByteArray == null) {
698       throw new IllegalArgumentException("convertByteArrayToLong called with a null array");
699     }
700 
701     int i = 0;
702     long result = 0;
703     boolean isNegative = false;
704 
705     if (numberAsByteArray[i] == ParseConstants.MINUS_SIGN) {
706       i++;
707       isNegative = true;
708     }
709 
710     while (i != numberAsByteArray.length) {
711       if (numberAsByteArray[i] < ParseConstants.ZERO ||
712           numberAsByteArray[i] > ParseConstants.NINE) {
713         throw new IllegalArgumentException("Byte Array should only contain digits");
714       }
715       result = result*10 + (numberAsByteArray[i] - ParseConstants.ZERO);
716       if (result < 0) {
717         throw new IllegalArgumentException("Long Argument too large");
718       }
719       i++;
720     }
721 
722     if (isNegative) {
723       return -result;
724     } else {
725       return result;
726     }
727   }
728 
729 /**
730  * Converts a boolean expressed in a byte array to an actual boolean
731  *<p>
732  * This doesn't used Bytes.toBoolean because Bytes.toBoolean(byte [])
733  * assumes that 1 stands for true and 0 for false.
734  * Here, the byte array representing "true" and "false" is parsed
735  * <p>
736  * @param booleanAsByteArray the boolean value expressed as a byte array
737  * @return the boolean value
738  */
739   public static boolean convertByteArrayToBoolean (byte [] booleanAsByteArray) {
740     if (booleanAsByteArray == null) {
741       throw new IllegalArgumentException("convertByteArrayToBoolean called with a null array");
742     }
743 
744     if (booleanAsByteArray.length == 4 &&
745         (booleanAsByteArray[0] == 't' || booleanAsByteArray[0] == 'T') &&
746         (booleanAsByteArray[1] == 'r' || booleanAsByteArray[1] == 'R') &&
747         (booleanAsByteArray[2] == 'u' || booleanAsByteArray[2] == 'U') &&
748         (booleanAsByteArray[3] == 'e' || booleanAsByteArray[3] == 'E')) {
749       return true;
750     }
751     else if (booleanAsByteArray.length == 5 &&
752              (booleanAsByteArray[0] == 'f' || booleanAsByteArray[0] == 'F') &&
753              (booleanAsByteArray[1] == 'a' || booleanAsByteArray[1] == 'A') &&
754              (booleanAsByteArray[2] == 'l' || booleanAsByteArray[2] == 'L') &&
755              (booleanAsByteArray[3] == 's' || booleanAsByteArray[3] == 'S') &&
756              (booleanAsByteArray[4] == 'e' || booleanAsByteArray[4] == 'E')) {
757       return false;
758     }
759     else {
760       throw new IllegalArgumentException("Incorrect Boolean Expression");
761     }
762   }
763 
764 /**
765  * Takes a compareOperator symbol as a byte array and returns the corresponding CompareOperator
766  * <p>
767  * @param compareOpAsByteArray the comparatorOperator symbol as a byte array
768  * @return the Compare Operator
769  */
770   public static CompareFilter.CompareOp createCompareOp (byte [] compareOpAsByteArray) {
771     ByteBuffer compareOp = ByteBuffer.wrap(compareOpAsByteArray);
772     if (compareOp.equals(ParseConstants.LESS_THAN_BUFFER))
773       return CompareOp.LESS;
774     else if (compareOp.equals(ParseConstants.LESS_THAN_OR_EQUAL_TO_BUFFER))
775       return CompareOp.LESS_OR_EQUAL;
776     else if (compareOp.equals(ParseConstants.GREATER_THAN_BUFFER))
777       return CompareOp.GREATER;
778     else if (compareOp.equals(ParseConstants.GREATER_THAN_OR_EQUAL_TO_BUFFER))
779       return CompareOp.GREATER_OR_EQUAL;
780     else if (compareOp.equals(ParseConstants.NOT_EQUAL_TO_BUFFER))
781       return CompareOp.NOT_EQUAL;
782     else if (compareOp.equals(ParseConstants.EQUAL_TO_BUFFER))
783       return CompareOp.EQUAL;
784     else
785       throw new IllegalArgumentException("Invalid compare operator");
786   }
787 
788 /**
789  * Parses a comparator of the form comparatorType:comparatorValue form and returns a comparator
790  * <p>
791  * @param comparator the comparator in the form comparatorType:comparatorValue
792  * @return the parsed comparator
793  */
794   public static ByteArrayComparable createComparator (byte [] comparator) {
795     if (comparator == null)
796       throw new IllegalArgumentException("Incorrect Comparator");
797     byte [][] parsedComparator = ParseFilter.parseComparator(comparator);
798     byte [] comparatorType = parsedComparator[0];
799     byte [] comparatorValue = parsedComparator[1];
800 
801 
802     if (Bytes.equals(comparatorType, ParseConstants.binaryType))
803       return new BinaryComparator(comparatorValue);
804     else if (Bytes.equals(comparatorType, ParseConstants.binaryPrefixType))
805       return new BinaryPrefixComparator(comparatorValue);
806     else if (Bytes.equals(comparatorType, ParseConstants.regexStringType))
807       return new RegexStringComparator(new String(comparatorValue));
808     else if (Bytes.equals(comparatorType, ParseConstants.substringType))
809       return new SubstringComparator(new String(comparatorValue));
810     else
811       throw new IllegalArgumentException("Incorrect comparatorType");
812   }
813 
814 /**
815  * Splits a column in comparatorType:comparatorValue form into separate byte arrays
816  * <p>
817  * @param comparator the comparator
818  * @return the parsed arguments of the comparator as a 2D byte array
819  */
820   public static byte [][] parseComparator (byte [] comparator) {
821     final int index = KeyValue.getDelimiter(comparator, 0, comparator.length, ParseConstants.COLON);
822     if (index == -1) {
823       throw new IllegalArgumentException("Incorrect comparator");
824     }
825 
826     byte [][] result = new byte [2][0];
827     result[0] = new byte [index];
828     System.arraycopy(comparator, 0, result[0], 0, index);
829 
830     final int len = comparator.length - (index + 1);
831     result[1] = new byte[len];
832     System.arraycopy(comparator, index + 1, result[1], 0, len);
833 
834     return result;
835   }
836 
837 /**
838  * Return a Set of filters supported by the Filter Language
839  */
840   public Set<String> getSupportedFilters () {
841     return filterHashMap.keySet();
842   }
843 
844   /**
845    * Returns all known filters
846    * @return an unmodifiable map of filters
847    */
848   public static Map<String, String> getAllFilters() {
849     return Collections.unmodifiableMap(filterHashMap);
850   }
851 
852   /**
853    * Register a new filter with the parser.  If the filter is already registered,
854    * an IllegalArgumentException will be thrown.
855    *
856    * @param name a name for the filter
857    * @param filterClass fully qualified class name
858    */
859   public static void registerFilter(String name, String filterClass) {
860     if(LOG.isInfoEnabled())
861       LOG.info("Registering new filter " + name);
862 
863     filterHashMap.put(name, filterClass);
864   }
865 }