001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.filter;
019
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.nio.ByteBuffer;
023import java.nio.charset.CharacterCodingException;
024import java.nio.charset.StandardCharsets;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.EmptyStackException;
028import java.util.HashMap;
029import java.util.Map;
030import java.util.Set;
031import java.util.Stack;
032import org.apache.hadoop.hbase.CompareOperator;
033import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
034import org.apache.hadoop.hbase.util.Bytes;
035import org.apache.yetus.audience.InterfaceAudience;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * This class allows a user to specify a filter via a string The string is parsed using the methods
041 * of this class and a filter object is constructed. This filter object is then wrapped in a scanner
042 * object which is then returned
043 * <p>
044 * This class addresses the HBASE-4176 JIRA. More documentation on this Filter Language can be found
045 * at: https://issues.apache.org/jira/browse/HBASE-4176
046 */
047@InterfaceAudience.Public
048public class ParseFilter {
049  private static final Logger LOG = LoggerFactory.getLogger(ParseFilter.class);
050
051  private static HashMap<ByteBuffer, Integer> operatorPrecedenceHashMap;
052  private static HashMap<String, String> filterHashMap;
053
054  static {
055    // Registers all the filter supported by the Filter Language
056    filterHashMap = new HashMap<>();
057    filterHashMap.put("KeyOnlyFilter", ParseConstants.FILTER_PACKAGE + "." + "KeyOnlyFilter");
058    filterHashMap.put("FirstKeyOnlyFilter",
059      ParseConstants.FILTER_PACKAGE + "." + "FirstKeyOnlyFilter");
060    filterHashMap.put("PrefixFilter", ParseConstants.FILTER_PACKAGE + "." + "PrefixFilter");
061    filterHashMap.put("ColumnPrefixFilter",
062      ParseConstants.FILTER_PACKAGE + "." + "ColumnPrefixFilter");
063    filterHashMap.put("MultipleColumnPrefixFilter",
064      ParseConstants.FILTER_PACKAGE + "." + "MultipleColumnPrefixFilter");
065    filterHashMap.put("ColumnCountGetFilter",
066      ParseConstants.FILTER_PACKAGE + "." + "ColumnCountGetFilter");
067    filterHashMap.put("PageFilter", ParseConstants.FILTER_PACKAGE + "." + "PageFilter");
068    filterHashMap.put("ColumnPaginationFilter",
069      ParseConstants.FILTER_PACKAGE + "." + "ColumnPaginationFilter");
070    filterHashMap.put("InclusiveStopFilter",
071      ParseConstants.FILTER_PACKAGE + "." + "InclusiveStopFilter");
072    filterHashMap.put("TimestampsFilter", ParseConstants.FILTER_PACKAGE + "." + "TimestampsFilter");
073    filterHashMap.put("RowFilter", ParseConstants.FILTER_PACKAGE + "." + "RowFilter");
074    filterHashMap.put("FamilyFilter", ParseConstants.FILTER_PACKAGE + "." + "FamilyFilter");
075    filterHashMap.put("QualifierFilter", ParseConstants.FILTER_PACKAGE + "." + "QualifierFilter");
076    filterHashMap.put("ValueFilter", ParseConstants.FILTER_PACKAGE + "." + "ValueFilter");
077    filterHashMap.put("ColumnRangeFilter",
078      ParseConstants.FILTER_PACKAGE + "." + "ColumnRangeFilter");
079    filterHashMap.put("SingleColumnValueFilter",
080      ParseConstants.FILTER_PACKAGE + "." + "SingleColumnValueFilter");
081    filterHashMap.put("SingleColumnValueExcludeFilter",
082      ParseConstants.FILTER_PACKAGE + "." + "SingleColumnValueExcludeFilter");
083    filterHashMap.put("DependentColumnFilter",
084      ParseConstants.FILTER_PACKAGE + "." + "DependentColumnFilter");
085    filterHashMap.put("ColumnValueFilter",
086      ParseConstants.FILTER_PACKAGE + "." + "ColumnValueFilter");
087
088    // Creates the operatorPrecedenceHashMap
089    operatorPrecedenceHashMap = new HashMap<>();
090    operatorPrecedenceHashMap.put(ParseConstants.SKIP_BUFFER, 1);
091    operatorPrecedenceHashMap.put(ParseConstants.WHILE_BUFFER, 1);
092    operatorPrecedenceHashMap.put(ParseConstants.AND_BUFFER, 2);
093    operatorPrecedenceHashMap.put(ParseConstants.OR_BUFFER, 3);
094  }
095
096  /**
097   * Parses the filterString and constructs a filter using it
098   * <p>
099   * @param filterString filter string given by the user
100   * @return filter object we constructed
101   */
102  public Filter parseFilterString(String filterString) throws CharacterCodingException {
103    return parseFilterString(Bytes.toBytes(filterString));
104  }
105
106  /**
107   * Parses the filterString and constructs a filter using it
108   * <p>
109   * @param filterStringAsByteArray filter string given by the user
110   * @return filter object we constructed
111   */
112  public Filter parseFilterString(byte[] filterStringAsByteArray) throws CharacterCodingException {
113    // stack for the operators and parenthesis
114    Stack<ByteBuffer> operatorStack = new Stack<>();
115    // stack for the filter objects
116    Stack<Filter> filterStack = new Stack<>();
117
118    Filter filter = null;
119    for (int i = 0; i < filterStringAsByteArray.length; i++) {
120      if (filterStringAsByteArray[i] == ParseConstants.LPAREN) {
121        // LPAREN found
122        operatorStack.push(ParseConstants.LPAREN_BUFFER);
123      } else if (
124        filterStringAsByteArray[i] == ParseConstants.WHITESPACE
125          || filterStringAsByteArray[i] == ParseConstants.TAB
126      ) {
127        // WHITESPACE or TAB found
128        continue;
129      } else if (checkForOr(filterStringAsByteArray, i)) {
130        // OR found
131        i += ParseConstants.OR_ARRAY.length - 1;
132        reduce(operatorStack, filterStack, ParseConstants.OR_BUFFER);
133        operatorStack.push(ParseConstants.OR_BUFFER);
134      } else if (checkForAnd(filterStringAsByteArray, i)) {
135        // AND found
136        i += ParseConstants.AND_ARRAY.length - 1;
137        reduce(operatorStack, filterStack, ParseConstants.AND_BUFFER);
138        operatorStack.push(ParseConstants.AND_BUFFER);
139      } else if (checkForSkip(filterStringAsByteArray, i)) {
140        // SKIP found
141        i += ParseConstants.SKIP_ARRAY.length - 1;
142        reduce(operatorStack, filterStack, ParseConstants.SKIP_BUFFER);
143        operatorStack.push(ParseConstants.SKIP_BUFFER);
144      } else if (checkForWhile(filterStringAsByteArray, i)) {
145        // WHILE found
146        i += ParseConstants.WHILE_ARRAY.length - 1;
147        reduce(operatorStack, filterStack, ParseConstants.WHILE_BUFFER);
148        operatorStack.push(ParseConstants.WHILE_BUFFER);
149      } else if (filterStringAsByteArray[i] == ParseConstants.RPAREN) {
150        // RPAREN found
151        if (operatorStack.empty()) {
152          throw new IllegalArgumentException("Mismatched parenthesis");
153        }
154        ByteBuffer argumentOnTopOfStack = operatorStack.peek();
155        if (argumentOnTopOfStack.equals(ParseConstants.LPAREN_BUFFER)) {
156          operatorStack.pop();
157          continue;
158        }
159        while (!(argumentOnTopOfStack.equals(ParseConstants.LPAREN_BUFFER))) {
160          filterStack.push(popArguments(operatorStack, filterStack));
161          if (operatorStack.empty()) {
162            throw new IllegalArgumentException("Mismatched parenthesis");
163          }
164          argumentOnTopOfStack = operatorStack.pop();
165        }
166      } else {
167        // SimpleFilterExpression found
168        byte[] filterSimpleExpression = extractFilterSimpleExpression(filterStringAsByteArray, i);
169        i += (filterSimpleExpression.length - 1);
170        filter = parseSimpleFilterExpression(filterSimpleExpression);
171        filterStack.push(filter);
172      }
173    }
174
175    // Finished parsing filterString
176    while (!operatorStack.empty()) {
177      filterStack.push(popArguments(operatorStack, filterStack));
178    }
179    if (filterStack.empty()) {
180      throw new IllegalArgumentException("Incorrect Filter String");
181    }
182    filter = filterStack.pop();
183    if (!filterStack.empty()) {
184      throw new IllegalArgumentException("Incorrect Filter String");
185    }
186    return filter;
187  }
188
189  /**
190   * Extracts a simple filter expression from the filter string given by the user
191   * <p>
192   * A simpleFilterExpression is of the form: FilterName('arg', 'arg', 'arg') The user given filter
193   * string can have many simpleFilterExpressions combined using operators.
194   * <p>
195   * This function extracts a simpleFilterExpression from the larger filterString given the start
196   * offset of the simpler expression
197   * <p>
198   * @param filterStringAsByteArray     filter string given by the user
199   * @param filterExpressionStartOffset start index of the simple filter expression
200   * @return byte array containing the simple filter expression
201   */
202  public byte[] extractFilterSimpleExpression(byte[] filterStringAsByteArray,
203    int filterExpressionStartOffset) throws CharacterCodingException {
204    int quoteCount = 0;
205    for (int i = filterExpressionStartOffset; i < filterStringAsByteArray.length; i++) {
206      if (filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE) {
207        if (isQuoteUnescaped(filterStringAsByteArray, i)) {
208          quoteCount++;
209        } else {
210          // To skip the next quote that has been escaped
211          i++;
212        }
213      }
214      if (filterStringAsByteArray[i] == ParseConstants.RPAREN && (quoteCount % 2) == 0) {
215        byte[] filterSimpleExpression = new byte[i - filterExpressionStartOffset + 1];
216        Bytes.putBytes(filterSimpleExpression, 0, filterStringAsByteArray,
217          filterExpressionStartOffset, i - filterExpressionStartOffset + 1);
218        return filterSimpleExpression;
219      }
220    }
221    throw new IllegalArgumentException("Incorrect Filter String");
222  }
223
224  /**
225   * Constructs a filter object given a simple filter expression
226   * <p>
227   * @param filterStringAsByteArray filter string given by the user
228   * @return filter object we constructed
229   */
230  public Filter parseSimpleFilterExpression(byte[] filterStringAsByteArray)
231    throws CharacterCodingException {
232
233    String filterName = Bytes.toString(getFilterName(filterStringAsByteArray));
234    ArrayList<byte[]> filterArguments = getFilterArguments(filterStringAsByteArray);
235    if (!filterHashMap.containsKey(filterName)) {
236      throw new IllegalArgumentException("Filter Name " + filterName + " not supported");
237    }
238    try {
239      filterName = filterHashMap.get(filterName);
240      Class<?> c = Class.forName(filterName);
241      Class<?>[] argTypes = new Class[] { ArrayList.class };
242      Method m = c.getDeclaredMethod("createFilterFromArguments", argTypes);
243      return (Filter) m.invoke(null, filterArguments);
244    } catch (ClassNotFoundException e) {
245      e.printStackTrace();
246    } catch (NoSuchMethodException e) {
247      e.printStackTrace();
248    } catch (IllegalAccessException e) {
249      e.printStackTrace();
250    } catch (InvocationTargetException e) {
251      e.printStackTrace();
252    }
253    throw new IllegalArgumentException(
254      "Incorrect filter string " + new String(filterStringAsByteArray, StandardCharsets.UTF_8));
255  }
256
257  /**
258   * Returns the filter name given a simple filter expression
259   * <p>
260   * @param filterStringAsByteArray a simple filter expression
261   * @return name of filter in the simple filter expression
262   */
263  public static byte[] getFilterName(byte[] filterStringAsByteArray) {
264    int filterNameStartIndex = 0;
265    int filterNameEndIndex = 0;
266
267    for (int i = filterNameStartIndex; i < filterStringAsByteArray.length; i++) {
268      if (
269        filterStringAsByteArray[i] == ParseConstants.LPAREN
270          || filterStringAsByteArray[i] == ParseConstants.WHITESPACE
271      ) {
272        filterNameEndIndex = i;
273        break;
274      }
275    }
276
277    if (filterNameEndIndex == 0) {
278      throw new IllegalArgumentException("Incorrect Filter Name");
279    }
280
281    byte[] filterName = new byte[filterNameEndIndex - filterNameStartIndex];
282    Bytes.putBytes(filterName, 0, filterStringAsByteArray, 0,
283      filterNameEndIndex - filterNameStartIndex);
284    return filterName;
285  }
286
287  /**
288   * Returns the arguments of the filter from the filter string
289   * <p>
290   * @param filterStringAsByteArray filter string given by the user
291   * @return an ArrayList containing the arguments of the filter in the filter string
292   */
293  public static ArrayList<byte[]> getFilterArguments(byte[] filterStringAsByteArray) {
294    int argumentListStartIndex = Bytes.searchDelimiterIndex(filterStringAsByteArray, 0,
295      filterStringAsByteArray.length, ParseConstants.LPAREN);
296    if (argumentListStartIndex == -1) {
297      throw new IllegalArgumentException("Incorrect argument list");
298    }
299
300    int argumentStartIndex = 0;
301    int argumentEndIndex = 0;
302    ArrayList<byte[]> filterArguments = new ArrayList<>();
303
304    for (int i = argumentListStartIndex + 1; i < filterStringAsByteArray.length; i++) {
305
306      if (
307        filterStringAsByteArray[i] == ParseConstants.WHITESPACE
308          || filterStringAsByteArray[i] == ParseConstants.COMMA
309          || filterStringAsByteArray[i] == ParseConstants.RPAREN
310      ) {
311        continue;
312      }
313
314      // The argument is in single quotes - for example 'prefix'
315      if (filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE) {
316        argumentStartIndex = i;
317        for (int j = argumentStartIndex + 1; j < filterStringAsByteArray.length; j++) {
318          if (filterStringAsByteArray[j] == ParseConstants.SINGLE_QUOTE) {
319            if (isQuoteUnescaped(filterStringAsByteArray, j)) {
320              argumentEndIndex = j;
321              i = j + 1;
322              byte[] filterArgument = createUnescapdArgument(filterStringAsByteArray,
323                argumentStartIndex, argumentEndIndex);
324              filterArguments.add(filterArgument);
325              break;
326            } else {
327              // To jump over the second escaped quote
328              j++;
329            }
330          } else if (j == filterStringAsByteArray.length - 1) {
331            throw new IllegalArgumentException("Incorrect argument list");
332          }
333        }
334      } else {
335        // The argument is an integer, boolean, comparison operator like <, >, != etc
336        argumentStartIndex = i;
337        for (int j = argumentStartIndex; j < filterStringAsByteArray.length; j++) {
338          if (
339            filterStringAsByteArray[j] == ParseConstants.WHITESPACE
340              || filterStringAsByteArray[j] == ParseConstants.COMMA
341              || filterStringAsByteArray[j] == ParseConstants.RPAREN
342          ) {
343            argumentEndIndex = j - 1;
344            i = j;
345            byte[] filterArgument = new byte[argumentEndIndex - argumentStartIndex + 1];
346            Bytes.putBytes(filterArgument, 0, filterStringAsByteArray, argumentStartIndex,
347              argumentEndIndex - argumentStartIndex + 1);
348            filterArguments.add(filterArgument);
349            break;
350          } else if (j == filterStringAsByteArray.length - 1) {
351            throw new IllegalArgumentException("Incorrect argument list");
352          }
353        }
354      }
355    }
356    return filterArguments;
357  }
358
359  /**
360   * This function is called while parsing the filterString and an operator is parsed
361   * <p>
362   * @param operatorStack the stack containing the operators and parenthesis
363   * @param filterStack   the stack containing the filters
364   * @param operator      the operator found while parsing the filterString
365   */
366  public void reduce(Stack<ByteBuffer> operatorStack, Stack<Filter> filterStack,
367    ByteBuffer operator) {
368    while (
369      !operatorStack.empty() && !(ParseConstants.LPAREN_BUFFER.equals(operatorStack.peek()))
370        && hasHigherPriority(operatorStack.peek(), operator)
371    ) {
372      filterStack.push(popArguments(operatorStack, filterStack));
373    }
374  }
375
376  /**
377   * Pops an argument from the operator stack and the number of arguments required by the operator
378   * from the filterStack and evaluates them
379   * <p>
380   * @param operatorStack the stack containing the operators
381   * @param filterStack   the stack containing the filters
382   * @return the evaluated filter
383   */
384  public static Filter popArguments(Stack<ByteBuffer> operatorStack, Stack<Filter> filterStack) {
385    ByteBuffer argumentOnTopOfStack = operatorStack.peek();
386
387    if (argumentOnTopOfStack.equals(ParseConstants.OR_BUFFER)) {
388      // The top of the stack is an OR
389      try {
390        ArrayList<Filter> listOfFilters = new ArrayList<>();
391        while (!operatorStack.empty() && operatorStack.peek().equals(ParseConstants.OR_BUFFER)) {
392          Filter filter = filterStack.pop();
393          listOfFilters.add(0, filter);
394          operatorStack.pop();
395        }
396        Filter filter = filterStack.pop();
397        listOfFilters.add(0, filter);
398        Filter orFilter = new FilterList(FilterList.Operator.MUST_PASS_ONE, listOfFilters);
399        return orFilter;
400      } catch (EmptyStackException e) {
401        throw new IllegalArgumentException("Incorrect input string - an OR needs two filters");
402      }
403
404    } else if (argumentOnTopOfStack.equals(ParseConstants.AND_BUFFER)) {
405      // The top of the stack is an AND
406      try {
407        ArrayList<Filter> listOfFilters = new ArrayList<>();
408        while (!operatorStack.empty() && operatorStack.peek().equals(ParseConstants.AND_BUFFER)) {
409          Filter filter = filterStack.pop();
410          listOfFilters.add(0, filter);
411          operatorStack.pop();
412        }
413        Filter filter = filterStack.pop();
414        listOfFilters.add(0, filter);
415        Filter andFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, listOfFilters);
416        return andFilter;
417      } catch (EmptyStackException e) {
418        throw new IllegalArgumentException("Incorrect input string - an AND needs two filters");
419      }
420
421    } else if (argumentOnTopOfStack.equals(ParseConstants.SKIP_BUFFER)) {
422      // The top of the stack is a SKIP
423      try {
424        Filter wrappedFilter = filterStack.pop();
425        Filter skipFilter = new SkipFilter(wrappedFilter);
426        operatorStack.pop();
427        return skipFilter;
428      } catch (EmptyStackException e) {
429        throw new IllegalArgumentException("Incorrect input string - a SKIP wraps a filter");
430      }
431
432    } else if (argumentOnTopOfStack.equals(ParseConstants.WHILE_BUFFER)) {
433      // The top of the stack is a WHILE
434      try {
435        Filter wrappedFilter = filterStack.pop();
436        Filter whileMatchFilter = new WhileMatchFilter(wrappedFilter);
437        operatorStack.pop();
438        return whileMatchFilter;
439      } catch (EmptyStackException e) {
440        throw new IllegalArgumentException("Incorrect input string - a WHILE wraps a filter");
441      }
442
443    } else if (argumentOnTopOfStack.equals(ParseConstants.LPAREN_BUFFER)) {
444      // The top of the stack is a LPAREN
445      try {
446        Filter filter = filterStack.pop();
447        operatorStack.pop();
448        return filter;
449      } catch (EmptyStackException e) {
450        throw new IllegalArgumentException("Incorrect Filter String");
451      }
452
453    } else {
454      throw new IllegalArgumentException("Incorrect arguments on operatorStack");
455    }
456  }
457
458  /**
459   * Returns which operator has higher precedence
460   * <p>
461   * If a has higher precedence than b, it returns true If they have the same precedence, it returns
462   * false
463   */
464  public boolean hasHigherPriority(ByteBuffer a, ByteBuffer b) {
465    if ((operatorPrecedenceHashMap.get(a) - operatorPrecedenceHashMap.get(b)) < 0) {
466      return true;
467    }
468    return false;
469  }
470
471  /**
472   * Removes the single quote escaping a single quote - thus it returns an unescaped argument
473   * <p>
474   * @param filterStringAsByteArray filter string given by user
475   * @param argumentStartIndex      start index of the argument
476   * @param argumentEndIndex        end index of the argument
477   * @return returns an unescaped argument
478   */
479  public static byte[] createUnescapdArgument(byte[] filterStringAsByteArray,
480    int argumentStartIndex, int argumentEndIndex) {
481    int unescapedArgumentLength = 2;
482    for (int i = argumentStartIndex + 1; i <= argumentEndIndex - 1; i++) {
483      unescapedArgumentLength++;
484      if (
485        filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE && i != (argumentEndIndex - 1)
486          && filterStringAsByteArray[i + 1] == ParseConstants.SINGLE_QUOTE
487      ) {
488        i++;
489        continue;
490      }
491    }
492
493    byte[] unescapedArgument = new byte[unescapedArgumentLength];
494    int count = 1;
495    unescapedArgument[0] = '\'';
496    for (int i = argumentStartIndex + 1; i <= argumentEndIndex - 1; i++) {
497      if (
498        filterStringAsByteArray[i] == ParseConstants.SINGLE_QUOTE && i != (argumentEndIndex - 1)
499          && filterStringAsByteArray[i + 1] == ParseConstants.SINGLE_QUOTE
500      ) {
501        unescapedArgument[count++] = filterStringAsByteArray[i + 1];
502        i++;
503      } else {
504        unescapedArgument[count++] = filterStringAsByteArray[i];
505      }
506    }
507    unescapedArgument[unescapedArgumentLength - 1] = '\'';
508    return unescapedArgument;
509  }
510
511  /**
512   * Checks if the current index of filter string we are on is the beginning of the keyword 'OR'
513   * <p>
514   * @param filterStringAsByteArray filter string given by the user
515   * @param indexOfOr               index at which an 'O' was read
516   * @return true if the keyword 'OR' is at the current index
517   */
518  public static boolean checkForOr(byte[] filterStringAsByteArray, int indexOfOr)
519    throws CharacterCodingException, ArrayIndexOutOfBoundsException {
520
521    try {
522      if (
523        filterStringAsByteArray[indexOfOr] == ParseConstants.O
524          && filterStringAsByteArray[indexOfOr + 1] == ParseConstants.R
525          && (filterStringAsByteArray[indexOfOr - 1] == ParseConstants.WHITESPACE
526            || filterStringAsByteArray[indexOfOr - 1] == ParseConstants.RPAREN)
527          && (filterStringAsByteArray[indexOfOr + 2] == ParseConstants.WHITESPACE
528            || filterStringAsByteArray[indexOfOr + 2] == ParseConstants.LPAREN)
529      ) {
530        return true;
531      } else {
532        return false;
533      }
534    } catch (ArrayIndexOutOfBoundsException e) {
535      return false;
536    }
537  }
538
539  /**
540   * Checks if the current index of filter string we are on is the beginning of the keyword 'AND'
541   * <p>
542   * @param filterStringAsByteArray filter string given by the user
543   * @param indexOfAnd              index at which an 'A' was read
544   * @return true if the keyword 'AND' is at the current index
545   */
546  public static boolean checkForAnd(byte[] filterStringAsByteArray, int indexOfAnd)
547    throws CharacterCodingException {
548
549    try {
550      if (
551        filterStringAsByteArray[indexOfAnd] == ParseConstants.A
552          && filterStringAsByteArray[indexOfAnd + 1] == ParseConstants.N
553          && filterStringAsByteArray[indexOfAnd + 2] == ParseConstants.D
554          && (filterStringAsByteArray[indexOfAnd - 1] == ParseConstants.WHITESPACE
555            || filterStringAsByteArray[indexOfAnd - 1] == ParseConstants.RPAREN)
556          && (filterStringAsByteArray[indexOfAnd + 3] == ParseConstants.WHITESPACE
557            || filterStringAsByteArray[indexOfAnd + 3] == ParseConstants.LPAREN)
558      ) {
559        return true;
560      } else {
561        return false;
562      }
563    } catch (ArrayIndexOutOfBoundsException e) {
564      return false;
565    }
566  }
567
568  /**
569   * Checks if the current index of filter string we are on is the beginning of the keyword 'SKIP'
570   * <p>
571   * @param filterStringAsByteArray filter string given by the user
572   * @param indexOfSkip             index at which an 'S' was read
573   * @return true if the keyword 'SKIP' is at the current index
574   */
575  public static boolean checkForSkip(byte[] filterStringAsByteArray, int indexOfSkip)
576    throws CharacterCodingException {
577
578    try {
579      if (
580        filterStringAsByteArray[indexOfSkip] == ParseConstants.S
581          && filterStringAsByteArray[indexOfSkip + 1] == ParseConstants.K
582          && filterStringAsByteArray[indexOfSkip + 2] == ParseConstants.I
583          && filterStringAsByteArray[indexOfSkip + 3] == ParseConstants.P
584          && (indexOfSkip == 0
585            || filterStringAsByteArray[indexOfSkip - 1] == ParseConstants.WHITESPACE
586            || filterStringAsByteArray[indexOfSkip - 1] == ParseConstants.RPAREN
587            || filterStringAsByteArray[indexOfSkip - 1] == ParseConstants.LPAREN)
588          && (filterStringAsByteArray[indexOfSkip + 4] == ParseConstants.WHITESPACE
589            || filterStringAsByteArray[indexOfSkip + 4] == ParseConstants.LPAREN)
590      ) {
591        return true;
592      } else {
593        return false;
594      }
595    } catch (ArrayIndexOutOfBoundsException e) {
596      return false;
597    }
598  }
599
600  /**
601   * Checks if the current index of filter string we are on is the beginning of the keyword 'WHILE'
602   * <p>
603   * @param filterStringAsByteArray filter string given by the user
604   * @param indexOfWhile            index at which an 'W' was read
605   * @return true if the keyword 'WHILE' is at the current index
606   */
607  public static boolean checkForWhile(byte[] filterStringAsByteArray, int indexOfWhile)
608    throws CharacterCodingException {
609
610    try {
611      if (
612        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
618            || 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      ) {
624        return true;
625      } else {
626        return false;
627      }
628    } catch (ArrayIndexOutOfBoundsException e) {
629      return false;
630    }
631  }
632
633  /**
634   * Returns a boolean indicating whether the quote was escaped or not
635   * <p>
636   * @param array      byte array in which the quote was found
637   * @param quoteIndex index of the single quote
638   * @return returns true if the quote was unescaped
639   */
640  public static boolean isQuoteUnescaped(byte[] array, int quoteIndex) {
641    if (array == null) {
642      throw new IllegalArgumentException("isQuoteUnescaped called with a null array");
643    }
644
645    if (quoteIndex == array.length - 1 || array[quoteIndex + 1] != ParseConstants.SINGLE_QUOTE) {
646      return true;
647    } else {
648      return false;
649    }
650  }
651
652  /**
653   * Takes a quoted byte array and converts it into an unquoted byte array For example: given a byte
654   * array representing 'abc', it returns a 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 (
661      quotedByteArray == null || quotedByteArray.length < 2
662        || quotedByteArray[0] != ParseConstants.SINGLE_QUOTE
663        || quotedByteArray[quotedByteArray.length - 1] != ParseConstants.SINGLE_QUOTE
664    ) {
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 that there will be {@link Bytes#SIZEOF_INT}
677   * 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 that there will be {@link Bytes#SIZEOF_INT}
700   * 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 (
721        numberAsByteArray[i] < ParseConstants.ZERO || numberAsByteArray[i] > ParseConstants.NINE
722      ) {
723        throw new IllegalArgumentException("Byte Array should only contain digits");
724      }
725      result = result * 10 + (numberAsByteArray[i] - ParseConstants.ZERO);
726      if (result < 0) {
727        throw new IllegalArgumentException("Long Argument too large");
728      }
729      i++;
730    }
731
732    if (isNegative) {
733      return -result;
734    } else {
735      return result;
736    }
737  }
738
739  /**
740   * Converts a boolean expressed in a byte array to an actual boolean
741   * <p>
742   * This doesn't used Bytes.toBoolean because Bytes.toBoolean(byte []) assumes that 1 stands for
743   * true and 0 for false. 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 (
754      booleanAsByteArray.length == 4
755        && (booleanAsByteArray[0] == 't' || booleanAsByteArray[0] == 'T')
756        && (booleanAsByteArray[1] == 'r' || booleanAsByteArray[1] == 'R')
757        && (booleanAsByteArray[2] == 'u' || booleanAsByteArray[2] == 'U')
758        && (booleanAsByteArray[3] == 'e' || booleanAsByteArray[3] == 'E')
759    ) {
760      return true;
761    } else if (
762      booleanAsByteArray.length == 5
763        && (booleanAsByteArray[0] == 'f' || booleanAsByteArray[0] == 'F')
764        && (booleanAsByteArray[1] == 'a' || booleanAsByteArray[1] == 'A')
765        && (booleanAsByteArray[2] == 'l' || booleanAsByteArray[2] == 'L')
766        && (booleanAsByteArray[3] == 's' || booleanAsByteArray[3] == 'S')
767        && (booleanAsByteArray[4] == 'e' || booleanAsByteArray[4] == 'E')
768    ) {
769      return false;
770    } else {
771      throw new IllegalArgumentException("Incorrect Boolean Expression");
772    }
773  }
774
775  /**
776   * Takes a compareOperator symbol as a byte array and returns the corresponding CompareOperator
777   * @param compareOpAsByteArray the comparatorOperator symbol as a byte array
778   * @return the Compare Operator
779   */
780  public static CompareOperator createCompareOperator(byte[] compareOpAsByteArray) {
781    ByteBuffer compareOp = ByteBuffer.wrap(compareOpAsByteArray);
782    if (compareOp.equals(ParseConstants.LESS_THAN_BUFFER)) return CompareOperator.LESS;
783    else if (compareOp.equals(ParseConstants.LESS_THAN_OR_EQUAL_TO_BUFFER))
784      return CompareOperator.LESS_OR_EQUAL;
785    else if (compareOp.equals(ParseConstants.GREATER_THAN_BUFFER)) return CompareOperator.GREATER;
786    else if (compareOp.equals(ParseConstants.GREATER_THAN_OR_EQUAL_TO_BUFFER))
787      return CompareOperator.GREATER_OR_EQUAL;
788    else if (compareOp.equals(ParseConstants.NOT_EQUAL_TO_BUFFER)) return CompareOperator.NOT_EQUAL;
789    else if (compareOp.equals(ParseConstants.EQUAL_TO_BUFFER)) return CompareOperator.EQUAL;
790    else throw new IllegalArgumentException("Invalid compare operator");
791  }
792
793  /**
794   * Takes a compareOperator symbol as a byte array and returns the corresponding CompareOperator
795   * @deprecated Since 2.0
796   *             <p>
797   * @param compareOpAsByteArray the comparatorOperator symbol as a byte array
798   * @return the Compare Operator
799   * @deprecated Since 2.0.0. Will be removed in 3.0.0. Use {@link #createCompareOperator(byte [])}
800   */
801  @Deprecated
802  public static CompareFilter.CompareOp createCompareOp(byte[] compareOpAsByteArray) {
803    ByteBuffer compareOp = ByteBuffer.wrap(compareOpAsByteArray);
804    if (compareOp.equals(ParseConstants.LESS_THAN_BUFFER)) return CompareOp.LESS;
805    else if (compareOp.equals(ParseConstants.LESS_THAN_OR_EQUAL_TO_BUFFER))
806      return CompareOp.LESS_OR_EQUAL;
807    else if (compareOp.equals(ParseConstants.GREATER_THAN_BUFFER)) return CompareOp.GREATER;
808    else if (compareOp.equals(ParseConstants.GREATER_THAN_OR_EQUAL_TO_BUFFER))
809      return CompareOp.GREATER_OR_EQUAL;
810    else if (compareOp.equals(ParseConstants.NOT_EQUAL_TO_BUFFER)) return CompareOp.NOT_EQUAL;
811    else if (compareOp.equals(ParseConstants.EQUAL_TO_BUFFER)) return CompareOp.EQUAL;
812    else throw new IllegalArgumentException("Invalid compare operator");
813  }
814
815  /**
816   * Parses a comparator of the form comparatorType:comparatorValue form and returns a comparator
817   * <p>
818   * @param comparator the comparator in the form comparatorType:comparatorValue
819   * @return the parsed comparator
820   */
821  public static ByteArrayComparable createComparator(byte[] comparator) {
822    if (comparator == null) throw new IllegalArgumentException("Incorrect Comparator");
823    byte[][] parsedComparator = ParseFilter.parseComparator(comparator);
824    byte[] comparatorType = parsedComparator[0];
825    byte[] comparatorValue = parsedComparator[1];
826
827    if (Bytes.equals(comparatorType, ParseConstants.binaryType))
828      return new BinaryComparator(comparatorValue);
829    else if (Bytes.equals(comparatorType, ParseConstants.binaryPrefixType))
830      return new BinaryPrefixComparator(comparatorValue);
831    else if (Bytes.equals(comparatorType, ParseConstants.regexStringType))
832      return new RegexStringComparator(new String(comparatorValue, StandardCharsets.UTF_8));
833    else if (Bytes.equals(comparatorType, ParseConstants.substringType))
834      return new SubstringComparator(new String(comparatorValue, StandardCharsets.UTF_8));
835    else throw new IllegalArgumentException("Incorrect comparatorType");
836  }
837
838  /**
839   * Splits a column in comparatorType:comparatorValue form into separate byte arrays
840   * <p>
841   * @param comparator the comparator
842   * @return the parsed arguments of the comparator as a 2D byte array
843   */
844  public static byte[][] parseComparator(byte[] comparator) {
845    final int index =
846      Bytes.searchDelimiterIndex(comparator, 0, comparator.length, ParseConstants.COLON);
847    if (index == -1) {
848      throw new IllegalArgumentException("Incorrect comparator");
849    }
850
851    byte[][] result = new byte[2][0];
852    result[0] = new byte[index];
853    System.arraycopy(comparator, 0, result[0], 0, index);
854
855    final int len = comparator.length - (index + 1);
856    result[1] = new byte[len];
857    System.arraycopy(comparator, index + 1, result[1], 0, len);
858
859    return result;
860  }
861
862  /**
863   * Return a Set of filters supported by the Filter Language
864   */
865  public Set<String> getSupportedFilters() {
866    return filterHashMap.keySet();
867  }
868
869  /**
870   * Returns all known filters
871   * @return an unmodifiable map of filters
872   */
873  public static Map<String, String> getAllFilters() {
874    return Collections.unmodifiableMap(filterHashMap);
875  }
876
877  /**
878   * Register a new filter with the parser. If the filter is already registered, an
879   * IllegalArgumentException will be thrown.
880   * @param name        a name for the filter
881   * @param filterClass fully qualified class name
882   */
883  public static void registerFilter(String name, String filterClass) {
884    if (LOG.isInfoEnabled()) LOG.info("Registering new filter " + name);
885
886    filterHashMap.put(name, filterClass);
887  }
888}