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}