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