001/* 002 * Copyright 2015-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2015-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2015-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.jsonfilter; 037 038 039 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.HashSet; 044import java.util.LinkedHashMap; 045import java.util.List; 046import java.util.Set; 047 048import com.unboundid.util.Mutable; 049import com.unboundid.util.StaticUtils; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052import com.unboundid.util.Validator; 053import com.unboundid.util.json.JSONArray; 054import com.unboundid.util.json.JSONBoolean; 055import com.unboundid.util.json.JSONException; 056import com.unboundid.util.json.JSONObject; 057import com.unboundid.util.json.JSONString; 058import com.unboundid.util.json.JSONValue; 059 060import static com.unboundid.ldap.sdk.unboundidds.jsonfilter.JFMessages.*; 061 062 063 064/** 065 * This class provides an implementation of a JSON object filter that can be 066 * used to identify JSON objects that have string value that matches a specified 067 * substring. At least one of the {@code startsWith}, {@code contains}, and 068 * {@code endsWith} components must be included in the filter. If multiple 069 * substring components are present, then any matching value must contain all 070 * of those components, and the components must not overlap. 071 * <BR> 072 * <BLOCKQUOTE> 073 * <B>NOTE:</B> This class, and other classes within the 074 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 075 * supported for use against Ping Identity, UnboundID, and 076 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 077 * for proprietary functionality or for external specifications that are not 078 * considered stable or mature enough to be guaranteed to work in an 079 * interoperable way with other types of LDAP servers. 080 * </BLOCKQUOTE> 081 * <BR> 082 * The fields that are required to be included in a "substring" filter are: 083 * <UL> 084 * <LI> 085 * {@code field} -- A field path specifier for the JSON field for which 086 * to make the determination. This may be either a single string or an 087 * array of strings as described in the "Targeting Fields in JSON Objects" 088 * section of the class-level documentation for {@link JSONObjectFilter}. 089 * </LI> 090 * </UL> 091 * The fields that may optionally be included in a "substring" filter are: 092 * <UL> 093 * <LI> 094 * {@code startsWith} -- A string that must appear at the beginning of 095 * matching values. 096 * </LI> 097 * <LI> 098 * {@code contains} -- A string, or an array of strings, that must appear in 099 * matching values. If this is an array of strings, then a matching value 100 * must contain all of these strings in the order provided in the array. 101 * </LI> 102 * <LI> 103 * {@code endsWith} -- A string that must appear at the end of matching 104 * values. 105 * </LI> 106 * <LI> 107 * {@code caseSensitive} -- Indicates whether string values should be 108 * treated in a case-sensitive manner. If present, this field must have a 109 * Boolean value of either {@code true} or {@code false}. If it is not 110 * provided, then a default value of {@code false} will be assumed so that 111 * strings are treated in a case-insensitive manner. 112 * </LI> 113 * </UL> 114 * <H2>Examples</H2> 115 * The following is an example of a substring filter that will match any JSON 116 * object with a top-level field named "accountCreateTime" with a string value 117 * that starts with "2015": 118 * <PRE> 119 * { "filterType" : "substring", 120 * "field" : "accountCreateTime", 121 * "startsWith" : "2015" } 122 * </PRE> 123 * The above filter can be created with the code: 124 * <PRE> 125 * SubstringJSONObjectFilter filter = 126 * new SubstringJSONObjectFilter("accountCreateTime", "2015", null, 127 * null); 128 * </PRE> 129 * <BR><BR> 130 * The following is an example of a substring filter that will match any JSON 131 * object with a top-level field named "fullName" that contains the substrings 132 * "John" and "Doe", in that order, somewhere in the value: 133 * <PRE> 134 * { "filterType" : "substring", 135 * "field" : "fullName", 136 * "contains" : [ "John", "Doe" ] } 137 * </PRE> 138 * The above filter can be created with the code: 139 * <PRE> 140 * SubstringJSONObjectFilter filter = 141 * new SubstringJSONObjectFilter(Collections.singletonList("fullName"), 142 * null, Arrays.asList("John", "Doe"), null); 143 * </PRE> 144 */ 145@Mutable() 146@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 147public final class SubstringJSONObjectFilter 148 extends JSONObjectFilter 149{ 150 /** 151 * The value that should be used for the filterType element of the JSON object 152 * that represents a "substring" filter. 153 */ 154 public static final String FILTER_TYPE = "substring"; 155 156 157 158 /** 159 * The name of the JSON field that is used to specify the field in the target 160 * JSON object for which to make the determination. 161 */ 162 public static final String FIELD_FIELD_PATH = "field"; 163 164 165 166 /** 167 * The name of the JSON field that is used to specify a string that must 168 * appear at the beginning of a matching value. 169 */ 170 public static final String FIELD_STARTS_WITH = "startsWith"; 171 172 173 174 /** 175 * The name of the JSON field that is used to specify one or more strings 176 * that must appear somewhere in a matching value. 177 */ 178 public static final String FIELD_CONTAINS = "contains"; 179 180 181 182 /** 183 * The name of the JSON field that is used to specify a string that must 184 * appear at the end of a matching value. 185 */ 186 public static final String FIELD_ENDS_WITH = "endsWith"; 187 188 189 190 /** 191 * The name of the JSON field that is used to indicate whether string matching 192 * should be case-sensitive. 193 */ 194 public static final String FIELD_CASE_SENSITIVE = "caseSensitive"; 195 196 197 198 /** 199 * The pre-allocated set of required field names. 200 */ 201 private static final Set<String> REQUIRED_FIELD_NAMES = 202 Collections.unmodifiableSet(new HashSet<>( 203 Collections.singletonList(FIELD_FIELD_PATH))); 204 205 206 207 /** 208 * The pre-allocated set of optional field names. 209 */ 210 private static final Set<String> OPTIONAL_FIELD_NAMES = 211 Collections.unmodifiableSet(new HashSet<>( 212 Arrays.asList(FIELD_STARTS_WITH, FIELD_CONTAINS, FIELD_ENDS_WITH, 213 FIELD_CASE_SENSITIVE))); 214 215 216 /** 217 * The serial version UID for this serializable class. 218 */ 219 private static final long serialVersionUID = 811514243548895420L; 220 221 222 223 // Indicates whether string matching should be case-sensitive. 224 private volatile boolean caseSensitive; 225 226 // The minimum length that a string must have to match the substring 227 // assertion. 228 private volatile int minLength; 229 230 // The substring(s) that must appear somewhere in matching values. 231 private volatile List<String> contains; 232 233 // The "contains" values that should be used for matching purposes. If 234 // caseSensitive is false, then this will be an all-lowercase version of 235 // contains. Otherwise, it will be the same as contains. 236 private volatile List<String> matchContains; 237 238 // The field path specifier for the target field. 239 private volatile List<String> field; 240 241 // The substring that must appear at the end of matching values. 242 private volatile String endsWith; 243 244 // The "ends with" value that should be used for matching purposes. If 245 // caseSensitive is false, then this will be an all-lowercase version of 246 // endsWith. Otherwise, it will be the same as endsWith. 247 private volatile String matchEndsWith; 248 249 // The "starts with" value that should be used for matching purposes. If 250 // caseSensitive is false, then this will be an all-lowercase version of 251 // startsWith. Otherwise, it will be the same as startsWith. 252 private volatile String matchStartsWith; 253 254 // The substring that must appear at the beginning of matching values. 255 private volatile String startsWith; 256 257 258 259 /** 260 * Creates an instance of this filter type that can only be used for decoding 261 * JSON objects as "substring" filters. It cannot be used as a regular 262 * "substring" filter. 263 */ 264 SubstringJSONObjectFilter() 265 { 266 field = null; 267 startsWith = null; 268 contains = null; 269 endsWith = null; 270 caseSensitive = false; 271 272 minLength = 0; 273 matchStartsWith = null; 274 matchContains = null; 275 matchEndsWith = null; 276 } 277 278 279 280 /** 281 * Creates a new instance of this filter type with the provided information. 282 * 283 * @param field The field path specifier for the target field. 284 * @param startsWith The substring that must appear at the beginning of 285 * matching values. 286 * @param contains The substrings that must appear somewhere in 287 * matching values. 288 * @param endsWith The substring that must appear at the end of 289 * matching values. 290 * @param caseSensitive Indicates whether matching should be case sensitive. 291 */ 292 private SubstringJSONObjectFilter(final List<String> field, 293 final String startsWith, 294 final List<String> contains, 295 final String endsWith, 296 final boolean caseSensitive) 297 { 298 this.field = field; 299 this.caseSensitive = caseSensitive; 300 301 setSubstringComponents(startsWith, contains, endsWith); 302 } 303 304 305 306 /** 307 * Creates a new instance of this filter type with the provided information. 308 * At least one {@code startsWith}, {@code contains}, or {@code endsWith} 309 * value must be present. 310 * 311 * @param field The name of the top-level field to target with this 312 * filter. It must not be {@code null} . See the 313 * class-level documentation for the 314 * {@link JSONObjectFilter} class for information about 315 * field path specifiers. 316 * @param startsWith An optional substring that must appear at the beginning 317 * of matching values. This may be {@code null} if 318 * matching will be performed using only {@code contains} 319 * and/or {@code endsWith} substrings. 320 * @param contains An optional substring that must appear somewhere in 321 * matching values. This may be {@code null} if matching 322 * will be performed using only {@code startsWith} and/or 323 * {@code endsWith} substrings. 324 * @param endsWith An optional substring that must appear at the end 325 * of matching values. This may be {@code null} if 326 * matching will be performed using only 327 * {@code startsWith} and/or {@code contains} substrings. 328 */ 329 public SubstringJSONObjectFilter(final String field, final String startsWith, 330 final String contains, final String endsWith) 331 { 332 this(Collections.singletonList(field), startsWith, 333 ((contains == null) ? null : Collections.singletonList(contains)), 334 endsWith); 335 } 336 337 338 339 /** 340 * Creates a new instance of this filter type with the provided information. 341 * At least one {@code startsWith}, {@code contains}, or {@code endsWith} 342 * value must be present. 343 * 344 * @param field The field path specifier for this filter. It must not 345 * be {@code null} or empty. See the class-level 346 * documentation for the {@link JSONObjectFilter} class 347 * for information about field path specifiers. 348 * @param startsWith An optional substring that must appear at the beginning 349 * of matching values. This may be {@code null} if 350 * matching will be performed using only {@code contains} 351 * and/or {@code endsWith} substrings. 352 * @param contains An optional set of substrings that must appear 353 * somewhere in matching values. This may be {@code null} 354 * or empty if matching will be performed using only 355 * {@code startsWith} and/or {@code endsWith} substrings. 356 * @param endsWith An optional substring that must appear at the end 357 * of matching values. This may be {@code null} if 358 * matching will be performed using only 359 * {@code startsWith} and/or {@code contains} substrings. 360 */ 361 public SubstringJSONObjectFilter(final List<String> field, 362 final String startsWith, 363 final List<String> contains, 364 final String endsWith) 365 { 366 Validator.ensureNotNull(field); 367 Validator.ensureFalse(field.isEmpty()); 368 369 this.field = Collections.unmodifiableList(new ArrayList<>(field)); 370 caseSensitive = false; 371 372 setSubstringComponents(startsWith, contains, endsWith); 373 } 374 375 376 377 /** 378 * Retrieves the field path specifier for this filter. 379 * 380 * @return The field path specifier for this filter. 381 */ 382 public List<String> getField() 383 { 384 return field; 385 } 386 387 388 389 /** 390 * Sets the field path specifier for this filter. 391 * 392 * @param field The field path specifier for this filter. It must not be 393 * {@code null} or empty. See the class-level documentation 394 * for the {@link JSONObjectFilter} class for information about 395 * field path specifiers. 396 */ 397 public void setField(final String... field) 398 { 399 setField(StaticUtils.toList(field)); 400 } 401 402 403 404 /** 405 * Sets the field path specifier for this filter. 406 * 407 * @param field The field path specifier for this filter. It must not be 408 * {@code null} or empty. See the class-level documentation 409 * for the {@link JSONObjectFilter} class for information about 410 * field path specifiers. 411 */ 412 public void setField(final List<String> field) 413 { 414 Validator.ensureNotNull(field); 415 Validator.ensureFalse(field.isEmpty()); 416 417 this.field= Collections.unmodifiableList(new ArrayList<>(field)); 418 } 419 420 421 422 /** 423 * Retrieves the substring that must appear at the beginning of matching 424 * values, if defined. 425 * 426 * @return The substring that must appear at the beginning of matching 427 * values, or {@code null} if no "starts with" substring has been 428 * defined. 429 */ 430 public String getStartsWith() 431 { 432 return startsWith; 433 } 434 435 436 437 /** 438 * Retrieves the list of strings that must appear somewhere in the value 439 * (after any defined "starts with" value, and before any defined "ends with" 440 * value). 441 * 442 * @return The list of strings that must appear somewhere in the value, or 443 * an empty list if no "contains" substrings have been defined. 444 */ 445 public List<String> getContains() 446 { 447 return contains; 448 } 449 450 451 452 /** 453 * Retrieves the substring that must appear at the end of matching values, if 454 * defined. 455 * 456 * @return The substring that must appear at the end of matching values, or 457 * {@code null} if no "starts with" substring has been defined. 458 */ 459 public String getEndsWith() 460 { 461 return endsWith; 462 } 463 464 465 466 /** 467 * Specifies the substring components that must be present in matching values. 468 * At least one {@code startsWith}, {@code contains}, or {@code endsWith} 469 * value must be present. 470 * 471 * @param startsWith An optional substring that must appear at the beginning 472 * of matching values. This may be {@code null} if 473 * matching will be performed using only {@code contains} 474 * and/or {@code endsWith} substrings. 475 * @param contains An optional substring that must appear somewhere in 476 * matching values. This may be {@code null} if matching 477 * will be performed using only {@code startsWith} and/or 478 * {@code endsWith} substrings. 479 * @param endsWith An optional substring that must appear at the end 480 * of matching values. This may be {@code null} if 481 * matching will be performed using only 482 * {@code startsWith} and/or {@code contains} substrings. 483 */ 484 public void setSubstringComponents(final String startsWith, 485 final String contains, 486 final String endsWith) 487 { 488 setSubstringComponents(startsWith, 489 (contains == null) ? null : Collections.singletonList(contains), 490 endsWith); 491 } 492 493 494 495 /** 496 * Specifies the substring components that must be present in matching values. 497 * At least one {@code startsWith}, {@code contains}, or {@code endsWith} 498 * value must be present. 499 * 500 * @param startsWith An optional substring that must appear at the beginning 501 * of matching values. This may be {@code null} if 502 * matching will be performed using only {@code contains} 503 * and/or {@code endsWith} substrings. 504 * @param contains An optional set of substrings that must appear 505 * somewhere in matching values. This may be {@code null} 506 * or empty if matching will be performed using only 507 * {@code startsWith} and/or {@code endsWith} substrings. 508 * @param endsWith An optional substring that must appear at the end 509 * of matching values. This may be {@code null} if 510 * matching will be performed using only 511 * {@code startsWith} and/or {@code contains} substrings. 512 */ 513 public void setSubstringComponents(final String startsWith, 514 final List<String> contains, 515 final String endsWith) 516 { 517 Validator.ensureFalse((startsWith == null) && (contains == null) && 518 (endsWith == null)); 519 520 minLength = 0; 521 522 this.startsWith = startsWith; 523 if (startsWith != null) 524 { 525 minLength += startsWith.length(); 526 if (caseSensitive) 527 { 528 matchStartsWith = startsWith; 529 } 530 else 531 { 532 matchStartsWith = StaticUtils.toLowerCase(startsWith); 533 } 534 } 535 536 if (contains == null) 537 { 538 this.contains = Collections.emptyList(); 539 matchContains = this.contains; 540 } 541 else 542 { 543 this.contains = 544 Collections.unmodifiableList(new ArrayList<>(contains)); 545 546 final ArrayList<String> mcList = new ArrayList<>(contains.size()); 547 for (final String s : contains) 548 { 549 minLength += s.length(); 550 if (caseSensitive) 551 { 552 mcList.add(s); 553 } 554 else 555 { 556 mcList.add(StaticUtils.toLowerCase(s)); 557 } 558 } 559 560 matchContains = Collections.unmodifiableList(mcList); 561 } 562 563 this.endsWith = endsWith; 564 if (endsWith != null) 565 { 566 minLength += endsWith.length(); 567 if (caseSensitive) 568 { 569 matchEndsWith = endsWith; 570 } 571 else 572 { 573 matchEndsWith = StaticUtils.toLowerCase(endsWith); 574 } 575 } 576 } 577 578 579 580 /** 581 * Indicates whether string matching should be performed in a case-sensitive 582 * manner. 583 * 584 * @return {@code true} if string matching should be case sensitive, or 585 * {@code false} if not. 586 */ 587 public boolean caseSensitive() 588 { 589 return caseSensitive; 590 } 591 592 593 594 /** 595 * Specifies whether string matching should be performed in a case-sensitive 596 * manner. 597 * 598 * @param caseSensitive Indicates whether string matching should be 599 * case sensitive. 600 */ 601 public void setCaseSensitive(final boolean caseSensitive) 602 { 603 this.caseSensitive = caseSensitive; 604 setSubstringComponents(startsWith, contains, endsWith); 605 } 606 607 608 609 /** 610 * {@inheritDoc} 611 */ 612 @Override() 613 public String getFilterType() 614 { 615 return FILTER_TYPE; 616 } 617 618 619 620 /** 621 * {@inheritDoc} 622 */ 623 @Override() 624 protected Set<String> getRequiredFieldNames() 625 { 626 return REQUIRED_FIELD_NAMES; 627 } 628 629 630 631 /** 632 * {@inheritDoc} 633 */ 634 @Override() 635 protected Set<String> getOptionalFieldNames() 636 { 637 return OPTIONAL_FIELD_NAMES; 638 } 639 640 641 642 /** 643 * {@inheritDoc} 644 */ 645 @Override() 646 public boolean matchesJSONObject(final JSONObject o) 647 { 648 final List<JSONValue> candidates = getValues(o, field); 649 if (candidates.isEmpty()) 650 { 651 return false; 652 } 653 654 for (final JSONValue v : candidates) 655 { 656 if (v instanceof JSONString) 657 { 658 if (matchesValue(v)) 659 { 660 return true; 661 } 662 } 663 else if (v instanceof JSONArray) 664 { 665 for (final JSONValue arrayValue : ((JSONArray) v).getValues()) 666 { 667 if (matchesValue(arrayValue)) 668 { 669 return true; 670 } 671 } 672 } 673 } 674 675 return false; 676 } 677 678 679 680 /** 681 * Indicates whether the substring assertion defined in this filter matches 682 * the provided JSON value. 683 * 684 * @param v The value for which to make the determination. 685 * 686 * @return {@code true} if the substring assertion matches the provided 687 * value, or {@code false} if not. 688 */ 689 private boolean matchesValue(final JSONValue v) 690 { 691 if (! (v instanceof JSONString)) 692 { 693 return false; 694 } 695 696 return matchesString(((JSONString) v).stringValue()); 697 } 698 699 700 701 /** 702 * Indicates whether the substring assertion defined in this filter matches 703 * the provided string. 704 * 705 * @param s The string for which to make the determination. 706 * 707 * @return {@code true} if the substring assertion defined in this filter 708 * matches the provided string, or {@code false} if not. 709 */ 710 public boolean matchesString(final String s) 711 { 712 713 final String stringValue; 714 if (caseSensitive) 715 { 716 stringValue = s; 717 } 718 else 719 { 720 stringValue = StaticUtils.toLowerCase(s); 721 } 722 723 if (stringValue.length() < minLength) 724 { 725 return false; 726 } 727 728 final StringBuilder buffer = new StringBuilder(stringValue); 729 if (matchStartsWith != null) 730 { 731 if (buffer.indexOf(matchStartsWith) != 0) 732 { 733 return false; 734 } 735 buffer.delete(0, matchStartsWith.length()); 736 } 737 738 if (matchEndsWith != null) 739 { 740 final int lengthMinusEndsWith = buffer.length() - matchEndsWith.length(); 741 if (buffer.lastIndexOf(matchEndsWith) != lengthMinusEndsWith) 742 { 743 return false; 744 } 745 buffer.setLength(lengthMinusEndsWith); 746 } 747 748 for (final String containsElement : matchContains) 749 { 750 final int index = buffer.indexOf(containsElement); 751 if (index < 0) 752 { 753 return false; 754 } 755 buffer.delete(0, (index+containsElement.length())); 756 } 757 758 return true; 759 } 760 761 762 763 /** 764 * {@inheritDoc} 765 */ 766 @Override() 767 public JSONObject toJSONObject() 768 { 769 final LinkedHashMap<String,JSONValue> fields = 770 new LinkedHashMap<>(StaticUtils.computeMapCapacity(6)); 771 772 fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE)); 773 774 if (field.size() == 1) 775 { 776 fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0))); 777 } 778 else 779 { 780 final ArrayList<JSONValue> fieldNameValues = 781 new ArrayList<>(field.size()); 782 for (final String s : field) 783 { 784 fieldNameValues.add(new JSONString(s)); 785 } 786 fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues)); 787 } 788 789 if (startsWith != null) 790 { 791 fields.put(FIELD_STARTS_WITH, new JSONString(startsWith)); 792 } 793 794 if (! contains.isEmpty()) 795 { 796 if (contains.size() == 1) 797 { 798 fields.put(FIELD_CONTAINS, new JSONString(contains.get(0))); 799 } 800 else 801 { 802 final ArrayList<JSONValue> containsValues = 803 new ArrayList<>(contains.size()); 804 for (final String s : contains) 805 { 806 containsValues.add(new JSONString(s)); 807 } 808 fields.put(FIELD_CONTAINS, new JSONArray(containsValues)); 809 } 810 } 811 812 if (endsWith != null) 813 { 814 fields.put(FIELD_ENDS_WITH, new JSONString(endsWith)); 815 } 816 817 if (caseSensitive) 818 { 819 fields.put(FIELD_CASE_SENSITIVE, JSONBoolean.TRUE); 820 } 821 822 return new JSONObject(fields); 823 } 824 825 826 827 /** 828 * {@inheritDoc} 829 */ 830 @Override() 831 protected SubstringJSONObjectFilter decodeFilter( 832 final JSONObject filterObject) 833 throws JSONException 834 { 835 final List<String> fieldPath = 836 getStrings(filterObject, FIELD_FIELD_PATH, false, null); 837 838 final String subInitial = getString(filterObject, FIELD_STARTS_WITH, null, 839 false); 840 841 final List<String> subAny = getStrings(filterObject, FIELD_CONTAINS, true, 842 Collections.<String>emptyList()); 843 844 final String subFinal = getString(filterObject, FIELD_ENDS_WITH, null, 845 false); 846 847 if ((subInitial == null) && (subFinal == null) && subAny.isEmpty()) 848 { 849 throw new JSONException(ERR_SUBSTRING_FILTER_NO_COMPONENTS.get( 850 String.valueOf(filterObject), FILTER_TYPE, FIELD_STARTS_WITH, 851 FIELD_CONTAINS, FIELD_ENDS_WITH)); 852 } 853 854 final boolean isCaseSensitive = getBoolean(filterObject, 855 FIELD_CASE_SENSITIVE, false); 856 857 return new SubstringJSONObjectFilter(fieldPath, subInitial, subAny, 858 subFinal, isCaseSensitive); 859 } 860}