001/*
002 * Copyright 2007-2021 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2021 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) 2007-2021 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;
037
038
039
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.Collection;
044import java.util.HashSet;
045import java.util.LinkedHashSet;
046import java.util.List;
047import java.util.TreeMap;
048
049import com.unboundid.asn1.ASN1Boolean;
050import com.unboundid.asn1.ASN1Buffer;
051import com.unboundid.asn1.ASN1BufferSequence;
052import com.unboundid.asn1.ASN1BufferSet;
053import com.unboundid.asn1.ASN1Element;
054import com.unboundid.asn1.ASN1Exception;
055import com.unboundid.asn1.ASN1OctetString;
056import com.unboundid.asn1.ASN1Sequence;
057import com.unboundid.asn1.ASN1Set;
058import com.unboundid.asn1.ASN1StreamReader;
059import com.unboundid.asn1.ASN1StreamReaderSequence;
060import com.unboundid.asn1.ASN1StreamReaderSet;
061import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
062import com.unboundid.ldap.matchingrules.MatchingRule;
063import com.unboundid.ldap.sdk.schema.Schema;
064import com.unboundid.ldap.sdk.unboundidds.jsonfilter.JSONObjectFilter;
065import com.unboundid.util.ByteStringBuffer;
066import com.unboundid.util.Debug;
067import com.unboundid.util.NotMutable;
068import com.unboundid.util.NotNull;
069import com.unboundid.util.Nullable;
070import com.unboundid.util.StaticUtils;
071import com.unboundid.util.ThreadSafety;
072import com.unboundid.util.ThreadSafetyLevel;
073import com.unboundid.util.Validator;
074import com.unboundid.util.json.JSONObject;
075
076import static com.unboundid.ldap.sdk.LDAPMessages.*;
077
078
079
080/**
081 * This class provides a data structure that represents an LDAP search filter.
082 * It provides methods for creating various types of filters, as well as parsing
083 * a filter from a string.  See
084 * <A HREF="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</A> for more
085 * information about representing search filters as strings.
086 * <BR><BR>
087 * The following filter types are defined:
088 * <UL>
089 *   <LI><B>AND</B> -- This is used to indicate that a filter should match an
090 *       entry only if all of the embedded filter components match that entry.
091 *       An AND filter with zero embedded filter components is considered an
092 *       LDAP TRUE filter as defined in
093 *       <A HREF="http://www.ietf.org/rfc/rfc4526.txt">RFC 4526</A> and will
094 *       match any entry.  AND filters contain only a set of embedded filter
095 *       components, and each of those embedded components can itself be any
096 *       type of filter, including an AND, OR, or NOT filter with additional
097 *       embedded components.</LI>
098 *   <LI><B>OR</B> -- This is used to indicate that a filter should match an
099 *       entry only if at least one of the embedded filter components matches
100 *       that entry.   An OR filter with zero embedded filter components is
101 *       considered an LDAP FALSE filter as defined in
102 *       <A HREF="http://www.ietf.org/rfc/rfc4526.txt">RFC 4526</A> and will
103 *       never match any entry.  OR filters contain only a set of embedded
104 *       filter components, and each of those embedded components can itself be
105 *       any type of filter, including an AND, OR, or NOT filter with additional
106 *       embedded components.</LI>
107 *   <LI><B>NOT</B> -- This is used to indicate that a filter should match an
108 *       entry only if the embedded NOT component does not match the entry.  A
109 *       NOT filter contains only a single embedded NOT filter component, but
110 *       that embedded component can itself be any type of filter, including an
111 *       AND, OR, or NOT filter with additional embedded components.</LI>
112 *   <LI><B>EQUALITY</B> -- This is used to indicate that a filter should match
113 *       an entry only if the entry contains a value for the specified attribute
114 *       that is equal to the provided assertion value.  An equality filter
115 *       contains only an attribute name and an assertion value.</LI>
116 *   <LI><B>SUBSTRING</B> -- This is used to indicate that a filter should match
117 *       an entry only if the entry contains at least one value for the
118 *       specified attribute that matches the provided substring assertion.  The
119 *       substring assertion must contain at least one element of the following
120 *       types:
121 *       <UL>
122 *         <LI>subInitial -- This indicates that the specified string must
123 *             appear at the beginning of the attribute value.  There can be at
124 *             most one subInitial element in a substring assertion.</LI>
125 *         <LI>subAny -- This indicates that the specified string may appear
126 *             anywhere in the attribute value.  There can be any number of
127 *             substring subAny elements in a substring assertion.  If there are
128 *             multiple subAny elements, then they must match in the order that
129 *             they are provided.</LI>
130 *         <LI>subFinal -- This indicates that the specified string must appear
131 *             at the end of the attribute value.  There can be at most one
132 *             subFinal element in a substring assertion.</LI>
133 *       </UL>
134 *       A substring filter contains only an attribute name and subInitial,
135 *       subAny, and subFinal elements.</LI>
136 *   <LI><B>GREATER-OR-EQUAL</B> -- This is used to indicate that a filter
137 *       should match an entry only if that entry contains at least one value
138 *       for the specified attribute that is greater than or equal to the
139 *       provided assertion value.  A greater-or-equal filter contains only an
140 *       attribute name and an assertion value.</LI>
141 *   <LI><B>LESS-OR-EQUAL</B> -- This is used to indicate that a filter should
142 *       match an entry only if that entry contains at least one value for the
143 *       specified attribute that is less than or equal to the provided
144 *       assertion value.  A less-or-equal filter contains only an attribute
145 *       name and an assertion value.</LI>
146 *   <LI><B>PRESENCE</B> -- This is used to indicate that a filter should match
147 *       an entry only if the entry contains at least one value for the
148 *       specified attribute.  A presence filter contains only an attribute
149 *       name.</LI>
150 *   <LI><B>APPROXIMATE-MATCH</B> -- This is used to indicate that a filter
151 *       should match an entry only if the entry contains at least one value for
152 *       the specified attribute that is approximately equal to the provided
153 *       assertion value.  The definition of "approximately equal to" may vary
154 *       from one server to another, and from one attribute to another, but it
155 *       is often implemented as a "sounds like" match using a variant of the
156 *       metaphone or double-metaphone algorithm.  An approximate-match filter
157 *       contains only an attribute name and an assertion value.</LI>
158 *   <LI><B>EXTENSIBLE-MATCH</B> -- This is used to perform advanced types of
159 *       matching against entries, according to the following criteria:
160 *       <UL>
161 *         <LI>If an attribute name is provided, then the assertion value must
162 *             match one of the values for that attribute (potentially including
163 *             values contained in the entry's DN).  If a matching rule ID is
164 *             also provided, then the associated matching rule will be used to
165 *             determine whether there is a match; otherwise the default
166 *             equality matching rule for that attribute will be used.</LI>
167 *         <LI>If no attribute name is provided, then a matching rule ID must be
168 *             given, and the corresponding matching rule will be used to
169 *             determine whether any attribute in the target entry (potentially
170 *             including attributes contained in the entry's DN) has at least
171 *             one value that matches the provided assertion value.</LI>
172 *         <LI>If the dnAttributes flag is set, then attributes contained in the
173 *             entry's DN will also be evaluated to determine if they match the
174 *             filter criteria.  If it is not set, then attributes contained in
175 *             the entry's DN (other than those contained in its RDN which are
176 *             also present as separate attributes in the entry) will not be
177*             examined.</LI>
178 *       </UL>
179 *       An extensible match filter contains only an attribute name, matching
180 *       rule ID, dnAttributes flag, and an assertion value.</LI>
181 * </UL>
182 * <BR><BR>
183 * There are two primary ways to create a search filter.  The first is to create
184 * a filter from its string representation with the
185 * {@link Filter#create(String)} method, using the syntax described in RFC 4515.
186 * For example:
187 * <PRE>
188 *   Filter f1 = Filter.create("(objectClass=*)");
189 *   Filter f2 = Filter.create("(uid=john.doe)");
190 *   Filter f3 = Filter.create("(|(givenName=John)(givenName=Johnathan))");
191 * </PRE>
192 * <BR><BR>
193 * Creating a filter from its string representation is a common approach and
194 * seems to be relatively straightforward, but it does have some hidden dangers.
195 * This primarily comes from the potential for special characters in the filter
196 * string which need to be properly escaped.  If this isn't done, then the
197 * search may fail or behave unexpectedly, or worse it could lead to a
198 * vulnerability in the application in which a malicious user could trick the
199 * application into retrieving more information than it should have.  To avoid
200 * these problems, it may be better to construct filters from their individual
201 * components rather than their string representations, like:
202 * <PRE>
203 *   Filter f1 = Filter.createPresenceFilter("objectClass");
204 *   Filter f2 = Filter.createEqualityFilter("uid", "john.doe");
205 *   Filter f3 = Filter.createORFilter(
206 *                    Filter.createEqualityFilter("givenName", "John"),
207 *                    Filter.createEqualityFilter("givenName", "Johnathan"));
208 * </PRE>
209 * In general, it is recommended to avoid creating filters from their string
210 * representations if any of that string representation may include
211 * user-provided data or special characters including non-ASCII characters,
212 * parentheses, asterisks, or backslashes.
213 */
214@NotMutable()
215@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
216public final class Filter
217       implements Serializable
218{
219  /**
220   * The BER type for AND search filters.
221   */
222  public static final byte FILTER_TYPE_AND = (byte) 0xA0;
223
224
225
226  /**
227   * The BER type for OR search filters.
228   */
229  public static final byte FILTER_TYPE_OR = (byte) 0xA1;
230
231
232
233  /**
234   * The BER type for NOT search filters.
235   */
236  public static final byte FILTER_TYPE_NOT = (byte) 0xA2;
237
238
239
240  /**
241   * The BER type for equality search filters.
242   */
243  public static final byte FILTER_TYPE_EQUALITY = (byte) 0xA3;
244
245
246
247  /**
248   * The BER type for substring search filters.
249   */
250  public static final byte FILTER_TYPE_SUBSTRING = (byte) 0xA4;
251
252
253
254  /**
255   * The BER type for greaterOrEqual search filters.
256   */
257  public static final byte FILTER_TYPE_GREATER_OR_EQUAL = (byte) 0xA5;
258
259
260
261  /**
262   * The BER type for lessOrEqual search filters.
263   */
264  public static final byte FILTER_TYPE_LESS_OR_EQUAL = (byte) 0xA6;
265
266
267
268  /**
269   * The BER type for presence search filters.
270   */
271  public static final byte FILTER_TYPE_PRESENCE = (byte) 0x87;
272
273
274
275  /**
276   * The BER type for approximate match search filters.
277   */
278  public static final byte FILTER_TYPE_APPROXIMATE_MATCH = (byte) 0xA8;
279
280
281
282  /**
283   * The BER type for extensible match search filters.
284   */
285  public static final byte FILTER_TYPE_EXTENSIBLE_MATCH = (byte) 0xA9;
286
287
288
289  /**
290   * The BER type for the subInitial substring filter element.
291   */
292  private static final byte SUBSTRING_TYPE_SUBINITIAL = (byte) 0x80;
293
294
295
296  /**
297   * The BER type for the subAny substring filter element.
298   */
299  private static final byte SUBSTRING_TYPE_SUBANY = (byte) 0x81;
300
301
302
303  /**
304   * The BER type for the subFinal substring filter element.
305   */
306  private static final byte SUBSTRING_TYPE_SUBFINAL = (byte) 0x82;
307
308
309
310  /**
311   * The BER type for the matching rule ID extensible match filter element.
312   */
313  private static final byte EXTENSIBLE_TYPE_MATCHING_RULE_ID = (byte) 0x81;
314
315
316
317  /**
318   * The BER type for the attribute name extensible match filter element.
319   */
320  private static final byte EXTENSIBLE_TYPE_ATTRIBUTE_NAME = (byte) 0x82;
321
322
323
324  /**
325   * The BER type for the match value extensible match filter element.
326   */
327  private static final byte EXTENSIBLE_TYPE_MATCH_VALUE = (byte) 0x83;
328
329
330
331  /**
332   * The BER type for the DN attributes extensible match filter element.
333   */
334  private static final byte EXTENSIBLE_TYPE_DN_ATTRIBUTES = (byte) 0x84;
335
336
337
338  /**
339   * The set of filters that will be used if there are no subordinate filters.
340   */
341  @NotNull private static final Filter[] NO_FILTERS = new Filter[0];
342
343
344
345  /**
346   * The set of subAny components that will be used if there are no subAny
347   * components.
348   */
349  @NotNull private static final ASN1OctetString[] NO_SUB_ANY =
350       new ASN1OctetString[0];
351
352
353
354  /**
355   * The serial version UID for this serializable class.
356   */
357  private static final long serialVersionUID = -2734184402804691970L;
358
359
360
361  // The assertion value for this filter.
362  @Nullable private final ASN1OctetString assertionValue;
363
364  // The subFinal component for this filter.
365  @Nullable private final ASN1OctetString subFinal;
366
367  // The subInitial component for this filter.
368  @Nullable private final ASN1OctetString subInitial;
369
370  // The subAny components for this filter.
371  @NotNull private final ASN1OctetString[] subAny;
372
373  // The dnAttrs element for this filter.
374  private final boolean dnAttributes;
375
376  // The filter component to include in a NOT filter.
377  @Nullable private final Filter notComp;
378
379  // The set of filter components to include in an AND or OR filter.
380  @NotNull private final Filter[] filterComps;
381
382  // The filter type for this search filter.
383  private final byte filterType;
384
385  // The attribute name for this filter.
386  @Nullable private final String attrName;
387
388  // The string representation of this search filter.
389  @Nullable private volatile String filterString;
390
391  // The matching rule ID for this filter.
392  @Nullable private final String matchingRuleID;
393
394  // The normalized string representation of this search filter.
395  @Nullable private volatile String normalizedString;
396
397
398
399  /**
400   * Creates a new filter with the appropriate subset of the provided
401   * information.
402   *
403   * @param  filterString    The string representation of this search filter.
404   *                         It may be {@code null} if it is not yet known.
405   * @param  filterType      The filter type for this filter.
406   * @param  filterComps     The set of filter components for this filter.
407   * @param  notComp         The filter component for this NOT filter.
408   * @param  attrName        The name of the target attribute for this filter.
409   * @param  assertionValue  Then assertion value for this filter.
410   * @param  subInitial      The subInitial component for this filter.
411   * @param  subAny          The set of subAny components for this filter.
412   * @param  subFinal        The subFinal component for this filter.
413   * @param  matchingRuleID  The matching rule ID for this filter.
414   * @param  dnAttributes    The dnAttributes flag.
415   */
416  private Filter(@Nullable final String filterString, final byte filterType,
417                 @NotNull final Filter[] filterComps,
418                 @Nullable final Filter notComp,
419                 @Nullable final String attrName,
420                 @Nullable final ASN1OctetString assertionValue,
421                 @Nullable final ASN1OctetString subInitial,
422                 @NotNull final ASN1OctetString[] subAny,
423                 @Nullable final ASN1OctetString subFinal,
424                 @Nullable final String matchingRuleID,
425                 final boolean dnAttributes)
426  {
427    this.filterString   = filterString;
428    this.filterType     = filterType;
429    this.filterComps    = filterComps;
430    this.notComp        = notComp;
431    this.attrName       = attrName;
432    this.assertionValue = assertionValue;
433    this.subInitial     = subInitial;
434    this.subAny         = subAny;
435    this.subFinal       = subFinal;
436    this.matchingRuleID = matchingRuleID;
437    this.dnAttributes  = dnAttributes;
438  }
439
440
441
442  /**
443   * Creates a new AND search filter with the provided components.
444   *
445   * @param  andComponents  The set of filter components to include in the AND
446   *                        filter.  It must not be {@code null}.
447   *
448   * @return  The created AND search filter.
449   */
450  @NotNull()
451  public static Filter createANDFilter(@NotNull final Filter... andComponents)
452  {
453    Validator.ensureNotNull(andComponents);
454
455    return new Filter(null, FILTER_TYPE_AND, andComponents, null, null, null,
456                      null, NO_SUB_ANY, null, null, false);
457  }
458
459
460
461  /**
462   * Creates a new AND search filter with the provided components.
463   *
464   * @param  andComponents  The set of filter components to include in the AND
465   *                        filter.  It must not be {@code null}.
466   *
467   * @return  The created AND search filter.
468   */
469  @NotNull()
470  public static Filter createANDFilter(
471                            @NotNull final List<Filter> andComponents)
472  {
473    Validator.ensureNotNull(andComponents);
474
475    return new Filter(null, FILTER_TYPE_AND,
476                      andComponents.toArray(new Filter[andComponents.size()]),
477                      null, null, null, null, NO_SUB_ANY, null, null, false);
478  }
479
480
481
482  /**
483   * Creates a new AND search filter with the provided components.
484   *
485   * @param  andComponents  The set of filter components to include in the AND
486   *                        filter.  It must not be {@code null}.
487   *
488   * @return  The created AND search filter.
489   */
490  @NotNull()
491  public static Filter createANDFilter(
492                            @NotNull final Collection<Filter> andComponents)
493  {
494    Validator.ensureNotNull(andComponents);
495
496    return new Filter(null, FILTER_TYPE_AND,
497                      andComponents.toArray(new Filter[andComponents.size()]),
498                      null, null, null, null, NO_SUB_ANY, null, null, false);
499  }
500
501
502
503  /**
504   * Creates a new OR search filter with the provided components.
505   *
506   * @param  orComponents  The set of filter components to include in the OR
507   *                       filter.  It must not be {@code null}.
508   *
509   * @return  The created OR search filter.
510   */
511  @NotNull()
512  public static Filter createORFilter(@NotNull final Filter... orComponents)
513  {
514    Validator.ensureNotNull(orComponents);
515
516    return new Filter(null, FILTER_TYPE_OR, orComponents, null, null, null,
517                      null, NO_SUB_ANY, null, null, false);
518  }
519
520
521
522  /**
523   * Creates a new OR search filter with the provided components.
524   *
525   * @param  orComponents  The set of filter components to include in the OR
526   *                       filter.  It must not be {@code null}.
527   *
528   * @return  The created OR search filter.
529   */
530  @NotNull()
531  public static Filter createORFilter(@NotNull final List<Filter> orComponents)
532  {
533    Validator.ensureNotNull(orComponents);
534
535    return new Filter(null, FILTER_TYPE_OR,
536                      orComponents.toArray(new Filter[orComponents.size()]),
537                      null, null, null, null, NO_SUB_ANY, null, null, false);
538  }
539
540
541
542  /**
543   * Creates a new OR search filter with the provided components.
544   *
545   * @param  orComponents  The set of filter components to include in the OR
546   *                       filter.  It must not be {@code null}.
547   *
548   * @return  The created OR search filter.
549   */
550  @NotNull()
551  public static Filter createORFilter(
552                            @NotNull final Collection<Filter> orComponents)
553  {
554    Validator.ensureNotNull(orComponents);
555
556    return new Filter(null, FILTER_TYPE_OR,
557                      orComponents.toArray(new Filter[orComponents.size()]),
558                      null, null, null, null, NO_SUB_ANY, null, null, false);
559  }
560
561
562
563  /**
564   * Creates a new NOT search filter with the provided component.
565   *
566   * @param  notComponent  The filter component to include in this NOT filter.
567   *                       It must not be {@code null}.
568   *
569   * @return  The created NOT search filter.
570   */
571  @NotNull()
572  public static Filter createNOTFilter(@NotNull final Filter notComponent)
573  {
574    Validator.ensureNotNull(notComponent);
575
576    return new Filter(null, FILTER_TYPE_NOT, NO_FILTERS, notComponent, null,
577                      null, null, NO_SUB_ANY, null, null, false);
578  }
579
580
581
582  /**
583   * Creates a new equality search filter with the provided information.
584   *
585   * @param  attributeName   The attribute name for this equality filter.  It
586   *                         must not be {@code null}.
587   * @param  assertionValue  The assertion value for this equality filter.  It
588   *                         must not be {@code null}.
589   *
590   * @return  The created equality search filter.
591   */
592  @NotNull()
593  public static Filter createEqualityFilter(@NotNull final String attributeName,
594                            @NotNull final String assertionValue)
595  {
596    Validator.ensureNotNull(attributeName, assertionValue);
597
598    return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null,
599                      attributeName, new ASN1OctetString(assertionValue), null,
600                      NO_SUB_ANY, null, null, false);
601  }
602
603
604
605  /**
606   * Creates a new equality search filter with the provided information.
607   *
608   * @param  attributeName   The attribute name for this equality filter.  It
609   *                         must not be {@code null}.
610   * @param  assertionValue  The assertion value for this equality filter.  It
611   *                         must not be {@code null}.
612   *
613   * @return  The created equality search filter.
614   */
615  @NotNull()
616  public static Filter createEqualityFilter(@NotNull final String attributeName,
617                            @NotNull final byte[] assertionValue)
618  {
619    Validator.ensureNotNull(attributeName, assertionValue);
620
621    return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null,
622                      attributeName, new ASN1OctetString(assertionValue), null,
623                      NO_SUB_ANY, null, null, false);
624  }
625
626
627
628  /**
629   * Creates a new equality search filter with the provided information.
630   *
631   * @param  attributeName   The attribute name for this equality filter.  It
632   *                         must not be {@code null}.
633   * @param  assertionValue  The assertion value for this equality filter.  It
634   *                         must not be {@code null}.
635   *
636   * @return  The created equality search filter.
637   */
638  @NotNull()
639  static Filter createEqualityFilter(@NotNull final String attributeName,
640                     @NotNull final ASN1OctetString assertionValue)
641  {
642    Validator.ensureNotNull(attributeName, assertionValue);
643
644    return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null,
645                      attributeName, assertionValue, null, NO_SUB_ANY, null,
646                      null, false);
647  }
648
649
650
651  /**
652   * Creates a new substring search filter with the provided information.  At
653   * least one of the subInitial, subAny, and subFinal components must not be
654   * {@code null}.
655   *
656   * @param  attributeName  The attribute name for this substring filter.  It
657   *                        must not be {@code null}.
658   * @param  subInitial     The subInitial component for this substring filter.
659   * @param  subAny         The set of subAny components for this substring
660   *                        filter.
661   * @param  subFinal       The subFinal component for this substring filter.
662   *
663   * @return  The created substring search filter.
664   */
665  @NotNull()
666  public static Filter createSubstringFilter(
667                            @NotNull final String attributeName,
668                            @Nullable final String subInitial,
669                            @Nullable final String[] subAny,
670                            @Nullable final String subFinal)
671  {
672    Validator.ensureNotNull(attributeName);
673    Validator.ensureTrue((subInitial != null) ||
674         ((subAny != null) && (subAny.length > 0)) ||
675         (subFinal != null));
676
677    final ASN1OctetString subInitialOS;
678    if (subInitial == null)
679    {
680      subInitialOS = null;
681    }
682    else
683    {
684      subInitialOS = new ASN1OctetString(subInitial);
685    }
686
687    final ASN1OctetString[] subAnyArray;
688    if (subAny == null)
689    {
690      subAnyArray = NO_SUB_ANY;
691    }
692    else
693    {
694      subAnyArray = new ASN1OctetString[subAny.length];
695      for (int i=0; i < subAny.length; i++)
696      {
697        subAnyArray[i] = new ASN1OctetString(subAny[i]);
698      }
699    }
700
701    final ASN1OctetString subFinalOS;
702    if (subFinal == null)
703    {
704      subFinalOS = null;
705    }
706    else
707    {
708      subFinalOS = new ASN1OctetString(subFinal);
709    }
710
711    return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
712                      attributeName, null, subInitialOS, subAnyArray,
713                      subFinalOS, null, false);
714  }
715
716
717
718  /**
719   * Creates a new substring search filter with the provided information.  At
720   * least one of the subInitial, subAny, and subFinal components must not be
721   * {@code null}.
722   *
723   * @param  attributeName  The attribute name for this substring filter.  It
724   *                        must not be {@code null}.
725   * @param  subInitial     The subInitial component for this substring filter.
726   * @param  subAny         The set of subAny components for this substring
727   *                        filter.
728   * @param  subFinal       The subFinal component for this substring filter.
729   *
730   * @return  The created substring search filter.
731   */
732  @NotNull()
733  public static Filter createSubstringFilter(
734                            @NotNull final String attributeName,
735                            @Nullable final byte[] subInitial,
736                            @Nullable final byte[][] subAny,
737                            @Nullable final byte[] subFinal)
738  {
739    Validator.ensureNotNull(attributeName);
740    Validator.ensureTrue((subInitial != null) ||
741         ((subAny != null) && (subAny.length > 0)) ||
742         (subFinal != null));
743
744    final ASN1OctetString subInitialOS;
745    if (subInitial == null)
746    {
747      subInitialOS = null;
748    }
749    else
750    {
751      subInitialOS = new ASN1OctetString(subInitial);
752    }
753
754    final ASN1OctetString[] subAnyArray;
755    if (subAny == null)
756    {
757      subAnyArray = NO_SUB_ANY;
758    }
759    else
760    {
761      subAnyArray = new ASN1OctetString[subAny.length];
762      for (int i=0; i < subAny.length; i++)
763      {
764        subAnyArray[i] = new ASN1OctetString(subAny[i]);
765      }
766    }
767
768    final ASN1OctetString subFinalOS;
769    if (subFinal == null)
770    {
771      subFinalOS = null;
772    }
773    else
774    {
775      subFinalOS = new ASN1OctetString(subFinal);
776    }
777
778    return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
779                      attributeName, null, subInitialOS, subAnyArray,
780                      subFinalOS, null, false);
781  }
782
783
784
785  /**
786   * Creates a new substring search filter with the provided information.  At
787   * least one of the subInitial, subAny, and subFinal components must not be
788   * {@code null}.
789   *
790   * @param  attributeName  The attribute name for this substring filter.  It
791   *                        must not be {@code null}.
792   * @param  subInitial     The subInitial component for this substring filter.
793   * @param  subAny         The set of subAny components for this substring
794   *                        filter.
795   * @param  subFinal       The subFinal component for this substring filter.
796   *
797   * @return  The created substring search filter.
798   */
799  @NotNull()
800  static Filter createSubstringFilter(@NotNull final String attributeName,
801                     @Nullable final ASN1OctetString subInitial,
802                     @Nullable final ASN1OctetString[] subAny,
803                     @Nullable final ASN1OctetString subFinal)
804  {
805    Validator.ensureNotNull(attributeName);
806    Validator.ensureTrue((subInitial != null) ||
807         ((subAny != null) && (subAny.length > 0)) ||
808         (subFinal != null));
809
810    if (subAny == null)
811    {
812      return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
813                        attributeName, null, subInitial, NO_SUB_ANY, subFinal,
814                        null, false);
815    }
816    else
817    {
818      return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
819                        attributeName, null, subInitial, subAny, subFinal, null,
820                        false);
821    }
822  }
823
824
825
826  /**
827   * Creates a new substring search filter with only a subInitial (starts with)
828   * component.
829   *
830   * @param  attributeName  The attribute name for this substring filter.  It
831   *                        must not be {@code null}.
832   * @param  subInitial     The subInitial component for this substring filter.
833   *                        It must not be {@code null}.
834   *
835   * @return  The created substring search filter.
836   */
837  @NotNull()
838  public static Filter createSubInitialFilter(
839                            @NotNull final String attributeName,
840                            @NotNull final String subInitial)
841  {
842    return createSubstringFilter(attributeName, subInitial, null, null);
843  }
844
845
846
847  /**
848   * Creates a new substring search filter with only a subInitial (starts with)
849   * component.
850   *
851   * @param  attributeName  The attribute name for this substring filter.  It
852   *                        must not be {@code null}.
853   * @param  subInitial     The subInitial component for this substring filter.
854   *                        It must not be {@code null}.
855   *
856   * @return  The created substring search filter.
857   */
858  @NotNull()
859  public static Filter createSubInitialFilter(
860                            @NotNull final String attributeName,
861                            @NotNull final byte[] subInitial)
862  {
863    return createSubstringFilter(attributeName, subInitial, null, null);
864  }
865
866
867
868  /**
869   * Creates a new substring search filter with only a subAny (contains)
870   * component.
871   *
872   * @param  attributeName  The attribute name for this substring filter.  It
873   *                        must not be {@code null}.
874   * @param  subAny         The subAny values for this substring filter.  It
875   *                        must not be {@code null} or empty.
876   *
877   * @return  The created substring search filter.
878   */
879  @NotNull()
880  public static Filter createSubAnyFilter(@NotNull final String attributeName,
881                                          @NotNull final String... subAny)
882  {
883    return createSubstringFilter(attributeName, null, subAny, null);
884  }
885
886
887
888  /**
889   * Creates a new substring search filter with only a subAny (contains)
890   * component.
891   *
892   * @param  attributeName  The attribute name for this substring filter.  It
893   *                        must not be {@code null}.
894   * @param  subAny         The subAny values for this substring filter.  It
895   *                        must not be {@code null} or empty.
896   *
897   * @return  The created substring search filter.
898   */
899  @NotNull()
900  public static Filter createSubAnyFilter(@NotNull final String attributeName,
901                                          @NotNull final byte[]... subAny)
902  {
903    return createSubstringFilter(attributeName, null, subAny, null);
904  }
905
906
907
908  /**
909   * Creates a new substring search filter with only a subFinal (ends with)
910   * component.
911   *
912   * @param  attributeName  The attribute name for this substring filter.  It
913   *                        must not be {@code null}.
914   * @param  subFinal       The subFinal component for this substring filter.
915   *                        It must not be {@code null}.
916   *
917   * @return  The created substring search filter.
918   */
919  @NotNull()
920  public static Filter createSubFinalFilter(@NotNull final String attributeName,
921                                            @NotNull final String subFinal)
922  {
923    return createSubstringFilter(attributeName, null, null, subFinal);
924  }
925
926
927
928  /**
929   * Creates a new substring search filter with only a subFinal (ends with)
930   * component.
931   *
932   * @param  attributeName  The attribute name for this substring filter.  It
933   *                        must not be {@code null}.
934   * @param  subFinal       The subFinal component for this substring filter.
935   *                        It must not be {@code null}.
936   *
937   * @return  The created substring search filter.
938   */
939  @NotNull()
940  public static Filter createSubFinalFilter(@NotNull final String attributeName,
941                                            @NotNull final byte[] subFinal)
942  {
943    return createSubstringFilter(attributeName, null, null, subFinal);
944  }
945
946
947
948  /**
949   * Creates a new greater-or-equal search filter with the provided information.
950   *
951   * @param  attributeName   The attribute name for this greater-or-equal
952   *                         filter.  It must not be {@code null}.
953   * @param  assertionValue  The assertion value for this greater-or-equal
954   *                         filter.  It must not be {@code null}.
955   *
956   * @return  The created greater-or-equal search filter.
957   */
958  @NotNull()
959  public static Filter createGreaterOrEqualFilter(
960                            @NotNull final String attributeName,
961                            @NotNull final String assertionValue)
962  {
963    Validator.ensureNotNull(attributeName, assertionValue);
964
965    return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null,
966                      attributeName, new ASN1OctetString(assertionValue), null,
967                      NO_SUB_ANY, null, null, false);
968  }
969
970
971
972  /**
973   * Creates a new greater-or-equal search filter with the provided information.
974   *
975   * @param  attributeName   The attribute name for this greater-or-equal
976   *                         filter.  It must not be {@code null}.
977   * @param  assertionValue  The assertion value for this greater-or-equal
978   *                         filter.  It must not be {@code null}.
979   *
980   * @return  The created greater-or-equal search filter.
981   */
982  @NotNull()
983  public static Filter createGreaterOrEqualFilter(
984                            @NotNull final String attributeName,
985                            @NotNull final byte[] assertionValue)
986  {
987    Validator.ensureNotNull(attributeName, assertionValue);
988
989    return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null,
990                      attributeName, new ASN1OctetString(assertionValue), null,
991                      NO_SUB_ANY, null, null, false);
992  }
993
994
995
996  /**
997   * Creates a new greater-or-equal search filter with the provided information.
998   *
999   * @param  attributeName   The attribute name for this greater-or-equal
1000   *                         filter.  It must not be {@code null}.
1001   * @param  assertionValue  The assertion value for this greater-or-equal
1002   *                         filter.  It must not be {@code null}.
1003   *
1004   * @return  The created greater-or-equal search filter.
1005   */
1006  @NotNull()
1007  static Filter createGreaterOrEqualFilter(
1008                     @NotNull final String attributeName,
1009                     @NotNull final ASN1OctetString assertionValue)
1010  {
1011    Validator.ensureNotNull(attributeName, assertionValue);
1012
1013    return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null,
1014                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1015                      null, false);
1016  }
1017
1018
1019
1020  /**
1021   * Creates a new less-or-equal search filter with the provided information.
1022   *
1023   * @param  attributeName   The attribute name for this less-or-equal
1024   *                         filter.  It must not be {@code null}.
1025   * @param  assertionValue  The assertion value for this less-or-equal
1026   *                         filter.  It must not be {@code null}.
1027   *
1028   * @return  The created less-or-equal search filter.
1029   */
1030  @NotNull()
1031  public static Filter createLessOrEqualFilter(
1032                            @NotNull final String attributeName,
1033                            @NotNull final String assertionValue)
1034  {
1035    Validator.ensureNotNull(attributeName, assertionValue);
1036
1037    return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null,
1038                      attributeName, new ASN1OctetString(assertionValue), null,
1039                      NO_SUB_ANY, null, null, false);
1040  }
1041
1042
1043
1044  /**
1045   * Creates a new less-or-equal search filter with the provided information.
1046   *
1047   * @param  attributeName   The attribute name for this less-or-equal
1048   *                         filter.  It must not be {@code null}.
1049   * @param  assertionValue  The assertion value for this less-or-equal
1050   *                         filter.  It must not be {@code null}.
1051   *
1052   * @return  The created less-or-equal search filter.
1053   */
1054  @NotNull()
1055  public static Filter createLessOrEqualFilter(
1056                            @NotNull final String attributeName,
1057                            @NotNull final byte[] assertionValue)
1058  {
1059    Validator.ensureNotNull(attributeName, assertionValue);
1060
1061    return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null,
1062                      attributeName, new ASN1OctetString(assertionValue), null,
1063                      NO_SUB_ANY, null, null, false);
1064  }
1065
1066
1067
1068  /**
1069   * Creates a new less-or-equal search filter with the provided information.
1070   *
1071   * @param  attributeName   The attribute name for this less-or-equal
1072   *                         filter.  It must not be {@code null}.
1073   * @param  assertionValue  The assertion value for this less-or-equal
1074   *                         filter.  It must not be {@code null}.
1075   *
1076   * @return  The created less-or-equal search filter.
1077   */
1078  @NotNull()
1079  static Filter createLessOrEqualFilter(
1080                     @NotNull final String attributeName,
1081                     @NotNull final ASN1OctetString assertionValue)
1082  {
1083    Validator.ensureNotNull(attributeName, assertionValue);
1084
1085    return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null,
1086                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1087                      null, false);
1088  }
1089
1090
1091
1092  /**
1093   * Creates a new presence search filter with the provided information.
1094   *
1095   * @param  attributeName   The attribute name for this presence filter.  It
1096   *                         must not be {@code null}.
1097   *
1098   * @return  The created presence search filter.
1099   */
1100  @NotNull()
1101  public static Filter createPresenceFilter(@NotNull final String attributeName)
1102  {
1103    Validator.ensureNotNull(attributeName);
1104
1105    return new Filter(null, FILTER_TYPE_PRESENCE, NO_FILTERS, null,
1106                      attributeName, null, null, NO_SUB_ANY, null, null, false);
1107  }
1108
1109
1110
1111  /**
1112   * Creates a new approximate match search filter with the provided
1113   * information.
1114   *
1115   * @param  attributeName   The attribute name for this approximate match
1116   *                         filter.  It must not be {@code null}.
1117   * @param  assertionValue  The assertion value for this approximate match
1118   *                         filter.  It must not be {@code null}.
1119   *
1120   * @return  The created approximate match search filter.
1121   */
1122  @NotNull()
1123  public static Filter createApproximateMatchFilter(
1124                            @NotNull final String attributeName,
1125                            @NotNull final String assertionValue)
1126  {
1127    Validator.ensureNotNull(attributeName, assertionValue);
1128
1129    return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null,
1130                      attributeName, new ASN1OctetString(assertionValue), null,
1131                      NO_SUB_ANY, null, null, false);
1132  }
1133
1134
1135
1136  /**
1137   * Creates a new approximate match search filter with the provided
1138   * information.
1139   *
1140   * @param  attributeName   The attribute name for this approximate match
1141   *                         filter.  It must not be {@code null}.
1142   * @param  assertionValue  The assertion value for this approximate match
1143   *                         filter.  It must not be {@code null}.
1144   *
1145   * @return  The created approximate match search filter.
1146   */
1147  @NotNull()
1148  public static Filter createApproximateMatchFilter(
1149                            @NotNull final String attributeName,
1150                            @NotNull final byte[] assertionValue)
1151  {
1152    Validator.ensureNotNull(attributeName, assertionValue);
1153
1154    return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null,
1155                      attributeName, new ASN1OctetString(assertionValue), null,
1156                      NO_SUB_ANY, null, null, false);
1157  }
1158
1159
1160
1161  /**
1162   * Creates a new approximate match search filter with the provided
1163   * information.
1164   *
1165   * @param  attributeName   The attribute name for this approximate match
1166   *                         filter.  It must not be {@code null}.
1167   * @param  assertionValue  The assertion value for this approximate match
1168   *                         filter.  It must not be {@code null}.
1169   *
1170   * @return  The created approximate match search filter.
1171   */
1172  @NotNull()
1173  static Filter createApproximateMatchFilter(
1174                     @NotNull final String attributeName,
1175                     @NotNull final ASN1OctetString assertionValue)
1176  {
1177    Validator.ensureNotNull(attributeName, assertionValue);
1178
1179    return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null,
1180                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1181                      null, false);
1182  }
1183
1184
1185
1186  /**
1187   * Creates a new extensible match search filter with the provided
1188   * information.  At least one of the attribute name and matching rule ID must
1189   * be specified, and the assertion value must always be present.
1190   *
1191   * @param  attributeName   The attribute name for this extensible match
1192   *                         filter.
1193   * @param  matchingRuleID  The matching rule ID for this extensible match
1194   *                         filter.
1195   * @param  dnAttributes    Indicates whether the match should be performed
1196   *                         against attributes in the target entry's DN.
1197   * @param  assertionValue  The assertion value for this extensible match
1198   *                         filter.  It must not be {@code null}.
1199   *
1200   * @return  The created extensible match search filter.
1201   */
1202  @NotNull()
1203  public static Filter createExtensibleMatchFilter(
1204                            @Nullable final String attributeName,
1205                            @Nullable final String matchingRuleID,
1206                            final boolean dnAttributes,
1207                            @NotNull final String assertionValue)
1208  {
1209    Validator.ensureNotNull(assertionValue);
1210    Validator.ensureFalse((attributeName == null) && (matchingRuleID == null));
1211
1212    return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null,
1213                      attributeName, new ASN1OctetString(assertionValue), null,
1214                      NO_SUB_ANY, null, matchingRuleID, dnAttributes);
1215  }
1216
1217
1218
1219  /**
1220   * Creates a new extensible match search filter with the provided
1221   * information.  At least one of the attribute name and matching rule ID must
1222   * be specified, and the assertion value must always be present.
1223   *
1224   * @param  attributeName   The attribute name for this extensible match
1225   *                         filter.
1226   * @param  matchingRuleID  The matching rule ID for this extensible match
1227   *                         filter.
1228   * @param  dnAttributes    Indicates whether the match should be performed
1229   *                         against attributes in the target entry's DN.
1230   * @param  assertionValue  The assertion value for this extensible match
1231   *                         filter.  It must not be {@code null}.
1232   *
1233   * @return  The created extensible match search filter.
1234   */
1235  @NotNull()
1236  public static Filter createExtensibleMatchFilter(
1237                            @Nullable final String attributeName,
1238                            @Nullable final String matchingRuleID,
1239                            final boolean dnAttributes,
1240                            @NotNull final byte[] assertionValue)
1241  {
1242    Validator.ensureNotNull(assertionValue);
1243    Validator.ensureFalse((attributeName == null) && (matchingRuleID == null));
1244
1245    return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null,
1246                      attributeName, new ASN1OctetString(assertionValue), null,
1247                      NO_SUB_ANY, null, matchingRuleID, dnAttributes);
1248  }
1249
1250
1251
1252  /**
1253   * Creates a new extensible match search filter with the provided
1254   * information.  At least one of the attribute name and matching rule ID must
1255   * be specified, and the assertion value must always be present.
1256   *
1257   * @param  attributeName   The attribute name for this extensible match
1258   *                         filter.
1259   * @param  matchingRuleID  The matching rule ID for this extensible match
1260   *                         filter.
1261   * @param  dnAttributes    Indicates whether the match should be performed
1262   *                         against attributes in the target entry's DN.
1263   * @param  assertionValue  The assertion value for this extensible match
1264   *                         filter.  It must not be {@code null}.
1265   *
1266   * @return  The created approximate match search filter.
1267   */
1268  @NotNull()
1269  static Filter createExtensibleMatchFilter(
1270                     @Nullable final String attributeName,
1271                     @Nullable final String matchingRuleID,
1272                     final boolean dnAttributes,
1273                     @NotNull final ASN1OctetString assertionValue)
1274  {
1275    Validator.ensureNotNull(assertionValue);
1276    Validator.ensureFalse((attributeName == null) && (matchingRuleID == null));
1277
1278    return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null,
1279                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1280                      matchingRuleID, dnAttributes);
1281  }
1282
1283
1284
1285  /**
1286   * Creates a new search filter from the provided string representation.
1287   *
1288   * @param  filterString  The string representation of the filter to create.
1289   *                       It must not be {@code null}.
1290   *
1291   * @return  The search filter decoded from the provided filter string.
1292   *
1293   * @throws  LDAPException  If the provided string cannot be decoded as a valid
1294   *                         LDAP search filter.
1295   */
1296  @NotNull()
1297  public static Filter create(@NotNull final String filterString)
1298         throws LDAPException
1299  {
1300    Validator.ensureNotNull(filterString);
1301
1302    return create(filterString, 0, (filterString.length() - 1), 0);
1303  }
1304
1305
1306
1307  /**
1308   * Creates a new search filter from the specified portion of the provided
1309   * string representation.
1310   *
1311   * @param  filterString  The string representation of the filter to create.
1312   * @param  startPos      The position of the first character to consider as
1313   *                       part of the filter.
1314   * @param  endPos        The position of the last character to consider as
1315   *                       part of the filter.
1316   * @param  depth         The current nesting depth for this filter.  It should
1317   *                       be increased by one for each AND, OR, or NOT filter
1318   *                       encountered, in order to prevent stack overflow
1319   *                       errors from excessive recursion.
1320   *
1321   * @return  The decoded search filter.
1322   *
1323   * @throws  LDAPException  If the provided string cannot be decoded as a valid
1324   *                         LDAP search filter.
1325   */
1326  @NotNull()
1327  private static Filter create(@NotNull final String filterString,
1328                               final int startPos, final int endPos,
1329                               final int depth)
1330          throws LDAPException
1331  {
1332    if (depth > 100)
1333    {
1334      throw new LDAPException(ResultCode.FILTER_ERROR,
1335           ERR_FILTER_TOO_DEEP.get(filterString));
1336    }
1337
1338    final byte              filterType;
1339    final Filter[]          filterComps;
1340    final Filter            notComp;
1341    final String            attrName;
1342    final ASN1OctetString   assertionValue;
1343    final ASN1OctetString   subInitial;
1344    final ASN1OctetString[] subAny;
1345    final ASN1OctetString   subFinal;
1346    final String            matchingRuleID;
1347    final boolean           dnAttributes;
1348
1349    if (startPos >= endPos)
1350    {
1351      throw new LDAPException(ResultCode.FILTER_ERROR,
1352           ERR_FILTER_TOO_SHORT.get(filterString));
1353    }
1354
1355    int l = startPos;
1356    int r = endPos;
1357
1358    // First, see if the provided filter string is enclosed in parentheses, like
1359    // it should be.  If so, then strip off the outer parentheses.
1360    if (filterString.charAt(l) == '(')
1361    {
1362      if (filterString.charAt(r) == ')')
1363      {
1364        l++;
1365        r--;
1366      }
1367      else
1368      {
1369        throw new LDAPException(ResultCode.FILTER_ERROR,
1370             ERR_FILTER_OPEN_WITHOUT_CLOSE.get(filterString, l, r));
1371      }
1372    }
1373    else
1374    {
1375      // This is technically an error, and it's a bad practice.  If we're
1376      // working on the complete filter string then we'll let it slide, but
1377      // otherwise we'll raise an error.
1378      if (l != 0)
1379      {
1380        throw new LDAPException(ResultCode.FILTER_ERROR,
1381             ERR_FILTER_MISSING_PARENTHESES.get(filterString,
1382                  filterString.substring(l, r+1)));
1383      }
1384    }
1385
1386
1387    // Look at the first character of the filter to see if it's an '&', '|', or
1388    // '!'.  If we find a parenthesis, then that's an error.
1389    switch (filterString.charAt(l))
1390    {
1391      case '&':
1392        filterType     = FILTER_TYPE_AND;
1393        filterComps    = parseFilterComps(filterString, l+1, r, depth+1);
1394        notComp        = null;
1395        attrName       = null;
1396        assertionValue = null;
1397        subInitial     = null;
1398        subAny         = NO_SUB_ANY;
1399        subFinal       = null;
1400        matchingRuleID = null;
1401        dnAttributes   = false;
1402        break;
1403
1404      case '|':
1405        filterType     = FILTER_TYPE_OR;
1406        filterComps    = parseFilterComps(filterString, l+1, r, depth+1);
1407        notComp        = null;
1408        attrName       = null;
1409        assertionValue = null;
1410        subInitial     = null;
1411        subAny         = NO_SUB_ANY;
1412        subFinal       = null;
1413        matchingRuleID = null;
1414        dnAttributes   = false;
1415        break;
1416
1417      case '!':
1418        filterType     = FILTER_TYPE_NOT;
1419        filterComps    = NO_FILTERS;
1420        notComp        = create(filterString, l+1, r, depth+1);
1421        attrName       = null;
1422        assertionValue = null;
1423        subInitial     = null;
1424        subAny         = NO_SUB_ANY;
1425        subFinal       = null;
1426        matchingRuleID = null;
1427        dnAttributes   = false;
1428        break;
1429
1430      case '(':
1431        throw new LDAPException(ResultCode.FILTER_ERROR,
1432             ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l));
1433
1434      case ':':
1435        // This must be an extensible matching filter that starts with a
1436        // dnAttributes flag and/or matching rule ID, and we should parse it
1437        // accordingly.
1438        filterType  = FILTER_TYPE_EXTENSIBLE_MATCH;
1439        filterComps = NO_FILTERS;
1440        notComp     = null;
1441        attrName    = null;
1442        subInitial  = null;
1443        subAny      = NO_SUB_ANY;
1444        subFinal    = null;
1445
1446        // The next element must be either the "dn:{matchingruleid}" or just
1447        // "{matchingruleid}", and it must be followed by a colon.
1448        final int dnMRIDStart = ++l;
1449        while ((l <= r) && (filterString.charAt(l) != ':'))
1450        {
1451          l++;
1452        }
1453
1454        if (l > r)
1455        {
1456          throw new LDAPException(ResultCode.FILTER_ERROR,
1457               ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos));
1458        }
1459        else if (l == dnMRIDStart)
1460        {
1461          throw new LDAPException(ResultCode.FILTER_ERROR,
1462               ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
1463        }
1464        final String s = filterString.substring(dnMRIDStart, l++);
1465        if (s.equalsIgnoreCase("dn"))
1466        {
1467          dnAttributes = true;
1468
1469          // The colon must be followed by the matching rule ID and another
1470          // colon.
1471          final int mrIDStart = l;
1472          while ((l < r) && (filterString.charAt(l) != ':'))
1473          {
1474            l++;
1475          }
1476
1477          if (l >= r)
1478          {
1479            throw new LDAPException(ResultCode.FILTER_ERROR,
1480                 ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos));
1481          }
1482
1483          matchingRuleID = filterString.substring(mrIDStart, l);
1484          if (matchingRuleID.isEmpty())
1485          {
1486            throw new LDAPException(ResultCode.FILTER_ERROR,
1487                 ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
1488          }
1489
1490          if ((++l > r) || (filterString.charAt(l) != '='))
1491          {
1492            throw new LDAPException(ResultCode.FILTER_ERROR,
1493                 ERR_FILTER_UNEXPECTED_CHAR_AFTER_MRID.get(filterString,
1494                      startPos, filterString.charAt(l)));
1495          }
1496        }
1497        else
1498        {
1499          matchingRuleID = s;
1500          dnAttributes = false;
1501
1502          // The colon must be followed by an equal sign.
1503          if ((l > r) || (filterString.charAt(l) != '='))
1504          {
1505            throw new LDAPException(ResultCode.FILTER_ERROR,
1506                 ERR_FILTER_NO_EQUAL_AFTER_MRID.get(filterString, startPos));
1507          }
1508        }
1509
1510        // Now we should be able to read the value, handling any escape
1511        // characters as we go.
1512        l++;
1513        final ByteStringBuffer valueBuffer = new ByteStringBuffer(r - l + 1);
1514        while (l <= r)
1515        {
1516          final char c = filterString.charAt(l);
1517          if (c == '\\')
1518          {
1519            l = readEscapedHexString(filterString, ++l, valueBuffer);
1520          }
1521          else if (c == '(')
1522          {
1523            throw new LDAPException(ResultCode.FILTER_ERROR,
1524                 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l));
1525          }
1526          else if (c == ')')
1527          {
1528            throw new LDAPException(ResultCode.FILTER_ERROR,
1529                 ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l));
1530          }
1531          else
1532          {
1533            valueBuffer.append(c);
1534            l++;
1535          }
1536        }
1537        assertionValue = new ASN1OctetString(valueBuffer.toByteArray());
1538        break;
1539
1540
1541      default:
1542        // We know that it's not an AND, OR, or NOT filter, so we can eliminate
1543        // the variables used only for them.
1544        filterComps = NO_FILTERS;
1545        notComp     = null;
1546
1547
1548        // We should now be able to read a non-empty attribute name.
1549        final int attrStartPos = l;
1550        int     attrEndPos   = -1;
1551        byte    tempFilterType = 0x00;
1552        boolean filterTypeKnown = false;
1553        boolean equalFound = false;
1554attrNameLoop:
1555        while (l <= r)
1556        {
1557          final char c = filterString.charAt(l++);
1558          switch (c)
1559          {
1560            case ':':
1561              tempFilterType = FILTER_TYPE_EXTENSIBLE_MATCH;
1562              filterTypeKnown = true;
1563              attrEndPos = l - 1;
1564              break attrNameLoop;
1565
1566            case '>':
1567              tempFilterType = FILTER_TYPE_GREATER_OR_EQUAL;
1568              filterTypeKnown = true;
1569              attrEndPos = l - 1;
1570
1571              if (l <= r)
1572              {
1573                if (filterString.charAt(l++) != '=')
1574                {
1575                  throw new LDAPException(ResultCode.FILTER_ERROR,
1576                       ERR_FILTER_UNEXPECTED_CHAR_AFTER_GT.get(filterString,
1577                            startPos, filterString.charAt(l-1)));
1578                }
1579              }
1580              else
1581              {
1582                throw new LDAPException(ResultCode.FILTER_ERROR,
1583                     ERR_FILTER_END_AFTER_GT.get(filterString, startPos));
1584              }
1585              break attrNameLoop;
1586
1587            case '<':
1588              tempFilterType = FILTER_TYPE_LESS_OR_EQUAL;
1589              filterTypeKnown = true;
1590              attrEndPos = l - 1;
1591
1592              if (l <= r)
1593              {
1594                if (filterString.charAt(l++) != '=')
1595                {
1596                  throw new LDAPException(ResultCode.FILTER_ERROR,
1597                       ERR_FILTER_UNEXPECTED_CHAR_AFTER_LT.get(filterString,
1598                            startPos, filterString.charAt(l-1)));
1599                }
1600              }
1601              else
1602              {
1603                throw new LDAPException(ResultCode.FILTER_ERROR,
1604                     ERR_FILTER_END_AFTER_LT.get(filterString, startPos));
1605              }
1606              break attrNameLoop;
1607
1608            case '~':
1609              tempFilterType = FILTER_TYPE_APPROXIMATE_MATCH;
1610              filterTypeKnown = true;
1611              attrEndPos = l - 1;
1612
1613              if (l <= r)
1614              {
1615                if (filterString.charAt(l++) != '=')
1616                {
1617                  throw new LDAPException(ResultCode.FILTER_ERROR,
1618                       ERR_FILTER_UNEXPECTED_CHAR_AFTER_TILDE.get(filterString,
1619                            startPos, filterString.charAt(l-1)));
1620                }
1621              }
1622              else
1623              {
1624                throw new LDAPException(ResultCode.FILTER_ERROR,
1625                     ERR_FILTER_END_AFTER_TILDE.get(filterString, startPos));
1626              }
1627              break attrNameLoop;
1628
1629            case '=':
1630              // It could be either an equality, presence, or substring filter.
1631              // We'll need to look at the value to determine that.
1632              attrEndPos = l - 1;
1633              equalFound = true;
1634              break attrNameLoop;
1635          }
1636        }
1637
1638        if (attrEndPos <= attrStartPos)
1639        {
1640          if (equalFound)
1641          {
1642            throw new LDAPException(ResultCode.FILTER_ERROR,
1643                 ERR_FILTER_EMPTY_ATTR_NAME.get(filterString, startPos));
1644          }
1645          else
1646          {
1647            throw new LDAPException(ResultCode.FILTER_ERROR,
1648                 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos));
1649          }
1650        }
1651        attrName = filterString.substring(attrStartPos, attrEndPos);
1652
1653
1654        // See if we're dealing with an extensible match filter.  If so, then
1655        // we may still need to do additional parsing to get the matching rule
1656        // ID and/or the dnAttributes flag.  Otherwise, we can rule out any
1657        // variables that are specific to extensible matching filters.
1658        if (filterTypeKnown && (tempFilterType == FILTER_TYPE_EXTENSIBLE_MATCH))
1659        {
1660          if (l > r)
1661          {
1662            throw new LDAPException(ResultCode.FILTER_ERROR,
1663                 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos));
1664          }
1665
1666          final char c = filterString.charAt(l++);
1667          if (c == '=')
1668          {
1669            matchingRuleID = null;
1670            dnAttributes   = false;
1671          }
1672          else
1673          {
1674            // We have either a matching rule ID or a dnAttributes flag, or
1675            // both.  Iterate through the filter until we find the equal sign,
1676            // and then figure out what we have from that.
1677            equalFound = false;
1678            final int substrStartPos = l - 1;
1679            while (l <= r)
1680            {
1681              if (filterString.charAt(l++) == '=')
1682              {
1683                equalFound = true;
1684                break;
1685              }
1686            }
1687
1688            if (! equalFound)
1689            {
1690              throw new LDAPException(ResultCode.FILTER_ERROR,
1691                   ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos));
1692            }
1693
1694            final String substr = filterString.substring(substrStartPos, l-1);
1695            final String lowerSubstr = StaticUtils.toLowerCase(substr);
1696            if (! substr.endsWith(":"))
1697            {
1698              throw new LDAPException(ResultCode.FILTER_ERROR,
1699                   ERR_FILTER_CANNOT_PARSE_MRID.get(filterString, startPos));
1700            }
1701
1702            if (lowerSubstr.equals("dn:"))
1703            {
1704              matchingRuleID = null;
1705              dnAttributes   = true;
1706            }
1707            else if (lowerSubstr.startsWith("dn:"))
1708            {
1709              matchingRuleID = substr.substring(3, substr.length() - 1);
1710              if (matchingRuleID.isEmpty())
1711              {
1712                throw new LDAPException(ResultCode.FILTER_ERROR,
1713                     ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
1714              }
1715
1716              dnAttributes   = true;
1717            }
1718            else
1719            {
1720              matchingRuleID = substr.substring(0, substr.length() - 1);
1721              dnAttributes   = false;
1722
1723              if (matchingRuleID.isEmpty())
1724              {
1725                throw new LDAPException(ResultCode.FILTER_ERROR,
1726                     ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
1727              }
1728            }
1729          }
1730        }
1731        else
1732        {
1733          matchingRuleID = null;
1734          dnAttributes   = false;
1735        }
1736
1737
1738        // At this point, we're ready to read the value.  If we still don't
1739        // know what type of filter we're dealing with, then we can tell that
1740        // based on asterisks in the value.
1741        if (l > r)
1742        {
1743          assertionValue = new ASN1OctetString();
1744          if (! filterTypeKnown)
1745          {
1746            tempFilterType = FILTER_TYPE_EQUALITY;
1747          }
1748
1749          subInitial = null;
1750          subAny     = NO_SUB_ANY;
1751          subFinal   = null;
1752        }
1753        else if (l == r)
1754        {
1755          if (filterTypeKnown)
1756          {
1757            switch (filterString.charAt(l))
1758            {
1759              case '*':
1760              case '(':
1761              case ')':
1762              case '\\':
1763                throw new LDAPException(ResultCode.FILTER_ERROR,
1764                     ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString,
1765                          startPos, filterString.charAt(l)));
1766            }
1767
1768            assertionValue =
1769                 new ASN1OctetString(filterString.substring(l, l+1));
1770          }
1771          else
1772          {
1773            final char c = filterString.charAt(l);
1774            switch (c)
1775            {
1776              case '*':
1777                tempFilterType = FILTER_TYPE_PRESENCE;
1778                assertionValue = null;
1779                break;
1780
1781              case '\\':
1782              case '(':
1783              case ')':
1784                throw new LDAPException(ResultCode.FILTER_ERROR,
1785                     ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString,
1786                          startPos, filterString.charAt(l)));
1787
1788              default:
1789                tempFilterType = FILTER_TYPE_EQUALITY;
1790                assertionValue =
1791                     new ASN1OctetString(filterString.substring(l, l+1));
1792                break;
1793            }
1794          }
1795
1796          subInitial     = null;
1797          subAny         = NO_SUB_ANY;
1798          subFinal       = null;
1799        }
1800        else
1801        {
1802          if (! filterTypeKnown)
1803          {
1804            tempFilterType = FILTER_TYPE_EQUALITY;
1805          }
1806
1807          final int valueStartPos = l;
1808          ASN1OctetString tempSubInitial = null;
1809          ASN1OctetString tempSubFinal   = null;
1810          final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1);
1811          ByteStringBuffer buffer = new ByteStringBuffer(r - l + 1);
1812          while (l <= r)
1813          {
1814            final char c = filterString.charAt(l++);
1815            switch (c)
1816            {
1817              case '*':
1818                if (filterTypeKnown)
1819                {
1820                  throw new LDAPException(ResultCode.FILTER_ERROR,
1821                       ERR_FILTER_UNEXPECTED_ASTERISK.get(filterString,
1822                            startPos));
1823                }
1824                else
1825                {
1826                  if ((l-1) == valueStartPos)
1827                  {
1828                    // The first character is an asterisk, so there is no
1829                    // subInitial.
1830                  }
1831                  else
1832                  {
1833                    if (tempFilterType == FILTER_TYPE_SUBSTRING)
1834                    {
1835                      // We already know that it's a substring filter, so this
1836                      // must be a subAny portion.  However, if the buffer is
1837                      // empty, then that means that there were two asterisks
1838                      // right next to each other, which is invalid.
1839                      if (buffer.length() == 0)
1840                      {
1841                        throw new LDAPException(ResultCode.FILTER_ERROR,
1842                             ERR_FILTER_UNEXPECTED_DOUBLE_ASTERISK.get(
1843                                  filterString, startPos));
1844                      }
1845                      else
1846                      {
1847                        subAnyList.add(
1848                             new ASN1OctetString(buffer.toByteArray()));
1849                        buffer = new ByteStringBuffer(r - l + 1);
1850                      }
1851                    }
1852                    else
1853                    {
1854                      // We haven't yet set the filter type, so the buffer must
1855                      // contain the subInitial portion.  We also know it's not
1856                      // empty because of an earlier check.
1857                      tempSubInitial =
1858                           new ASN1OctetString(buffer.toByteArray());
1859                      buffer = new ByteStringBuffer(r - l + 1);
1860                    }
1861                  }
1862
1863                  tempFilterType = FILTER_TYPE_SUBSTRING;
1864                }
1865                break;
1866
1867              case '\\':
1868                l = readEscapedHexString(filterString, l, buffer);
1869                break;
1870
1871              case '(':
1872                throw new LDAPException(ResultCode.FILTER_ERROR,
1873                     ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l));
1874
1875              case ')':
1876                throw new LDAPException(ResultCode.FILTER_ERROR,
1877                     ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l));
1878
1879              default:
1880                if (Character.isHighSurrogate(c))
1881                {
1882                  if (l <= r)
1883                  {
1884                    final char c2 = filterString.charAt(l);
1885                    if (Character.isLowSurrogate(c2))
1886                    {
1887                      l++;
1888                      final int codePoint = Character.toCodePoint(c, c2);
1889                      buffer.append(new String(new int[] { codePoint }, 0, 1));
1890                      break;
1891                    }
1892                  }
1893                }
1894
1895                buffer.append(c);
1896                break;
1897            }
1898          }
1899
1900          if ((tempFilterType == FILTER_TYPE_SUBSTRING) &&
1901               (! buffer.isEmpty()))
1902          {
1903            // The buffer must contain the subFinal portion.
1904            tempSubFinal = new ASN1OctetString(buffer.toByteArray());
1905          }
1906
1907          subInitial = tempSubInitial;
1908          subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
1909          subFinal = tempSubFinal;
1910
1911          if (tempFilterType == FILTER_TYPE_SUBSTRING)
1912          {
1913            assertionValue = null;
1914          }
1915          else
1916          {
1917            assertionValue = new ASN1OctetString(buffer.toByteArray());
1918          }
1919        }
1920
1921        filterType = tempFilterType;
1922        break;
1923    }
1924
1925
1926    if (startPos == 0)
1927    {
1928      return new Filter(filterString, filterType, filterComps, notComp,
1929                        attrName, assertionValue, subInitial, subAny, subFinal,
1930                        matchingRuleID, dnAttributes);
1931    }
1932    else
1933    {
1934      return new Filter(filterString.substring(startPos, endPos+1), filterType,
1935                        filterComps, notComp, attrName, assertionValue,
1936                        subInitial, subAny, subFinal, matchingRuleID,
1937                        dnAttributes);
1938    }
1939  }
1940
1941
1942
1943  /**
1944   * Parses the specified portion of the provided filter string to obtain a set
1945   * of filter components for use in an AND or OR filter.
1946   *
1947   * @param  filterString  The string representation for the set of filters.
1948   * @param  startPos      The position of the first character to consider as
1949   *                       part of the first filter.
1950   * @param  endPos        The position of the last character to consider as
1951   *                       part of the last filter.
1952   * @param  depth         The current nesting depth for this filter.  It should
1953   *                       be increased by one for each AND, OR, or NOT filter
1954   *                       encountered, in order to prevent stack overflow
1955   *                       errors from excessive recursion.
1956   *
1957   * @return  The decoded set of search filters.
1958   *
1959   * @throws  LDAPException  If the provided string cannot be decoded as a set
1960   *                         of LDAP search filters.
1961   */
1962  @NotNull()
1963  private static Filter[] parseFilterComps(@NotNull final String filterString,
1964                                           final int startPos, final int endPos,
1965                                           final int depth)
1966          throws LDAPException
1967  {
1968    if (startPos > endPos)
1969    {
1970      // This is acceptable, since it can represent an LDAP TRUE or FALSE filter
1971      // as described in RFC 4526.
1972      return NO_FILTERS;
1973    }
1974
1975
1976    // The set of filters must start with an opening parenthesis, and end with a
1977    // closing parenthesis.
1978    if (filterString.charAt(startPos) != '(')
1979    {
1980      throw new LDAPException(ResultCode.FILTER_ERROR,
1981           ERR_FILTER_EXPECTED_OPEN_PAREN.get(filterString, startPos));
1982    }
1983    if (filterString.charAt(endPos) != ')')
1984    {
1985      throw new LDAPException(ResultCode.FILTER_ERROR,
1986           ERR_FILTER_EXPECTED_CLOSE_PAREN.get(filterString, startPos));
1987    }
1988
1989
1990    // Iterate through the specified portion of the filter string and count
1991    // opening and closing parentheses to figure out where one filter ends and
1992    // another begins.
1993    final ArrayList<Filter> filterList = new ArrayList<>(5);
1994    int filterStartPos = startPos;
1995    int pos = startPos;
1996    int numOpen = 0;
1997    while (pos <= endPos)
1998    {
1999      final char c = filterString.charAt(pos++);
2000      if (c == '(')
2001      {
2002        numOpen++;
2003      }
2004      else if (c == ')')
2005      {
2006        numOpen--;
2007        if (numOpen == 0)
2008        {
2009          filterList.add(create(filterString, filterStartPos, pos-1, depth));
2010          filterStartPos = pos;
2011        }
2012      }
2013    }
2014
2015    if (numOpen != 0)
2016    {
2017      throw new LDAPException(ResultCode.FILTER_ERROR,
2018           ERR_FILTER_MISMATCHED_PARENS.get(filterString, startPos, endPos));
2019    }
2020
2021    return filterList.toArray(new Filter[filterList.size()]);
2022  }
2023
2024
2025
2026  /**
2027   * Reads one or more hex-encoded bytes from the specified portion of the
2028   * filter string.
2029   *
2030   * @param  filterString  The string from which the data is to be read.
2031   * @param  startPos      The position at which to start reading.  This should
2032   *                       be the position of first hex character immediately
2033   *                       after the initial backslash.
2034   * @param  buffer        The buffer to which the decoded string portion should
2035   *                       be appended.
2036   *
2037   * @return  The position at which the caller may resume parsing.
2038   *
2039   * @throws  LDAPException  If a problem occurs while reading hex-encoded
2040   *                         bytes.
2041   */
2042  private static int readEscapedHexString(@NotNull final String filterString,
2043                          final int startPos,
2044                          @NotNull final ByteStringBuffer buffer)
2045          throws LDAPException
2046  {
2047    final byte b;
2048    switch (filterString.charAt(startPos))
2049    {
2050      case '0':
2051        b = 0x00;
2052        break;
2053      case '1':
2054        b = 0x10;
2055        break;
2056      case '2':
2057        b = 0x20;
2058        break;
2059      case '3':
2060        b = 0x30;
2061        break;
2062      case '4':
2063        b = 0x40;
2064        break;
2065      case '5':
2066        b = 0x50;
2067        break;
2068      case '6':
2069        b = 0x60;
2070        break;
2071      case '7':
2072        b = 0x70;
2073        break;
2074      case '8':
2075        b = (byte) 0x80;
2076        break;
2077      case '9':
2078        b = (byte) 0x90;
2079        break;
2080      case 'a':
2081      case 'A':
2082        b = (byte) 0xA0;
2083        break;
2084      case 'b':
2085      case 'B':
2086        b = (byte) 0xB0;
2087        break;
2088      case 'c':
2089      case 'C':
2090        b = (byte) 0xC0;
2091        break;
2092      case 'd':
2093      case 'D':
2094        b = (byte) 0xD0;
2095        break;
2096      case 'e':
2097      case 'E':
2098        b = (byte) 0xE0;
2099        break;
2100      case 'f':
2101      case 'F':
2102        b = (byte) 0xF0;
2103        break;
2104      default:
2105        throw new LDAPException(ResultCode.FILTER_ERROR,
2106             ERR_FILTER_INVALID_HEX_CHAR.get(filterString,
2107                  filterString.charAt(startPos), startPos));
2108    }
2109
2110    switch (filterString.charAt(startPos+1))
2111    {
2112      case '0':
2113        buffer.append(b);
2114        break;
2115      case '1':
2116        buffer.append((byte) (b | 0x01));
2117        break;
2118      case '2':
2119        buffer.append((byte) (b | 0x02));
2120        break;
2121      case '3':
2122        buffer.append((byte) (b | 0x03));
2123        break;
2124      case '4':
2125        buffer.append((byte) (b | 0x04));
2126        break;
2127      case '5':
2128        buffer.append((byte) (b | 0x05));
2129        break;
2130      case '6':
2131        buffer.append((byte) (b | 0x06));
2132        break;
2133      case '7':
2134        buffer.append((byte) (b | 0x07));
2135        break;
2136      case '8':
2137        buffer.append((byte) (b | 0x08));
2138        break;
2139      case '9':
2140        buffer.append((byte) (b | 0x09));
2141        break;
2142      case 'a':
2143      case 'A':
2144        buffer.append((byte) (b | 0x0A));
2145        break;
2146      case 'b':
2147      case 'B':
2148        buffer.append((byte) (b | 0x0B));
2149        break;
2150      case 'c':
2151      case 'C':
2152        buffer.append((byte) (b | 0x0C));
2153        break;
2154      case 'd':
2155      case 'D':
2156        buffer.append((byte) (b | 0x0D));
2157        break;
2158      case 'e':
2159      case 'E':
2160        buffer.append((byte) (b | 0x0E));
2161        break;
2162      case 'f':
2163      case 'F':
2164        buffer.append((byte) (b | 0x0F));
2165        break;
2166      default:
2167        throw new LDAPException(ResultCode.FILTER_ERROR,
2168             ERR_FILTER_INVALID_HEX_CHAR.get(filterString,
2169                  filterString.charAt(startPos+1), (startPos+1)));
2170    }
2171
2172    return startPos+2;
2173  }
2174
2175
2176
2177  /**
2178   * Writes an ASN.1-encoded representation of this filter to the provided ASN.1
2179   * buffer.
2180   *
2181   * @param  buffer  The ASN.1 buffer to which the encoded representation should
2182   *                 be written.
2183   */
2184  public void writeTo(@NotNull final ASN1Buffer buffer)
2185  {
2186    switch (filterType)
2187    {
2188      case FILTER_TYPE_AND:
2189      case FILTER_TYPE_OR:
2190        final ASN1BufferSet compSet = buffer.beginSet(filterType);
2191        for (final Filter f : filterComps)
2192        {
2193          f.writeTo(buffer);
2194        }
2195        compSet.end();
2196        break;
2197
2198      case FILTER_TYPE_NOT:
2199        buffer.addElement(
2200             new ASN1Element(filterType, notComp.encode().encode()));
2201        break;
2202
2203      case FILTER_TYPE_EQUALITY:
2204      case FILTER_TYPE_GREATER_OR_EQUAL:
2205      case FILTER_TYPE_LESS_OR_EQUAL:
2206      case FILTER_TYPE_APPROXIMATE_MATCH:
2207        final ASN1BufferSequence avaSequence = buffer.beginSequence(filterType);
2208        buffer.addOctetString(attrName);
2209        buffer.addElement(assertionValue);
2210        avaSequence.end();
2211        break;
2212
2213      case FILTER_TYPE_SUBSTRING:
2214        final ASN1BufferSequence subFilterSequence =
2215             buffer.beginSequence(filterType);
2216        buffer.addOctetString(attrName);
2217
2218        final ASN1BufferSequence valueSequence = buffer.beginSequence();
2219        if (subInitial != null)
2220        {
2221          buffer.addOctetString(SUBSTRING_TYPE_SUBINITIAL,
2222                                subInitial.getValue());
2223        }
2224
2225        for (final ASN1OctetString s : subAny)
2226        {
2227          buffer.addOctetString(SUBSTRING_TYPE_SUBANY, s.getValue());
2228        }
2229
2230        if (subFinal != null)
2231        {
2232          buffer.addOctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue());
2233        }
2234        valueSequence.end();
2235        subFilterSequence.end();
2236        break;
2237
2238      case FILTER_TYPE_PRESENCE:
2239        buffer.addOctetString(filterType, attrName);
2240        break;
2241
2242      case FILTER_TYPE_EXTENSIBLE_MATCH:
2243        final ASN1BufferSequence mrSequence = buffer.beginSequence(filterType);
2244        if (matchingRuleID != null)
2245        {
2246          buffer.addOctetString(EXTENSIBLE_TYPE_MATCHING_RULE_ID,
2247                                matchingRuleID);
2248        }
2249
2250        if (attrName != null)
2251        {
2252          buffer.addOctetString(EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName);
2253        }
2254
2255        buffer.addOctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2256                              assertionValue.getValue());
2257
2258        if (dnAttributes)
2259        {
2260          buffer.addBoolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true);
2261        }
2262        mrSequence.end();
2263        break;
2264    }
2265  }
2266
2267
2268
2269  /**
2270   * Encodes this search filter to an ASN.1 element suitable for inclusion in an
2271   * LDAP search request protocol op.
2272   *
2273   * @return  An ASN.1 element containing the encoded search filter.
2274   */
2275  @NotNull()
2276  public ASN1Element encode()
2277  {
2278    switch (filterType)
2279    {
2280      case FILTER_TYPE_AND:
2281      case FILTER_TYPE_OR:
2282        final ASN1Element[] filterElements =
2283             new ASN1Element[filterComps.length];
2284        for (int i=0; i < filterComps.length; i++)
2285        {
2286          filterElements[i] = filterComps[i].encode();
2287        }
2288        return new ASN1Set(filterType, filterElements);
2289
2290
2291      case FILTER_TYPE_NOT:
2292        return new ASN1Element(filterType, notComp.encode().encode());
2293
2294
2295      case FILTER_TYPE_EQUALITY:
2296      case FILTER_TYPE_GREATER_OR_EQUAL:
2297      case FILTER_TYPE_LESS_OR_EQUAL:
2298      case FILTER_TYPE_APPROXIMATE_MATCH:
2299        final ASN1OctetString[] attrValueAssertionElements =
2300        {
2301          new ASN1OctetString(attrName),
2302          assertionValue
2303        };
2304        return new ASN1Sequence(filterType, attrValueAssertionElements);
2305
2306
2307      case FILTER_TYPE_SUBSTRING:
2308        final ArrayList<ASN1OctetString> subList =
2309             new ArrayList<>(2 + subAny.length);
2310        if (subInitial != null)
2311        {
2312          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBINITIAL,
2313                                          subInitial.getValue()));
2314        }
2315
2316        for (final ASN1Element subAnyElement : subAny)
2317        {
2318          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBANY,
2319                                          subAnyElement.getValue()));
2320        }
2321
2322
2323        if (subFinal != null)
2324        {
2325          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBFINAL,
2326                                          subFinal.getValue()));
2327        }
2328
2329        final ASN1Element[] subFilterElements =
2330        {
2331          new ASN1OctetString(attrName),
2332          new ASN1Sequence(subList)
2333        };
2334        return new ASN1Sequence(filterType, subFilterElements);
2335
2336
2337      case FILTER_TYPE_PRESENCE:
2338        return new ASN1OctetString(filterType, attrName);
2339
2340
2341      case FILTER_TYPE_EXTENSIBLE_MATCH:
2342        final ArrayList<ASN1Element> emElementList = new ArrayList<>(4);
2343        if (matchingRuleID != null)
2344        {
2345          emElementList.add(new ASN1OctetString(
2346               EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID));
2347        }
2348
2349        if (attrName != null)
2350        {
2351          emElementList.add(new ASN1OctetString(
2352               EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName));
2353        }
2354
2355        emElementList.add(new ASN1OctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2356             assertionValue.getValue()));
2357
2358        if (dnAttributes)
2359        {
2360          emElementList.add(new ASN1Boolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES,
2361                                            true));
2362        }
2363
2364        return new ASN1Sequence(filterType, emElementList);
2365
2366
2367      default:
2368        throw new AssertionError(ERR_FILTER_INVALID_TYPE.get(
2369             StaticUtils.toHex(filterType)));
2370    }
2371  }
2372
2373
2374
2375  /**
2376   * Reads and decodes a search filter from the provided ASN.1 stream reader.
2377   *
2378   * @param  reader  The ASN.1 stream reader from which to read the filter.
2379   *
2380   * @return  The decoded search filter.
2381   *
2382   * @throws  LDAPException  If an error occurs while reading or parsing the
2383   *                         search filter.
2384   */
2385  @NotNull()
2386  public static Filter readFrom(@NotNull final ASN1StreamReader reader)
2387         throws LDAPException
2388  {
2389    try
2390    {
2391      final Filter[]          filterComps;
2392      final Filter            notComp;
2393      final String            attrName;
2394      final ASN1OctetString   assertionValue;
2395      final ASN1OctetString   subInitial;
2396      final ASN1OctetString[] subAny;
2397      final ASN1OctetString   subFinal;
2398      final String            matchingRuleID;
2399      final boolean           dnAttributes;
2400
2401      final byte filterType = (byte) reader.peek();
2402
2403      switch (filterType)
2404      {
2405        case FILTER_TYPE_AND:
2406        case FILTER_TYPE_OR:
2407          final ArrayList<Filter> comps = new ArrayList<>(5);
2408          final ASN1StreamReaderSet elementSet = reader.beginSet();
2409          while (elementSet.hasMoreElements())
2410          {
2411            comps.add(readFrom(reader));
2412          }
2413
2414          filterComps = new Filter[comps.size()];
2415          comps.toArray(filterComps);
2416
2417          notComp        = null;
2418          attrName       = null;
2419          assertionValue = null;
2420          subInitial     = null;
2421          subAny         = NO_SUB_ANY;
2422          subFinal       = null;
2423          matchingRuleID = null;
2424          dnAttributes   = false;
2425          break;
2426
2427
2428        case FILTER_TYPE_NOT:
2429          final ASN1Element notFilterElement;
2430          try
2431          {
2432            final ASN1Element e = reader.readElement();
2433            notFilterElement = ASN1Element.decode(e.getValue());
2434          }
2435          catch (final ASN1Exception ae)
2436          {
2437            Debug.debugException(ae);
2438            throw new LDAPException(ResultCode.DECODING_ERROR,
2439                 ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(
2440                      StaticUtils.getExceptionMessage(ae)),
2441                 ae);
2442          }
2443          notComp = decode(notFilterElement);
2444
2445          filterComps    = NO_FILTERS;
2446          attrName       = null;
2447          assertionValue = null;
2448          subInitial     = null;
2449          subAny         = NO_SUB_ANY;
2450          subFinal       = null;
2451          matchingRuleID = null;
2452          dnAttributes   = false;
2453          break;
2454
2455
2456        case FILTER_TYPE_EQUALITY:
2457        case FILTER_TYPE_GREATER_OR_EQUAL:
2458        case FILTER_TYPE_LESS_OR_EQUAL:
2459        case FILTER_TYPE_APPROXIMATE_MATCH:
2460          reader.beginSequence();
2461          attrName = reader.readString();
2462          assertionValue = new ASN1OctetString(reader.readBytes());
2463
2464          filterComps    = NO_FILTERS;
2465          notComp        = null;
2466          subInitial     = null;
2467          subAny         = NO_SUB_ANY;
2468          subFinal       = null;
2469          matchingRuleID = null;
2470          dnAttributes   = false;
2471          break;
2472
2473
2474        case FILTER_TYPE_SUBSTRING:
2475          reader.beginSequence();
2476          attrName = reader.readString();
2477
2478          ASN1OctetString tempSubInitial = null;
2479          ASN1OctetString tempSubFinal   = null;
2480          final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1);
2481          final ASN1StreamReaderSequence subSequence = reader.beginSequence();
2482          while (subSequence.hasMoreElements())
2483          {
2484            final byte type = (byte) reader.peek();
2485            final ASN1OctetString s =
2486                 new ASN1OctetString(type, reader.readBytes());
2487            switch (type)
2488            {
2489              case SUBSTRING_TYPE_SUBINITIAL:
2490                tempSubInitial = s;
2491                break;
2492              case SUBSTRING_TYPE_SUBANY:
2493                subAnyList.add(s);
2494                break;
2495              case SUBSTRING_TYPE_SUBFINAL:
2496                tempSubFinal = s;
2497                break;
2498              default:
2499                throw new LDAPException(ResultCode.DECODING_ERROR,
2500                     ERR_FILTER_INVALID_SUBSTR_TYPE.get(
2501                          StaticUtils.toHex(type)));
2502            }
2503          }
2504
2505          subInitial = tempSubInitial;
2506          subFinal   = tempSubFinal;
2507
2508          subAny = new ASN1OctetString[subAnyList.size()];
2509          subAnyList.toArray(subAny);
2510
2511          filterComps    = NO_FILTERS;
2512          notComp        = null;
2513          assertionValue = null;
2514          matchingRuleID = null;
2515          dnAttributes   = false;
2516          break;
2517
2518
2519        case FILTER_TYPE_PRESENCE:
2520          attrName = reader.readString();
2521
2522          filterComps    = NO_FILTERS;
2523          notComp        = null;
2524          assertionValue = null;
2525          subInitial     = null;
2526          subAny         = NO_SUB_ANY;
2527          subFinal       = null;
2528          matchingRuleID = null;
2529          dnAttributes   = false;
2530          break;
2531
2532
2533        case FILTER_TYPE_EXTENSIBLE_MATCH:
2534          String          tempAttrName       = null;
2535          ASN1OctetString tempAssertionValue = null;
2536          String          tempMatchingRuleID = null;
2537          boolean         tempDNAttributes   = false;
2538
2539          final ASN1StreamReaderSequence emSequence = reader.beginSequence();
2540          while (emSequence.hasMoreElements())
2541          {
2542            final byte type = (byte) reader.peek();
2543            switch (type)
2544            {
2545              case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
2546                tempAttrName = reader.readString();
2547                break;
2548              case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
2549                tempMatchingRuleID = reader.readString();
2550                break;
2551              case EXTENSIBLE_TYPE_MATCH_VALUE:
2552                tempAssertionValue =
2553                     new ASN1OctetString(type, reader.readBytes());
2554                break;
2555              case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
2556                tempDNAttributes = reader.readBoolean();
2557                break;
2558              default:
2559                throw new LDAPException(ResultCode.DECODING_ERROR,
2560                     ERR_FILTER_EXTMATCH_INVALID_TYPE.get(
2561                          StaticUtils.toHex(type)));
2562            }
2563          }
2564
2565          if ((tempAttrName == null) && (tempMatchingRuleID == null))
2566          {
2567            throw new LDAPException(ResultCode.DECODING_ERROR,
2568                                    ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
2569          }
2570
2571          if (tempAssertionValue == null)
2572          {
2573            throw new LDAPException(ResultCode.DECODING_ERROR,
2574                                    ERR_FILTER_EXTMATCH_NO_VALUE.get());
2575          }
2576
2577          attrName       = tempAttrName;
2578          assertionValue = tempAssertionValue;
2579          matchingRuleID = tempMatchingRuleID;
2580          dnAttributes   = tempDNAttributes;
2581
2582          filterComps    = NO_FILTERS;
2583          notComp        = null;
2584          subInitial     = null;
2585          subAny         = NO_SUB_ANY;
2586          subFinal       = null;
2587          break;
2588
2589
2590        default:
2591          throw new LDAPException(ResultCode.DECODING_ERROR,
2592               ERR_FILTER_ELEMENT_INVALID_TYPE.get(
2593                    StaticUtils.toHex(filterType)));
2594      }
2595
2596      return new Filter(null, filterType, filterComps, notComp, attrName,
2597                        assertionValue, subInitial, subAny, subFinal,
2598                        matchingRuleID, dnAttributes);
2599    }
2600    catch (final LDAPException le)
2601    {
2602      Debug.debugException(le);
2603      throw le;
2604    }
2605    catch (final Exception e)
2606    {
2607      Debug.debugException(e);
2608      throw new LDAPException(ResultCode.DECODING_ERROR,
2609           ERR_FILTER_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e);
2610    }
2611  }
2612
2613
2614
2615  /**
2616   * Decodes the provided ASN.1 element as a search filter.
2617   *
2618   * @param  filterElement  The ASN.1 element containing the encoded search
2619   *                        filter.
2620   *
2621   * @return  The decoded search filter.
2622   *
2623   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
2624   *                         a search filter.
2625   */
2626  @NotNull()
2627  public static Filter decode(@NotNull final ASN1Element filterElement)
2628         throws LDAPException
2629  {
2630    final byte              filterType = filterElement.getType();
2631    final Filter[]          filterComps;
2632    final Filter            notComp;
2633    final String            attrName;
2634    final ASN1OctetString   assertionValue;
2635    final ASN1OctetString   subInitial;
2636    final ASN1OctetString[] subAny;
2637    final ASN1OctetString   subFinal;
2638    final String            matchingRuleID;
2639    final boolean           dnAttributes;
2640
2641    switch (filterType)
2642    {
2643      case FILTER_TYPE_AND:
2644      case FILTER_TYPE_OR:
2645        notComp        = null;
2646        attrName       = null;
2647        assertionValue = null;
2648        subInitial     = null;
2649        subAny         = NO_SUB_ANY;
2650        subFinal       = null;
2651        matchingRuleID = null;
2652        dnAttributes   = false;
2653
2654        final ASN1Set compSet;
2655        try
2656        {
2657          compSet = ASN1Set.decodeAsSet(filterElement);
2658        }
2659        catch (final ASN1Exception ae)
2660        {
2661          Debug.debugException(ae);
2662          throw new LDAPException(ResultCode.DECODING_ERROR,
2663               ERR_FILTER_CANNOT_DECODE_COMPS.get(
2664                    StaticUtils.getExceptionMessage(ae)),
2665               ae);
2666        }
2667
2668        final ASN1Element[] compElements = compSet.elements();
2669        filterComps = new Filter[compElements.length];
2670        for (int i=0; i < compElements.length; i++)
2671        {
2672          filterComps[i] = decode(compElements[i]);
2673        }
2674        break;
2675
2676
2677      case FILTER_TYPE_NOT:
2678        filterComps    = NO_FILTERS;
2679        attrName       = null;
2680        assertionValue = null;
2681        subInitial     = null;
2682        subAny         = NO_SUB_ANY;
2683        subFinal       = null;
2684        matchingRuleID = null;
2685        dnAttributes   = false;
2686
2687        final ASN1Element notFilterElement;
2688        try
2689        {
2690          notFilterElement = ASN1Element.decode(filterElement.getValue());
2691        }
2692        catch (final ASN1Exception ae)
2693        {
2694          Debug.debugException(ae);
2695          throw new LDAPException(ResultCode.DECODING_ERROR,
2696               ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(
2697                    StaticUtils.getExceptionMessage(ae)),
2698               ae);
2699        }
2700        notComp = decode(notFilterElement);
2701        break;
2702
2703
2704
2705      case FILTER_TYPE_EQUALITY:
2706      case FILTER_TYPE_GREATER_OR_EQUAL:
2707      case FILTER_TYPE_LESS_OR_EQUAL:
2708      case FILTER_TYPE_APPROXIMATE_MATCH:
2709        filterComps    = NO_FILTERS;
2710        notComp        = null;
2711        subInitial     = null;
2712        subAny         = NO_SUB_ANY;
2713        subFinal       = null;
2714        matchingRuleID = null;
2715        dnAttributes   = false;
2716
2717        final ASN1Sequence avaSequence;
2718        try
2719        {
2720          avaSequence = ASN1Sequence.decodeAsSequence(filterElement);
2721        }
2722        catch (final ASN1Exception ae)
2723        {
2724          Debug.debugException(ae);
2725          throw new LDAPException(ResultCode.DECODING_ERROR,
2726               ERR_FILTER_CANNOT_DECODE_AVA.get(
2727                    StaticUtils.getExceptionMessage(ae)),
2728               ae);
2729        }
2730
2731        final ASN1Element[] avaElements = avaSequence.elements();
2732        if (avaElements.length != 2)
2733        {
2734          throw new LDAPException(ResultCode.DECODING_ERROR,
2735                                  ERR_FILTER_INVALID_AVA_ELEMENT_COUNT.get(
2736                                       avaElements.length));
2737        }
2738
2739        attrName =
2740             ASN1OctetString.decodeAsOctetString(avaElements[0]).stringValue();
2741        assertionValue = ASN1OctetString.decodeAsOctetString(avaElements[1]);
2742        break;
2743
2744
2745      case FILTER_TYPE_SUBSTRING:
2746        filterComps    = NO_FILTERS;
2747        notComp        = null;
2748        assertionValue = null;
2749        matchingRuleID = null;
2750        dnAttributes   = false;
2751
2752        final ASN1Sequence subFilterSequence;
2753        try
2754        {
2755          subFilterSequence = ASN1Sequence.decodeAsSequence(filterElement);
2756        }
2757        catch (final ASN1Exception ae)
2758        {
2759          Debug.debugException(ae);
2760          throw new LDAPException(ResultCode.DECODING_ERROR,
2761               ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(
2762                    StaticUtils.getExceptionMessage(ae)),
2763               ae);
2764        }
2765
2766        final ASN1Element[] subFilterElements = subFilterSequence.elements();
2767        if (subFilterElements.length != 2)
2768        {
2769          throw new LDAPException(ResultCode.DECODING_ERROR,
2770                                  ERR_FILTER_INVALID_SUBSTR_ASSERTION_COUNT.get(
2771                                       subFilterElements.length));
2772        }
2773
2774        attrName = ASN1OctetString.decodeAsOctetString(
2775                        subFilterElements[0]).stringValue();
2776
2777        final ASN1Sequence subSequence;
2778        try
2779        {
2780          subSequence = ASN1Sequence.decodeAsSequence(subFilterElements[1]);
2781        }
2782        catch (final ASN1Exception ae)
2783        {
2784          Debug.debugException(ae);
2785          throw new LDAPException(ResultCode.DECODING_ERROR,
2786               ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(
2787                    StaticUtils.getExceptionMessage(ae)),
2788               ae);
2789        }
2790
2791        ASN1OctetString tempSubInitial = null;
2792        ASN1OctetString tempSubFinal   = null;
2793        final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1);
2794
2795        final ASN1Element[] subElements = subSequence.elements();
2796        for (final ASN1Element subElement : subElements)
2797        {
2798          switch (subElement.getType())
2799          {
2800            case SUBSTRING_TYPE_SUBINITIAL:
2801              if (tempSubInitial == null)
2802              {
2803                tempSubInitial =
2804                     ASN1OctetString.decodeAsOctetString(subElement);
2805              }
2806              else
2807              {
2808                throw new LDAPException(ResultCode.DECODING_ERROR,
2809                                        ERR_FILTER_MULTIPLE_SUBINITIAL.get());
2810              }
2811              break;
2812
2813            case SUBSTRING_TYPE_SUBANY:
2814              subAnyList.add(ASN1OctetString.decodeAsOctetString(subElement));
2815              break;
2816
2817            case SUBSTRING_TYPE_SUBFINAL:
2818              if (tempSubFinal == null)
2819              {
2820                tempSubFinal = ASN1OctetString.decodeAsOctetString(subElement);
2821              }
2822              else
2823              {
2824                throw new LDAPException(ResultCode.DECODING_ERROR,
2825                                        ERR_FILTER_MULTIPLE_SUBFINAL.get());
2826              }
2827              break;
2828
2829            default:
2830              throw new LDAPException(ResultCode.DECODING_ERROR,
2831                   ERR_FILTER_INVALID_SUBSTR_TYPE.get(
2832                        StaticUtils.toHex(subElement.getType())));
2833          }
2834        }
2835
2836        subInitial = tempSubInitial;
2837        subAny     = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
2838        subFinal   = tempSubFinal;
2839        break;
2840
2841
2842      case FILTER_TYPE_PRESENCE:
2843        filterComps    = NO_FILTERS;
2844        notComp        = null;
2845        assertionValue = null;
2846        subInitial     = null;
2847        subAny         = NO_SUB_ANY;
2848        subFinal       = null;
2849        matchingRuleID = null;
2850        dnAttributes   = false;
2851        attrName       =
2852             ASN1OctetString.decodeAsOctetString(filterElement).stringValue();
2853        break;
2854
2855
2856      case FILTER_TYPE_EXTENSIBLE_MATCH:
2857        filterComps    = NO_FILTERS;
2858        notComp        = null;
2859        subInitial     = null;
2860        subAny         = NO_SUB_ANY;
2861        subFinal       = null;
2862
2863        final ASN1Sequence emSequence;
2864        try
2865        {
2866          emSequence = ASN1Sequence.decodeAsSequence(filterElement);
2867        }
2868        catch (final ASN1Exception ae)
2869        {
2870          Debug.debugException(ae);
2871          throw new LDAPException(ResultCode.DECODING_ERROR,
2872               ERR_FILTER_CANNOT_DECODE_EXTMATCH.get(
2873                    StaticUtils.getExceptionMessage(ae)),
2874               ae);
2875        }
2876
2877        String          tempAttrName       = null;
2878        ASN1OctetString tempAssertionValue = null;
2879        String          tempMatchingRuleID = null;
2880        boolean         tempDNAttributes   = false;
2881        for (final ASN1Element e : emSequence.elements())
2882        {
2883          switch (e.getType())
2884          {
2885            case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
2886              if (tempAttrName == null)
2887              {
2888                tempAttrName =
2889                     ASN1OctetString.decodeAsOctetString(e).stringValue();
2890              }
2891              else
2892              {
2893                throw new LDAPException(ResultCode.DECODING_ERROR,
2894                               ERR_FILTER_EXTMATCH_MULTIPLE_ATTRS.get());
2895              }
2896              break;
2897
2898            case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
2899              if (tempMatchingRuleID == null)
2900              {
2901                tempMatchingRuleID  =
2902                     ASN1OctetString.decodeAsOctetString(e).stringValue();
2903              }
2904              else
2905              {
2906                throw new LDAPException(ResultCode.DECODING_ERROR,
2907                               ERR_FILTER_EXTMATCH_MULTIPLE_MRIDS.get());
2908              }
2909              break;
2910
2911            case EXTENSIBLE_TYPE_MATCH_VALUE:
2912              if (tempAssertionValue == null)
2913              {
2914                tempAssertionValue = ASN1OctetString.decodeAsOctetString(e);
2915              }
2916              else
2917              {
2918                throw new LDAPException(ResultCode.DECODING_ERROR,
2919                               ERR_FILTER_EXTMATCH_MULTIPLE_VALUES.get());
2920              }
2921              break;
2922
2923            case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
2924              try
2925              {
2926                if (tempDNAttributes)
2927                {
2928                  throw new LDAPException(ResultCode.DECODING_ERROR,
2929                                 ERR_FILTER_EXTMATCH_MULTIPLE_DNATTRS.get());
2930                }
2931                else
2932                {
2933                  tempDNAttributes =
2934                       ASN1Boolean.decodeAsBoolean(e).booleanValue();
2935                }
2936              }
2937              catch (final ASN1Exception ae)
2938              {
2939                Debug.debugException(ae);
2940                throw new LDAPException(ResultCode.DECODING_ERROR,
2941                     ERR_FILTER_EXTMATCH_DNATTRS_NOT_BOOLEAN.get(
2942                          StaticUtils.getExceptionMessage(ae)),
2943                     ae);
2944              }
2945              break;
2946
2947            default:
2948              throw new LDAPException(ResultCode.DECODING_ERROR,
2949                   ERR_FILTER_EXTMATCH_INVALID_TYPE.get(
2950                        StaticUtils.toHex(e.getType())));
2951          }
2952        }
2953
2954        if ((tempAttrName == null) && (tempMatchingRuleID == null))
2955        {
2956          throw new LDAPException(ResultCode.DECODING_ERROR,
2957                                  ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
2958        }
2959
2960        if (tempAssertionValue == null)
2961        {
2962          throw new LDAPException(ResultCode.DECODING_ERROR,
2963                                  ERR_FILTER_EXTMATCH_NO_VALUE.get());
2964        }
2965
2966        attrName       = tempAttrName;
2967        assertionValue = tempAssertionValue;
2968        matchingRuleID = tempMatchingRuleID;
2969        dnAttributes   = tempDNAttributes;
2970        break;
2971
2972
2973      default:
2974        throw new LDAPException(ResultCode.DECODING_ERROR,
2975             ERR_FILTER_ELEMENT_INVALID_TYPE.get(
2976                  StaticUtils.toHex(filterElement.getType())));
2977    }
2978
2979
2980    return new Filter(null, filterType, filterComps, notComp, attrName,
2981                      assertionValue, subInitial, subAny, subFinal,
2982                      matchingRuleID, dnAttributes);
2983  }
2984
2985
2986
2987  /**
2988   * Retrieves the filter type for this filter.
2989   *
2990   * @return  The filter type for this filter.
2991   */
2992  public byte getFilterType()
2993  {
2994    return filterType;
2995  }
2996
2997
2998
2999  /**
3000   * Retrieves the set of filter components used in this AND or OR filter.  This
3001   * is not applicable for any other filter type.
3002   *
3003   * @return  The set of filter components used in this AND or OR filter, or an
3004   *          empty array if this is some other type of filter or if there are
3005   *          no components (i.e., as in an LDAP TRUE or LDAP FALSE filter).
3006   */
3007  @NotNull()
3008  public Filter[] getComponents()
3009  {
3010    return filterComps;
3011  }
3012
3013
3014
3015  /**
3016   * Retrieves the filter component used in this NOT filter.  This is not
3017   * applicable for any other filter type.
3018   *
3019   * @return  The filter component used in this NOT filter, or {@code null} if
3020   *          this is some other type of filter.
3021   */
3022  @Nullable()
3023  public Filter getNOTComponent()
3024  {
3025    return notComp;
3026  }
3027
3028
3029
3030  /**
3031   * Retrieves the name of the attribute type for this search filter.  This is
3032   * applicable for the following types of filters:
3033   * <UL>
3034   *   <LI>Equality</LI>
3035   *   <LI>Substring</LI>
3036   *   <LI>Greater or Equal</LI>
3037   *   <LI>Less or Equal</LI>
3038   *   <LI>Presence</LI>
3039   *   <LI>Approximate Match</LI>
3040   *   <LI>Extensible Match</LI>
3041   * </UL>
3042   *
3043   * @return  The name of the attribute type for this search filter, or
3044   *          {@code null} if it is not applicable for this type of filter.
3045   */
3046  @Nullable()
3047  public String getAttributeName()
3048  {
3049    return attrName;
3050  }
3051
3052
3053
3054  /**
3055   * Retrieves the string representation of the assertion value for this search
3056   * filter.  This is applicable for the following types of filters:
3057   * <UL>
3058   *   <LI>Equality</LI>
3059   *   <LI>Greater or Equal</LI>
3060   *   <LI>Less or Equal</LI>
3061   *   <LI>Approximate Match</LI>
3062   *   <LI>Extensible Match</LI>
3063   * </UL>
3064   *
3065   * @return  The string representation of the assertion value for this search
3066   *          filter, or {@code null} if it is not applicable for this type of
3067   *          filter.
3068   */
3069  @Nullable()
3070  public String getAssertionValue()
3071  {
3072    if (assertionValue == null)
3073    {
3074      return null;
3075    }
3076    else
3077    {
3078      return assertionValue.stringValue();
3079    }
3080  }
3081
3082
3083
3084  /**
3085   * Retrieves the binary representation of the assertion value for this search
3086   * filter.  This is applicable for the following types of filters:
3087   * <UL>
3088   *   <LI>Equality</LI>
3089   *   <LI>Greater or Equal</LI>
3090   *   <LI>Less or Equal</LI>
3091   *   <LI>Approximate Match</LI>
3092   *   <LI>Extensible Match</LI>
3093   * </UL>
3094   *
3095   * @return  The binary representation of the assertion value for this search
3096   *          filter, or {@code null} if it is not applicable for this type of
3097   *          filter.
3098   */
3099  @Nullable()
3100  public byte[] getAssertionValueBytes()
3101  {
3102    if (assertionValue == null)
3103    {
3104      return null;
3105    }
3106    else
3107    {
3108      return assertionValue.getValue();
3109    }
3110  }
3111
3112
3113
3114  /**
3115   * Retrieves the raw assertion value for this search filter as an ASN.1
3116   * octet string.  This is applicable for the following types of filters:
3117   * <UL>
3118   *   <LI>Equality</LI>
3119   *   <LI>Greater or Equal</LI>
3120   *   <LI>Less or Equal</LI>
3121   *   <LI>Approximate Match</LI>
3122   *   <LI>Extensible Match</LI>
3123   * </UL>
3124   *
3125   * @return  The raw assertion value for this search filter as an ASN.1 octet
3126   *          string, or {@code null} if it is not applicable for this type of
3127   *          filter.
3128   */
3129  @Nullable()
3130  public ASN1OctetString getRawAssertionValue()
3131  {
3132    return assertionValue;
3133  }
3134
3135
3136
3137  /**
3138   * Retrieves the string representation of the subInitial element for this
3139   * substring filter.  This is not applicable for any other filter type.
3140   *
3141   * @return  The string representation of the subInitial element for this
3142   *          substring filter, or {@code null} if this is some other type of
3143   *          filter, or if it is a substring filter with no subInitial element.
3144   */
3145  @Nullable()
3146  public String getSubInitialString()
3147  {
3148    if (subInitial == null)
3149    {
3150      return null;
3151    }
3152    else
3153    {
3154      return subInitial.stringValue();
3155    }
3156  }
3157
3158
3159
3160  /**
3161   * Retrieves the binary representation of the subInitial element for this
3162   * substring filter.  This is not applicable for any other filter type.
3163   *
3164   * @return  The binary representation of the subInitial element for this
3165   *          substring filter, or {@code null} if this is some other type of
3166   *          filter, or if it is a substring filter with no subInitial element.
3167   */
3168  @Nullable()
3169  public byte[] getSubInitialBytes()
3170  {
3171    if (subInitial == null)
3172    {
3173      return null;
3174    }
3175    else
3176    {
3177      return subInitial.getValue();
3178    }
3179  }
3180
3181
3182
3183  /**
3184   * Retrieves the raw subInitial element for this filter as an ASN.1 octet
3185   * string.  This is not applicable for any other filter type.
3186   *
3187   * @return  The raw subInitial element for this filter as an ASN.1 octet
3188   *          string, or {@code null} if this is not a substring filter, or if
3189   *          it is a substring filter with no subInitial element.
3190   */
3191  @Nullable()
3192  public ASN1OctetString getRawSubInitialValue()
3193  {
3194    return subInitial;
3195  }
3196
3197
3198
3199  /**
3200   * Retrieves the string representations of the subAny elements for this
3201   * substring filter.  This is not applicable for any other filter type.
3202   *
3203   * @return  The string representations of the subAny elements for this
3204   *          substring filter, or an empty array if this is some other type of
3205   *          filter, or if it is a substring filter with no subFinal element.
3206   */
3207  @NotNull()
3208  public String[] getSubAnyStrings()
3209  {
3210    final String[] subAnyStrings = new String[subAny.length];
3211    for (int i=0; i < subAny.length; i++)
3212    {
3213      subAnyStrings[i] = subAny[i].stringValue();
3214    }
3215
3216    return subAnyStrings;
3217  }
3218
3219
3220
3221  /**
3222   * Retrieves the binary representations of the subAny elements for this
3223   * substring filter.  This is not applicable for any other filter type.
3224   *
3225   * @return  The binary representations of the subAny elements for this
3226   *          substring filter, or an empty array if this is some other type of
3227   *          filter, or if it is a substring filter with no subFinal element.
3228   */
3229  @NotNull()
3230  public byte[][] getSubAnyBytes()
3231  {
3232    final byte[][] subAnyBytes = new byte[subAny.length][];
3233    for (int i=0; i < subAny.length; i++)
3234    {
3235      subAnyBytes[i] = subAny[i].getValue();
3236    }
3237
3238    return subAnyBytes;
3239  }
3240
3241
3242
3243  /**
3244   * Retrieves the raw subAny values for this substring filter.  This is not
3245   * applicable for any other filter type.
3246   *
3247   * @return  The raw subAny values for this substring filter, or an empty array
3248   *          if this is some other type of filter, or if it is a substring
3249   *          filter with no subFinal element.
3250   */
3251  @NotNull()
3252  public ASN1OctetString[] getRawSubAnyValues()
3253  {
3254    return subAny;
3255  }
3256
3257
3258
3259  /**
3260   * Retrieves the string representation of the subFinal element for this
3261   * substring filter.  This is not applicable for any other filter type.
3262   *
3263   * @return  The string representation of the subFinal element for this
3264   *          substring filter, or {@code null} if this is some other type of
3265   *          filter, or if it is a substring filter with no subFinal element.
3266   */
3267  @Nullable()
3268  public String getSubFinalString()
3269  {
3270    if (subFinal == null)
3271    {
3272      return null;
3273    }
3274    else
3275    {
3276      return subFinal.stringValue();
3277    }
3278  }
3279
3280
3281
3282  /**
3283   * Retrieves the binary representation of the subFinal element for this
3284   * substring filter.  This is not applicable for any other filter type.
3285   *
3286   * @return  The binary representation of the subFinal element for this
3287   *          substring filter, or {@code null} if this is some other type of
3288   *          filter, or if it is a substring filter with no subFinal element.
3289   */
3290  @Nullable()
3291  public byte[] getSubFinalBytes()
3292  {
3293    if (subFinal == null)
3294    {
3295      return null;
3296    }
3297    else
3298    {
3299      return subFinal.getValue();
3300    }
3301  }
3302
3303
3304
3305  /**
3306   * Retrieves the raw subFinal element for this filter as an ASN.1 octet
3307   * string.  This is not applicable for any other filter type.
3308   *
3309   * @return  The raw subFinal element for this filter as an ASN.1 octet
3310   *          string, or {@code null} if this is not a substring filter, or if
3311   *          it is a substring filter with no subFinal element.
3312   */
3313  @Nullable()
3314  public ASN1OctetString getRawSubFinalValue()
3315  {
3316    return subFinal;
3317  }
3318
3319
3320
3321  /**
3322   * Retrieves the matching rule ID for this extensible match filter.  This is
3323   * not applicable for any other filter type.
3324   *
3325   * @return  The matching rule ID for this extensible match filter, or
3326   *          {@code null} if this is some other type of filter, or if this
3327   *          extensible match filter does not have a matching rule ID.
3328   */
3329  @Nullable()
3330  public String getMatchingRuleID()
3331  {
3332    return matchingRuleID;
3333  }
3334
3335
3336
3337  /**
3338   * Retrieves the dnAttributes flag for this extensible match filter.  This is
3339   * not applicable for any other filter type.
3340   *
3341   * @return  The dnAttributes flag for this extensible match filter.
3342   */
3343  public boolean getDNAttributes()
3344  {
3345    return dnAttributes;
3346  }
3347
3348
3349
3350  /**
3351   * Indicates whether this filter matches the provided entry.  Note that this
3352   * is a best-guess effort and may not be completely accurate in all cases.
3353   * All matching will be performed using case-ignore string matching, which may
3354   * yield an unexpected result for values that should not be treated as simple
3355   * strings.  For example:
3356   * <UL>
3357   *   <LI>Two DN values which are logically equivalent may not be considered
3358   *       matches if they have different spacing.</LI>
3359   *   <LI>Ordering comparisons against numeric values may yield unexpected
3360   *       results (e.g., "2" will be considered greater than "10" because the
3361   *       character "2" has a larger ASCII value than the character "1").</LI>
3362   * </UL>
3363   * <BR>
3364   * In addition to the above constraints, it should be noted that neither
3365   * approximate matching nor extensible matching are currently supported.
3366   *
3367   * @param  entry  The entry for which to make the determination.  It must not
3368   *                be {@code null}.
3369   *
3370   * @return  {@code true} if this filter appears to match the provided entry,
3371   *          or {@code false} if not.
3372   *
3373   * @throws  LDAPException  If a problem occurs while trying to make the
3374   *                         determination.
3375   */
3376  public boolean matchesEntry(@NotNull final Entry entry)
3377         throws LDAPException
3378  {
3379    return matchesEntry(entry, entry.getSchema());
3380  }
3381
3382
3383
3384  /**
3385   * Indicates whether this filter matches the provided entry.  Note that this
3386   * is a best-guess effort and may not be completely accurate in all cases.
3387   * If provided, the given schema will be used in an attempt to determine the
3388   * appropriate matching rule for making the determinations, but some corner
3389   * cases may not be handled accurately.  Neither approximate matching nor
3390   * extensible matching are currently supported.
3391   *
3392   * @param  entry   The entry for which to make the determination.  It must not
3393   *                 be {@code null}.
3394   * @param  schema  The schema to use when making the determination.  If this
3395   *                 is {@code null}, then all matching will be performed using
3396   *                 a case-ignore matching rule.
3397   *
3398   * @return  {@code true} if this filter appears to match the provided entry,
3399   *          or {@code false} if not.
3400   *
3401   * @throws  LDAPException  If a problem occurs while trying to make the
3402   *                         determination.
3403   */
3404  public boolean matchesEntry(@NotNull final Entry entry,
3405                              @Nullable final Schema schema)
3406         throws LDAPException
3407  {
3408    Validator.ensureNotNull(entry);
3409
3410    switch (filterType)
3411    {
3412      case FILTER_TYPE_AND:
3413        for (final Filter f : filterComps)
3414        {
3415          if (! f.matchesEntry(entry, schema))
3416          {
3417            return false;
3418          }
3419        }
3420        return true;
3421
3422      case FILTER_TYPE_OR:
3423        for (final Filter f : filterComps)
3424        {
3425          if (f.matchesEntry(entry, schema))
3426          {
3427            return true;
3428          }
3429        }
3430        return false;
3431
3432      case FILTER_TYPE_NOT:
3433        return (! notComp.matchesEntry(entry, schema));
3434
3435      case FILTER_TYPE_EQUALITY:
3436        Attribute a = entry.getAttribute(attrName, schema);
3437        if (a == null)
3438        {
3439          return false;
3440        }
3441
3442        MatchingRule matchingRule =
3443             MatchingRule.selectEqualityMatchingRule(attrName, schema);
3444        return matchingRule.matchesAnyValue(assertionValue, a.getRawValues());
3445
3446      case FILTER_TYPE_SUBSTRING:
3447        a = entry.getAttribute(attrName, schema);
3448        if (a == null)
3449        {
3450          return false;
3451        }
3452
3453        matchingRule =
3454             MatchingRule.selectSubstringMatchingRule(attrName, schema);
3455        for (final ASN1OctetString v : a.getRawValues())
3456        {
3457          if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal))
3458          {
3459            return true;
3460          }
3461        }
3462        return false;
3463
3464      case FILTER_TYPE_GREATER_OR_EQUAL:
3465        a = entry.getAttribute(attrName, schema);
3466        if (a == null)
3467        {
3468          return false;
3469        }
3470
3471        matchingRule =
3472             MatchingRule.selectOrderingMatchingRule(attrName, schema);
3473        for (final ASN1OctetString v : a.getRawValues())
3474        {
3475          if (matchingRule.compareValues(v, assertionValue) >= 0)
3476          {
3477            return true;
3478          }
3479        }
3480        return false;
3481
3482      case FILTER_TYPE_LESS_OR_EQUAL:
3483        a = entry.getAttribute(attrName, schema);
3484        if (a == null)
3485        {
3486          return false;
3487        }
3488
3489        matchingRule =
3490             MatchingRule.selectOrderingMatchingRule(attrName, schema);
3491        for (final ASN1OctetString v : a.getRawValues())
3492        {
3493          if (matchingRule.compareValues(v, assertionValue) <= 0)
3494          {
3495            return true;
3496          }
3497        }
3498        return false;
3499
3500      case FILTER_TYPE_PRESENCE:
3501        return (entry.hasAttribute(attrName));
3502
3503      case FILTER_TYPE_APPROXIMATE_MATCH:
3504        throw new LDAPException(ResultCode.NOT_SUPPORTED,
3505             ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get());
3506
3507      case FILTER_TYPE_EXTENSIBLE_MATCH:
3508        return extensibleMatchFilterMatchesEntry(entry, schema);
3509
3510      default:
3511        throw new LDAPException(ResultCode.PARAM_ERROR,
3512                                ERR_FILTER_INVALID_TYPE.get());
3513    }
3514  }
3515
3516
3517
3518  /**
3519   * Indicates whether the provided extensible matching filter component matches
3520   * the provided entry.  This method provides very limited support for
3521   * extensible matching  It can only be used for filters that contain both an
3522   * attribute type and a matching rule ID, and when the matching rule ID is
3523   * one of the following:
3524   * <OL>
3525   *   <LI>jsonObjectFilterExtensibleMatch (or 1.3.6.1.4.1.30221.2.4.13)</LI>
3526   * </OL>
3527   *
3528   * @param  entry   The entry for which to make the determination.  It must not
3529   *                 be {@code null}.
3530   * @param  schema  The schema to use when making the determination.  If this
3531   *                 is {@code null}, then all matching will be performed using
3532   *                 a case-ignore matching rule.
3533   *
3534   * @return  {@code true} if this filter appears to match the provided entry,
3535   *          or {@code false} if not.
3536   *
3537   * @throws  LDAPException  If a problem occurs while trying to make the
3538   *                         determination.
3539   */
3540  private boolean extensibleMatchFilterMatchesEntry(@NotNull final Entry entry,
3541                       @Nullable final Schema schema)
3542          throws LDAPException
3543  {
3544    if ((attrName != null) && (matchingRuleID != null) && (! dnAttributes))
3545    {
3546      if (matchingRuleID.equalsIgnoreCase("jsonObjectFilterExtensibleMatch") ||
3547           matchingRuleID.equals("1.3.6.1.4.1.30221.2.4.13"))
3548      {
3549        final JSONObjectFilter jsonObjectFilter;
3550        try
3551        {
3552          final JSONObject jsonObject =
3553               new JSONObject(assertionValue.stringValue());
3554          jsonObjectFilter = JSONObjectFilter.decode(jsonObject);
3555        }
3556        catch (final Exception e)
3557        {
3558          Debug.debugException(e);
3559          throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING,
3560               ERR_FILTER_EXTENSIBLE_MATCH_MALFORMED_JSON_OBJECT_FILTER.get(
3561                    toString(), entry.getDN(),
3562                    StaticUtils.getExceptionMessage(e)),
3563               e);
3564        }
3565
3566        final Attribute attr = entry.getAttribute(attrName, schema);
3567        if (attr != null)
3568        {
3569          for (final ASN1OctetString v : attr.getRawValues())
3570          {
3571            try
3572            {
3573              final JSONObject jsonObject = new JSONObject(v.stringValue());
3574              if (jsonObjectFilter.matchesJSONObject(jsonObject))
3575              {
3576                return true;
3577              }
3578            }
3579            catch (final Exception e)
3580            {
3581              Debug.debugException(e);
3582            }
3583          }
3584        }
3585
3586        return false;
3587      }
3588    }
3589
3590    throw new LDAPException(ResultCode.NOT_SUPPORTED,
3591         ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get());
3592  }
3593
3594
3595
3596  /**
3597   * Attempts to simplify the provided filter to allow it to be more efficiently
3598   * processed by the server.  The simplifications it will make include:
3599   * <UL>
3600   *   <LI>Any AND or OR filter that contains only a single filter component
3601   *       will be converted to just that embedded filter component to eliminate
3602   *       the unnecessary AND or OR wrapper.  For example, the filter
3603   *       "(&amp;(uid=john.doe))" will be converted to just
3604   *       "(uid=john.doe)".</LI>
3605   *   <LI>Any AND components inside of an AND filter will be merged into the
3606   *       outer AND filter.  Any OR components inside of an OR filter will be
3607   *       merged into the outer OR filter.  For example, the filter
3608   *       "(&amp;(objectClass=person)(&amp;(givenName=John)(sn=Doe)))" will be
3609   *       converted to
3610   *       "(&amp;(objectClass=person)(givenName=John)(sn=Doe))".</LI>
3611   *   <LI>Any AND filter that contains an LDAP false filter will be converted
3612   *       to just an LDAP false filter.</LI>
3613   *   <LI>Any OR filter that contains an LDAP true filter will be converted
3614   *       to just an LDAP true filter.</LI>
3615   *   <LI>If {@code reOrderElements} is true, then this method will attempt to
3616   *       re-order the elements inside AND and OR filters in an attempt to
3617   *       ensure that the components which are likely to be the most efficient
3618   *       come earlier than those which are likely to be the least efficient.
3619   *       This can speed up processing in servers that process filter
3620   *       components in a left-to-right order.</LI>
3621   * </UL>
3622   * <BR><BR>
3623   * The simplification will happen recursively, in an attempt to generate a
3624   * filter that is as simple and efficient as possible.
3625   *
3626   * @param  filter           The filter to attempt to simplify.
3627   * @param  reOrderElements  Indicates whether this method may re-order the
3628   *                          elements in the filter so that, in a server that
3629   *                          evaluates the components in a left-to-right order,
3630   *                          the components which are likely to be more
3631   *                          efficient to process will be listed before those
3632   *                          which are likely to be less efficient.
3633   *
3634   * @return  The simplified filter, or the original filter if the provided
3635   *          filter is not one that can be simplified any further.
3636   */
3637  @NotNull()
3638  public static Filter simplifyFilter(@NotNull final Filter filter,
3639                                      final boolean reOrderElements)
3640  {
3641    final byte filterType = filter.filterType;
3642    switch (filterType)
3643    {
3644      case FILTER_TYPE_AND:
3645      case FILTER_TYPE_OR:
3646        // These will be handled below.
3647        break;
3648
3649      case FILTER_TYPE_NOT:
3650        // We may be able to simplify the filter component contained inside the
3651        // NOT.
3652        return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements));
3653
3654      default:
3655        // We can't simplify this filter, so just return what was provided.
3656        return filter;
3657    }
3658
3659
3660    // An AND filter with zero components is an LDAP true filter, and we can't
3661    // simplify that.  An OR filter with zero components is an LDAP false
3662    // filter, and we can't simplify that either.  The set of components
3663    // should never be null for an AND or OR filter, but if that happens to be
3664    // the case, then we'll return the original filter.
3665    final Filter[] components = filter.filterComps;
3666    if ((components == null) || (components.length == 0))
3667    {
3668      return filter;
3669    }
3670
3671
3672    // For either an AND or an OR filter with just a single component, then just
3673    // return that embedded component.  But simplify it first.
3674    if (components.length == 1)
3675    {
3676      return simplifyFilter(components[0], reOrderElements);
3677    }
3678
3679
3680    // If we've gotten here, then we have a filter with multiple components.
3681    // Simplify each of them to the extent possible, un-embed any ANDs
3682    // contained inside an AND or ORs contained inside an OR, and eliminate any
3683    // duplicate components in the resulting top-level filter.
3684    final LinkedHashSet<Filter> componentSet =
3685         new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
3686    for (final Filter f : components)
3687    {
3688      final Filter simplifiedFilter = simplifyFilter(f, reOrderElements);
3689      if (simplifiedFilter.filterType == FILTER_TYPE_AND)
3690      {
3691        if (filterType == FILTER_TYPE_AND)
3692        {
3693          // This is an AND nested inside an AND.  In that case, we'll just put
3694          // all the nested components inside the outer AND.
3695          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3696        }
3697        else
3698        {
3699          componentSet.add(simplifiedFilter);
3700        }
3701      }
3702      else if (simplifiedFilter.filterType == FILTER_TYPE_OR)
3703      {
3704        if (filterType == FILTER_TYPE_OR)
3705        {
3706          // This is an OR nested inside an OR.  In that case, we'll just put
3707          // all the nested components inside the outer OR.
3708          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3709        }
3710        else
3711        {
3712          componentSet.add(simplifiedFilter);
3713        }
3714      }
3715      else
3716      {
3717        componentSet.add(simplifiedFilter);
3718      }
3719    }
3720
3721
3722    // It's possible at this point that we are down to just a single component.
3723    // That can happen if the filter was an AND or an OR with a duplicate
3724    // element, like "(&(a=b)(a=b))".  In that case, just return that one
3725    // component.
3726    if (componentSet.size() == 1)
3727    {
3728      return componentSet.iterator().next();
3729    }
3730
3731
3732    // If we have an AND filter that contains an embedded LDAP false filter,
3733    // then just return the LDAP false filter.  If we have an OR filter that
3734    // contains an embedded LDAP true filter, then just return the LDAP true
3735    // filter.
3736    if (filterType == FILTER_TYPE_AND)
3737    {
3738      for (final Filter f : componentSet)
3739      {
3740        if ((f.filterType == FILTER_TYPE_OR) && (f.filterComps.length == 0))
3741        {
3742          return f;
3743        }
3744      }
3745    }
3746    else if (filterType == FILTER_TYPE_OR)
3747    {
3748      for (final Filter f : componentSet)
3749      {
3750        if ((f.filterType == FILTER_TYPE_AND) && (f.filterComps.length == 0))
3751        {
3752          return f;
3753        }
3754      }
3755    }
3756
3757
3758    // If we should re-order the components, then use the following priority
3759    // list:
3760    //
3761    // 1.  Equality components that target an attribute other than objectClass.
3762    //     These are most likely to require only a single database lookup to get
3763    //     the candidate list, and that candidate list will frequently be small.
3764    // 2.  Equality components that target the objectClass attribute.  These are
3765    //     likely to require only a single database lookup to get the candidate
3766    //     list, but the candidate list is more likely to be larger.
3767    // 3.  Approximate match components.  These are also likely to require only
3768    //     a single database lookup to get the candidate list, but that
3769    //     candidate list is likely to have a larger number of candidates.
3770    // 4.  Presence components that target an attribute other than objectClass.
3771    //     These are also likely to require only a single database lookup to get
3772    //     the candidate list, but are likely to have a large number of
3773    //     candidates.
3774    // 5.  Substring components that have a subInitial element.  These are
3775    //     generally the most efficient substring filters to process, requiring
3776    //     access to fewer database keys than substring filters with only subAny
3777    //     and/or subFinal components.
3778    // 6.  Substring components that only have subAny and/or subFinal elements.
3779    //     These will probably require a number of database lookups and will
3780    //     probably result in large candidate lists.
3781    // 7.  Greater-or-equal components and less-or-equal components.  These
3782    //     will probably require a number of database lookups and will probably
3783    //     result in large candidate lists.
3784    // 8.  Extensible match components.  Even if these are indexed, there isn't
3785    //     any good way to know how expensive they might be to process or how
3786    //     big the candidate list might be.
3787    // 9.  Presence components that target the objectClass attribute.  This is
3788    //     likely to require only a single database lookup to get the candidate
3789    //     list, but the candidate list will also be extremely large (if it's
3790    //     indexed at all) since it will match every entry.
3791    // 10. NOT components.  These are generally not possible to index and
3792    //     therefore cannot be used to create a candidate list.
3793    //
3794    // AND and OR components will be ordered according to the first of their
3795    // embedded components  Since the filter has already been simplified, then
3796    // the first element in the list will be the one we think will be the most
3797    // efficient to process.
3798    if (reOrderElements)
3799    {
3800      final TreeMap<Integer,LinkedHashSet<Filter>> m = new TreeMap<>();
3801      for (final Filter f : componentSet)
3802      {
3803        final Filter prioritizeComp;
3804        if ((f.filterType == FILTER_TYPE_AND) ||
3805            (f.filterType == FILTER_TYPE_OR))
3806        {
3807          if (f.filterComps.length > 0)
3808          {
3809            prioritizeComp = f.filterComps[0];
3810          }
3811          else
3812          {
3813            prioritizeComp = f;
3814          }
3815        }
3816        else
3817        {
3818          prioritizeComp = f;
3819        }
3820
3821        final Integer slot;
3822        switch (prioritizeComp.filterType)
3823        {
3824          case FILTER_TYPE_EQUALITY:
3825            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3826            {
3827              slot = 2;
3828            }
3829            else
3830            {
3831              slot = 1;
3832            }
3833            break;
3834
3835          case FILTER_TYPE_APPROXIMATE_MATCH:
3836            slot = 3;
3837            break;
3838
3839          case FILTER_TYPE_PRESENCE:
3840            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3841            {
3842              slot = 9;
3843            }
3844            else
3845            {
3846              slot = 4;
3847            }
3848            break;
3849
3850          case FILTER_TYPE_SUBSTRING:
3851            if (prioritizeComp.subInitial == null)
3852            {
3853              slot = 6;
3854            }
3855            else
3856            {
3857              slot = 5;
3858            }
3859            break;
3860
3861          case FILTER_TYPE_GREATER_OR_EQUAL:
3862          case FILTER_TYPE_LESS_OR_EQUAL:
3863            slot = 7;
3864            break;
3865
3866          case FILTER_TYPE_EXTENSIBLE_MATCH:
3867            slot = 8;
3868            break;
3869
3870          case FILTER_TYPE_NOT:
3871          default:
3872            slot = 10;
3873            break;
3874        }
3875
3876        LinkedHashSet<Filter> filterSet = m.get(slot-1);
3877        if (filterSet == null)
3878        {
3879          filterSet = new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
3880          m.put(slot-1, filterSet);
3881        }
3882        filterSet.add(f);
3883      }
3884
3885      componentSet.clear();
3886      for (final LinkedHashSet<Filter> filterSet : m.values())
3887      {
3888        componentSet.addAll(filterSet);
3889      }
3890    }
3891
3892
3893    // Return the new, possibly simplified filter.
3894    if (filterType == FILTER_TYPE_AND)
3895    {
3896      return createANDFilter(componentSet);
3897    }
3898    else
3899    {
3900      return createORFilter(componentSet);
3901    }
3902  }
3903
3904
3905
3906  /**
3907   * Generates a hash code for this search filter.
3908   *
3909   * @return  The generated hash code for this search filter.
3910   */
3911  @Override()
3912  public int hashCode()
3913  {
3914    final CaseIgnoreStringMatchingRule matchingRule =
3915         CaseIgnoreStringMatchingRule.getInstance();
3916    int hashCode = filterType;
3917
3918    switch (filterType)
3919    {
3920      case FILTER_TYPE_AND:
3921      case FILTER_TYPE_OR:
3922        for (final Filter f : filterComps)
3923        {
3924          hashCode += f.hashCode();
3925        }
3926        break;
3927
3928      case FILTER_TYPE_NOT:
3929        hashCode += notComp.hashCode();
3930        break;
3931
3932      case FILTER_TYPE_EQUALITY:
3933      case FILTER_TYPE_GREATER_OR_EQUAL:
3934      case FILTER_TYPE_LESS_OR_EQUAL:
3935      case FILTER_TYPE_APPROXIMATE_MATCH:
3936        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3937        hashCode += matchingRule.normalize(assertionValue).hashCode();
3938        break;
3939
3940      case FILTER_TYPE_SUBSTRING:
3941        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3942        if (subInitial != null)
3943        {
3944          hashCode += matchingRule.normalizeSubstring(subInitial,
3945                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode();
3946        }
3947        for (final ASN1OctetString s : subAny)
3948        {
3949          hashCode += matchingRule.normalizeSubstring(s,
3950                           MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode();
3951        }
3952        if (subFinal != null)
3953        {
3954          hashCode += matchingRule.normalizeSubstring(subFinal,
3955                           MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode();
3956        }
3957        break;
3958
3959      case FILTER_TYPE_PRESENCE:
3960        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3961        break;
3962
3963      case FILTER_TYPE_EXTENSIBLE_MATCH:
3964        if (attrName != null)
3965        {
3966          hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3967        }
3968
3969        if (matchingRuleID != null)
3970        {
3971          hashCode += StaticUtils.toLowerCase(matchingRuleID).hashCode();
3972        }
3973
3974        if (dnAttributes)
3975        {
3976          hashCode++;
3977        }
3978
3979        hashCode += matchingRule.normalize(assertionValue).hashCode();
3980        break;
3981    }
3982
3983    return hashCode;
3984  }
3985
3986
3987
3988  /**
3989   * Indicates whether the provided object is equal to this search filter.
3990   *
3991   * @param  o  The object for which to make the determination.
3992   *
3993   * @return  {@code true} if the provided object can be considered equal to
3994   *          this search filter, or {@code false} if not.
3995   */
3996  @Override()
3997  public boolean equals(@Nullable final Object o)
3998  {
3999    if (o == null)
4000    {
4001      return false;
4002    }
4003
4004    if (o == this)
4005    {
4006      return true;
4007    }
4008
4009    if (! (o instanceof Filter))
4010    {
4011      return false;
4012    }
4013
4014    final Filter f = (Filter) o;
4015    if (filterType != f.filterType)
4016    {
4017      return false;
4018    }
4019
4020    final CaseIgnoreStringMatchingRule matchingRule =
4021         CaseIgnoreStringMatchingRule.getInstance();
4022
4023    switch (filterType)
4024    {
4025      case FILTER_TYPE_AND:
4026      case FILTER_TYPE_OR:
4027        if (filterComps.length != f.filterComps.length)
4028        {
4029          return false;
4030        }
4031
4032        final HashSet<Filter> compSet =
4033             new HashSet<>(StaticUtils.computeMapCapacity(10));
4034        compSet.addAll(Arrays.asList(filterComps));
4035
4036        for (final Filter filterComp : f.filterComps)
4037        {
4038          if (! compSet.remove(filterComp))
4039          {
4040            return false;
4041          }
4042        }
4043
4044        return true;
4045
4046
4047    case FILTER_TYPE_NOT:
4048      return notComp.equals(f.notComp);
4049
4050
4051      case FILTER_TYPE_EQUALITY:
4052      case FILTER_TYPE_GREATER_OR_EQUAL:
4053      case FILTER_TYPE_LESS_OR_EQUAL:
4054      case FILTER_TYPE_APPROXIMATE_MATCH:
4055        return (attrName.equalsIgnoreCase(f.attrName) &&
4056                matchingRule.valuesMatch(assertionValue, f.assertionValue));
4057
4058
4059      case FILTER_TYPE_SUBSTRING:
4060        if (! attrName.equalsIgnoreCase(f.attrName))
4061        {
4062          return false;
4063        }
4064
4065        if (subAny.length != f.subAny.length)
4066        {
4067          return false;
4068        }
4069
4070        if (subInitial == null)
4071        {
4072          if (f.subInitial != null)
4073          {
4074            return false;
4075          }
4076        }
4077        else
4078        {
4079          if (f.subInitial == null)
4080          {
4081            return false;
4082          }
4083
4084          final ASN1OctetString si1 = matchingRule.normalizeSubstring(
4085               subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
4086          final ASN1OctetString si2 = matchingRule.normalizeSubstring(
4087               f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
4088          if (! si1.equals(si2))
4089          {
4090            return false;
4091          }
4092        }
4093
4094        for (int i=0; i < subAny.length; i++)
4095        {
4096          final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i],
4097               MatchingRule.SUBSTRING_TYPE_SUBANY);
4098          final ASN1OctetString sa2 = matchingRule.normalizeSubstring(
4099               f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY);
4100          if (! sa1.equals(sa2))
4101          {
4102            return false;
4103          }
4104        }
4105
4106        if (subFinal == null)
4107        {
4108          if (f.subFinal != null)
4109          {
4110            return false;
4111          }
4112        }
4113        else
4114        {
4115          if (f.subFinal == null)
4116          {
4117            return false;
4118          }
4119
4120          final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal,
4121               MatchingRule.SUBSTRING_TYPE_SUBFINAL);
4122          final ASN1OctetString sf2 = matchingRule.normalizeSubstring(
4123               f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL);
4124          if (! sf1.equals(sf2))
4125          {
4126            return false;
4127          }
4128        }
4129
4130        return true;
4131
4132
4133      case FILTER_TYPE_PRESENCE:
4134        return (attrName.equalsIgnoreCase(f.attrName));
4135
4136
4137      case FILTER_TYPE_EXTENSIBLE_MATCH:
4138        if (attrName == null)
4139        {
4140          if (f.attrName != null)
4141          {
4142            return false;
4143          }
4144        }
4145        else
4146        {
4147          if (f.attrName == null)
4148          {
4149            return false;
4150          }
4151          else
4152          {
4153            if (! attrName.equalsIgnoreCase(f.attrName))
4154            {
4155              return false;
4156            }
4157          }
4158        }
4159
4160        if (matchingRuleID == null)
4161        {
4162          if (f.matchingRuleID != null)
4163          {
4164            return false;
4165          }
4166        }
4167        else
4168        {
4169          if (f.matchingRuleID == null)
4170          {
4171            return false;
4172          }
4173          else
4174          {
4175            if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID))
4176            {
4177              return false;
4178            }
4179          }
4180        }
4181
4182        if (dnAttributes != f.dnAttributes)
4183        {
4184          return false;
4185        }
4186
4187        return matchingRule.valuesMatch(assertionValue, f.assertionValue);
4188
4189
4190      default:
4191        return false;
4192    }
4193  }
4194
4195
4196
4197  /**
4198   * Retrieves a string representation of this search filter.
4199   *
4200   * @return  A string representation of this search filter.
4201   */
4202  @Override()
4203  @NotNull()
4204  public String toString()
4205  {
4206    if (filterString == null)
4207    {
4208      final StringBuilder buffer = new StringBuilder();
4209      toString(buffer);
4210      filterString = buffer.toString();
4211    }
4212
4213    return filterString;
4214  }
4215
4216
4217
4218  /**
4219   * Appends a string representation of this search filter to the provided
4220   * buffer.
4221   *
4222   * @param  buffer  The buffer to which to append a string representation of
4223   *                 this search filter.
4224   */
4225  public void toString(@NotNull final StringBuilder buffer)
4226  {
4227    switch (filterType)
4228    {
4229      case FILTER_TYPE_AND:
4230        buffer.append("(&");
4231        for (final Filter f : filterComps)
4232        {
4233          f.toString(buffer);
4234        }
4235        buffer.append(')');
4236        break;
4237
4238      case FILTER_TYPE_OR:
4239        buffer.append("(|");
4240        for (final Filter f : filterComps)
4241        {
4242          f.toString(buffer);
4243        }
4244        buffer.append(')');
4245        break;
4246
4247      case FILTER_TYPE_NOT:
4248        buffer.append("(!");
4249        notComp.toString(buffer);
4250        buffer.append(')');
4251        break;
4252
4253      case FILTER_TYPE_EQUALITY:
4254        buffer.append('(');
4255        buffer.append(attrName);
4256        buffer.append('=');
4257        encodeValue(assertionValue, buffer);
4258        buffer.append(')');
4259        break;
4260
4261      case FILTER_TYPE_SUBSTRING:
4262        buffer.append('(');
4263        buffer.append(attrName);
4264        buffer.append('=');
4265        if (subInitial != null)
4266        {
4267          encodeValue(subInitial, buffer);
4268        }
4269        buffer.append('*');
4270        for (final ASN1OctetString s : subAny)
4271        {
4272          encodeValue(s, buffer);
4273          buffer.append('*');
4274        }
4275        if (subFinal != null)
4276        {
4277          encodeValue(subFinal, buffer);
4278        }
4279        buffer.append(')');
4280        break;
4281
4282      case FILTER_TYPE_GREATER_OR_EQUAL:
4283        buffer.append('(');
4284        buffer.append(attrName);
4285        buffer.append(">=");
4286        encodeValue(assertionValue, buffer);
4287        buffer.append(')');
4288        break;
4289
4290      case FILTER_TYPE_LESS_OR_EQUAL:
4291        buffer.append('(');
4292        buffer.append(attrName);
4293        buffer.append("<=");
4294        encodeValue(assertionValue, buffer);
4295        buffer.append(')');
4296        break;
4297
4298      case FILTER_TYPE_PRESENCE:
4299        buffer.append('(');
4300        buffer.append(attrName);
4301        buffer.append("=*)");
4302        break;
4303
4304      case FILTER_TYPE_APPROXIMATE_MATCH:
4305        buffer.append('(');
4306        buffer.append(attrName);
4307        buffer.append("~=");
4308        encodeValue(assertionValue, buffer);
4309        buffer.append(')');
4310        break;
4311
4312      case FILTER_TYPE_EXTENSIBLE_MATCH:
4313        buffer.append('(');
4314        if (attrName != null)
4315        {
4316          buffer.append(attrName);
4317        }
4318
4319        if (dnAttributes)
4320        {
4321          buffer.append(":dn");
4322        }
4323
4324        if (matchingRuleID != null)
4325        {
4326          buffer.append(':');
4327          buffer.append(matchingRuleID);
4328        }
4329
4330        buffer.append(":=");
4331        encodeValue(assertionValue, buffer);
4332        buffer.append(')');
4333        break;
4334    }
4335  }
4336
4337
4338
4339  /**
4340   * Retrieves a normalized string representation of this search filter.
4341   *
4342   * @return  A normalized string representation of this search filter.
4343   */
4344  @NotNull()
4345  public String toNormalizedString()
4346  {
4347    if (normalizedString == null)
4348    {
4349      final StringBuilder buffer = new StringBuilder();
4350      toNormalizedString(buffer);
4351      normalizedString = buffer.toString();
4352    }
4353
4354    return normalizedString;
4355  }
4356
4357
4358
4359  /**
4360   * Appends a normalized string representation of this search filter to the
4361   * provided buffer.
4362   *
4363   * @param  buffer  The buffer to which to append a normalized string
4364   *                 representation of this search filter.
4365   */
4366  public void toNormalizedString(@NotNull final StringBuilder buffer)
4367  {
4368    final CaseIgnoreStringMatchingRule mr =
4369         CaseIgnoreStringMatchingRule.getInstance();
4370
4371    switch (filterType)
4372    {
4373      case FILTER_TYPE_AND:
4374        buffer.append("(&");
4375        for (final Filter f : filterComps)
4376        {
4377          f.toNormalizedString(buffer);
4378        }
4379        buffer.append(')');
4380        break;
4381
4382      case FILTER_TYPE_OR:
4383        buffer.append("(|");
4384        for (final Filter f : filterComps)
4385        {
4386          f.toNormalizedString(buffer);
4387        }
4388        buffer.append(')');
4389        break;
4390
4391      case FILTER_TYPE_NOT:
4392        buffer.append("(!");
4393        notComp.toNormalizedString(buffer);
4394        buffer.append(')');
4395        break;
4396
4397      case FILTER_TYPE_EQUALITY:
4398        buffer.append('(');
4399        buffer.append(StaticUtils.toLowerCase(attrName));
4400        buffer.append('=');
4401        encodeValue(mr.normalize(assertionValue), buffer);
4402        buffer.append(')');
4403        break;
4404
4405      case FILTER_TYPE_SUBSTRING:
4406        buffer.append('(');
4407        buffer.append(StaticUtils.toLowerCase(attrName));
4408        buffer.append('=');
4409        if (subInitial != null)
4410        {
4411          encodeValue(mr.normalizeSubstring(subInitial,
4412                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer);
4413        }
4414        buffer.append('*');
4415        for (final ASN1OctetString s : subAny)
4416        {
4417          encodeValue(mr.normalizeSubstring(s,
4418                           MatchingRule.SUBSTRING_TYPE_SUBANY), buffer);
4419          buffer.append('*');
4420        }
4421        if (subFinal != null)
4422        {
4423          encodeValue(mr.normalizeSubstring(subFinal,
4424                           MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer);
4425        }
4426        buffer.append(')');
4427        break;
4428
4429      case FILTER_TYPE_GREATER_OR_EQUAL:
4430        buffer.append('(');
4431        buffer.append(StaticUtils.toLowerCase(attrName));
4432        buffer.append(">=");
4433        encodeValue(mr.normalize(assertionValue), buffer);
4434        buffer.append(')');
4435        break;
4436
4437      case FILTER_TYPE_LESS_OR_EQUAL:
4438        buffer.append('(');
4439        buffer.append(StaticUtils.toLowerCase(attrName));
4440        buffer.append("<=");
4441        encodeValue(mr.normalize(assertionValue), buffer);
4442        buffer.append(')');
4443        break;
4444
4445      case FILTER_TYPE_PRESENCE:
4446        buffer.append('(');
4447        buffer.append(StaticUtils.toLowerCase(attrName));
4448        buffer.append("=*)");
4449        break;
4450
4451      case FILTER_TYPE_APPROXIMATE_MATCH:
4452        buffer.append('(');
4453        buffer.append(StaticUtils.toLowerCase(attrName));
4454        buffer.append("~=");
4455        encodeValue(mr.normalize(assertionValue), buffer);
4456        buffer.append(')');
4457        break;
4458
4459      case FILTER_TYPE_EXTENSIBLE_MATCH:
4460        buffer.append('(');
4461        if (attrName != null)
4462        {
4463          buffer.append(StaticUtils.toLowerCase(attrName));
4464        }
4465
4466        if (dnAttributes)
4467        {
4468          buffer.append(":dn");
4469        }
4470
4471        if (matchingRuleID != null)
4472        {
4473          buffer.append(':');
4474          buffer.append(StaticUtils.toLowerCase(matchingRuleID));
4475        }
4476
4477        buffer.append(":=");
4478        encodeValue(mr.normalize(assertionValue), buffer);
4479        buffer.append(')');
4480        break;
4481    }
4482  }
4483
4484
4485
4486  /**
4487   * Encodes the provided value into a form suitable for use as the assertion
4488   * value in the string representation of a search filter.  Parentheses,
4489   * asterisks, backslashes, null characters, and any non-ASCII characters will
4490   * be escaped using a backslash before the hexadecimal representation of each
4491   * byte in the character to escape.
4492   *
4493   * @param  value  The value to be encoded.  It must not be {@code null}.
4494   *
4495   * @return  The encoded representation of the provided string.
4496   */
4497  @NotNull()
4498  public static String encodeValue(@NotNull final String value)
4499  {
4500    Validator.ensureNotNull(value);
4501
4502    final StringBuilder buffer = new StringBuilder();
4503    encodeValue(new ASN1OctetString(value), buffer);
4504    return buffer.toString();
4505  }
4506
4507
4508
4509  /**
4510   * Encodes the provided value into a form suitable for use as the assertion
4511   * value in the string representation of a search filter.  Parentheses,
4512   * asterisks, backslashes, null characters, and any non-ASCII characters will
4513   * be escaped using a backslash before the hexadecimal representation of each
4514   * byte in the character to escape.
4515   *
4516   * @param  value  The value to be encoded.  It must not be {@code null}.
4517   *
4518   * @return  The encoded representation of the provided string.
4519   */
4520  @NotNull()
4521  public static String encodeValue(@NotNull final byte[]value)
4522  {
4523    Validator.ensureNotNull(value);
4524
4525    final StringBuilder buffer = new StringBuilder();
4526    encodeValue(new ASN1OctetString(value), buffer);
4527    return buffer.toString();
4528  }
4529
4530
4531
4532  /**
4533   * Appends the assertion value for this filter to the provided buffer,
4534   * encoding any special characters as necessary.
4535   *
4536   * @param  value   The value to be encoded.
4537   * @param  buffer  The buffer to which the assertion value should be appended.
4538   */
4539  public static void encodeValue(@NotNull final ASN1OctetString value,
4540                                 @NotNull final StringBuilder buffer)
4541  {
4542    final byte[] valueBytes = value.getValue();
4543    for (int i=0; i < valueBytes.length; i++)
4544    {
4545      switch (StaticUtils.numBytesInUTF8CharacterWithFirstByte(valueBytes[i]))
4546      {
4547        case 1:
4548          // This character is ASCII, but might still need to be escaped.
4549          if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters.
4550              (valueBytes[i] == 0x28) || // Open parenthesis
4551              (valueBytes[i] == 0x29) || // Close parenthesis
4552              (valueBytes[i] == 0x2A) || // Asterisk
4553              (valueBytes[i] == 0x5C) || // Backslash
4554              (valueBytes[i] == 0x7F))   // DEL
4555          {
4556            buffer.append('\\');
4557            StaticUtils.toHex(valueBytes[i], buffer);
4558          }
4559          else
4560          {
4561            buffer.append((char) valueBytes[i]);
4562          }
4563          break;
4564
4565        case 2:
4566          // If there are at least two bytes left, then we'll hex-encode the
4567          // next two bytes.  Otherwise we'll hex-encode whatever is left.
4568          buffer.append('\\');
4569          StaticUtils.toHex(valueBytes[i++], buffer);
4570          if (i < valueBytes.length)
4571          {
4572            buffer.append('\\');
4573            StaticUtils.toHex(valueBytes[i], buffer);
4574          }
4575          break;
4576
4577        case 3:
4578          // If there are at least three bytes left, then we'll hex-encode the
4579          // next three bytes.  Otherwise we'll hex-encode whatever is left.
4580          buffer.append('\\');
4581          StaticUtils.toHex(valueBytes[i++], buffer);
4582          if (i < valueBytes.length)
4583          {
4584            buffer.append('\\');
4585            StaticUtils.toHex(valueBytes[i++], buffer);
4586          }
4587          if (i < valueBytes.length)
4588          {
4589            buffer.append('\\');
4590            StaticUtils.toHex(valueBytes[i], buffer);
4591          }
4592          break;
4593
4594        case 4:
4595          // If there are at least four bytes left, then we'll hex-encode the
4596          // next four bytes.  Otherwise we'll hex-encode whatever is left.
4597          buffer.append('\\');
4598          StaticUtils.toHex(valueBytes[i++], buffer);
4599          if (i < valueBytes.length)
4600          {
4601            buffer.append('\\');
4602            StaticUtils.toHex(valueBytes[i++], buffer);
4603          }
4604          if (i < valueBytes.length)
4605          {
4606            buffer.append('\\');
4607            StaticUtils.toHex(valueBytes[i++], buffer);
4608          }
4609          if (i < valueBytes.length)
4610          {
4611            buffer.append('\\');
4612            StaticUtils.toHex(valueBytes[i], buffer);
4613          }
4614          break;
4615
4616        default:
4617          // We'll hex-encode whatever is left in the buffer.
4618          while (i < valueBytes.length)
4619          {
4620            buffer.append('\\');
4621            StaticUtils.toHex(valueBytes[i++], buffer);
4622          }
4623          break;
4624      }
4625    }
4626  }
4627
4628
4629
4630  /**
4631   * Appends a number of lines comprising the Java source code that can be used
4632   * to recreate this filter to the given list.  Note that unless a first line
4633   * prefix and/or last line suffix are provided, this will just include the
4634   * code for the static method used to create the filter, starting with
4635   * "Filter.createXFilter(" and ending with the closing parenthesis for that
4636   * method call.
4637   *
4638   * @param  lineList         The list to which the source code lines should be
4639   *                          added.
4640   * @param  indentSpaces     The number of spaces that should be used to indent
4641   *                          the generated code.  It must not be negative.
4642   * @param  firstLinePrefix  An optional string that should precede the static
4643   *                          method call (e.g., it could be used for an
4644   *                          attribute assignment, like "Filter f = ").  It may
4645   *                          be {@code null} or empty if there should be no
4646   *                          first line prefix.
4647   * @param  lastLineSuffix   An optional suffix that should follow the closing
4648   *                          parenthesis of the static method call (e.g., it
4649   *                          could be a semicolon to represent the end of a
4650   *                          Java statement).  It may be {@code null} or empty
4651   *                          if there should be no last line suffix.
4652   */
4653  public void toCode(@NotNull final List<String> lineList,
4654                     final int indentSpaces,
4655                     @Nullable final String firstLinePrefix,
4656                     @Nullable final String lastLineSuffix)
4657  {
4658    // Generate a string with the appropriate indent.
4659    final StringBuilder buffer = new StringBuilder();
4660    for (int i = 0; i < indentSpaces; i++)
4661    {
4662      buffer.append(' ');
4663    }
4664    final String indent = buffer.toString();
4665
4666
4667    // Start the first line, including any appropriate prefix.
4668    buffer.setLength(0);
4669    buffer.append(indent);
4670    if (firstLinePrefix != null)
4671    {
4672      buffer.append(firstLinePrefix);
4673    }
4674
4675
4676    // Figure out what type of filter it is and create the appropriate code for
4677    // that type of filter.
4678    switch (filterType)
4679    {
4680      case FILTER_TYPE_AND:
4681      case FILTER_TYPE_OR:
4682        if (filterType == FILTER_TYPE_AND)
4683        {
4684          buffer.append("Filter.createANDFilter(");
4685        }
4686        else
4687        {
4688          buffer.append("Filter.createORFilter(");
4689        }
4690        if (filterComps.length == 0)
4691        {
4692          buffer.append(')');
4693          if (lastLineSuffix != null)
4694          {
4695            buffer.append(lastLineSuffix);
4696          }
4697          lineList.add(buffer.toString());
4698          return;
4699        }
4700
4701        for (int i = 0; i < filterComps.length; i++)
4702        {
4703          String suffix;
4704          if (i == (filterComps.length - 1))
4705          {
4706            suffix = ")";
4707            if (lastLineSuffix != null)
4708            {
4709              suffix += lastLineSuffix;
4710            }
4711          }
4712          else
4713          {
4714            suffix = ",";
4715          }
4716
4717          filterComps[i].toCode(lineList, indentSpaces + 5, null, suffix);
4718        }
4719        return;
4720
4721
4722      case FILTER_TYPE_NOT:
4723        buffer.append("Filter.createNOTFilter(");
4724        lineList.add(buffer.toString());
4725
4726        final String suffix;
4727        if (lastLineSuffix == null)
4728        {
4729          suffix = ")";
4730        }
4731        else
4732        {
4733          suffix = ')' + lastLineSuffix;
4734        }
4735        notComp.toCode(lineList, indentSpaces + 5, null, suffix);
4736        return;
4737
4738      case FILTER_TYPE_PRESENCE:
4739        buffer.append("Filter.createPresenceFilter(");
4740        lineList.add(buffer.toString());
4741
4742        buffer.setLength(0);
4743        buffer.append(indent);
4744        buffer.append("     \"");
4745        buffer.append(attrName);
4746        buffer.append("\")");
4747
4748        if (lastLineSuffix != null)
4749        {
4750          buffer.append(lastLineSuffix);
4751        }
4752
4753        lineList.add(buffer.toString());
4754        return;
4755
4756
4757      case FILTER_TYPE_EQUALITY:
4758      case FILTER_TYPE_GREATER_OR_EQUAL:
4759      case FILTER_TYPE_LESS_OR_EQUAL:
4760      case FILTER_TYPE_APPROXIMATE_MATCH:
4761        if (filterType == FILTER_TYPE_EQUALITY)
4762        {
4763          buffer.append("Filter.createEqualityFilter(");
4764        }
4765        else if (filterType == FILTER_TYPE_GREATER_OR_EQUAL)
4766        {
4767          buffer.append("Filter.createGreaterOrEqualFilter(");
4768        }
4769        else if (filterType == FILTER_TYPE_LESS_OR_EQUAL)
4770        {
4771          buffer.append("Filter.createLessOrEqualFilter(");
4772        }
4773        else
4774        {
4775          buffer.append("Filter.createApproximateMatchFilter(");
4776        }
4777        lineList.add(buffer.toString());
4778
4779        buffer.setLength(0);
4780        buffer.append(indent);
4781        buffer.append("     \"");
4782        buffer.append(attrName);
4783        buffer.append("\",");
4784        lineList.add(buffer.toString());
4785
4786        buffer.setLength(0);
4787        buffer.append(indent);
4788        buffer.append("     ");
4789        if (StaticUtils.isSensitiveToCodeAttribute(attrName))
4790        {
4791          buffer.append("\"---redacted-value---\"");
4792        }
4793        else if (StaticUtils.isPrintableString(assertionValue.getValue()))
4794        {
4795          buffer.append('"');
4796          buffer.append(assertionValue.stringValue());
4797          buffer.append('"');
4798        }
4799        else
4800        {
4801          StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
4802        }
4803
4804        buffer.append(')');
4805
4806        if (lastLineSuffix != null)
4807        {
4808          buffer.append(lastLineSuffix);
4809        }
4810
4811        lineList.add(buffer.toString());
4812        return;
4813
4814
4815      case FILTER_TYPE_SUBSTRING:
4816        buffer.append("Filter.createSubstringFilter(");
4817        lineList.add(buffer.toString());
4818
4819        buffer.setLength(0);
4820        buffer.append(indent);
4821        buffer.append("     \"");
4822        buffer.append(attrName);
4823        buffer.append("\",");
4824        lineList.add(buffer.toString());
4825
4826        final boolean isRedacted =
4827             StaticUtils.isSensitiveToCodeAttribute(attrName);
4828        boolean isPrintable = true;
4829        if (subInitial != null)
4830        {
4831          isPrintable = StaticUtils.isPrintableString(subInitial.getValue());
4832        }
4833
4834        if (isPrintable && (subAny != null))
4835        {
4836          for (final ASN1OctetString s : subAny)
4837          {
4838            if (! StaticUtils.isPrintableString(s.getValue()))
4839            {
4840              isPrintable = false;
4841              break;
4842            }
4843          }
4844        }
4845
4846        if (isPrintable && (subFinal != null))
4847        {
4848          isPrintable = StaticUtils.isPrintableString(subFinal.getValue());
4849        }
4850
4851        buffer.setLength(0);
4852        buffer.append(indent);
4853        buffer.append("     ");
4854        if (subInitial == null)
4855        {
4856          buffer.append("null");
4857        }
4858        else if (isRedacted)
4859        {
4860          buffer.append("\"---redacted-subInitial---\"");
4861        }
4862        else if (isPrintable)
4863        {
4864          buffer.append('"');
4865          buffer.append(subInitial.stringValue());
4866          buffer.append('"');
4867        }
4868        else
4869        {
4870          StaticUtils.byteArrayToCode(subInitial.getValue(), buffer);
4871        }
4872        buffer.append(',');
4873        lineList.add(buffer.toString());
4874
4875        buffer.setLength(0);
4876        buffer.append(indent);
4877        buffer.append("     ");
4878        if ((subAny == null) || (subAny.length == 0))
4879        {
4880          buffer.append("null,");
4881          lineList.add(buffer.toString());
4882        }
4883        else if (isRedacted)
4884        {
4885          buffer.append("new String[]");
4886          lineList.add(buffer.toString());
4887
4888          lineList.add(indent + "     {");
4889
4890          for (int i=0; i < subAny.length; i++)
4891          {
4892            buffer.setLength(0);
4893            buffer.append(indent);
4894            buffer.append("       \"---redacted-subAny-");
4895            buffer.append(i+1);
4896            buffer.append("---\"");
4897            if (i < (subAny.length-1))
4898            {
4899              buffer.append(',');
4900            }
4901            lineList.add(buffer.toString());
4902          }
4903
4904          lineList.add(indent + "     },");
4905        }
4906        else if (isPrintable)
4907        {
4908          buffer.append("new String[]");
4909          lineList.add(buffer.toString());
4910
4911          lineList.add(indent + "     {");
4912
4913          for (int i=0; i < subAny.length; i++)
4914          {
4915            buffer.setLength(0);
4916            buffer.append(indent);
4917            buffer.append("       \"");
4918            buffer.append(subAny[i].stringValue());
4919            buffer.append('"');
4920            if (i < (subAny.length-1))
4921            {
4922              buffer.append(',');
4923            }
4924            lineList.add(buffer.toString());
4925          }
4926
4927          lineList.add(indent + "     },");
4928        }
4929        else
4930        {
4931          buffer.append("new String[]");
4932          lineList.add(buffer.toString());
4933
4934          lineList.add(indent + "     {");
4935
4936          for (int i=0; i < subAny.length; i++)
4937          {
4938            buffer.setLength(0);
4939            buffer.append(indent);
4940            buffer.append("       ");
4941            StaticUtils.byteArrayToCode(subAny[i].getValue(), buffer);
4942            if (i < (subAny.length-1))
4943            {
4944              buffer.append(',');
4945            }
4946            lineList.add(buffer.toString());
4947          }
4948
4949          lineList.add(indent + "     },");
4950        }
4951
4952        buffer.setLength(0);
4953        buffer.append(indent);
4954        buffer.append("     ");
4955        if (subFinal == null)
4956        {
4957          buffer.append("null)");
4958        }
4959        else if (isRedacted)
4960        {
4961          buffer.append("\"---redacted-subFinal---\")");
4962        }
4963        else if (isPrintable)
4964        {
4965          buffer.append('"');
4966          buffer.append(subFinal.stringValue());
4967          buffer.append("\")");
4968        }
4969        else
4970        {
4971          StaticUtils.byteArrayToCode(subFinal.getValue(), buffer);
4972          buffer.append(')');
4973        }
4974        if (lastLineSuffix != null)
4975        {
4976          buffer.append(lastLineSuffix);
4977        }
4978        lineList.add(buffer.toString());
4979        return;
4980
4981
4982      case FILTER_TYPE_EXTENSIBLE_MATCH:
4983        buffer.append("Filter.createExtensibleMatchFilter(");
4984        lineList.add(buffer.toString());
4985
4986        buffer.setLength(0);
4987        buffer.append(indent);
4988        buffer.append("     ");
4989        if (attrName == null)
4990        {
4991          buffer.append("null, // Attribute Description");
4992        }
4993        else
4994        {
4995          buffer.append('"');
4996          buffer.append(attrName);
4997          buffer.append("\",");
4998        }
4999        lineList.add(buffer.toString());
5000
5001        buffer.setLength(0);
5002        buffer.append(indent);
5003        buffer.append("     ");
5004        if (matchingRuleID == null)
5005        {
5006          buffer.append("null, // Matching Rule ID");
5007        }
5008        else
5009        {
5010          buffer.append('"');
5011          buffer.append(matchingRuleID);
5012          buffer.append("\",");
5013        }
5014        lineList.add(buffer.toString());
5015
5016        buffer.setLength(0);
5017        buffer.append(indent);
5018        buffer.append("     ");
5019        buffer.append(dnAttributes);
5020        buffer.append(", // DN Attributes");
5021        lineList.add(buffer.toString());
5022
5023        buffer.setLength(0);
5024        buffer.append(indent);
5025        buffer.append("     ");
5026        if ((attrName != null) &&
5027             StaticUtils.isSensitiveToCodeAttribute(attrName))
5028        {
5029          buffer.append("\"---redacted-value---\")");
5030        }
5031        else
5032        {
5033          if (StaticUtils.isPrintableString(assertionValue.getValue()))
5034          {
5035            buffer.append('"');
5036            buffer.append(assertionValue.stringValue());
5037            buffer.append("\")");
5038          }
5039          else
5040          {
5041            StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
5042            buffer.append(')');
5043          }
5044        }
5045
5046        if (lastLineSuffix != null)
5047        {
5048          buffer.append(lastLineSuffix);
5049        }
5050        lineList.add(buffer.toString());
5051        return;
5052    }
5053  }
5054}