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.yetus.audience.InterfaceAudience;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
039import org.apache.hadoop.hbase.util.Bytes;
040
041/**
042 * This class allows a user to specify a filter via a string
043 * The string is parsed using the methods of this class and
044 * a filter object is constructed. This filter object is then wrapped
045 * in a scanner object which is then returned
046 * <p>
047 * This class addresses the HBASE-4168 JIRA. More documentation on this
048 * Filter Language can be found at: https://issues.apache.org/jira/browse/HBASE-4176
049 */
050@InterfaceAudience.Public
051public class ParseFilter {
052  private static final Logger LOG = LoggerFactory.getLogger(ParseFilter.class);
053
054  private static HashMap<ByteBuffer, Integer> operatorPrecedenceHashMap;
055  private static HashMap<String, String> filterHashMap;
056
057  static {
058    // Registers all the filter supported by the Filter Language
059    filterHashMap = new HashMap<>();
060    filterHashMap.put("KeyOnlyFilter", ParseConstants.FILTER_PACKAGE + "." +
061                      "KeyOnlyFilter");
062    filterHashMap.put("FirstKeyOnlyFilter", ParseConstants.FILTER_PACKAGE + "." +
063                      "FirstKeyOnlyFilter");
064    filterHashMap.put("PrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
065                      "PrefixFilter");
066    filterHashMap.put("ColumnPrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
067                      "ColumnPrefixFilter");
068    filterHashMap.put("MultipleColumnPrefixFilter", ParseConstants.FILTER_PACKAGE + "." +
069                      "MultipleColumnPrefixFilter");
070    filterHashMap.put("ColumnCountGetFilter", ParseConstants.FILTER_PACKAGE + "." +
071                      "ColumnCountGetFilter");
072    filterHashMap.put("PageFilter", ParseConstants.FILTER_PACKAGE + "." +
073                      "PageFilter");
074    filterHashMap.put("ColumnPaginationFilter", ParseConstants.FILTER_PACKAGE + "." +
075                      "ColumnPaginationFilter");
076    filterHashMap.put("InclusiveStopFilter", ParseConstants.FILTER_PACKAGE + "." +
077                      "InclusiveStopFilter");
078    filterHashMap.put("TimestampsFilter", ParseConstants.FILTER_PACKAGE + "." +
079                      "TimestampsFilter");
080    filterHashMap.put("RowFilter", ParseConstants.FILTER_PACKAGE + "." +
081                      "RowFilter");
082    filterHashMap.put("FamilyFilter", ParseConstants.FILTER_PACKAGE + "." +
083                      "FamilyFilter");
084    filterHashMap.put("QualifierFilter", ParseConstants.FILTER_PACKAGE + "." +
085                      "QualifierFilter");
086    filterHashMap.put("ValueFilter", ParseConstants.FILTER_PACKAGE + "." +
087                      "ValueFilter");
088    filterHashMap.put("ColumnRangeFilter", ParseConstants.FILTER_PACKAGE + "." +
089                      "ColumnRangeFilter");
090    filterHashMap.put("SingleColumnValueFilter", ParseConstants.FILTER_PACKAGE + "." +
091                      "SingleColumnValueFilter");
092    filterHashMap.put("SingleColumnValueExcludeFilter", ParseConstants.FILTER_PACKAGE + "." +
093                      "SingleColumnValueExcludeFilter");
094    filterHashMap.put("DependentColumnFilter", ParseConstants.FILTER_PACKAGE + "." +
095                      "DependentColumnFilter");
096    filterHashMap.put("ColumnValueFilter", ParseConstants.FILTER_PACKAGE + "." +
097                      "ColumnValueFilter");
098
099    // Creates the operatorPrecedenceHashMap
100    operatorPrecedenceHashMap = new HashMap<>();
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<>();
128    // stack for the filter objects
129    Stack <Filter> filterStack = new Stack<>();
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 = 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   * Takes a compareOperator symbol as a byte array and returns the corresponding CompareOperator
797   * @deprecated Since 2.0
798   * <p>
799   * @param compareOpAsByteArray the comparatorOperator symbol as a byte array
800   * @return the Compare Operator
801   * @deprecated Since 2.0.0. Will be removed in 3.0.0. Use {@link #createCompareOperator(byte [])}
802   */
803  @Deprecated
804  public static CompareFilter.CompareOp createCompareOp (byte [] compareOpAsByteArray) {
805    ByteBuffer compareOp = ByteBuffer.wrap(compareOpAsByteArray);
806    if (compareOp.equals(ParseConstants.LESS_THAN_BUFFER))
807      return CompareOp.LESS;
808    else if (compareOp.equals(ParseConstants.LESS_THAN_OR_EQUAL_TO_BUFFER))
809      return CompareOp.LESS_OR_EQUAL;
810    else if (compareOp.equals(ParseConstants.GREATER_THAN_BUFFER))
811      return CompareOp.GREATER;
812    else if (compareOp.equals(ParseConstants.GREATER_THAN_OR_EQUAL_TO_BUFFER))
813      return CompareOp.GREATER_OR_EQUAL;
814    else if (compareOp.equals(ParseConstants.NOT_EQUAL_TO_BUFFER))
815      return CompareOp.NOT_EQUAL;
816    else if (compareOp.equals(ParseConstants.EQUAL_TO_BUFFER))
817      return CompareOp.EQUAL;
818    else
819      throw new IllegalArgumentException("Invalid compare operator");
820  }
821
822/**
823 * Parses a comparator of the form comparatorType:comparatorValue form and returns a comparator
824 * <p>
825 * @param comparator the comparator in the form comparatorType:comparatorValue
826 * @return the parsed comparator
827 */
828  public static ByteArrayComparable createComparator (byte [] comparator) {
829    if (comparator == null)
830      throw new IllegalArgumentException("Incorrect Comparator");
831    byte [][] parsedComparator = ParseFilter.parseComparator(comparator);
832    byte [] comparatorType = parsedComparator[0];
833    byte [] comparatorValue = parsedComparator[1];
834
835
836    if (Bytes.equals(comparatorType, ParseConstants.binaryType))
837      return new BinaryComparator(comparatorValue);
838    else if (Bytes.equals(comparatorType, ParseConstants.binaryPrefixType))
839      return new BinaryPrefixComparator(comparatorValue);
840    else if (Bytes.equals(comparatorType, ParseConstants.regexStringType))
841      return new RegexStringComparator(new String(comparatorValue, StandardCharsets.UTF_8));
842    else if (Bytes.equals(comparatorType, ParseConstants.substringType))
843      return new SubstringComparator(new String(comparatorValue, StandardCharsets.UTF_8));
844    else
845      throw new IllegalArgumentException("Incorrect comparatorType");
846  }
847
848/**
849 * Splits a column in comparatorType:comparatorValue form into separate byte arrays
850 * <p>
851 * @param comparator the comparator
852 * @return the parsed arguments of the comparator as a 2D byte array
853 */
854  public static byte [][] parseComparator (byte [] comparator) {
855    final int index = Bytes.searchDelimiterIndex(comparator, 0, comparator.length,
856        ParseConstants.COLON);
857    if (index == -1) {
858      throw new IllegalArgumentException("Incorrect comparator");
859    }
860
861    byte [][] result = new byte [2][0];
862    result[0] = new byte [index];
863    System.arraycopy(comparator, 0, result[0], 0, index);
864
865    final int len = comparator.length - (index + 1);
866    result[1] = new byte[len];
867    System.arraycopy(comparator, index + 1, result[1], 0, len);
868
869    return result;
870  }
871
872/**
873 * Return a Set of filters supported by the Filter Language
874 */
875  public Set<String> getSupportedFilters () {
876    return filterHashMap.keySet();
877  }
878
879  /**
880   * Returns all known filters
881   * @return an unmodifiable map of filters
882   */
883  public static Map<String, String> getAllFilters() {
884    return Collections.unmodifiableMap(filterHashMap);
885  }
886
887  /**
888   * Register a new filter with the parser.  If the filter is already registered,
889   * an IllegalArgumentException will be thrown.
890   *
891   * @param name a name for the filter
892   * @param filterClass fully qualified class name
893   */
894  public static void registerFilter(String name, String filterClass) {
895    if(LOG.isInfoEnabled())
896      LOG.info("Registering new filter " + name);
897
898    filterHashMap.put(name, filterClass);
899  }
900}