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-4168 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}