001/*
002 * Copyright 2007-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2022 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-2022 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          try
3416          {
3417            if (! f.matchesEntry(entry, schema))
3418            {
3419              return false;
3420            }
3421          }
3422          catch (final Exception e)
3423          {
3424            Debug.debugException(e);
3425            return false;
3426          }
3427        }
3428        return true;
3429
3430      case FILTER_TYPE_OR:
3431        for (final Filter f : filterComps)
3432        {
3433          try
3434          {
3435            if (f.matchesEntry(entry, schema))
3436            {
3437              return true;
3438            }
3439          }
3440          catch (final Exception e)
3441          {
3442            Debug.debugException(e);
3443          }
3444        }
3445        return false;
3446
3447      case FILTER_TYPE_NOT:
3448        return (! notComp.matchesEntry(entry, schema));
3449
3450      case FILTER_TYPE_EQUALITY:
3451        Attribute a = entry.getAttribute(attrName, schema);
3452        if (a == null)
3453        {
3454          return false;
3455        }
3456
3457        MatchingRule matchingRule =
3458             MatchingRule.selectEqualityMatchingRule(attrName, schema);
3459        return matchingRule.matchesAnyValue(assertionValue, a.getRawValues());
3460
3461      case FILTER_TYPE_SUBSTRING:
3462        a = entry.getAttribute(attrName, schema);
3463        if (a == null)
3464        {
3465          return false;
3466        }
3467
3468        matchingRule =
3469             MatchingRule.selectSubstringMatchingRule(attrName, schema);
3470        for (final ASN1OctetString v : a.getRawValues())
3471        {
3472          if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal))
3473          {
3474            return true;
3475          }
3476        }
3477        return false;
3478
3479      case FILTER_TYPE_GREATER_OR_EQUAL:
3480        a = entry.getAttribute(attrName, schema);
3481        if (a == null)
3482        {
3483          return false;
3484        }
3485
3486        matchingRule =
3487             MatchingRule.selectOrderingMatchingRule(attrName, schema);
3488        for (final ASN1OctetString v : a.getRawValues())
3489        {
3490          if (matchingRule.compareValues(v, assertionValue) >= 0)
3491          {
3492            return true;
3493          }
3494        }
3495        return false;
3496
3497      case FILTER_TYPE_LESS_OR_EQUAL:
3498        a = entry.getAttribute(attrName, schema);
3499        if (a == null)
3500        {
3501          return false;
3502        }
3503
3504        matchingRule =
3505             MatchingRule.selectOrderingMatchingRule(attrName, schema);
3506        for (final ASN1OctetString v : a.getRawValues())
3507        {
3508          if (matchingRule.compareValues(v, assertionValue) <= 0)
3509          {
3510            return true;
3511          }
3512        }
3513        return false;
3514
3515      case FILTER_TYPE_PRESENCE:
3516        return (entry.hasAttribute(attrName));
3517
3518      case FILTER_TYPE_APPROXIMATE_MATCH:
3519        throw new LDAPException(ResultCode.NOT_SUPPORTED,
3520             ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get());
3521
3522      case FILTER_TYPE_EXTENSIBLE_MATCH:
3523        return extensibleMatchFilterMatchesEntry(entry, schema);
3524
3525      default:
3526        throw new LDAPException(ResultCode.PARAM_ERROR,
3527                                ERR_FILTER_INVALID_TYPE.get());
3528    }
3529  }
3530
3531
3532
3533  /**
3534   * Indicates whether the provided extensible matching filter component matches
3535   * the provided entry.  This method provides very limited support for
3536   * extensible matching  It can only be used for filters that contain both an
3537   * attribute type and a matching rule ID, and when the matching rule ID is
3538   * one of the following:
3539   * <OL>
3540   *   <LI>jsonObjectFilterExtensibleMatch (or 1.3.6.1.4.1.30221.2.4.13)</LI>
3541   * </OL>
3542   *
3543   * @param  entry   The entry for which to make the determination.  It must not
3544   *                 be {@code null}.
3545   * @param  schema  The schema to use when making the determination.  If this
3546   *                 is {@code null}, then all matching will be performed using
3547   *                 a case-ignore matching rule.
3548   *
3549   * @return  {@code true} if this filter appears to match the provided entry,
3550   *          or {@code false} if not.
3551   *
3552   * @throws  LDAPException  If a problem occurs while trying to make the
3553   *                         determination.
3554   */
3555  private boolean extensibleMatchFilterMatchesEntry(@NotNull final Entry entry,
3556                       @Nullable final Schema schema)
3557          throws LDAPException
3558  {
3559    if ((attrName != null) && (matchingRuleID != null) && (! dnAttributes))
3560    {
3561      if (matchingRuleID.equalsIgnoreCase("jsonObjectFilterExtensibleMatch") ||
3562           matchingRuleID.equals("1.3.6.1.4.1.30221.2.4.13"))
3563      {
3564        final JSONObjectFilter jsonObjectFilter;
3565        try
3566        {
3567          final JSONObject jsonObject =
3568               new JSONObject(assertionValue.stringValue());
3569          jsonObjectFilter = JSONObjectFilter.decode(jsonObject);
3570        }
3571        catch (final Exception e)
3572        {
3573          Debug.debugException(e);
3574          throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING,
3575               ERR_FILTER_EXTENSIBLE_MATCH_MALFORMED_JSON_OBJECT_FILTER.get(
3576                    toString(), entry.getDN(),
3577                    StaticUtils.getExceptionMessage(e)),
3578               e);
3579        }
3580
3581        final Attribute attr = entry.getAttribute(attrName, schema);
3582        if (attr != null)
3583        {
3584          for (final ASN1OctetString v : attr.getRawValues())
3585          {
3586            try
3587            {
3588              final JSONObject jsonObject = new JSONObject(v.stringValue());
3589              if (jsonObjectFilter.matchesJSONObject(jsonObject))
3590              {
3591                return true;
3592              }
3593            }
3594            catch (final Exception e)
3595            {
3596              Debug.debugException(e);
3597            }
3598          }
3599        }
3600
3601        return false;
3602      }
3603    }
3604
3605    throw new LDAPException(ResultCode.NOT_SUPPORTED,
3606         ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get());
3607  }
3608
3609
3610
3611  /**
3612   * Attempts to simplify the provided filter to allow it to be more efficiently
3613   * processed by the server.  The simplifications it will make include:
3614   * <UL>
3615   *   <LI>Any AND or OR filter that contains only a single filter component
3616   *       will be converted to just that embedded filter component to eliminate
3617   *       the unnecessary AND or OR wrapper.  For example, the filter
3618   *       "(&amp;(uid=john.doe))" will be converted to just
3619   *       "(uid=john.doe)".</LI>
3620   *   <LI>Any AND components inside of an AND filter will be merged into the
3621   *       outer AND filter.  Any OR components inside of an OR filter will be
3622   *       merged into the outer OR filter.  For example, the filter
3623   *       "(&amp;(objectClass=person)(&amp;(givenName=John)(sn=Doe)))" will be
3624   *       converted to
3625   *       "(&amp;(objectClass=person)(givenName=John)(sn=Doe))".</LI>
3626   *   <LI>Any AND filter that contains an LDAP false filter will be converted
3627   *       to just an LDAP false filter.</LI>
3628   *   <LI>Any OR filter that contains an LDAP true filter will be converted
3629   *       to just an LDAP true filter.</LI>
3630   *   <LI>If {@code reOrderElements} is true, then this method will attempt to
3631   *       re-order the elements inside AND and OR filters in an attempt to
3632   *       ensure that the components which are likely to be the most efficient
3633   *       come earlier than those which are likely to be the least efficient.
3634   *       This can speed up processing in servers that process filter
3635   *       components in a left-to-right order.</LI>
3636   * </UL>
3637   * <BR><BR>
3638   * The simplification will happen recursively, in an attempt to generate a
3639   * filter that is as simple and efficient as possible.
3640   *
3641   * @param  filter           The filter to attempt to simplify.
3642   * @param  reOrderElements  Indicates whether this method may re-order the
3643   *                          elements in the filter so that, in a server that
3644   *                          evaluates the components in a left-to-right order,
3645   *                          the components which are likely to be more
3646   *                          efficient to process will be listed before those
3647   *                          which are likely to be less efficient.
3648   *
3649   * @return  The simplified filter, or the original filter if the provided
3650   *          filter is not one that can be simplified any further.
3651   */
3652  @NotNull()
3653  public static Filter simplifyFilter(@NotNull final Filter filter,
3654                                      final boolean reOrderElements)
3655  {
3656    final byte filterType = filter.filterType;
3657    switch (filterType)
3658    {
3659      case FILTER_TYPE_AND:
3660      case FILTER_TYPE_OR:
3661        // These will be handled below.
3662        break;
3663
3664      case FILTER_TYPE_NOT:
3665        // We may be able to simplify the filter component contained inside the
3666        // NOT.
3667        return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements));
3668
3669      default:
3670        // We can't simplify this filter, so just return what was provided.
3671        return filter;
3672    }
3673
3674
3675    // An AND filter with zero components is an LDAP true filter, and we can't
3676    // simplify that.  An OR filter with zero components is an LDAP false
3677    // filter, and we can't simplify that either.  The set of components
3678    // should never be null for an AND or OR filter, but if that happens to be
3679    // the case, then we'll return the original filter.
3680    final Filter[] components = filter.filterComps;
3681    if ((components == null) || (components.length == 0))
3682    {
3683      return filter;
3684    }
3685
3686
3687    // For either an AND or an OR filter with just a single component, then just
3688    // return that embedded component.  But simplify it first.
3689    if (components.length == 1)
3690    {
3691      return simplifyFilter(components[0], reOrderElements);
3692    }
3693
3694
3695    // If we've gotten here, then we have a filter with multiple components.
3696    // Simplify each of them to the extent possible, un-embed any ANDs
3697    // contained inside an AND or ORs contained inside an OR, and eliminate any
3698    // duplicate components in the resulting top-level filter.
3699    final LinkedHashSet<Filter> componentSet =
3700         new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
3701    for (final Filter f : components)
3702    {
3703      final Filter simplifiedFilter = simplifyFilter(f, reOrderElements);
3704      if (simplifiedFilter.filterType == FILTER_TYPE_AND)
3705      {
3706        if (filterType == FILTER_TYPE_AND)
3707        {
3708          // This is an AND nested inside an AND.  In that case, we'll just put
3709          // all the nested components inside the outer AND.
3710          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3711        }
3712        else
3713        {
3714          componentSet.add(simplifiedFilter);
3715        }
3716      }
3717      else if (simplifiedFilter.filterType == FILTER_TYPE_OR)
3718      {
3719        if (filterType == FILTER_TYPE_OR)
3720        {
3721          // This is an OR nested inside an OR.  In that case, we'll just put
3722          // all the nested components inside the outer OR.
3723          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3724        }
3725        else
3726        {
3727          componentSet.add(simplifiedFilter);
3728        }
3729      }
3730      else
3731      {
3732        componentSet.add(simplifiedFilter);
3733      }
3734    }
3735
3736
3737    // It's possible at this point that we are down to just a single component.
3738    // That can happen if the filter was an AND or an OR with a duplicate
3739    // element, like "(&(a=b)(a=b))".  In that case, just return that one
3740    // component.
3741    if (componentSet.size() == 1)
3742    {
3743      return componentSet.iterator().next();
3744    }
3745
3746
3747    // If we have an AND filter that contains an embedded LDAP false filter,
3748    // then just return the LDAP false filter.  If we have an OR filter that
3749    // contains an embedded LDAP true filter, then just return the LDAP true
3750    // filter.
3751    if (filterType == FILTER_TYPE_AND)
3752    {
3753      for (final Filter f : componentSet)
3754      {
3755        if ((f.filterType == FILTER_TYPE_OR) && (f.filterComps.length == 0))
3756        {
3757          return f;
3758        }
3759      }
3760    }
3761    else if (filterType == FILTER_TYPE_OR)
3762    {
3763      for (final Filter f : componentSet)
3764      {
3765        if ((f.filterType == FILTER_TYPE_AND) && (f.filterComps.length == 0))
3766        {
3767          return f;
3768        }
3769      }
3770    }
3771
3772
3773    // If we should re-order the components, then use the following priority
3774    // list:
3775    //
3776    // 1.  Equality components that target an attribute other than objectClass.
3777    //     These are most likely to require only a single database lookup to get
3778    //     the candidate list, and that candidate list will frequently be small.
3779    // 2.  Equality components that target the objectClass attribute.  These are
3780    //     likely to require only a single database lookup to get the candidate
3781    //     list, but the candidate list is more likely to be larger.
3782    // 3.  Approximate match components.  These are also likely to require only
3783    //     a single database lookup to get the candidate list, but that
3784    //     candidate list is likely to have a larger number of candidates.
3785    // 4.  Presence components that target an attribute other than objectClass.
3786    //     These are also likely to require only a single database lookup to get
3787    //     the candidate list, but are likely to have a large number of
3788    //     candidates.
3789    // 5.  Substring components that have a subInitial element.  These are
3790    //     generally the most efficient substring filters to process, requiring
3791    //     access to fewer database keys than substring filters with only subAny
3792    //     and/or subFinal components.
3793    // 6.  Substring components that only have subAny and/or subFinal elements.
3794    //     These will probably require a number of database lookups and will
3795    //     probably result in large candidate lists.
3796    // 7.  Greater-or-equal components and less-or-equal components.  These
3797    //     will probably require a number of database lookups and will probably
3798    //     result in large candidate lists.
3799    // 8.  Extensible match components.  Even if these are indexed, there isn't
3800    //     any good way to know how expensive they might be to process or how
3801    //     big the candidate list might be.
3802    // 9.  Presence components that target the objectClass attribute.  This is
3803    //     likely to require only a single database lookup to get the candidate
3804    //     list, but the candidate list will also be extremely large (if it's
3805    //     indexed at all) since it will match every entry.
3806    // 10. NOT components.  These are generally not possible to index and
3807    //     therefore cannot be used to create a candidate list.
3808    //
3809    // AND and OR components will be ordered according to the first of their
3810    // embedded components  Since the filter has already been simplified, then
3811    // the first element in the list will be the one we think will be the most
3812    // efficient to process.
3813    if (reOrderElements)
3814    {
3815      final TreeMap<Integer,LinkedHashSet<Filter>> m = new TreeMap<>();
3816      for (final Filter f : componentSet)
3817      {
3818        final Filter prioritizeComp;
3819        if ((f.filterType == FILTER_TYPE_AND) ||
3820            (f.filterType == FILTER_TYPE_OR))
3821        {
3822          if (f.filterComps.length > 0)
3823          {
3824            prioritizeComp = f.filterComps[0];
3825          }
3826          else
3827          {
3828            prioritizeComp = f;
3829          }
3830        }
3831        else
3832        {
3833          prioritizeComp = f;
3834        }
3835
3836        final Integer slot;
3837        switch (prioritizeComp.filterType)
3838        {
3839          case FILTER_TYPE_EQUALITY:
3840            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3841            {
3842              slot = 2;
3843            }
3844            else
3845            {
3846              slot = 1;
3847            }
3848            break;
3849
3850          case FILTER_TYPE_APPROXIMATE_MATCH:
3851            slot = 3;
3852            break;
3853
3854          case FILTER_TYPE_PRESENCE:
3855            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3856            {
3857              slot = 9;
3858            }
3859            else
3860            {
3861              slot = 4;
3862            }
3863            break;
3864
3865          case FILTER_TYPE_SUBSTRING:
3866            if (prioritizeComp.subInitial == null)
3867            {
3868              slot = 6;
3869            }
3870            else
3871            {
3872              slot = 5;
3873            }
3874            break;
3875
3876          case FILTER_TYPE_GREATER_OR_EQUAL:
3877          case FILTER_TYPE_LESS_OR_EQUAL:
3878            slot = 7;
3879            break;
3880
3881          case FILTER_TYPE_EXTENSIBLE_MATCH:
3882            slot = 8;
3883            break;
3884
3885          case FILTER_TYPE_NOT:
3886          default:
3887            slot = 10;
3888            break;
3889        }
3890
3891        LinkedHashSet<Filter> filterSet = m.get(slot-1);
3892        if (filterSet == null)
3893        {
3894          filterSet = new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
3895          m.put(slot-1, filterSet);
3896        }
3897        filterSet.add(f);
3898      }
3899
3900      componentSet.clear();
3901      for (final LinkedHashSet<Filter> filterSet : m.values())
3902      {
3903        componentSet.addAll(filterSet);
3904      }
3905    }
3906
3907
3908    // Return the new, possibly simplified filter.
3909    if (filterType == FILTER_TYPE_AND)
3910    {
3911      return createANDFilter(componentSet);
3912    }
3913    else
3914    {
3915      return createORFilter(componentSet);
3916    }
3917  }
3918
3919
3920
3921  /**
3922   * Generates a hash code for this search filter.
3923   *
3924   * @return  The generated hash code for this search filter.
3925   */
3926  @Override()
3927  public int hashCode()
3928  {
3929    final CaseIgnoreStringMatchingRule matchingRule =
3930         CaseIgnoreStringMatchingRule.getInstance();
3931    int hashCode = filterType;
3932
3933    switch (filterType)
3934    {
3935      case FILTER_TYPE_AND:
3936      case FILTER_TYPE_OR:
3937        for (final Filter f : filterComps)
3938        {
3939          hashCode += f.hashCode();
3940        }
3941        break;
3942
3943      case FILTER_TYPE_NOT:
3944        hashCode += notComp.hashCode();
3945        break;
3946
3947      case FILTER_TYPE_EQUALITY:
3948      case FILTER_TYPE_GREATER_OR_EQUAL:
3949      case FILTER_TYPE_LESS_OR_EQUAL:
3950      case FILTER_TYPE_APPROXIMATE_MATCH:
3951        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3952        hashCode += matchingRule.normalize(assertionValue).hashCode();
3953        break;
3954
3955      case FILTER_TYPE_SUBSTRING:
3956        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3957        if (subInitial != null)
3958        {
3959          hashCode += matchingRule.normalizeSubstring(subInitial,
3960                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode();
3961        }
3962        for (final ASN1OctetString s : subAny)
3963        {
3964          hashCode += matchingRule.normalizeSubstring(s,
3965                           MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode();
3966        }
3967        if (subFinal != null)
3968        {
3969          hashCode += matchingRule.normalizeSubstring(subFinal,
3970                           MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode();
3971        }
3972        break;
3973
3974      case FILTER_TYPE_PRESENCE:
3975        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3976        break;
3977
3978      case FILTER_TYPE_EXTENSIBLE_MATCH:
3979        if (attrName != null)
3980        {
3981          hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3982        }
3983
3984        if (matchingRuleID != null)
3985        {
3986          hashCode += StaticUtils.toLowerCase(matchingRuleID).hashCode();
3987        }
3988
3989        if (dnAttributes)
3990        {
3991          hashCode++;
3992        }
3993
3994        hashCode += matchingRule.normalize(assertionValue).hashCode();
3995        break;
3996    }
3997
3998    return hashCode;
3999  }
4000
4001
4002
4003  /**
4004   * Indicates whether the provided object is equal to this search filter.
4005   *
4006   * @param  o  The object for which to make the determination.
4007   *
4008   * @return  {@code true} if the provided object can be considered equal to
4009   *          this search filter, or {@code false} if not.
4010   */
4011  @Override()
4012  public boolean equals(@Nullable final Object o)
4013  {
4014    if (o == null)
4015    {
4016      return false;
4017    }
4018
4019    if (o == this)
4020    {
4021      return true;
4022    }
4023
4024    if (! (o instanceof Filter))
4025    {
4026      return false;
4027    }
4028
4029    final Filter f = (Filter) o;
4030    if (filterType != f.filterType)
4031    {
4032      return false;
4033    }
4034
4035    final CaseIgnoreStringMatchingRule matchingRule =
4036         CaseIgnoreStringMatchingRule.getInstance();
4037
4038    switch (filterType)
4039    {
4040      case FILTER_TYPE_AND:
4041      case FILTER_TYPE_OR:
4042        if (filterComps.length != f.filterComps.length)
4043        {
4044          return false;
4045        }
4046
4047        final HashSet<Filter> compSet =
4048             new HashSet<>(StaticUtils.computeMapCapacity(10));
4049        compSet.addAll(Arrays.asList(filterComps));
4050
4051        for (final Filter filterComp : f.filterComps)
4052        {
4053          if (! compSet.remove(filterComp))
4054          {
4055            return false;
4056          }
4057        }
4058
4059        return true;
4060
4061
4062    case FILTER_TYPE_NOT:
4063      return notComp.equals(f.notComp);
4064
4065
4066      case FILTER_TYPE_EQUALITY:
4067      case FILTER_TYPE_GREATER_OR_EQUAL:
4068      case FILTER_TYPE_LESS_OR_EQUAL:
4069      case FILTER_TYPE_APPROXIMATE_MATCH:
4070        return (attrName.equalsIgnoreCase(f.attrName) &&
4071                matchingRule.valuesMatch(assertionValue, f.assertionValue));
4072
4073
4074      case FILTER_TYPE_SUBSTRING:
4075        if (! attrName.equalsIgnoreCase(f.attrName))
4076        {
4077          return false;
4078        }
4079
4080        if (subAny.length != f.subAny.length)
4081        {
4082          return false;
4083        }
4084
4085        if (subInitial == null)
4086        {
4087          if (f.subInitial != null)
4088          {
4089            return false;
4090          }
4091        }
4092        else
4093        {
4094          if (f.subInitial == null)
4095          {
4096            return false;
4097          }
4098
4099          final ASN1OctetString si1 = matchingRule.normalizeSubstring(
4100               subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
4101          final ASN1OctetString si2 = matchingRule.normalizeSubstring(
4102               f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
4103          if (! si1.equals(si2))
4104          {
4105            return false;
4106          }
4107        }
4108
4109        for (int i=0; i < subAny.length; i++)
4110        {
4111          final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i],
4112               MatchingRule.SUBSTRING_TYPE_SUBANY);
4113          final ASN1OctetString sa2 = matchingRule.normalizeSubstring(
4114               f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY);
4115          if (! sa1.equals(sa2))
4116          {
4117            return false;
4118          }
4119        }
4120
4121        if (subFinal == null)
4122        {
4123          if (f.subFinal != null)
4124          {
4125            return false;
4126          }
4127        }
4128        else
4129        {
4130          if (f.subFinal == null)
4131          {
4132            return false;
4133          }
4134
4135          final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal,
4136               MatchingRule.SUBSTRING_TYPE_SUBFINAL);
4137          final ASN1OctetString sf2 = matchingRule.normalizeSubstring(
4138               f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL);
4139          if (! sf1.equals(sf2))
4140          {
4141            return false;
4142          }
4143        }
4144
4145        return true;
4146
4147
4148      case FILTER_TYPE_PRESENCE:
4149        return (attrName.equalsIgnoreCase(f.attrName));
4150
4151
4152      case FILTER_TYPE_EXTENSIBLE_MATCH:
4153        if (attrName == null)
4154        {
4155          if (f.attrName != null)
4156          {
4157            return false;
4158          }
4159        }
4160        else
4161        {
4162          if (f.attrName == null)
4163          {
4164            return false;
4165          }
4166          else
4167          {
4168            if (! attrName.equalsIgnoreCase(f.attrName))
4169            {
4170              return false;
4171            }
4172          }
4173        }
4174
4175        if (matchingRuleID == null)
4176        {
4177          if (f.matchingRuleID != null)
4178          {
4179            return false;
4180          }
4181        }
4182        else
4183        {
4184          if (f.matchingRuleID == null)
4185          {
4186            return false;
4187          }
4188          else
4189          {
4190            if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID))
4191            {
4192              return false;
4193            }
4194          }
4195        }
4196
4197        if (dnAttributes != f.dnAttributes)
4198        {
4199          return false;
4200        }
4201
4202        return matchingRule.valuesMatch(assertionValue, f.assertionValue);
4203
4204
4205      default:
4206        return false;
4207    }
4208  }
4209
4210
4211
4212  /**
4213   * Retrieves a string representation of this search filter.
4214   *
4215   * @return  A string representation of this search filter.
4216   */
4217  @Override()
4218  @NotNull()
4219  public String toString()
4220  {
4221    if (filterString == null)
4222    {
4223      final StringBuilder buffer = new StringBuilder();
4224      toString(buffer);
4225      filterString = buffer.toString();
4226    }
4227
4228    return filterString;
4229  }
4230
4231
4232
4233  /**
4234   * Appends a string representation of this search filter to the provided
4235   * buffer.
4236   *
4237   * @param  buffer  The buffer to which to append a string representation of
4238   *                 this search filter.
4239   */
4240  public void toString(@NotNull final StringBuilder buffer)
4241  {
4242    switch (filterType)
4243    {
4244      case FILTER_TYPE_AND:
4245        buffer.append("(&");
4246        for (final Filter f : filterComps)
4247        {
4248          f.toString(buffer);
4249        }
4250        buffer.append(')');
4251        break;
4252
4253      case FILTER_TYPE_OR:
4254        buffer.append("(|");
4255        for (final Filter f : filterComps)
4256        {
4257          f.toString(buffer);
4258        }
4259        buffer.append(')');
4260        break;
4261
4262      case FILTER_TYPE_NOT:
4263        buffer.append("(!");
4264        notComp.toString(buffer);
4265        buffer.append(')');
4266        break;
4267
4268      case FILTER_TYPE_EQUALITY:
4269        buffer.append('(');
4270        buffer.append(attrName);
4271        buffer.append('=');
4272        encodeValue(assertionValue, buffer);
4273        buffer.append(')');
4274        break;
4275
4276      case FILTER_TYPE_SUBSTRING:
4277        buffer.append('(');
4278        buffer.append(attrName);
4279        buffer.append('=');
4280        if (subInitial != null)
4281        {
4282          encodeValue(subInitial, buffer);
4283        }
4284        buffer.append('*');
4285        for (final ASN1OctetString s : subAny)
4286        {
4287          encodeValue(s, buffer);
4288          buffer.append('*');
4289        }
4290        if (subFinal != null)
4291        {
4292          encodeValue(subFinal, buffer);
4293        }
4294        buffer.append(')');
4295        break;
4296
4297      case FILTER_TYPE_GREATER_OR_EQUAL:
4298        buffer.append('(');
4299        buffer.append(attrName);
4300        buffer.append(">=");
4301        encodeValue(assertionValue, buffer);
4302        buffer.append(')');
4303        break;
4304
4305      case FILTER_TYPE_LESS_OR_EQUAL:
4306        buffer.append('(');
4307        buffer.append(attrName);
4308        buffer.append("<=");
4309        encodeValue(assertionValue, buffer);
4310        buffer.append(')');
4311        break;
4312
4313      case FILTER_TYPE_PRESENCE:
4314        buffer.append('(');
4315        buffer.append(attrName);
4316        buffer.append("=*)");
4317        break;
4318
4319      case FILTER_TYPE_APPROXIMATE_MATCH:
4320        buffer.append('(');
4321        buffer.append(attrName);
4322        buffer.append("~=");
4323        encodeValue(assertionValue, buffer);
4324        buffer.append(')');
4325        break;
4326
4327      case FILTER_TYPE_EXTENSIBLE_MATCH:
4328        buffer.append('(');
4329        if (attrName != null)
4330        {
4331          buffer.append(attrName);
4332        }
4333
4334        if (dnAttributes)
4335        {
4336          buffer.append(":dn");
4337        }
4338
4339        if (matchingRuleID != null)
4340        {
4341          buffer.append(':');
4342          buffer.append(matchingRuleID);
4343        }
4344
4345        buffer.append(":=");
4346        encodeValue(assertionValue, buffer);
4347        buffer.append(')');
4348        break;
4349    }
4350  }
4351
4352
4353
4354  /**
4355   * Retrieves a normalized string representation of this search filter.
4356   *
4357   * @return  A normalized string representation of this search filter.
4358   */
4359  @NotNull()
4360  public String toNormalizedString()
4361  {
4362    if (normalizedString == null)
4363    {
4364      final StringBuilder buffer = new StringBuilder();
4365      toNormalizedString(buffer);
4366      normalizedString = buffer.toString();
4367    }
4368
4369    return normalizedString;
4370  }
4371
4372
4373
4374  /**
4375   * Appends a normalized string representation of this search filter to the
4376   * provided buffer.
4377   *
4378   * @param  buffer  The buffer to which to append a normalized string
4379   *                 representation of this search filter.
4380   */
4381  public void toNormalizedString(@NotNull final StringBuilder buffer)
4382  {
4383    final CaseIgnoreStringMatchingRule mr =
4384         CaseIgnoreStringMatchingRule.getInstance();
4385
4386    switch (filterType)
4387    {
4388      case FILTER_TYPE_AND:
4389        buffer.append("(&");
4390        for (final Filter f : filterComps)
4391        {
4392          f.toNormalizedString(buffer);
4393        }
4394        buffer.append(')');
4395        break;
4396
4397      case FILTER_TYPE_OR:
4398        buffer.append("(|");
4399        for (final Filter f : filterComps)
4400        {
4401          f.toNormalizedString(buffer);
4402        }
4403        buffer.append(')');
4404        break;
4405
4406      case FILTER_TYPE_NOT:
4407        buffer.append("(!");
4408        notComp.toNormalizedString(buffer);
4409        buffer.append(')');
4410        break;
4411
4412      case FILTER_TYPE_EQUALITY:
4413        buffer.append('(');
4414        buffer.append(StaticUtils.toLowerCase(attrName));
4415        buffer.append('=');
4416        encodeValue(mr.normalize(assertionValue), buffer);
4417        buffer.append(')');
4418        break;
4419
4420      case FILTER_TYPE_SUBSTRING:
4421        buffer.append('(');
4422        buffer.append(StaticUtils.toLowerCase(attrName));
4423        buffer.append('=');
4424        if (subInitial != null)
4425        {
4426          encodeValue(mr.normalizeSubstring(subInitial,
4427                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer);
4428        }
4429        buffer.append('*');
4430        for (final ASN1OctetString s : subAny)
4431        {
4432          encodeValue(mr.normalizeSubstring(s,
4433                           MatchingRule.SUBSTRING_TYPE_SUBANY), buffer);
4434          buffer.append('*');
4435        }
4436        if (subFinal != null)
4437        {
4438          encodeValue(mr.normalizeSubstring(subFinal,
4439                           MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer);
4440        }
4441        buffer.append(')');
4442        break;
4443
4444      case FILTER_TYPE_GREATER_OR_EQUAL:
4445        buffer.append('(');
4446        buffer.append(StaticUtils.toLowerCase(attrName));
4447        buffer.append(">=");
4448        encodeValue(mr.normalize(assertionValue), buffer);
4449        buffer.append(')');
4450        break;
4451
4452      case FILTER_TYPE_LESS_OR_EQUAL:
4453        buffer.append('(');
4454        buffer.append(StaticUtils.toLowerCase(attrName));
4455        buffer.append("<=");
4456        encodeValue(mr.normalize(assertionValue), buffer);
4457        buffer.append(')');
4458        break;
4459
4460      case FILTER_TYPE_PRESENCE:
4461        buffer.append('(');
4462        buffer.append(StaticUtils.toLowerCase(attrName));
4463        buffer.append("=*)");
4464        break;
4465
4466      case FILTER_TYPE_APPROXIMATE_MATCH:
4467        buffer.append('(');
4468        buffer.append(StaticUtils.toLowerCase(attrName));
4469        buffer.append("~=");
4470        encodeValue(mr.normalize(assertionValue), buffer);
4471        buffer.append(')');
4472        break;
4473
4474      case FILTER_TYPE_EXTENSIBLE_MATCH:
4475        buffer.append('(');
4476        if (attrName != null)
4477        {
4478          buffer.append(StaticUtils.toLowerCase(attrName));
4479        }
4480
4481        if (dnAttributes)
4482        {
4483          buffer.append(":dn");
4484        }
4485
4486        if (matchingRuleID != null)
4487        {
4488          buffer.append(':');
4489          buffer.append(StaticUtils.toLowerCase(matchingRuleID));
4490        }
4491
4492        buffer.append(":=");
4493        encodeValue(mr.normalize(assertionValue), buffer);
4494        buffer.append(')');
4495        break;
4496    }
4497  }
4498
4499
4500
4501  /**
4502   * Encodes the provided value into a form suitable for use as the assertion
4503   * value in the string representation of a search filter.  Parentheses,
4504   * asterisks, backslashes, null characters, and any non-ASCII characters will
4505   * be escaped using a backslash before the hexadecimal representation of each
4506   * byte in the character to escape.
4507   *
4508   * @param  value  The value to be encoded.  It must not be {@code null}.
4509   *
4510   * @return  The encoded representation of the provided string.
4511   */
4512  @NotNull()
4513  public static String encodeValue(@NotNull final String value)
4514  {
4515    Validator.ensureNotNull(value);
4516
4517    final StringBuilder buffer = new StringBuilder();
4518    encodeValue(new ASN1OctetString(value), buffer);
4519    return buffer.toString();
4520  }
4521
4522
4523
4524  /**
4525   * Encodes the provided value into a form suitable for use as the assertion
4526   * value in the string representation of a search filter.  Parentheses,
4527   * asterisks, backslashes, null characters, and any non-ASCII characters will
4528   * be escaped using a backslash before the hexadecimal representation of each
4529   * byte in the character to escape.
4530   *
4531   * @param  value  The value to be encoded.  It must not be {@code null}.
4532   *
4533   * @return  The encoded representation of the provided string.
4534   */
4535  @NotNull()
4536  public static String encodeValue(@NotNull final byte[]value)
4537  {
4538    Validator.ensureNotNull(value);
4539
4540    final StringBuilder buffer = new StringBuilder();
4541    encodeValue(new ASN1OctetString(value), buffer);
4542    return buffer.toString();
4543  }
4544
4545
4546
4547  /**
4548   * Appends the assertion value for this filter to the provided buffer,
4549   * encoding any special characters as necessary.
4550   *
4551   * @param  value   The value to be encoded.
4552   * @param  buffer  The buffer to which the assertion value should be appended.
4553   */
4554  public static void encodeValue(@NotNull final ASN1OctetString value,
4555                                 @NotNull final StringBuilder buffer)
4556  {
4557    final byte[] valueBytes = value.getValue();
4558    for (int i=0; i < valueBytes.length; i++)
4559    {
4560      switch (StaticUtils.numBytesInUTF8CharacterWithFirstByte(valueBytes[i]))
4561      {
4562        case 1:
4563          // This character is ASCII, but might still need to be escaped.
4564          if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters.
4565              (valueBytes[i] == 0x28) || // Open parenthesis
4566              (valueBytes[i] == 0x29) || // Close parenthesis
4567              (valueBytes[i] == 0x2A) || // Asterisk
4568              (valueBytes[i] == 0x5C) || // Backslash
4569              (valueBytes[i] == 0x7F))   // DEL
4570          {
4571            buffer.append('\\');
4572            StaticUtils.toHex(valueBytes[i], buffer);
4573          }
4574          else
4575          {
4576            buffer.append((char) valueBytes[i]);
4577          }
4578          break;
4579
4580        case 2:
4581          // If there are at least two bytes left, then we'll hex-encode the
4582          // next two bytes.  Otherwise we'll hex-encode whatever is left.
4583          buffer.append('\\');
4584          StaticUtils.toHex(valueBytes[i++], buffer);
4585          if (i < valueBytes.length)
4586          {
4587            buffer.append('\\');
4588            StaticUtils.toHex(valueBytes[i], buffer);
4589          }
4590          break;
4591
4592        case 3:
4593          // If there are at least three bytes left, then we'll hex-encode the
4594          // next three bytes.  Otherwise we'll hex-encode whatever is left.
4595          buffer.append('\\');
4596          StaticUtils.toHex(valueBytes[i++], buffer);
4597          if (i < valueBytes.length)
4598          {
4599            buffer.append('\\');
4600            StaticUtils.toHex(valueBytes[i++], buffer);
4601          }
4602          if (i < valueBytes.length)
4603          {
4604            buffer.append('\\');
4605            StaticUtils.toHex(valueBytes[i], buffer);
4606          }
4607          break;
4608
4609        case 4:
4610          // If there are at least four bytes left, then we'll hex-encode the
4611          // next four bytes.  Otherwise we'll hex-encode whatever is left.
4612          buffer.append('\\');
4613          StaticUtils.toHex(valueBytes[i++], buffer);
4614          if (i < valueBytes.length)
4615          {
4616            buffer.append('\\');
4617            StaticUtils.toHex(valueBytes[i++], buffer);
4618          }
4619          if (i < valueBytes.length)
4620          {
4621            buffer.append('\\');
4622            StaticUtils.toHex(valueBytes[i++], buffer);
4623          }
4624          if (i < valueBytes.length)
4625          {
4626            buffer.append('\\');
4627            StaticUtils.toHex(valueBytes[i], buffer);
4628          }
4629          break;
4630
4631        default:
4632          // We'll hex-encode whatever is left in the buffer.
4633          while (i < valueBytes.length)
4634          {
4635            buffer.append('\\');
4636            StaticUtils.toHex(valueBytes[i++], buffer);
4637          }
4638          break;
4639      }
4640    }
4641  }
4642
4643
4644
4645  /**
4646   * Appends a number of lines comprising the Java source code that can be used
4647   * to recreate this filter to the given list.  Note that unless a first line
4648   * prefix and/or last line suffix are provided, this will just include the
4649   * code for the static method used to create the filter, starting with
4650   * "Filter.createXFilter(" and ending with the closing parenthesis for that
4651   * method call.
4652   *
4653   * @param  lineList         The list to which the source code lines should be
4654   *                          added.
4655   * @param  indentSpaces     The number of spaces that should be used to indent
4656   *                          the generated code.  It must not be negative.
4657   * @param  firstLinePrefix  An optional string that should precede the static
4658   *                          method call (e.g., it could be used for an
4659   *                          attribute assignment, like "Filter f = ").  It may
4660   *                          be {@code null} or empty if there should be no
4661   *                          first line prefix.
4662   * @param  lastLineSuffix   An optional suffix that should follow the closing
4663   *                          parenthesis of the static method call (e.g., it
4664   *                          could be a semicolon to represent the end of a
4665   *                          Java statement).  It may be {@code null} or empty
4666   *                          if there should be no last line suffix.
4667   */
4668  public void toCode(@NotNull final List<String> lineList,
4669                     final int indentSpaces,
4670                     @Nullable final String firstLinePrefix,
4671                     @Nullable final String lastLineSuffix)
4672  {
4673    // Generate a string with the appropriate indent.
4674    final StringBuilder buffer = new StringBuilder();
4675    for (int i = 0; i < indentSpaces; i++)
4676    {
4677      buffer.append(' ');
4678    }
4679    final String indent = buffer.toString();
4680
4681
4682    // Start the first line, including any appropriate prefix.
4683    buffer.setLength(0);
4684    buffer.append(indent);
4685    if (firstLinePrefix != null)
4686    {
4687      buffer.append(firstLinePrefix);
4688    }
4689
4690
4691    // Figure out what type of filter it is and create the appropriate code for
4692    // that type of filter.
4693    switch (filterType)
4694    {
4695      case FILTER_TYPE_AND:
4696      case FILTER_TYPE_OR:
4697        if (filterType == FILTER_TYPE_AND)
4698        {
4699          buffer.append("Filter.createANDFilter(");
4700        }
4701        else
4702        {
4703          buffer.append("Filter.createORFilter(");
4704        }
4705        if (filterComps.length == 0)
4706        {
4707          buffer.append(')');
4708          if (lastLineSuffix != null)
4709          {
4710            buffer.append(lastLineSuffix);
4711          }
4712          lineList.add(buffer.toString());
4713          return;
4714        }
4715
4716        for (int i = 0; i < filterComps.length; i++)
4717        {
4718          String suffix;
4719          if (i == (filterComps.length - 1))
4720          {
4721            suffix = ")";
4722            if (lastLineSuffix != null)
4723            {
4724              suffix += lastLineSuffix;
4725            }
4726          }
4727          else
4728          {
4729            suffix = ",";
4730          }
4731
4732          filterComps[i].toCode(lineList, indentSpaces + 5, null, suffix);
4733        }
4734        return;
4735
4736
4737      case FILTER_TYPE_NOT:
4738        buffer.append("Filter.createNOTFilter(");
4739        lineList.add(buffer.toString());
4740
4741        final String suffix;
4742        if (lastLineSuffix == null)
4743        {
4744          suffix = ")";
4745        }
4746        else
4747        {
4748          suffix = ')' + lastLineSuffix;
4749        }
4750        notComp.toCode(lineList, indentSpaces + 5, null, suffix);
4751        return;
4752
4753      case FILTER_TYPE_PRESENCE:
4754        buffer.append("Filter.createPresenceFilter(");
4755        lineList.add(buffer.toString());
4756
4757        buffer.setLength(0);
4758        buffer.append(indent);
4759        buffer.append("     \"");
4760        buffer.append(attrName);
4761        buffer.append("\")");
4762
4763        if (lastLineSuffix != null)
4764        {
4765          buffer.append(lastLineSuffix);
4766        }
4767
4768        lineList.add(buffer.toString());
4769        return;
4770
4771
4772      case FILTER_TYPE_EQUALITY:
4773      case FILTER_TYPE_GREATER_OR_EQUAL:
4774      case FILTER_TYPE_LESS_OR_EQUAL:
4775      case FILTER_TYPE_APPROXIMATE_MATCH:
4776        if (filterType == FILTER_TYPE_EQUALITY)
4777        {
4778          buffer.append("Filter.createEqualityFilter(");
4779        }
4780        else if (filterType == FILTER_TYPE_GREATER_OR_EQUAL)
4781        {
4782          buffer.append("Filter.createGreaterOrEqualFilter(");
4783        }
4784        else if (filterType == FILTER_TYPE_LESS_OR_EQUAL)
4785        {
4786          buffer.append("Filter.createLessOrEqualFilter(");
4787        }
4788        else
4789        {
4790          buffer.append("Filter.createApproximateMatchFilter(");
4791        }
4792        lineList.add(buffer.toString());
4793
4794        buffer.setLength(0);
4795        buffer.append(indent);
4796        buffer.append("     \"");
4797        buffer.append(attrName);
4798        buffer.append("\",");
4799        lineList.add(buffer.toString());
4800
4801        buffer.setLength(0);
4802        buffer.append(indent);
4803        buffer.append("     ");
4804        if (StaticUtils.isSensitiveToCodeAttribute(attrName))
4805        {
4806          buffer.append("\"---redacted-value---\"");
4807        }
4808        else if (StaticUtils.isPrintableString(assertionValue.getValue()))
4809        {
4810          buffer.append('"');
4811          buffer.append(assertionValue.stringValue());
4812          buffer.append('"');
4813        }
4814        else
4815        {
4816          StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
4817        }
4818
4819        buffer.append(')');
4820
4821        if (lastLineSuffix != null)
4822        {
4823          buffer.append(lastLineSuffix);
4824        }
4825
4826        lineList.add(buffer.toString());
4827        return;
4828
4829
4830      case FILTER_TYPE_SUBSTRING:
4831        buffer.append("Filter.createSubstringFilter(");
4832        lineList.add(buffer.toString());
4833
4834        buffer.setLength(0);
4835        buffer.append(indent);
4836        buffer.append("     \"");
4837        buffer.append(attrName);
4838        buffer.append("\",");
4839        lineList.add(buffer.toString());
4840
4841        final boolean isRedacted =
4842             StaticUtils.isSensitiveToCodeAttribute(attrName);
4843        boolean isPrintable = true;
4844        if (subInitial != null)
4845        {
4846          isPrintable = StaticUtils.isPrintableString(subInitial.getValue());
4847        }
4848
4849        if (isPrintable && (subAny != null))
4850        {
4851          for (final ASN1OctetString s : subAny)
4852          {
4853            if (! StaticUtils.isPrintableString(s.getValue()))
4854            {
4855              isPrintable = false;
4856              break;
4857            }
4858          }
4859        }
4860
4861        if (isPrintable && (subFinal != null))
4862        {
4863          isPrintable = StaticUtils.isPrintableString(subFinal.getValue());
4864        }
4865
4866        buffer.setLength(0);
4867        buffer.append(indent);
4868        buffer.append("     ");
4869        if (subInitial == null)
4870        {
4871          buffer.append("null");
4872        }
4873        else if (isRedacted)
4874        {
4875          buffer.append("\"---redacted-subInitial---\"");
4876        }
4877        else if (isPrintable)
4878        {
4879          buffer.append('"');
4880          buffer.append(subInitial.stringValue());
4881          buffer.append('"');
4882        }
4883        else
4884        {
4885          StaticUtils.byteArrayToCode(subInitial.getValue(), buffer);
4886        }
4887        buffer.append(',');
4888        lineList.add(buffer.toString());
4889
4890        buffer.setLength(0);
4891        buffer.append(indent);
4892        buffer.append("     ");
4893        if ((subAny == null) || (subAny.length == 0))
4894        {
4895          buffer.append("null,");
4896          lineList.add(buffer.toString());
4897        }
4898        else if (isRedacted)
4899        {
4900          buffer.append("new String[]");
4901          lineList.add(buffer.toString());
4902
4903          lineList.add(indent + "     {");
4904
4905          for (int i=0; i < subAny.length; i++)
4906          {
4907            buffer.setLength(0);
4908            buffer.append(indent);
4909            buffer.append("       \"---redacted-subAny-");
4910            buffer.append(i+1);
4911            buffer.append("---\"");
4912            if (i < (subAny.length-1))
4913            {
4914              buffer.append(',');
4915            }
4916            lineList.add(buffer.toString());
4917          }
4918
4919          lineList.add(indent + "     },");
4920        }
4921        else if (isPrintable)
4922        {
4923          buffer.append("new String[]");
4924          lineList.add(buffer.toString());
4925
4926          lineList.add(indent + "     {");
4927
4928          for (int i=0; i < subAny.length; i++)
4929          {
4930            buffer.setLength(0);
4931            buffer.append(indent);
4932            buffer.append("       \"");
4933            buffer.append(subAny[i].stringValue());
4934            buffer.append('"');
4935            if (i < (subAny.length-1))
4936            {
4937              buffer.append(',');
4938            }
4939            lineList.add(buffer.toString());
4940          }
4941
4942          lineList.add(indent + "     },");
4943        }
4944        else
4945        {
4946          buffer.append("new String[]");
4947          lineList.add(buffer.toString());
4948
4949          lineList.add(indent + "     {");
4950
4951          for (int i=0; i < subAny.length; i++)
4952          {
4953            buffer.setLength(0);
4954            buffer.append(indent);
4955            buffer.append("       ");
4956            StaticUtils.byteArrayToCode(subAny[i].getValue(), buffer);
4957            if (i < (subAny.length-1))
4958            {
4959              buffer.append(',');
4960            }
4961            lineList.add(buffer.toString());
4962          }
4963
4964          lineList.add(indent + "     },");
4965        }
4966
4967        buffer.setLength(0);
4968        buffer.append(indent);
4969        buffer.append("     ");
4970        if (subFinal == null)
4971        {
4972          buffer.append("null)");
4973        }
4974        else if (isRedacted)
4975        {
4976          buffer.append("\"---redacted-subFinal---\")");
4977        }
4978        else if (isPrintable)
4979        {
4980          buffer.append('"');
4981          buffer.append(subFinal.stringValue());
4982          buffer.append("\")");
4983        }
4984        else
4985        {
4986          StaticUtils.byteArrayToCode(subFinal.getValue(), buffer);
4987          buffer.append(')');
4988        }
4989        if (lastLineSuffix != null)
4990        {
4991          buffer.append(lastLineSuffix);
4992        }
4993        lineList.add(buffer.toString());
4994        return;
4995
4996
4997      case FILTER_TYPE_EXTENSIBLE_MATCH:
4998        buffer.append("Filter.createExtensibleMatchFilter(");
4999        lineList.add(buffer.toString());
5000
5001        buffer.setLength(0);
5002        buffer.append(indent);
5003        buffer.append("     ");
5004        if (attrName == null)
5005        {
5006          buffer.append("null, // Attribute Description");
5007        }
5008        else
5009        {
5010          buffer.append('"');
5011          buffer.append(attrName);
5012          buffer.append("\",");
5013        }
5014        lineList.add(buffer.toString());
5015
5016        buffer.setLength(0);
5017        buffer.append(indent);
5018        buffer.append("     ");
5019        if (matchingRuleID == null)
5020        {
5021          buffer.append("null, // Matching Rule ID");
5022        }
5023        else
5024        {
5025          buffer.append('"');
5026          buffer.append(matchingRuleID);
5027          buffer.append("\",");
5028        }
5029        lineList.add(buffer.toString());
5030
5031        buffer.setLength(0);
5032        buffer.append(indent);
5033        buffer.append("     ");
5034        buffer.append(dnAttributes);
5035        buffer.append(", // DN Attributes");
5036        lineList.add(buffer.toString());
5037
5038        buffer.setLength(0);
5039        buffer.append(indent);
5040        buffer.append("     ");
5041        if ((attrName != null) &&
5042             StaticUtils.isSensitiveToCodeAttribute(attrName))
5043        {
5044          buffer.append("\"---redacted-value---\")");
5045        }
5046        else
5047        {
5048          if (StaticUtils.isPrintableString(assertionValue.getValue()))
5049          {
5050            buffer.append('"');
5051            buffer.append(assertionValue.stringValue());
5052            buffer.append("\")");
5053          }
5054          else
5055          {
5056            StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
5057            buffer.append(')');
5058          }
5059        }
5060
5061        if (lastLineSuffix != null)
5062        {
5063          buffer.append(lastLineSuffix);
5064        }
5065        lineList.add(buffer.toString());
5066        return;
5067    }
5068  }
5069}