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