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