001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.filter;
020
021import java.lang.reflect.InvocationTargetException;
022import java.lang.reflect.Method;
023import java.nio.ByteBuffer;
024import java.nio.charset.CharacterCodingException;
025import java.nio.charset.StandardCharsets;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.EmptyStackException;
029import java.util.HashMap;
030import java.util.Map;
031import java.util.Set;
032import java.util.Stack;
033
034import org.apache.hadoop.hbase.CompareOperator;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.yetus.audience.InterfaceAudience;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * This class allows a user to specify a filter via a string
042 * The string is parsed using the methods of this class and
043 * a filter object is constructed. This filter object is then wrapped
044 * in a scanner object which is then returned
045 * <p>
046 * This class addresses the HBASE-4168 JIRA. More documentation on this
047 * Filter Language can be found at: https://issues.apache.org/jira/browse/HBASE-4176
048 */
049@InterfaceAudience.Public
050public class ParseFilter {
051  private static final Logger LOG = LoggerFactory.getLogger(ParseFilter.class);
052
053  private static HashMap<ByteBuffer, Integer> operatorPrecedenceHashMap;
054  private static HashMap<String, String> filterHashMap;
055
056  static {
057    // Registers all the filter supported by the Filter Language
058    filterHashMap = new HashMap<>();
059    filterHashMap.put("KeyOnlyFilter", ParseConstants.FILTER_PACKAGE + "." +
060                      "KeyOnlyFilter");
061    filterHashMap.put("FirstKeyOnlyFilter", ParseConstants.FILTER_PACKAGE + "." +
062                      "FirstKeyOnlyFilter");
063    filterHashMap.put("PrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
064                      "PrefixFilter");
065    filterHashMap.put("ColumnPrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
066                      "ColumnPrefixFilter");
067    filterHashMap.put("MultipleColumnPrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
068                      "MultipleColumnPrefixFilter");
069    filterHashMap.put("ColumnCountGetFilter", ParseConstants.FILTER_PACKAGE + "." +
070                      "ColumnCountGetFilter");
071    filterHashMap.put("PageFilter", ParseConstants.FILTER_PACKAGE + "." +
072                      "PageFilter");
073    filterHashMap.put("ColumnPaginationFilter", ParseConstants.FILTER_PACKAGE + "." +
074                      "ColumnPaginationFilter");
075    filterHashMap.put("InclusiveStopFilter", ParseConstants.FILTER_PACKAGE + "." +
076                      "InclusiveStopFilter");
077    filterHashMap.put("TimestampsFilter", ParseConstants.FILTER_PACKAGE + "." +
078                      "TimestampsFilter");
079    filterHashMap.put("RowFilter", ParseConstants.FILTER_PACKAGE + "." +
080                      "RowFilter");
081    filterHashMap.put("FamilyFilter", ParseConstants.FILTER_PACKAGE + "." +
082                      "FamilyFilter");
083    filterHashMap.put("QualifierFilter", ParseConstants.FILTER_PACKAGE + "." +
084                      "QualifierFilter");
085    filterHashMap.put("ValueFilter", ParseConstants.FILTER_PACKAGE + "." +
086                      "ValueFilter");
087    filterHashMap.put("ColumnRangeFilter", ParseConstants.FILTER_PACKAGE + "." +
088                      "ColumnRangeFilter");
089    filterHashMap.put("SingleColumnValueFilter", ParseConstants.FILTER_PACKAGE + "." +
090                      "SingleColumnValueFilter");
091    filterHashMap.put("SingleColumnValueExcludeFilter", ParseConstants.FILTER_PACKAGE + "." +
092                      "SingleColumnValueExcludeFilter");
093    filterHashMap.put("DependentColumnFilter", ParseConstants.FILTER_PACKAGE + "." +
094                      "DependentColumnFilter");
095    filterHashMap.put("ColumnValueFilter", ParseConstants.FILTER_PACKAGE + "." +
096                      "ColumnValueFilter");
097
098    // Creates the operatorPrecedenceHashMap
099    operatorPrecedenceHashMap = new HashMap<>();
100    operatorPrecedenceHashMap.put(ParseConstants.SKIP_BUFFER, 1);
101    operatorPrecedenceHashMap.put(ParseConstants.WHILE_BUFFER, 1);
102    operatorPrecedenceHashMap.put(ParseConstants.AND_BUFFER, 2);
103    operatorPrecedenceHashMap.put(ParseConstants.OR_BUFFER, 3);
104  }
105
106  /**
107   * Parses the filterString and constructs a filter using it
108   * <p>
109   * @param filterString filter string given by the user
110   * @return filter object we constructed
111   */
112  public Filter parseFilterString (String filterString)
113    throws CharacterCodingException {
114    return parseFilterString(Bytes.toBytes(filterString));
115  }
116
117  /**
118   * Parses the filterString and constructs a filter using it
119   * <p>
120   * @param filterStringAsByteArray filter string given by the user
121   * @return filter object we constructed
122   */
123  public Filter parseFilterString (byte [] filterStringAsByteArray)
124    throws CharacterCodingException {
125    // stack for the operators and parenthesis
126    Stack <ByteBuffer> operatorStack = new Stack<>();
127    // stack for the filter objects
128    Stack <Filter> filterStack = new Stack<>();
129
130    Filter filter = null;
131    for (int i=0; i<filterStringAsByteArray.length; i++) {
132      if (filterStringAsByteArray[i] == ParseConstants.LPAREN) {
133        // LPAREN found
134        operatorStack.push(ParseConstants.LPAREN_BUFFER);
135      } else if (filterStringAsByteArray[i] == ParseConstants.WHITESPACE ||
136                 filterStringAsByteArray[i] == ParseConstants.TAB) {
137        // WHITESPACE or TAB found
138        continue;
139      } else if (checkForOr(filterStringAsByteArray, i)) {
140        // OR found
141        i += ParseConstants.OR_ARRAY.length - 1;
142        reduce(operatorStack, filterStack, ParseConstants.OR_BUFFER);
143        operatorStack.push(ParseConstants.OR_BUFFER);
144      } else if (checkForAnd(filterStringAsByteArray, i)) {
145        // AND found
146        i += ParseConstants.AND_ARRAY.length - 1;
147        reduce(operatorStack, filterStack, ParseConstants.AND_BUFFER);
148        operatorStack.push(ParseConstants.AND_BUFFER);
149      } else if (checkForSkip(filterStringAsByteArray, i)) {
150        // SKIP found
151        i += ParseConstants.SKIP_ARRAY.length - 1;
152        reduce(operatorStack, filterStack, ParseConstants.SKIP_BUFFER);
153        operatorStack.push(ParseConstants.SKIP_BUFFER);
154      } else if (checkForWhile(filterStringAsByteArray, i)) {
155        // WHILE found
156        i += ParseConstants.WHILE_ARRAY.length - 1;
157        reduce(operatorStack, filterStack, ParseConstants.WHILE_BUFFER);
158        operatorStack.push(ParseConstants.WHILE_BUFFER);
159      } else if (filterStringAsByteArray[i] == ParseConstants.RPAREN) {
160        // RPAREN found
161        if (operatorStack.empty()) {
162          throw new IllegalArgumentException("Mismatched parenthesis");
163        }
164        ByteBuffer argumentOnTopOfStack = operatorStack.peek();
165        if (argumentOnTopOfStack.equals(ParseConstants.LPAREN_BUFFER)) {
166          operatorStack.pop();
167          continue;
168        }
169        while (!(argumentOnTopOfStack.equals(ParseConstants.LPAREN_BUFFER))) {
170          filterStack.push(popArguments(operatorStack, filterStack));
171          if (operatorStack.empty()) {
172            throw new IllegalArgumentException("Mismatched parenthesis");
173          }
174          argumentOnTopOfStack = operatorStack.pop();
175        }
176      } else {
177        // SimpleFilterExpression found
178        byte [] filterSimpleExpression = extractFilterSimpleExpression(filterStringAsByteArray, i);
179        i+= (filterSimpleExpression.length - 1);
180        filter = parseSimpleFilterExpression(filterSimpleExpression);
181        filterStack.push(filter);
182      }
183    }
184
185    // Finished parsing filterString
186    while (!operatorStack.empty()) {
187      filterStack.push(popArguments(operatorStack, filterStack));
188    }
189    if (filterStack.empty()) {
190        throw new IllegalArgumentException("Incorrect Filter String");
191    }
192    filter = filterStack.pop();
193    if (!filterStack.empty()) {
194      throw new IllegalArgumentException("Incorrect Filter String");
195    }
196    return filter;
197  }
198
199/**
200 * Extracts a simple filter expression from the filter string given by the user
201 * <p>
202 * A simpleFilterExpression is of the form: FilterName('arg', 'arg', 'arg')
203 * The user given filter string can have many simpleFilterExpressions combined
204 * using operators.
205 * <p>
206 * This function extracts a simpleFilterExpression from the
207 * larger filterString given the start offset of the simpler expression
208 * <p>
209 * @param filterStringAsByteArray filter string given by the user
210 * @param filterExpressionStartOffset start index of the simple filter expression
211 * @return byte array containing the simple filter expression
212 */
213  public byte [] extractFilterSimpleExpression (byte [] filterStringAsByteArray,
214                                                int filterExpressionStartOffset)
215    throws CharacterCodingException {
216    int quoteCount = 0;
217    for (int i=filterExpressionStartOffset; i<filterStringAsByteArray.length; i++) {
218      if (filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE) {
219        if (isQuoteUnescaped(filterStringAsByteArray, i)) {
220          quoteCount ++;
221        } else {
222          // To skip the next quote that has been escaped
223          i++;
224        }
225      }
226      if (filterStringAsByteArray[i] == ParseConstants.RPAREN && (quoteCount %2 ) == 0) {
227        byte [] filterSimpleExpression = new byte [i - filterExpressionStartOffset + 1];
228        Bytes.putBytes(filterSimpleExpression, 0, filterStringAsByteArray,
229                       filterExpressionStartOffset, i-filterExpressionStartOffset + 1);
230        return filterSimpleExpression;
231      }
232    }
233    throw new IllegalArgumentException("Incorrect Filter String");
234  }
235
236/**
237 * Constructs a filter object given a simple filter expression
238 * <p>
239 * @param filterStringAsByteArray filter string given by the user
240 * @return filter object we constructed
241 */
242  public Filter parseSimpleFilterExpression (byte [] filterStringAsByteArray)
243    throws CharacterCodingException {
244
245    String filterName = Bytes.toString(getFilterName(filterStringAsByteArray));
246    ArrayList<byte[]> filterArguments = getFilterArguments(filterStringAsByteArray);
247    if (!filterHashMap.containsKey(filterName)) {
248      throw new IllegalArgumentException("Filter Name " + filterName + " not supported");
249    }
250    filterName = filterHashMap.get(filterName);
251    final String methodName = "createFilterFromArguments";
252    try {
253      Class<?> c = Class.forName(filterName);
254      Class<?>[] argTypes = new Class[] { ArrayList.class };
255      Method m = c.getDeclaredMethod(methodName, argTypes);
256      return (Filter) m.invoke(null, filterArguments);
257    } catch (ClassNotFoundException e) {
258      LOG.error("Could not find class {}", filterName, e);
259    } catch (NoSuchMethodException e) {
260      LOG.error("Could not find method {} in {}", methodName, filterName, e);
261    } catch (IllegalAccessException e) {
262      LOG.error("Unable to access specified class {}", filterName, e);
263    } catch (InvocationTargetException e) {
264      LOG.error("Method {} threw an exception for {}", methodName, filterName, e);
265    }
266    throw new IllegalArgumentException(
267        "Incorrect filter string " + 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 = Bytes.searchDelimiterIndex(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<>();
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<>();
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<>();
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   * @param compareOpAsByteArray the comparatorOperator symbol as a byte array
775   * @return the Compare Operator
776   */
777  public static CompareOperator createCompareOperator (byte [] compareOpAsByteArray) {
778    ByteBuffer compareOp = ByteBuffer.wrap(compareOpAsByteArray);
779    if (compareOp.equals(ParseConstants.LESS_THAN_BUFFER))
780      return CompareOperator.LESS;
781    else if (compareOp.equals(ParseConstants.LESS_THAN_OR_EQUAL_TO_BUFFER))
782      return CompareOperator.LESS_OR_EQUAL;
783    else if (compareOp.equals(ParseConstants.GREATER_THAN_BUFFER))
784      return CompareOperator.GREATER;
785    else if (compareOp.equals(ParseConstants.GREATER_THAN_OR_EQUAL_TO_BUFFER))
786      return CompareOperator.GREATER_OR_EQUAL;
787    else if (compareOp.equals(ParseConstants.NOT_EQUAL_TO_BUFFER))
788      return CompareOperator.NOT_EQUAL;
789    else if (compareOp.equals(ParseConstants.EQUAL_TO_BUFFER))
790      return CompareOperator.EQUAL;
791    else
792      throw new IllegalArgumentException("Invalid compare operator");
793  }
794
795/**
796 * Parses a comparator of the form comparatorType:comparatorValue form and returns a comparator
797 * <p>
798 * @param comparator the comparator in the form comparatorType:comparatorValue
799 * @return the parsed comparator
800 */
801  public static ByteArrayComparable createComparator (byte [] comparator) {
802    if (comparator == null)
803      throw new IllegalArgumentException("Incorrect Comparator");
804    byte [][] parsedComparator = ParseFilter.parseComparator(comparator);
805    byte [] comparatorType = parsedComparator[0];
806    byte [] comparatorValue = parsedComparator[1];
807
808
809    if (Bytes.equals(comparatorType, ParseConstants.binaryType))
810      return new BinaryComparator(comparatorValue);
811    else if (Bytes.equals(comparatorType, ParseConstants.binaryPrefixType))
812      return new BinaryPrefixComparator(comparatorValue);
813    else if (Bytes.equals(comparatorType, ParseConstants.regexStringType))
814      return new RegexStringComparator(new String(comparatorValue, StandardCharsets.UTF_8));
815    else if (Bytes.equals(comparatorType, ParseConstants.substringType))
816      return new SubstringComparator(new String(comparatorValue, StandardCharsets.UTF_8));
817    else
818      throw new IllegalArgumentException("Incorrect comparatorType");
819  }
820
821/**
822 * Splits a column in comparatorType:comparatorValue form into separate byte arrays
823 * <p>
824 * @param comparator the comparator
825 * @return the parsed arguments of the comparator as a 2D byte array
826 */
827  public static byte [][] parseComparator (byte [] comparator) {
828    final int index = Bytes.searchDelimiterIndex(comparator, 0, comparator.length,
829        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}