View Javadoc

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