View Javadoc

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