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