001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2024 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-2024 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.present("objectClass");
204 *   Filter f2 = Filter.equals("uid", "john.doe");
205 *   Filter f3 = Filter.or(
206 *                    Filter.equals("givenName", "John"),
207 *                    Filter.equals("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   * <BR><BR>
445   * This method does exactly the same thing as
446   * {@link #createANDFilter(Filter...)}, but with a shorter method name for
447   * convenience.
448   *
449   * @param  andComponents  The set of filter components to include in the AND
450   *                        filter.  It must not be {@code null}.
451   *
452   * @return  The created AND search filter.
453   */
454  @NotNull()
455  public static Filter and(@NotNull final Filter... andComponents)
456  {
457    return createANDFilter(andComponents);
458  }
459
460
461
462  /**
463   * Creates a new AND search filter with the provided components.
464   * <BR><BR>
465   * This method does exactly the same thing as
466   * {@link #createANDFilter(Collection)}, but with a shorter method name for
467   * convenience.
468   *
469   * @param  andComponents  The set of filter components to include in the AND
470   *                        filter.  It must not be {@code null}.
471   *
472   * @return  The created AND search filter.
473   */
474  @NotNull()
475  public static Filter and(@NotNull final Collection<Filter> andComponents)
476  {
477    return createANDFilter(andComponents);
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(@NotNull final Filter... andComponents)
492  {
493    Validator.ensureNotNull(andComponents);
494
495    return new Filter(null, FILTER_TYPE_AND, andComponents, null, null, null,
496                      null, NO_SUB_ANY, null, null, false);
497  }
498
499
500
501  /**
502   * Creates a new AND search filter with the provided components.
503   *
504   * @param  andComponents  The set of filter components to include in the AND
505   *                        filter.  It must not be {@code null}.
506   *
507   * @return  The created AND search filter.
508   */
509  @NotNull()
510  public static Filter createANDFilter(
511                            @NotNull final List<Filter> andComponents)
512  {
513    Validator.ensureNotNull(andComponents);
514
515    return new Filter(null, FILTER_TYPE_AND,
516                      andComponents.toArray(new Filter[andComponents.size()]),
517                      null, null, null, null, NO_SUB_ANY, null, null, false);
518  }
519
520
521
522  /**
523   * Creates a new AND search filter with the provided components.
524   *
525   * @param  andComponents  The set of filter components to include in the AND
526   *                        filter.  It must not be {@code null}.
527   *
528   * @return  The created AND search filter.
529   */
530  @NotNull()
531  public static Filter createANDFilter(
532                            @NotNull final Collection<Filter> andComponents)
533  {
534    Validator.ensureNotNull(andComponents);
535
536    return new Filter(null, FILTER_TYPE_AND,
537                      andComponents.toArray(new Filter[andComponents.size()]),
538                      null, null, null, null, NO_SUB_ANY, null, null, false);
539  }
540
541
542
543  /**
544   * Creates a new OR search filter with the provided components.
545   * <BR><BR>
546   * This method does exactly the same thing as
547   * {@link #createORFilter(Filter...)}, but with a shorter method name for
548   * convenience.
549   *
550   * @param  orComponents  The set of filter components to include in the OR
551   *                       filter.  It must not be {@code null}.
552   *
553   * @return  The created OR search filter.
554   */
555  @NotNull()
556  public static Filter or(@NotNull final Filter... orComponents)
557  {
558    return createORFilter(orComponents);
559  }
560
561
562
563  /**
564   * Creates a new OR search filter with the provided components.
565   * <BR><BR>
566   * This method does exactly the same thing as
567   * {@link #createORFilter(Collection)}, but with a shorter method name for
568   * convenience.
569   *
570   * @param  orComponents  The set of filter components to include in the OR
571   *                       filter.  It must not be {@code null}.
572   *
573   * @return  The created OR search filter.
574   */
575  @NotNull()
576  public static Filter or(@NotNull final Collection<Filter> orComponents)
577  {
578    return createORFilter(orComponents);
579  }
580
581
582
583  /**
584   * Creates a new OR search filter with the provided components.
585   *
586   * @param  orComponents  The set of filter components to include in the OR
587   *                       filter.  It must not be {@code null}.
588   *
589   * @return  The created OR search filter.
590   */
591  @NotNull()
592  public static Filter createORFilter(@NotNull final Filter... orComponents)
593  {
594    Validator.ensureNotNull(orComponents);
595
596    return new Filter(null, FILTER_TYPE_OR, orComponents, null, null, null,
597                      null, NO_SUB_ANY, null, null, false);
598  }
599
600
601
602  /**
603   * Creates a new OR search filter with the provided components.
604   *
605   * @param  orComponents  The set of filter components to include in the OR
606   *                       filter.  It must not be {@code null}.
607   *
608   * @return  The created OR search filter.
609   */
610  @NotNull()
611  public static Filter createORFilter(@NotNull final List<Filter> orComponents)
612  {
613    Validator.ensureNotNull(orComponents);
614
615    return new Filter(null, FILTER_TYPE_OR,
616                      orComponents.toArray(new Filter[orComponents.size()]),
617                      null, null, null, null, NO_SUB_ANY, null, null, false);
618  }
619
620
621
622  /**
623   * Creates a new OR search filter with the provided components.
624   *
625   * @param  orComponents  The set of filter components to include in the OR
626   *                       filter.  It must not be {@code null}.
627   *
628   * @return  The created OR search filter.
629   */
630  @NotNull()
631  public static Filter createORFilter(
632                            @NotNull final Collection<Filter> orComponents)
633  {
634    Validator.ensureNotNull(orComponents);
635
636    return new Filter(null, FILTER_TYPE_OR,
637                      orComponents.toArray(new Filter[orComponents.size()]),
638                      null, null, null, null, NO_SUB_ANY, null, null, false);
639  }
640
641
642
643  /**
644   * Creates a new NOT search filter with the provided component.
645   * <BR><BR>
646   * This method does exactly the same thing as
647   * {@link #createNOTFilter(Filter)}, but with a shorter method name for
648   * convenience.
649   *
650   * @param  notComponent  The filter component to include in this NOT filter.
651   *                       It must not be {@code null}.
652   *
653   * @return  The created NOT search filter.
654   */
655  @NotNull()
656  public static Filter not(@NotNull final Filter notComponent)
657  {
658    return createNOTFilter(notComponent);
659  }
660
661
662
663  /**
664   * Creates a new NOT search filter with the provided component.
665   *
666   * @param  notComponent  The filter component to include in this NOT filter.
667   *                       It must not be {@code null}.
668   *
669   * @return  The created NOT search filter.
670   */
671  @NotNull()
672  public static Filter createNOTFilter(@NotNull final Filter notComponent)
673  {
674    Validator.ensureNotNull(notComponent);
675
676    return new Filter(null, FILTER_TYPE_NOT, NO_FILTERS, notComponent, null,
677                      null, null, NO_SUB_ANY, null, null, false);
678  }
679
680
681
682  /**
683   * Creates a new equality search filter with the provided information.
684   * <BR><BR>
685   * This method does exactly the same thing as
686   * {@link #createEqualityFilter(String,String)}, but with a shorter method
687   * name for convenience.
688   *
689   * @param  attributeName   The attribute name for this equality filter.  It
690   *                         must not be {@code null}.
691   * @param  assertionValue  The assertion value for this equality filter.  It
692   *                         must not be {@code null}.
693   *
694   * @return  The created equality search filter.
695   */
696  @NotNull()
697  public static Filter equals(@NotNull final String attributeName,
698                              @NotNull final String assertionValue)
699  {
700    return createEqualityFilter(attributeName, assertionValue);
701  }
702
703
704
705  /**
706   * Creates a new equality search filter with the provided information.
707   * <BR><BR>
708   * This method does exactly the same thing as
709   * {@link #createEqualityFilter(String,byte[])}, but with a shorter method
710   * name for convenience.
711   *
712   * @param  attributeName   The attribute name for this equality filter.  It
713   *                         must not be {@code null}.
714   * @param  assertionValue  The assertion value for this equality filter.  It
715   *                         must not be {@code null}.
716   *
717   * @return  The created equality search filter.
718   */
719  @NotNull()
720  public static Filter equals(@NotNull final String attributeName,
721                              @NotNull final byte[] assertionValue)
722  {
723    return createEqualityFilter(attributeName, assertionValue);
724  }
725
726
727
728  /**
729   * Creates a new equality search filter with the provided information.
730   *
731   * @param  attributeName   The attribute name for this equality filter.  It
732   *                         must not be {@code null}.
733   * @param  assertionValue  The assertion value for this equality filter.  It
734   *                         must not be {@code null}.
735   *
736   * @return  The created equality search filter.
737   */
738  @NotNull()
739  public static Filter createEqualityFilter(@NotNull final String attributeName,
740                            @NotNull final String assertionValue)
741  {
742    Validator.ensureNotNull(attributeName, assertionValue);
743
744    return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null,
745                      attributeName, new ASN1OctetString(assertionValue), null,
746                      NO_SUB_ANY, null, null, false);
747  }
748
749
750
751  /**
752   * Creates a new equality search filter with the provided information.
753   *
754   * @param  attributeName   The attribute name for this equality filter.  It
755   *                         must not be {@code null}.
756   * @param  assertionValue  The assertion value for this equality filter.  It
757   *                         must not be {@code null}.
758   *
759   * @return  The created equality search filter.
760   */
761  @NotNull()
762  public static Filter createEqualityFilter(@NotNull final String attributeName,
763                            @NotNull final byte[] assertionValue)
764  {
765    Validator.ensureNotNull(attributeName, assertionValue);
766
767    return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null,
768                      attributeName, new ASN1OctetString(assertionValue), null,
769                      NO_SUB_ANY, null, null, false);
770  }
771
772
773
774  /**
775   * Creates a new equality search filter with the provided information.
776   *
777   * @param  attributeName   The attribute name for this equality filter.  It
778   *                         must not be {@code null}.
779   * @param  assertionValue  The assertion value for this equality filter.  It
780   *                         must not be {@code null}.
781   *
782   * @return  The created equality search filter.
783   */
784  @NotNull()
785  static Filter createEqualityFilter(@NotNull final String attributeName,
786                     @NotNull final ASN1OctetString assertionValue)
787  {
788    Validator.ensureNotNull(attributeName, assertionValue);
789
790    return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null,
791                      attributeName, assertionValue, null, NO_SUB_ANY, null,
792                      null, false);
793  }
794
795
796
797  /**
798   * Creates a new substring search filter with the provided information.  At
799   * least one of the subInitial, subAny, and subFinal components must not be
800   * {@code null}.
801   * <BR><BR>
802   * This method does exactly the same thing as
803   * {@link #createSubstringFilter(String,String,String[],String)}, but with a
804   * shorter method name for convenience.
805   *
806   * @param  attributeName  The attribute name for this substring filter.  It
807   *                        must not be {@code null}.
808   * @param  subInitial     The subInitial component for this substring filter.
809   *                        It may be {@code null} if there is no subInitial
810   *                        component, but it must not be empty.
811   * @param  subAny         The set of subAny components for this substring
812   *                        filter.  It may be {@code null} or empty if there
813   *                        are no subAny components.
814   * @param  subFinal       The subFinal component for this substring filter.
815   *                        It may be {@code null} if there is no subFinal
816   *                        component, but it must not be empty.
817   *
818   * @return  The created substring search filter.
819   */
820  @NotNull()
821  public static Filter substring(@NotNull final String attributeName,
822                                 @Nullable final String subInitial,
823                                 @Nullable final String[] subAny,
824                                 @Nullable final String subFinal)
825  {
826    return createSubstringFilter(attributeName, subInitial, subAny, subFinal);
827  }
828
829
830
831  /**
832   * Creates a new substring search filter with the provided information.  At
833   * least one of the subInitial, subAny, and subFinal components must not be
834   * {@code null}.
835   * <BR><BR>
836   * This method does exactly the same thing as
837   * {@link #createSubstringFilter(String,byte[],byte[][],byte[])}, but with a
838   * shorter method name for convenience.
839   *
840   * @param  attributeName  The attribute name for this substring filter.  It
841   *                        must not be {@code null}.
842   * @param  subInitial     The subInitial component for this substring filter.
843   *                        It may be {@code null} if there is no subInitial
844   *                        component, but it must not be empty.
845   * @param  subAny         The set of subAny components for this substring
846   *                        filter.  It may be {@code null} or empty if there
847   *                        are no subAny components.
848   * @param  subFinal       The subFinal component for this substring filter.
849   *                        It may be {@code null} if there is no subFinal
850   *                        component, but it must not be empty.
851   *
852   * @return  The created substring search filter.
853   */
854  @NotNull()
855  public static Filter substring(@NotNull final String attributeName,
856                                 @Nullable final byte[] subInitial,
857                                 @Nullable final byte[][] subAny,
858                                 @Nullable final byte[] subFinal)
859  {
860    return createSubstringFilter(attributeName, subInitial, subAny, subFinal);
861  }
862
863
864
865  /**
866   * Creates a new substring search filter with the provided information.  At
867   * least one of the subInitial, subAny, and subFinal components must not be
868   * {@code null}.
869   *
870   * @param  attributeName  The attribute name for this substring filter.  It
871   *                        must not be {@code null}.
872   * @param  subInitial     The subInitial component for this substring filter.
873   *                        It may be {@code null} if there is no subInitial
874   *                        component, but it must not be empty.
875   * @param  subAny         The set of subAny components for this substring
876   *                        filter.  It may be {@code null} or empty if there
877   *                        are no subAny components.
878   * @param  subFinal       The subFinal component for this substring filter.
879   *                        It may be {@code null} if there is no subFinal
880   *                        component, but it must not be empty.
881   *
882   * @return  The created substring search filter.
883   */
884  @NotNull()
885  public static Filter createSubstringFilter(
886                            @NotNull final String attributeName,
887                            @Nullable final String subInitial,
888                            @Nullable final String[] subAny,
889                            @Nullable final String subFinal)
890  {
891    Validator.ensureNotNull(attributeName);
892    Validator.ensureTrue(
893         (((subInitial != null) && (subInitial.length() > 0)) ||
894              ((subAny != null) && (subAny.length > 0) &&
895                   (subAny[0].length() > 0)) ||
896              ((subFinal != null) && (subFinal.length() > 0))),
897         "At least one substring filter component must be non-null and " +
898              "non-empty");
899
900    final ASN1OctetString subInitialOS;
901    if ((subInitial == null) || subInitial.isEmpty())
902    {
903      subInitialOS = null;
904    }
905    else
906    {
907      subInitialOS = new ASN1OctetString(subInitial);
908    }
909
910    final ASN1OctetString[] subAnyArray;
911    if (subAny == null)
912    {
913      subAnyArray = NO_SUB_ANY;
914    }
915    else
916    {
917      if (subAny.length == 1)
918      {
919        if (subAny[0].length() == 0)
920        {
921          subAnyArray = NO_SUB_ANY;
922        }
923        else
924        {
925          subAnyArray = new ASN1OctetString[]
926          {
927            new ASN1OctetString(subAny[0])
928          };
929        }
930      }
931      else
932      {
933        subAnyArray = new ASN1OctetString[subAny.length];
934        for (int i=0; i < subAny.length; i++)
935        {
936          Validator.ensureFalse(subAny[i].isEmpty(),
937               "Individual substring filter components must not be empty");
938          subAnyArray[i] = new ASN1OctetString(subAny[i]);
939        }
940      }
941    }
942
943    final ASN1OctetString subFinalOS;
944    if ((subFinal == null) || subFinal.isEmpty())
945    {
946      subFinalOS = null;
947    }
948    else
949    {
950      subFinalOS = new ASN1OctetString(subFinal);
951    }
952
953    return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
954                      attributeName, null, subInitialOS, subAnyArray,
955                      subFinalOS, null, false);
956  }
957
958
959
960  /**
961   * Creates a new substring search filter with the provided information.  At
962   * least one of the subInitial, subAny, and subFinal components must not be
963   * {@code null}.
964   *
965   * @param  attributeName  The attribute name for this substring filter.  It
966   *                        must not be {@code null}.
967   * @param  subInitial     The subInitial component for this substring filter.
968   *                        It may be {@code null} if there is no subInitial
969   *                        component, but it must not be empty.
970   * @param  subAny         The set of subAny components for this substring
971   *                        filter.  It may be {@code null} or empty if there
972   *                        are no subAny components.
973   * @param  subFinal       The subFinal component for this substring filter.
974   *                        It may be {@code null} if there is no subFinal
975   *                        component, but it must not be empty.
976   *
977   * @return  The created substring search filter.
978   */
979  @NotNull()
980  public static Filter createSubstringFilter(
981                            @NotNull final String attributeName,
982                            @Nullable final byte[] subInitial,
983                            @Nullable final byte[][] subAny,
984                            @Nullable final byte[] subFinal)
985  {
986    Validator.ensureNotNull(attributeName);
987    Validator.ensureTrue(
988         (((subInitial != null) && (subInitial.length > 0)) ||
989              ((subAny != null) && (subAny.length > 0) &&
990                   (subAny[0].length > 0)) ||
991              ((subFinal != null) && (subFinal.length > 0))),
992         "At least one substring filter component must be non-null and " +
993              "non-empty");
994
995    final ASN1OctetString subInitialOS;
996    if ((subInitial == null) || (subInitial.length == 0))
997    {
998      subInitialOS = null;
999    }
1000    else
1001    {
1002      subInitialOS = new ASN1OctetString(subInitial);
1003    }
1004
1005    final ASN1OctetString[] subAnyArray;
1006    if (subAny == null)
1007    {
1008      subAnyArray = NO_SUB_ANY;
1009    }
1010    else
1011    {
1012      if (subAny.length == 1)
1013      {
1014        if (subAny[0].length == 0)
1015        {
1016          subAnyArray = NO_SUB_ANY;
1017        }
1018        else
1019        {
1020          subAnyArray = new ASN1OctetString[]
1021          {
1022            new ASN1OctetString(subAny[0])
1023          };
1024        }
1025      }
1026      else
1027      {
1028        subAnyArray = new ASN1OctetString[subAny.length];
1029        for (int i=0; i < subAny.length; i++)
1030        {
1031          Validator.ensureTrue((subAny[i].length > 0),
1032               "Individual substring filter components must not be empty");
1033          subAnyArray[i] = new ASN1OctetString(subAny[i]);
1034        }
1035      }
1036    }
1037
1038    final ASN1OctetString subFinalOS;
1039    if ((subFinal == null) || (subFinal.length == 0))
1040    {
1041      subFinalOS = null;
1042    }
1043    else
1044    {
1045      subFinalOS = new ASN1OctetString(subFinal);
1046    }
1047
1048    return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
1049                      attributeName, null, subInitialOS, subAnyArray,
1050                      subFinalOS, null, false);
1051  }
1052
1053
1054
1055  /**
1056   * Creates a new substring search filter with the provided information.  At
1057   * least one of the subInitial, subAny, and subFinal components must not be
1058   * {@code null}.
1059   *
1060   * @param  attributeName  The attribute name for this substring filter.  It
1061   *                        must not be {@code null}.
1062   * @param  subInitial     The subInitial component for this substring filter.
1063   *                        It may be {@code null} if there is no subInitial
1064   *                        component, but it must not be empty.
1065   * @param  subAny         The set of subAny components for this substring
1066   *                        filter.  It may be {@code null} or empty if there
1067   *                        are no subAny components.
1068   * @param  subFinal       The subFinal component for this substring filter.
1069   *                        It may be {@code null} if there is no subFinal
1070   *                        component, but it must not be empty.
1071   *
1072   * @return  The created substring search filter.
1073   */
1074  @NotNull()
1075  static Filter createSubstringFilter(@NotNull final String attributeName,
1076                     @Nullable final ASN1OctetString subInitial,
1077                     @Nullable final ASN1OctetString[] subAny,
1078                     @Nullable final ASN1OctetString subFinal)
1079  {
1080    Validator.ensureNotNull(attributeName);
1081    Validator.ensureTrue((subInitial != null) ||
1082         ((subAny != null) && (subAny.length > 0)) ||
1083         (subFinal != null));
1084
1085    if (subAny == null)
1086    {
1087      return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
1088                        attributeName, null, subInitial, NO_SUB_ANY, subFinal,
1089                        null, false);
1090    }
1091    else
1092    {
1093      return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null,
1094                        attributeName, null, subInitial, subAny, subFinal, null,
1095                        false);
1096    }
1097  }
1098
1099
1100
1101  /**
1102   * Creates a new substring search filter with only a subInitial (starts with)
1103   * component.
1104   * <BR><BR>
1105   * This method does exactly the same thing as
1106   * {@link #createSubInitialFilter(String,String)}, but with a shorter method
1107   * name for convenience.
1108   *
1109   * @param  attributeName  The attribute name for this substring filter.  It
1110   *                        must not be {@code null}.
1111   * @param  subInitial     The subInitial component for this substring filter.
1112   *                        It must not be {@code null} or empty.
1113   *
1114   * @return  The created substring search filter.
1115   */
1116  @NotNull()
1117  public static Filter subInitial(@NotNull final String attributeName,
1118                                  @NotNull final String subInitial)
1119  {
1120    return createSubInitialFilter(attributeName, subInitial);
1121  }
1122
1123
1124
1125  /**
1126   * Creates a new substring search filter with only a subInitial (starts with)
1127   * component.
1128   * <BR><BR>
1129   * This method does exactly the same thing as
1130   * {@link #createSubInitialFilter(String,byte[])}, but with a shorter method
1131   * name for convenience.
1132   *
1133   * @param  attributeName  The attribute name for this substring filter.  It
1134   *                        must not be {@code null}.
1135   * @param  subInitial     The subInitial component for this substring filter.
1136   *                        It must not be {@code null} or empty.
1137   *
1138   * @return  The created substring search filter.
1139   */
1140  @NotNull()
1141  public static Filter subInitial(@NotNull final String attributeName,
1142                                  @NotNull final byte[] subInitial)
1143  {
1144    return createSubInitialFilter(attributeName, subInitial);
1145  }
1146
1147
1148
1149  /**
1150   * Creates a new substring search filter with only a subInitial (starts with)
1151   * component.
1152   *
1153   * @param  attributeName  The attribute name for this substring filter.  It
1154   *                        must not be {@code null}.
1155   * @param  subInitial     The subInitial component for this substring filter.
1156   *                        It must not be {@code null} or empty.
1157   *
1158   * @return  The created substring search filter.
1159   */
1160  @NotNull()
1161  public static Filter createSubInitialFilter(
1162                            @NotNull final String attributeName,
1163                            @NotNull final String subInitial)
1164  {
1165    return createSubstringFilter(attributeName, subInitial, null, null);
1166  }
1167
1168
1169
1170  /**
1171   * Creates a new substring search filter with only a subInitial (starts with)
1172   * component.
1173   *
1174   * @param  attributeName  The attribute name for this substring filter.  It
1175   *                        must not be {@code null}.
1176   * @param  subInitial     The subInitial component for this substring filter.
1177   *                        It must not be {@code null} or empty.
1178   *
1179   * @return  The created substring search filter.
1180   */
1181  @NotNull()
1182  public static Filter createSubInitialFilter(
1183                            @NotNull final String attributeName,
1184                            @NotNull final byte[] subInitial)
1185  {
1186    return createSubstringFilter(attributeName, subInitial, null, null);
1187  }
1188
1189
1190
1191  /**
1192   * Creates a new substring search filter with only a subAny (contains)
1193   * component.
1194   * <BR><BR>
1195   * This method does exactly the same thing as
1196   * {@link #createSubAnyFilter(String,String...)}, but with a shorter method
1197   * name for convenience.
1198   *
1199   * @param  attributeName  The attribute name for this substring filter.  It
1200   *                        must not be {@code null}.
1201   * @param  subAny         The subAny values for this substring filter.  It
1202   *                        must not be {@code null} or empty.
1203   *
1204   * @return  The created substring search filter.
1205   */
1206  @NotNull()
1207  public static Filter subAny(@NotNull final String attributeName,
1208                              @NotNull final String... subAny)
1209  {
1210    return createSubAnyFilter(attributeName, subAny);
1211  }
1212
1213
1214
1215  /**
1216   * Creates a new substring search filter with only a subAny (contains)
1217   * component.
1218   * <BR><BR>
1219   * This method does exactly the same thing as
1220   * {@link #createSubAnyFilter(String,byte[][])}, but with a shorter method
1221   * name for convenience.
1222   *
1223   * @param  attributeName  The attribute name for this substring filter.  It
1224   *                        must not be {@code null}.
1225   * @param  subAny         The subAny values for this substring filter.  It
1226   *                        must not be {@code null} or empty.
1227   *
1228   * @return  The created substring search filter.
1229   */
1230  @NotNull()
1231  public static Filter subAny(@NotNull final String attributeName,
1232                              @NotNull final byte[]... subAny)
1233  {
1234    return createSubAnyFilter(attributeName, subAny);
1235  }
1236
1237
1238
1239  /**
1240   * Creates a new substring search filter with only a subAny (contains)
1241   * component.
1242   *
1243   * @param  attributeName  The attribute name for this substring filter.  It
1244   *                        must not be {@code null}.
1245   * @param  subAny         The subAny values for this substring filter.  It
1246   *                        must not be {@code null} or empty.
1247   *
1248   * @return  The created substring search filter.
1249   */
1250  @NotNull()
1251  public static Filter createSubAnyFilter(@NotNull final String attributeName,
1252                                          @NotNull final String... subAny)
1253  {
1254    return createSubstringFilter(attributeName, null, subAny, null);
1255  }
1256
1257
1258
1259  /**
1260   * Creates a new substring search filter with only a subAny (contains)
1261   * component.
1262   *
1263   * @param  attributeName  The attribute name for this substring filter.  It
1264   *                        must not be {@code null}.
1265   * @param  subAny         The subAny values for this substring filter.  It
1266   *                        must not be {@code null} or empty.
1267   *
1268   * @return  The created substring search filter.
1269   */
1270  @NotNull()
1271  public static Filter createSubAnyFilter(@NotNull final String attributeName,
1272                                          @NotNull final byte[]... subAny)
1273  {
1274    return createSubstringFilter(attributeName, null, subAny, null);
1275  }
1276
1277
1278
1279  /**
1280   * Creates a new substring search filter with only a subFinal (ends with)
1281   * component.
1282   * <BR><BR>
1283   * This method does exactly the same thing as
1284   * {@link #createSubFinalFilter(String,String)}, but with a shorter method
1285   * name for convenience.
1286   *
1287   * @param  attributeName  The attribute name for this substring filter.  It
1288   *                        must not be {@code null}.
1289   * @param  subFinal       The subFinal component for this substring filter.
1290   *                        It must not be {@code null} or empty.
1291   *
1292   * @return  The created substring search filter.
1293   */
1294  @NotNull()
1295  public static Filter subFinal(@NotNull final String attributeName,
1296                                @NotNull final String subFinal)
1297  {
1298    return createSubFinalFilter(attributeName, subFinal);
1299  }
1300
1301
1302
1303  /**
1304   * Creates a new substring search filter with only a subFinal (ends with)
1305   * component.
1306   * <BR><BR>
1307   * This method does exactly the same thing as
1308   * {@link #createSubFinalFilter(String,byte[])}, but with a shorter method
1309   * name for convenience.
1310   *
1311   * @param  attributeName  The attribute name for this substring filter.  It
1312   *                        must not be {@code null}.
1313   * @param  subFinal       The subFinal component for this substring filter.
1314   *                        It must not be {@code null} or empty.
1315   *
1316   * @return  The created substring search filter.
1317   */
1318  @NotNull()
1319  public static Filter subFinal(@NotNull final String attributeName,
1320                                @NotNull final byte[] subFinal)
1321  {
1322    return createSubFinalFilter(attributeName, subFinal);
1323  }
1324
1325
1326
1327  /**
1328   * Creates a new substring search filter with only a subFinal (ends with)
1329   * component.
1330   *
1331   * @param  attributeName  The attribute name for this substring filter.  It
1332   *                        must not be {@code null}.
1333   * @param  subFinal       The subFinal component for this substring filter.
1334   *                        It must not be {@code null} or empty.
1335   *
1336   * @return  The created substring search filter.
1337   */
1338  @NotNull()
1339  public static Filter createSubFinalFilter(@NotNull final String attributeName,
1340                                            @NotNull final String subFinal)
1341  {
1342    return createSubstringFilter(attributeName, null, null, subFinal);
1343  }
1344
1345
1346
1347  /**
1348   * Creates a new substring search filter with only a subFinal (ends with)
1349   * component.
1350   *
1351   * @param  attributeName  The attribute name for this substring filter.  It
1352   *                        must not be {@code null}.
1353   * @param  subFinal       The subFinal component for this substring filter.
1354   *                        It must not be {@code null} or empty.
1355   *
1356   * @return  The created substring search filter.
1357   */
1358  @NotNull()
1359  public static Filter createSubFinalFilter(@NotNull final String attributeName,
1360                                            @NotNull final byte[] subFinal)
1361  {
1362    return createSubstringFilter(attributeName, null, null, subFinal);
1363  }
1364
1365
1366
1367  /**
1368   * Creates a new greater-or-equal search filter with the provided information.
1369   * <BR><BR>
1370   * This method does exactly the same thing as
1371   * {@link #createGreaterOrEqualFilter(String,String)}, but with a shorter
1372   * method name for convenience.
1373   *
1374   * @param  attributeName   The attribute name for this greater-or-equal
1375   *                         filter.  It must not be {@code null}.
1376   * @param  assertionValue  The assertion value for this greater-or-equal
1377   *                         filter.  It must not be {@code null}.
1378   *
1379   * @return  The created greater-or-equal search filter.
1380   */
1381  @NotNull()
1382  public static Filter greaterOrEqual(@NotNull final String attributeName,
1383                                      @NotNull final String assertionValue)
1384  {
1385    return createGreaterOrEqualFilter(attributeName, assertionValue);
1386  }
1387
1388
1389
1390  /**
1391   * Creates a new greater-or-equal search filter with the provided information.
1392   * <BR><BR>
1393   * This method does exactly the same thing as
1394   * {@link #createGreaterOrEqualFilter(String,byte[])}, but with a shorter
1395   * method name for convenience.
1396   *
1397   * @param  attributeName   The attribute name for this greater-or-equal
1398   *                         filter.  It must not be {@code null}.
1399   * @param  assertionValue  The assertion value for this greater-or-equal
1400   *                         filter.  It must not be {@code null}.
1401   *
1402   * @return  The created greater-or-equal search filter.
1403   */
1404  @NotNull()
1405  public static Filter greaterOrEqual(@NotNull final String attributeName,
1406                                      @NotNull final byte[] assertionValue)
1407  {
1408    return createGreaterOrEqualFilter(attributeName, assertionValue);
1409  }
1410
1411
1412
1413  /**
1414   * Creates a new greater-or-equal search filter with the provided information.
1415   *
1416   * @param  attributeName   The attribute name for this greater-or-equal
1417   *                         filter.  It must not be {@code null}.
1418   * @param  assertionValue  The assertion value for this greater-or-equal
1419   *                         filter.  It must not be {@code null}.
1420   *
1421   * @return  The created greater-or-equal search filter.
1422   */
1423  @NotNull()
1424  public static Filter createGreaterOrEqualFilter(
1425                            @NotNull final String attributeName,
1426                            @NotNull final String assertionValue)
1427  {
1428    Validator.ensureNotNull(attributeName, assertionValue);
1429
1430    return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null,
1431                      attributeName, new ASN1OctetString(assertionValue), null,
1432                      NO_SUB_ANY, null, null, false);
1433  }
1434
1435
1436
1437  /**
1438   * Creates a new greater-or-equal search filter with the provided information.
1439   *
1440   * @param  attributeName   The attribute name for this greater-or-equal
1441   *                         filter.  It must not be {@code null}.
1442   * @param  assertionValue  The assertion value for this greater-or-equal
1443   *                         filter.  It must not be {@code null}.
1444   *
1445   * @return  The created greater-or-equal search filter.
1446   */
1447  @NotNull()
1448  public static Filter createGreaterOrEqualFilter(
1449                            @NotNull final String attributeName,
1450                            @NotNull final byte[] assertionValue)
1451  {
1452    Validator.ensureNotNull(attributeName, assertionValue);
1453
1454    return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null,
1455                      attributeName, new ASN1OctetString(assertionValue), null,
1456                      NO_SUB_ANY, null, null, false);
1457  }
1458
1459
1460
1461  /**
1462   * Creates a new greater-or-equal search filter with the provided information.
1463   *
1464   * @param  attributeName   The attribute name for this greater-or-equal
1465   *                         filter.  It must not be {@code null}.
1466   * @param  assertionValue  The assertion value for this greater-or-equal
1467   *                         filter.  It must not be {@code null}.
1468   *
1469   * @return  The created greater-or-equal search filter.
1470   */
1471  @NotNull()
1472  static Filter createGreaterOrEqualFilter(
1473                     @NotNull final String attributeName,
1474                     @NotNull final ASN1OctetString assertionValue)
1475  {
1476    Validator.ensureNotNull(attributeName, assertionValue);
1477
1478    return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null,
1479                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1480                      null, false);
1481  }
1482
1483
1484
1485  /**
1486   * Creates a new less-or-equal search filter with the provided information.
1487   * <BR><BR>
1488   * This method does exactly the same thing as
1489   * {@link #createLessOrEqualFilter(String,String)}, but with a shorter method
1490   * name for convenience.
1491   *
1492   * @param  attributeName   The attribute name for this less-or-equal
1493   *                         filter.  It must not be {@code null}.
1494   * @param  assertionValue  The assertion value for this less-or-equal
1495   *                         filter.  It must not be {@code null}.
1496   *
1497   * @return  The created less-or-equal search filter.
1498   */
1499  @NotNull()
1500  public static Filter lessOrEqual(@NotNull final String attributeName,
1501                                   @NotNull final String assertionValue)
1502  {
1503    return createLessOrEqualFilter(attributeName, assertionValue);
1504  }
1505
1506
1507
1508  /**
1509   * Creates a new less-or-equal search filter with the provided information.
1510   * <BR><BR>
1511   * This method does exactly the same thing as
1512   * {@link #createLessOrEqualFilter(String,byte[])}, but with a shorter method
1513   * name for convenience.
1514   *
1515   * @param  attributeName   The attribute name for this less-or-equal
1516   *                         filter.  It must not be {@code null}.
1517   * @param  assertionValue  The assertion value for this less-or-equal
1518   *                         filter.  It must not be {@code null}.
1519   *
1520   * @return  The created less-or-equal search filter.
1521   */
1522  @NotNull()
1523  public static Filter lessOrEqual(@NotNull final String attributeName,
1524                                   @NotNull final byte[] assertionValue)
1525  {
1526    return createLessOrEqualFilter(attributeName, assertionValue);
1527  }
1528
1529
1530
1531  /**
1532   * Creates a new less-or-equal search filter with the provided information.
1533   *
1534   * @param  attributeName   The attribute name for this less-or-equal
1535   *                         filter.  It must not be {@code null}.
1536   * @param  assertionValue  The assertion value for this less-or-equal
1537   *                         filter.  It must not be {@code null}.
1538   *
1539   * @return  The created less-or-equal search filter.
1540   */
1541  @NotNull()
1542  public static Filter createLessOrEqualFilter(
1543                            @NotNull final String attributeName,
1544                            @NotNull final String assertionValue)
1545  {
1546    Validator.ensureNotNull(attributeName, assertionValue);
1547
1548    return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null,
1549                      attributeName, new ASN1OctetString(assertionValue), null,
1550                      NO_SUB_ANY, null, null, false);
1551  }
1552
1553
1554
1555  /**
1556   * Creates a new less-or-equal search filter with the provided information.
1557   *
1558   * @param  attributeName   The attribute name for this less-or-equal
1559   *                         filter.  It must not be {@code null}.
1560   * @param  assertionValue  The assertion value for this less-or-equal
1561   *                         filter.  It must not be {@code null}.
1562   *
1563   * @return  The created less-or-equal search filter.
1564   */
1565  @NotNull()
1566  public static Filter createLessOrEqualFilter(
1567                            @NotNull final String attributeName,
1568                            @NotNull final byte[] assertionValue)
1569  {
1570    Validator.ensureNotNull(attributeName, assertionValue);
1571
1572    return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null,
1573                      attributeName, new ASN1OctetString(assertionValue), null,
1574                      NO_SUB_ANY, null, null, false);
1575  }
1576
1577
1578
1579  /**
1580   * Creates a new less-or-equal search filter with the provided information.
1581   *
1582   * @param  attributeName   The attribute name for this less-or-equal
1583   *                         filter.  It must not be {@code null}.
1584   * @param  assertionValue  The assertion value for this less-or-equal
1585   *                         filter.  It must not be {@code null}.
1586   *
1587   * @return  The created less-or-equal search filter.
1588   */
1589  @NotNull()
1590  static Filter createLessOrEqualFilter(
1591                     @NotNull final String attributeName,
1592                     @NotNull final ASN1OctetString assertionValue)
1593  {
1594    Validator.ensureNotNull(attributeName, assertionValue);
1595
1596    return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null,
1597                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1598                      null, false);
1599  }
1600
1601
1602
1603  /**
1604   * Creates a new presence search filter with the provided information.
1605   * <BR><BR>
1606   * This method does exactly the same thing as
1607   * {@link #createPresenceFilter(String)}, but with a shorter method name for
1608   * convenience.
1609   *
1610   * @param  attributeName   The attribute name for this presence filter.  It
1611   *                         must not be {@code null}.
1612   *
1613   * @return  The created presence search filter.
1614   */
1615  @NotNull()
1616  public static Filter present(@NotNull final String attributeName)
1617  {
1618    return createPresenceFilter(attributeName);
1619  }
1620
1621
1622
1623  /**
1624   * Creates a new presence search filter with the provided information.
1625   *
1626   * @param  attributeName   The attribute name for this presence filter.  It
1627   *                         must not be {@code null}.
1628   *
1629   * @return  The created presence search filter.
1630   */
1631  @NotNull()
1632  public static Filter createPresenceFilter(@NotNull final String attributeName)
1633  {
1634    Validator.ensureNotNull(attributeName);
1635
1636    return new Filter(null, FILTER_TYPE_PRESENCE, NO_FILTERS, null,
1637                      attributeName, null, null, NO_SUB_ANY, null, null, false);
1638  }
1639
1640
1641
1642  /**
1643   * Creates a new approximate match search filter with the provided
1644   * information.
1645   * <BR><BR>
1646   * This method does exactly the same thing as
1647   * {@link #createApproximateMatchFilter(String,String)}, but with a shorter
1648   * method name for convenience.
1649   *
1650   * @param  attributeName   The attribute name for this approximate match
1651   *                         filter.  It must not be {@code null}.
1652   * @param  assertionValue  The assertion value for this approximate match
1653   *                         filter.  It must not be {@code null}.
1654   *
1655   * @return  The created approximate match search filter.
1656   */
1657  @NotNull()
1658  public static Filter approximateMatch(@NotNull final String attributeName,
1659                                        @NotNull final String assertionValue)
1660  {
1661    return createApproximateMatchFilter(attributeName, assertionValue);
1662  }
1663
1664
1665
1666  /**
1667   * Creates a new approximate match search filter with the provided
1668   * information.
1669   * <BR><BR>
1670   * This method does exactly the same thing as
1671   * {@link #createApproximateMatchFilter(String,byte[])}, but with a shorter
1672   * method name for convenience.
1673   *
1674   * @param  attributeName   The attribute name for this approximate match
1675   *                         filter.  It must not be {@code null}.
1676   * @param  assertionValue  The assertion value for this approximate match
1677   *                         filter.  It must not be {@code null}.
1678   *
1679   * @return  The created approximate match search filter.
1680   */
1681  @NotNull()
1682  public static Filter approximateMatch(@NotNull final String attributeName,
1683                                        @NotNull final byte[] assertionValue)
1684  {
1685    return createApproximateMatchFilter(attributeName, assertionValue);
1686  }
1687
1688
1689
1690  /**
1691   * Creates a new approximate match search filter with the provided
1692   * information.
1693   *
1694   * @param  attributeName   The attribute name for this approximate match
1695   *                         filter.  It must not be {@code null}.
1696   * @param  assertionValue  The assertion value for this approximate match
1697   *                         filter.  It must not be {@code null}.
1698   *
1699   * @return  The created approximate match search filter.
1700   */
1701  @NotNull()
1702  public static Filter createApproximateMatchFilter(
1703                            @NotNull final String attributeName,
1704                            @NotNull final String assertionValue)
1705  {
1706    Validator.ensureNotNull(attributeName, assertionValue);
1707
1708    return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null,
1709                      attributeName, new ASN1OctetString(assertionValue), null,
1710                      NO_SUB_ANY, null, null, false);
1711  }
1712
1713
1714
1715  /**
1716   * Creates a new approximate match search filter with the provided
1717   * information.
1718   *
1719   * @param  attributeName   The attribute name for this approximate match
1720   *                         filter.  It must not be {@code null}.
1721   * @param  assertionValue  The assertion value for this approximate match
1722   *                         filter.  It must not be {@code null}.
1723   *
1724   * @return  The created approximate match search filter.
1725   */
1726  @NotNull()
1727  public static Filter createApproximateMatchFilter(
1728                            @NotNull final String attributeName,
1729                            @NotNull final byte[] assertionValue)
1730  {
1731    Validator.ensureNotNull(attributeName, assertionValue);
1732
1733    return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null,
1734                      attributeName, new ASN1OctetString(assertionValue), null,
1735                      NO_SUB_ANY, null, null, false);
1736  }
1737
1738
1739
1740  /**
1741   * Creates a new approximate match search filter with the provided
1742   * information.
1743   *
1744   * @param  attributeName   The attribute name for this approximate match
1745   *                         filter.  It must not be {@code null}.
1746   * @param  assertionValue  The assertion value for this approximate match
1747   *                         filter.  It must not be {@code null}.
1748   *
1749   * @return  The created approximate match search filter.
1750   */
1751  @NotNull()
1752  static Filter createApproximateMatchFilter(
1753                     @NotNull final String attributeName,
1754                     @NotNull final ASN1OctetString assertionValue)
1755  {
1756    Validator.ensureNotNull(attributeName, assertionValue);
1757
1758    return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null,
1759                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1760                      null, false);
1761  }
1762
1763
1764
1765  /**
1766   * Creates a new extensible match search filter with the provided
1767   * information.  At least one of the attribute name and matching rule ID must
1768   * be specified, and the assertion value must always be present.
1769   * <BR><BR>
1770   * This method does exactly the same thing as
1771   * {@link #createExtensibleMatchFilter(String,String,boolean,String)}, but
1772   * with a shorter method name for convenience.
1773   *
1774   * @param  attributeName   The attribute name for this extensible match
1775   *                         filter.
1776   * @param  matchingRuleID  The matching rule ID for this extensible match
1777   *                         filter.
1778   * @param  dnAttributes    Indicates whether the match should be performed
1779   *                         against attributes in the target entry's DN.
1780   * @param  assertionValue  The assertion value for this extensible match
1781   *                         filter.  It must not be {@code null}.
1782   *
1783   * @return  The created extensible match search filter.
1784   */
1785  @NotNull()
1786  public static Filter extensibleMatch(@Nullable final String attributeName,
1787                                       @Nullable final String matchingRuleID,
1788                                       final boolean dnAttributes,
1789                                       @NotNull final String assertionValue)
1790  {
1791    return createExtensibleMatchFilter(attributeName, matchingRuleID,
1792         dnAttributes, assertionValue);
1793  }
1794
1795
1796
1797  /**
1798   * Creates a new extensible match search filter with the provided
1799   * information.  At least one of the attribute name and matching rule ID must
1800   * be specified, and the assertion value must always be present.
1801   * <BR><BR>
1802   * This method does exactly the same thing as
1803   * {@link #createExtensibleMatchFilter(String,String,boolean,byte[])}, but
1804   * with a shorter method name for convenience.
1805   *
1806   * @param  attributeName   The attribute name for this extensible match
1807   *                         filter.
1808   * @param  matchingRuleID  The matching rule ID for this extensible match
1809   *                         filter.
1810   * @param  dnAttributes    Indicates whether the match should be performed
1811   *                         against attributes in the target entry's DN.
1812   * @param  assertionValue  The assertion value for this extensible match
1813   *                         filter.  It must not be {@code null}.
1814   *
1815   * @return  The created extensible match search filter.
1816   */
1817  @NotNull()
1818  public static Filter extensibleMatch(@Nullable final String attributeName,
1819                                       @Nullable final String matchingRuleID,
1820                                       final boolean dnAttributes,
1821                                       @NotNull final byte[] assertionValue)
1822  {
1823    return createExtensibleMatchFilter(attributeName, matchingRuleID,
1824         dnAttributes, assertionValue);
1825  }
1826
1827
1828
1829  /**
1830   * Creates a new extensible match search filter with the provided
1831   * information.  At least one of the attribute name and matching rule ID must
1832   * be specified, and the assertion value must always be present.
1833   *
1834   * @param  attributeName   The attribute name for this extensible match
1835   *                         filter.
1836   * @param  matchingRuleID  The matching rule ID for this extensible match
1837   *                         filter.
1838   * @param  dnAttributes    Indicates whether the match should be performed
1839   *                         against attributes in the target entry's DN.
1840   * @param  assertionValue  The assertion value for this extensible match
1841   *                         filter.  It must not be {@code null}.
1842   *
1843   * @return  The created extensible match search filter.
1844   */
1845  @NotNull()
1846  public static Filter createExtensibleMatchFilter(
1847                            @Nullable final String attributeName,
1848                            @Nullable final String matchingRuleID,
1849                            final boolean dnAttributes,
1850                            @NotNull final String assertionValue)
1851  {
1852    Validator.ensureNotNull(assertionValue);
1853    Validator.ensureFalse((attributeName == null) && (matchingRuleID == null));
1854
1855    return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null,
1856                      attributeName, new ASN1OctetString(assertionValue), null,
1857                      NO_SUB_ANY, null, matchingRuleID, dnAttributes);
1858  }
1859
1860
1861
1862  /**
1863   * Creates a new extensible match search filter with the provided
1864   * information.  At least one of the attribute name and matching rule ID must
1865   * be specified, and the assertion value must always be present.
1866   *
1867   * @param  attributeName   The attribute name for this extensible match
1868   *                         filter.
1869   * @param  matchingRuleID  The matching rule ID for this extensible match
1870   *                         filter.
1871   * @param  dnAttributes    Indicates whether the match should be performed
1872   *                         against attributes in the target entry's DN.
1873   * @param  assertionValue  The assertion value for this extensible match
1874   *                         filter.  It must not be {@code null}.
1875   *
1876   * @return  The created extensible match search filter.
1877   */
1878  @NotNull()
1879  public static Filter createExtensibleMatchFilter(
1880                            @Nullable final String attributeName,
1881                            @Nullable final String matchingRuleID,
1882                            final boolean dnAttributes,
1883                            @NotNull final byte[] assertionValue)
1884  {
1885    Validator.ensureNotNull(assertionValue);
1886    Validator.ensureFalse((attributeName == null) && (matchingRuleID == null));
1887
1888    return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null,
1889                      attributeName, new ASN1OctetString(assertionValue), null,
1890                      NO_SUB_ANY, null, matchingRuleID, dnAttributes);
1891  }
1892
1893
1894
1895  /**
1896   * Creates a new extensible match search filter with the provided
1897   * information.  At least one of the attribute name and matching rule ID must
1898   * be specified, and the assertion value must always be present.
1899   *
1900   * @param  attributeName   The attribute name for this extensible match
1901   *                         filter.
1902   * @param  matchingRuleID  The matching rule ID for this extensible match
1903   *                         filter.
1904   * @param  dnAttributes    Indicates whether the match should be performed
1905   *                         against attributes in the target entry's DN.
1906   * @param  assertionValue  The assertion value for this extensible match
1907   *                         filter.  It must not be {@code null}.
1908   *
1909   * @return  The created approximate match search filter.
1910   */
1911  @NotNull()
1912  static Filter createExtensibleMatchFilter(
1913                     @Nullable final String attributeName,
1914                     @Nullable final String matchingRuleID,
1915                     final boolean dnAttributes,
1916                     @NotNull final ASN1OctetString assertionValue)
1917  {
1918    Validator.ensureNotNull(assertionValue);
1919    Validator.ensureFalse((attributeName == null) && (matchingRuleID == null));
1920
1921    return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null,
1922                      attributeName, assertionValue, null, NO_SUB_ANY, null,
1923                      matchingRuleID, dnAttributes);
1924  }
1925
1926
1927
1928  /**
1929   * Creates a new search filter from the provided string representation.
1930   *
1931   * @param  filterString  The string representation of the filter to create.
1932   *                       It must not be {@code null}.
1933   *
1934   * @return  The search filter decoded from the provided filter string.
1935   *
1936   * @throws  LDAPException  If the provided string cannot be decoded as a valid
1937   *                         LDAP search filter.
1938   */
1939  @NotNull()
1940  public static Filter create(@NotNull final String filterString)
1941         throws LDAPException
1942  {
1943    Validator.ensureNotNull(filterString);
1944
1945    return create(filterString, 0, (filterString.length() - 1), 0);
1946  }
1947
1948
1949
1950  /**
1951   * Creates a new search filter from the specified portion of the provided
1952   * string representation.
1953   *
1954   * @param  filterString  The string representation of the filter to create.
1955   * @param  startPos      The position of the first character to consider as
1956   *                       part of the filter.
1957   * @param  endPos        The position of the last character to consider as
1958   *                       part of the filter.
1959   * @param  depth         The current nesting depth for this filter.  It should
1960   *                       be increased by one for each AND, OR, or NOT filter
1961   *                       encountered, in order to prevent stack overflow
1962   *                       errors from excessive recursion.
1963   *
1964   * @return  The decoded search filter.
1965   *
1966   * @throws  LDAPException  If the provided string cannot be decoded as a valid
1967   *                         LDAP search filter.
1968   */
1969  @NotNull()
1970  private static Filter create(@NotNull final String filterString,
1971                               final int startPos, final int endPos,
1972                               final int depth)
1973          throws LDAPException
1974  {
1975    if (depth > 100)
1976    {
1977      throw new LDAPException(ResultCode.FILTER_ERROR,
1978           ERR_FILTER_TOO_DEEP.get(filterString));
1979    }
1980
1981    final byte              filterType;
1982    final Filter[]          filterComps;
1983    final Filter            notComp;
1984    final String            attrName;
1985    final ASN1OctetString   assertionValue;
1986    final ASN1OctetString   subInitial;
1987    final ASN1OctetString[] subAny;
1988    final ASN1OctetString   subFinal;
1989    final String            matchingRuleID;
1990    final boolean           dnAttributes;
1991
1992    if (startPos >= endPos)
1993    {
1994      throw new LDAPException(ResultCode.FILTER_ERROR,
1995           ERR_FILTER_TOO_SHORT.get(filterString));
1996    }
1997
1998    int l = startPos;
1999    int r = endPos;
2000
2001    // First, see if the provided filter string is enclosed in parentheses, like
2002    // it should be.  If so, then strip off the outer parentheses.
2003    if (filterString.charAt(l) == '(')
2004    {
2005      if (filterString.charAt(r) == ')')
2006      {
2007        l++;
2008        r--;
2009      }
2010      else
2011      {
2012        throw new LDAPException(ResultCode.FILTER_ERROR,
2013             ERR_FILTER_OPEN_WITHOUT_CLOSE.get(filterString, l, r));
2014      }
2015    }
2016    else
2017    {
2018      // This is technically an error, and it's a bad practice.  If we're
2019      // working on the complete filter string then we'll let it slide, but
2020      // otherwise we'll raise an error.
2021      if (l != 0)
2022      {
2023        throw new LDAPException(ResultCode.FILTER_ERROR,
2024             ERR_FILTER_MISSING_PARENTHESES.get(filterString,
2025                  filterString.substring(l, r+1)));
2026      }
2027    }
2028
2029
2030    // Look at the first character of the filter to see if it's an '&', '|', or
2031    // '!'.  If we find a parenthesis, then that's an error.
2032    switch (filterString.charAt(l))
2033    {
2034      case '&':
2035        filterType     = FILTER_TYPE_AND;
2036        filterComps    = parseFilterComps(filterString, l+1, r, depth+1);
2037        notComp        = null;
2038        attrName       = null;
2039        assertionValue = null;
2040        subInitial     = null;
2041        subAny         = NO_SUB_ANY;
2042        subFinal       = null;
2043        matchingRuleID = null;
2044        dnAttributes   = false;
2045        break;
2046
2047      case '|':
2048        filterType     = FILTER_TYPE_OR;
2049        filterComps    = parseFilterComps(filterString, l+1, r, depth+1);
2050        notComp        = null;
2051        attrName       = null;
2052        assertionValue = null;
2053        subInitial     = null;
2054        subAny         = NO_SUB_ANY;
2055        subFinal       = null;
2056        matchingRuleID = null;
2057        dnAttributes   = false;
2058        break;
2059
2060      case '!':
2061        filterType     = FILTER_TYPE_NOT;
2062        filterComps    = NO_FILTERS;
2063        notComp        = create(filterString, l+1, r, depth+1);
2064        attrName       = null;
2065        assertionValue = null;
2066        subInitial     = null;
2067        subAny         = NO_SUB_ANY;
2068        subFinal       = null;
2069        matchingRuleID = null;
2070        dnAttributes   = false;
2071        break;
2072
2073      case '(':
2074        throw new LDAPException(ResultCode.FILTER_ERROR,
2075             ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l));
2076
2077      case ':':
2078        // This must be an extensible matching filter that starts with a
2079        // dnAttributes flag and/or matching rule ID, and we should parse it
2080        // accordingly.
2081        filterType  = FILTER_TYPE_EXTENSIBLE_MATCH;
2082        filterComps = NO_FILTERS;
2083        notComp     = null;
2084        attrName    = null;
2085        subInitial  = null;
2086        subAny      = NO_SUB_ANY;
2087        subFinal    = null;
2088
2089        // The next element must be either the "dn:{matchingruleid}" or just
2090        // "{matchingruleid}", and it must be followed by a colon.
2091        final int dnMRIDStart = ++l;
2092        while ((l <= r) && (filterString.charAt(l) != ':'))
2093        {
2094          l++;
2095        }
2096
2097        if (l > r)
2098        {
2099          throw new LDAPException(ResultCode.FILTER_ERROR,
2100               ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos));
2101        }
2102        else if (l == dnMRIDStart)
2103        {
2104          throw new LDAPException(ResultCode.FILTER_ERROR,
2105               ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
2106        }
2107        final String s = filterString.substring(dnMRIDStart, l++);
2108        if (s.equalsIgnoreCase("dn"))
2109        {
2110          dnAttributes = true;
2111
2112          // The colon must be followed by the matching rule ID and another
2113          // colon.
2114          final int mrIDStart = l;
2115          while ((l < r) && (filterString.charAt(l) != ':'))
2116          {
2117            l++;
2118          }
2119
2120          if (l >= r)
2121          {
2122            throw new LDAPException(ResultCode.FILTER_ERROR,
2123                 ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos));
2124          }
2125
2126          matchingRuleID = filterString.substring(mrIDStart, l);
2127          if (matchingRuleID.isEmpty())
2128          {
2129            throw new LDAPException(ResultCode.FILTER_ERROR,
2130                 ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
2131          }
2132
2133          if ((++l > r) || (filterString.charAt(l) != '='))
2134          {
2135            throw new LDAPException(ResultCode.FILTER_ERROR,
2136                 ERR_FILTER_UNEXPECTED_CHAR_AFTER_MRID.get(filterString,
2137                      startPos, filterString.charAt(l)));
2138          }
2139        }
2140        else
2141        {
2142          matchingRuleID = s;
2143          dnAttributes = false;
2144
2145          // The colon must be followed by an equal sign.
2146          if ((l > r) || (filterString.charAt(l) != '='))
2147          {
2148            throw new LDAPException(ResultCode.FILTER_ERROR,
2149                 ERR_FILTER_NO_EQUAL_AFTER_MRID.get(filterString, startPos));
2150          }
2151        }
2152
2153        // Now we should be able to read the value, handling any escape
2154        // characters as we go.
2155        l++;
2156        final ByteStringBuffer valueBuffer = new ByteStringBuffer(r - l + 1);
2157        while (l <= r)
2158        {
2159          final char c = filterString.charAt(l);
2160          if (c == '\\')
2161          {
2162            l = readEscapedHexString(filterString, ++l, valueBuffer);
2163          }
2164          else if (c == '(')
2165          {
2166            throw new LDAPException(ResultCode.FILTER_ERROR,
2167                 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l));
2168          }
2169          else if (c == ')')
2170          {
2171            throw new LDAPException(ResultCode.FILTER_ERROR,
2172                 ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l));
2173          }
2174          else
2175          {
2176            valueBuffer.append(c);
2177            l++;
2178          }
2179        }
2180        assertionValue = new ASN1OctetString(valueBuffer.toByteArray());
2181        break;
2182
2183
2184      default:
2185        // We know that it's not an AND, OR, or NOT filter, so we can eliminate
2186        // the variables used only for them.
2187        filterComps = NO_FILTERS;
2188        notComp     = null;
2189
2190
2191        // We should now be able to read a non-empty attribute name.
2192        final int attrStartPos = l;
2193        int     attrEndPos   = -1;
2194        byte    tempFilterType = 0x00;
2195        boolean filterTypeKnown = false;
2196        boolean equalFound = false;
2197attrNameLoop:
2198        while (l <= r)
2199        {
2200          final char c = filterString.charAt(l++);
2201          switch (c)
2202          {
2203            case ':':
2204              tempFilterType = FILTER_TYPE_EXTENSIBLE_MATCH;
2205              filterTypeKnown = true;
2206              attrEndPos = l - 1;
2207              break attrNameLoop;
2208
2209            case '>':
2210              tempFilterType = FILTER_TYPE_GREATER_OR_EQUAL;
2211              filterTypeKnown = true;
2212              attrEndPos = l - 1;
2213
2214              if (l <= r)
2215              {
2216                if (filterString.charAt(l++) != '=')
2217                {
2218                  throw new LDAPException(ResultCode.FILTER_ERROR,
2219                       ERR_FILTER_UNEXPECTED_CHAR_AFTER_GT.get(filterString,
2220                            startPos, filterString.charAt(l-1)));
2221                }
2222              }
2223              else
2224              {
2225                throw new LDAPException(ResultCode.FILTER_ERROR,
2226                     ERR_FILTER_END_AFTER_GT.get(filterString, startPos));
2227              }
2228              break attrNameLoop;
2229
2230            case '<':
2231              tempFilterType = FILTER_TYPE_LESS_OR_EQUAL;
2232              filterTypeKnown = true;
2233              attrEndPos = l - 1;
2234
2235              if (l <= r)
2236              {
2237                if (filterString.charAt(l++) != '=')
2238                {
2239                  throw new LDAPException(ResultCode.FILTER_ERROR,
2240                       ERR_FILTER_UNEXPECTED_CHAR_AFTER_LT.get(filterString,
2241                            startPos, filterString.charAt(l-1)));
2242                }
2243              }
2244              else
2245              {
2246                throw new LDAPException(ResultCode.FILTER_ERROR,
2247                     ERR_FILTER_END_AFTER_LT.get(filterString, startPos));
2248              }
2249              break attrNameLoop;
2250
2251            case '~':
2252              tempFilterType = FILTER_TYPE_APPROXIMATE_MATCH;
2253              filterTypeKnown = true;
2254              attrEndPos = l - 1;
2255
2256              if (l <= r)
2257              {
2258                if (filterString.charAt(l++) != '=')
2259                {
2260                  throw new LDAPException(ResultCode.FILTER_ERROR,
2261                       ERR_FILTER_UNEXPECTED_CHAR_AFTER_TILDE.get(filterString,
2262                            startPos, filterString.charAt(l-1)));
2263                }
2264              }
2265              else
2266              {
2267                throw new LDAPException(ResultCode.FILTER_ERROR,
2268                     ERR_FILTER_END_AFTER_TILDE.get(filterString, startPos));
2269              }
2270              break attrNameLoop;
2271
2272            case '=':
2273              // It could be either an equality, presence, or substring filter.
2274              // We'll need to look at the value to determine that.
2275              attrEndPos = l - 1;
2276              equalFound = true;
2277              break attrNameLoop;
2278          }
2279        }
2280
2281        if (attrEndPos <= attrStartPos)
2282        {
2283          if (equalFound)
2284          {
2285            throw new LDAPException(ResultCode.FILTER_ERROR,
2286                 ERR_FILTER_EMPTY_ATTR_NAME.get(filterString, startPos));
2287          }
2288          else
2289          {
2290            throw new LDAPException(ResultCode.FILTER_ERROR,
2291                 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos));
2292          }
2293        }
2294        attrName = filterString.substring(attrStartPos, attrEndPos);
2295
2296
2297        // See if we're dealing with an extensible match filter.  If so, then
2298        // we may still need to do additional parsing to get the matching rule
2299        // ID and/or the dnAttributes flag.  Otherwise, we can rule out any
2300        // variables that are specific to extensible matching filters.
2301        if (filterTypeKnown && (tempFilterType == FILTER_TYPE_EXTENSIBLE_MATCH))
2302        {
2303          if (l > r)
2304          {
2305            throw new LDAPException(ResultCode.FILTER_ERROR,
2306                 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos));
2307          }
2308
2309          final char c = filterString.charAt(l++);
2310          if (c == '=')
2311          {
2312            matchingRuleID = null;
2313            dnAttributes   = false;
2314          }
2315          else
2316          {
2317            // We have either a matching rule ID or a dnAttributes flag, or
2318            // both.  Iterate through the filter until we find the equal sign,
2319            // and then figure out what we have from that.
2320            equalFound = false;
2321            final int substrStartPos = l - 1;
2322            while (l <= r)
2323            {
2324              if (filterString.charAt(l++) == '=')
2325              {
2326                equalFound = true;
2327                break;
2328              }
2329            }
2330
2331            if (! equalFound)
2332            {
2333              throw new LDAPException(ResultCode.FILTER_ERROR,
2334                   ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos));
2335            }
2336
2337            final String substr = filterString.substring(substrStartPos, l-1);
2338            final String lowerSubstr = StaticUtils.toLowerCase(substr);
2339            if (! substr.endsWith(":"))
2340            {
2341              throw new LDAPException(ResultCode.FILTER_ERROR,
2342                   ERR_FILTER_CANNOT_PARSE_MRID.get(filterString, startPos));
2343            }
2344
2345            if (lowerSubstr.equals("dn:"))
2346            {
2347              matchingRuleID = null;
2348              dnAttributes   = true;
2349            }
2350            else if (lowerSubstr.startsWith("dn:"))
2351            {
2352              matchingRuleID = substr.substring(3, substr.length() - 1);
2353              if (matchingRuleID.isEmpty())
2354              {
2355                throw new LDAPException(ResultCode.FILTER_ERROR,
2356                     ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
2357              }
2358
2359              dnAttributes   = true;
2360            }
2361            else
2362            {
2363              matchingRuleID = substr.substring(0, substr.length() - 1);
2364              dnAttributes   = false;
2365
2366              if (matchingRuleID.isEmpty())
2367              {
2368                throw new LDAPException(ResultCode.FILTER_ERROR,
2369                     ERR_FILTER_EMPTY_MRID.get(filterString, startPos));
2370              }
2371            }
2372          }
2373        }
2374        else
2375        {
2376          matchingRuleID = null;
2377          dnAttributes   = false;
2378        }
2379
2380
2381        // At this point, we're ready to read the value.  If we still don't
2382        // know what type of filter we're dealing with, then we can tell that
2383        // based on asterisks in the value.
2384        if (l > r)
2385        {
2386          assertionValue = new ASN1OctetString();
2387          if (! filterTypeKnown)
2388          {
2389            tempFilterType = FILTER_TYPE_EQUALITY;
2390          }
2391
2392          subInitial = null;
2393          subAny     = NO_SUB_ANY;
2394          subFinal   = null;
2395        }
2396        else if (l == r)
2397        {
2398          if (filterTypeKnown)
2399          {
2400            switch (filterString.charAt(l))
2401            {
2402              case '*':
2403              case '(':
2404              case ')':
2405              case '\\':
2406                throw new LDAPException(ResultCode.FILTER_ERROR,
2407                     ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString,
2408                          startPos, filterString.charAt(l)));
2409            }
2410
2411            assertionValue =
2412                 new ASN1OctetString(filterString.substring(l, l+1));
2413          }
2414          else
2415          {
2416            final char c = filterString.charAt(l);
2417            switch (c)
2418            {
2419              case '*':
2420                tempFilterType = FILTER_TYPE_PRESENCE;
2421                assertionValue = null;
2422                break;
2423
2424              case '\\':
2425              case '(':
2426              case ')':
2427                throw new LDAPException(ResultCode.FILTER_ERROR,
2428                     ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString,
2429                          startPos, filterString.charAt(l)));
2430
2431              default:
2432                tempFilterType = FILTER_TYPE_EQUALITY;
2433                assertionValue =
2434                     new ASN1OctetString(filterString.substring(l, l+1));
2435                break;
2436            }
2437          }
2438
2439          subInitial     = null;
2440          subAny         = NO_SUB_ANY;
2441          subFinal       = null;
2442        }
2443        else
2444        {
2445          if (! filterTypeKnown)
2446          {
2447            tempFilterType = FILTER_TYPE_EQUALITY;
2448          }
2449
2450          final int valueStartPos = l;
2451          ASN1OctetString tempSubInitial = null;
2452          ASN1OctetString tempSubFinal   = null;
2453          final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1);
2454          ByteStringBuffer buffer = new ByteStringBuffer(r - l + 1);
2455          while (l <= r)
2456          {
2457            final char c = filterString.charAt(l++);
2458            switch (c)
2459            {
2460              case '*':
2461                if (filterTypeKnown)
2462                {
2463                  throw new LDAPException(ResultCode.FILTER_ERROR,
2464                       ERR_FILTER_UNEXPECTED_ASTERISK.get(filterString,
2465                            startPos));
2466                }
2467                else
2468                {
2469                  if ((l-1) == valueStartPos)
2470                  {
2471                    // The first character is an asterisk, so there is no
2472                    // subInitial.
2473                  }
2474                  else
2475                  {
2476                    if (tempFilterType == FILTER_TYPE_SUBSTRING)
2477                    {
2478                      // We already know that it's a substring filter, so this
2479                      // must be a subAny portion.  However, if the buffer is
2480                      // empty, then that means that there were two asterisks
2481                      // right next to each other, which is invalid.
2482                      if (buffer.length() == 0)
2483                      {
2484                        throw new LDAPException(ResultCode.FILTER_ERROR,
2485                             ERR_FILTER_UNEXPECTED_DOUBLE_ASTERISK.get(
2486                                  filterString, startPos));
2487                      }
2488                      else
2489                      {
2490                        subAnyList.add(
2491                             new ASN1OctetString(buffer.toByteArray()));
2492                        buffer = new ByteStringBuffer(r - l + 1);
2493                      }
2494                    }
2495                    else
2496                    {
2497                      // We haven't yet set the filter type, so the buffer must
2498                      // contain the subInitial portion.  We also know it's not
2499                      // empty because of an earlier check.
2500                      tempSubInitial =
2501                           new ASN1OctetString(buffer.toByteArray());
2502                      buffer = new ByteStringBuffer(r - l + 1);
2503                    }
2504                  }
2505
2506                  tempFilterType = FILTER_TYPE_SUBSTRING;
2507                }
2508                break;
2509
2510              case '\\':
2511                l = readEscapedHexString(filterString, l, buffer);
2512                break;
2513
2514              case '(':
2515                throw new LDAPException(ResultCode.FILTER_ERROR,
2516                     ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l));
2517
2518              case ')':
2519                throw new LDAPException(ResultCode.FILTER_ERROR,
2520                     ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l));
2521
2522              default:
2523                if (Character.isHighSurrogate(c))
2524                {
2525                  if (l <= r)
2526                  {
2527                    final char c2 = filterString.charAt(l);
2528                    if (Character.isLowSurrogate(c2))
2529                    {
2530                      l++;
2531                      final int codePoint = Character.toCodePoint(c, c2);
2532                      buffer.append(new String(new int[] { codePoint }, 0, 1));
2533                      break;
2534                    }
2535                  }
2536                }
2537
2538                buffer.append(c);
2539                break;
2540            }
2541          }
2542
2543          if ((tempFilterType == FILTER_TYPE_SUBSTRING) &&
2544               (! buffer.isEmpty()))
2545          {
2546            // The buffer must contain the subFinal portion.
2547            tempSubFinal = new ASN1OctetString(buffer.toByteArray());
2548          }
2549
2550          subInitial = tempSubInitial;
2551          subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
2552          subFinal = tempSubFinal;
2553
2554          if (tempFilterType == FILTER_TYPE_SUBSTRING)
2555          {
2556            assertionValue = null;
2557          }
2558          else
2559          {
2560            assertionValue = new ASN1OctetString(buffer.toByteArray());
2561          }
2562        }
2563
2564        filterType = tempFilterType;
2565        break;
2566    }
2567
2568
2569    if (startPos == 0)
2570    {
2571      return new Filter(filterString, filterType, filterComps, notComp,
2572                        attrName, assertionValue, subInitial, subAny, subFinal,
2573                        matchingRuleID, dnAttributes);
2574    }
2575    else
2576    {
2577      return new Filter(filterString.substring(startPos, endPos+1), filterType,
2578                        filterComps, notComp, attrName, assertionValue,
2579                        subInitial, subAny, subFinal, matchingRuleID,
2580                        dnAttributes);
2581    }
2582  }
2583
2584
2585
2586  /**
2587   * Parses the specified portion of the provided filter string to obtain a set
2588   * of filter components for use in an AND or OR filter.
2589   *
2590   * @param  filterString  The string representation for the set of filters.
2591   * @param  startPos      The position of the first character to consider as
2592   *                       part of the first filter.
2593   * @param  endPos        The position of the last character to consider as
2594   *                       part of the last filter.
2595   * @param  depth         The current nesting depth for this filter.  It should
2596   *                       be increased by one for each AND, OR, or NOT filter
2597   *                       encountered, in order to prevent stack overflow
2598   *                       errors from excessive recursion.
2599   *
2600   * @return  The decoded set of search filters.
2601   *
2602   * @throws  LDAPException  If the provided string cannot be decoded as a set
2603   *                         of LDAP search filters.
2604   */
2605  @NotNull()
2606  private static Filter[] parseFilterComps(@NotNull final String filterString,
2607                                           final int startPos, final int endPos,
2608                                           final int depth)
2609          throws LDAPException
2610  {
2611    if (startPos > endPos)
2612    {
2613      // This is acceptable, since it can represent an LDAP TRUE or FALSE filter
2614      // as described in RFC 4526.
2615      return NO_FILTERS;
2616    }
2617
2618
2619    // The set of filters must start with an opening parenthesis, and end with a
2620    // closing parenthesis.
2621    if (filterString.charAt(startPos) != '(')
2622    {
2623      throw new LDAPException(ResultCode.FILTER_ERROR,
2624           ERR_FILTER_EXPECTED_OPEN_PAREN.get(filterString, startPos));
2625    }
2626    if (filterString.charAt(endPos) != ')')
2627    {
2628      throw new LDAPException(ResultCode.FILTER_ERROR,
2629           ERR_FILTER_EXPECTED_CLOSE_PAREN.get(filterString, startPos));
2630    }
2631
2632
2633    // Iterate through the specified portion of the filter string and count
2634    // opening and closing parentheses to figure out where one filter ends and
2635    // another begins.
2636    final ArrayList<Filter> filterList = new ArrayList<>(5);
2637    int filterStartPos = startPos;
2638    int pos = startPos;
2639    int numOpen = 0;
2640    while (pos <= endPos)
2641    {
2642      final char c = filterString.charAt(pos++);
2643      if (c == '(')
2644      {
2645        numOpen++;
2646      }
2647      else if (c == ')')
2648      {
2649        numOpen--;
2650        if (numOpen == 0)
2651        {
2652          filterList.add(create(filterString, filterStartPos, pos-1, depth));
2653          filterStartPos = pos;
2654        }
2655      }
2656    }
2657
2658    if (numOpen != 0)
2659    {
2660      throw new LDAPException(ResultCode.FILTER_ERROR,
2661           ERR_FILTER_MISMATCHED_PARENS.get(filterString, startPos, endPos));
2662    }
2663
2664    return filterList.toArray(new Filter[filterList.size()]);
2665  }
2666
2667
2668
2669  /**
2670   * Reads one or more hex-encoded bytes from the specified portion of the
2671   * filter string.
2672   *
2673   * @param  filterString  The string from which the data is to be read.
2674   * @param  startPos      The position at which to start reading.  This should
2675   *                       be the position of first hex character immediately
2676   *                       after the initial backslash.
2677   * @param  buffer        The buffer to which the decoded string portion should
2678   *                       be appended.
2679   *
2680   * @return  The position at which the caller may resume parsing.
2681   *
2682   * @throws  LDAPException  If a problem occurs while reading hex-encoded
2683   *                         bytes.
2684   */
2685  private static int readEscapedHexString(@NotNull final String filterString,
2686                          final int startPos,
2687                          @NotNull final ByteStringBuffer buffer)
2688          throws LDAPException
2689  {
2690    final byte b;
2691    switch (filterString.charAt(startPos))
2692    {
2693      case '0':
2694        b = 0x00;
2695        break;
2696      case '1':
2697        b = 0x10;
2698        break;
2699      case '2':
2700        b = 0x20;
2701        break;
2702      case '3':
2703        b = 0x30;
2704        break;
2705      case '4':
2706        b = 0x40;
2707        break;
2708      case '5':
2709        b = 0x50;
2710        break;
2711      case '6':
2712        b = 0x60;
2713        break;
2714      case '7':
2715        b = 0x70;
2716        break;
2717      case '8':
2718        b = (byte) 0x80;
2719        break;
2720      case '9':
2721        b = (byte) 0x90;
2722        break;
2723      case 'a':
2724      case 'A':
2725        b = (byte) 0xA0;
2726        break;
2727      case 'b':
2728      case 'B':
2729        b = (byte) 0xB0;
2730        break;
2731      case 'c':
2732      case 'C':
2733        b = (byte) 0xC0;
2734        break;
2735      case 'd':
2736      case 'D':
2737        b = (byte) 0xD0;
2738        break;
2739      case 'e':
2740      case 'E':
2741        b = (byte) 0xE0;
2742        break;
2743      case 'f':
2744      case 'F':
2745        b = (byte) 0xF0;
2746        break;
2747      default:
2748        throw new LDAPException(ResultCode.FILTER_ERROR,
2749             ERR_FILTER_INVALID_HEX_CHAR.get(filterString,
2750                  filterString.charAt(startPos), startPos));
2751    }
2752
2753    switch (filterString.charAt(startPos+1))
2754    {
2755      case '0':
2756        buffer.append(b);
2757        break;
2758      case '1':
2759        buffer.append((byte) (b | 0x01));
2760        break;
2761      case '2':
2762        buffer.append((byte) (b | 0x02));
2763        break;
2764      case '3':
2765        buffer.append((byte) (b | 0x03));
2766        break;
2767      case '4':
2768        buffer.append((byte) (b | 0x04));
2769        break;
2770      case '5':
2771        buffer.append((byte) (b | 0x05));
2772        break;
2773      case '6':
2774        buffer.append((byte) (b | 0x06));
2775        break;
2776      case '7':
2777        buffer.append((byte) (b | 0x07));
2778        break;
2779      case '8':
2780        buffer.append((byte) (b | 0x08));
2781        break;
2782      case '9':
2783        buffer.append((byte) (b | 0x09));
2784        break;
2785      case 'a':
2786      case 'A':
2787        buffer.append((byte) (b | 0x0A));
2788        break;
2789      case 'b':
2790      case 'B':
2791        buffer.append((byte) (b | 0x0B));
2792        break;
2793      case 'c':
2794      case 'C':
2795        buffer.append((byte) (b | 0x0C));
2796        break;
2797      case 'd':
2798      case 'D':
2799        buffer.append((byte) (b | 0x0D));
2800        break;
2801      case 'e':
2802      case 'E':
2803        buffer.append((byte) (b | 0x0E));
2804        break;
2805      case 'f':
2806      case 'F':
2807        buffer.append((byte) (b | 0x0F));
2808        break;
2809      default:
2810        throw new LDAPException(ResultCode.FILTER_ERROR,
2811             ERR_FILTER_INVALID_HEX_CHAR.get(filterString,
2812                  filterString.charAt(startPos+1), (startPos+1)));
2813    }
2814
2815    return startPos+2;
2816  }
2817
2818
2819
2820  /**
2821   * Writes an ASN.1-encoded representation of this filter to the provided ASN.1
2822   * buffer.
2823   *
2824   * @param  buffer  The ASN.1 buffer to which the encoded representation should
2825   *                 be written.
2826   */
2827  public void writeTo(@NotNull final ASN1Buffer buffer)
2828  {
2829    switch (filterType)
2830    {
2831      case FILTER_TYPE_AND:
2832      case FILTER_TYPE_OR:
2833        final ASN1BufferSet compSet = buffer.beginSet(filterType);
2834        for (final Filter f : filterComps)
2835        {
2836          f.writeTo(buffer);
2837        }
2838        compSet.end();
2839        break;
2840
2841      case FILTER_TYPE_NOT:
2842        buffer.addElement(
2843             new ASN1Element(filterType, notComp.encode().encode()));
2844        break;
2845
2846      case FILTER_TYPE_EQUALITY:
2847      case FILTER_TYPE_GREATER_OR_EQUAL:
2848      case FILTER_TYPE_LESS_OR_EQUAL:
2849      case FILTER_TYPE_APPROXIMATE_MATCH:
2850        final ASN1BufferSequence avaSequence = buffer.beginSequence(filterType);
2851        buffer.addOctetString(attrName);
2852        buffer.addElement(assertionValue);
2853        avaSequence.end();
2854        break;
2855
2856      case FILTER_TYPE_SUBSTRING:
2857        final ASN1BufferSequence subFilterSequence =
2858             buffer.beginSequence(filterType);
2859        buffer.addOctetString(attrName);
2860
2861        final ASN1BufferSequence valueSequence = buffer.beginSequence();
2862        if (subInitial != null)
2863        {
2864          buffer.addOctetString(SUBSTRING_TYPE_SUBINITIAL,
2865                                subInitial.getValue());
2866        }
2867
2868        for (final ASN1OctetString s : subAny)
2869        {
2870          buffer.addOctetString(SUBSTRING_TYPE_SUBANY, s.getValue());
2871        }
2872
2873        if (subFinal != null)
2874        {
2875          buffer.addOctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue());
2876        }
2877        valueSequence.end();
2878        subFilterSequence.end();
2879        break;
2880
2881      case FILTER_TYPE_PRESENCE:
2882        buffer.addOctetString(filterType, attrName);
2883        break;
2884
2885      case FILTER_TYPE_EXTENSIBLE_MATCH:
2886        final ASN1BufferSequence mrSequence = buffer.beginSequence(filterType);
2887        if (matchingRuleID != null)
2888        {
2889          buffer.addOctetString(EXTENSIBLE_TYPE_MATCHING_RULE_ID,
2890                                matchingRuleID);
2891        }
2892
2893        if (attrName != null)
2894        {
2895          buffer.addOctetString(EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName);
2896        }
2897
2898        buffer.addOctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2899                              assertionValue.getValue());
2900
2901        if (dnAttributes)
2902        {
2903          buffer.addBoolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true);
2904        }
2905        mrSequence.end();
2906        break;
2907    }
2908  }
2909
2910
2911
2912  /**
2913   * Encodes this search filter to an ASN.1 element suitable for inclusion in an
2914   * LDAP search request protocol op.
2915   *
2916   * @return  An ASN.1 element containing the encoded search filter.
2917   */
2918  @NotNull()
2919  public ASN1Element encode()
2920  {
2921    switch (filterType)
2922    {
2923      case FILTER_TYPE_AND:
2924      case FILTER_TYPE_OR:
2925        final ASN1Element[] filterElements =
2926             new ASN1Element[filterComps.length];
2927        for (int i=0; i < filterComps.length; i++)
2928        {
2929          filterElements[i] = filterComps[i].encode();
2930        }
2931        return new ASN1Set(filterType, filterElements);
2932
2933
2934      case FILTER_TYPE_NOT:
2935        return new ASN1Element(filterType, notComp.encode().encode());
2936
2937
2938      case FILTER_TYPE_EQUALITY:
2939      case FILTER_TYPE_GREATER_OR_EQUAL:
2940      case FILTER_TYPE_LESS_OR_EQUAL:
2941      case FILTER_TYPE_APPROXIMATE_MATCH:
2942        final ASN1OctetString[] attrValueAssertionElements =
2943        {
2944          new ASN1OctetString(attrName),
2945          assertionValue
2946        };
2947        return new ASN1Sequence(filterType, attrValueAssertionElements);
2948
2949
2950      case FILTER_TYPE_SUBSTRING:
2951        final ArrayList<ASN1OctetString> subList =
2952             new ArrayList<>(2 + subAny.length);
2953        if (subInitial != null)
2954        {
2955          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBINITIAL,
2956                                          subInitial.getValue()));
2957        }
2958
2959        for (final ASN1Element subAnyElement : subAny)
2960        {
2961          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBANY,
2962                                          subAnyElement.getValue()));
2963        }
2964
2965
2966        if (subFinal != null)
2967        {
2968          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBFINAL,
2969                                          subFinal.getValue()));
2970        }
2971
2972        final ASN1Element[] subFilterElements =
2973        {
2974          new ASN1OctetString(attrName),
2975          new ASN1Sequence(subList)
2976        };
2977        return new ASN1Sequence(filterType, subFilterElements);
2978
2979
2980      case FILTER_TYPE_PRESENCE:
2981        return new ASN1OctetString(filterType, attrName);
2982
2983
2984      case FILTER_TYPE_EXTENSIBLE_MATCH:
2985        final ArrayList<ASN1Element> emElementList = new ArrayList<>(4);
2986        if (matchingRuleID != null)
2987        {
2988          emElementList.add(new ASN1OctetString(
2989               EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID));
2990        }
2991
2992        if (attrName != null)
2993        {
2994          emElementList.add(new ASN1OctetString(
2995               EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName));
2996        }
2997
2998        emElementList.add(new ASN1OctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2999             assertionValue.getValue()));
3000
3001        if (dnAttributes)
3002        {
3003          emElementList.add(new ASN1Boolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES,
3004                                            true));
3005        }
3006
3007        return new ASN1Sequence(filterType, emElementList);
3008
3009
3010      default:
3011        throw new AssertionError(ERR_FILTER_INVALID_TYPE.get(
3012             StaticUtils.toHex(filterType)));
3013    }
3014  }
3015
3016
3017
3018  /**
3019   * Reads and decodes a search filter from the provided ASN.1 stream reader.
3020   *
3021   * @param  reader  The ASN.1 stream reader from which to read the filter.
3022   *
3023   * @return  The decoded search filter.
3024   *
3025   * @throws  LDAPException  If an error occurs while reading or parsing the
3026   *                         search filter.
3027   */
3028  @NotNull()
3029  public static Filter readFrom(@NotNull final ASN1StreamReader reader)
3030         throws LDAPException
3031  {
3032    try
3033    {
3034      final Filter[]          filterComps;
3035      final Filter            notComp;
3036      final String            attrName;
3037      final ASN1OctetString   assertionValue;
3038      final ASN1OctetString   subInitial;
3039      final ASN1OctetString[] subAny;
3040      final ASN1OctetString   subFinal;
3041      final String            matchingRuleID;
3042      final boolean           dnAttributes;
3043
3044      final byte filterType = (byte) reader.peek();
3045
3046      switch (filterType)
3047      {
3048        case FILTER_TYPE_AND:
3049        case FILTER_TYPE_OR:
3050          final ArrayList<Filter> comps = new ArrayList<>(5);
3051          final ASN1StreamReaderSet elementSet = reader.beginSet();
3052          while (elementSet.hasMoreElements())
3053          {
3054            comps.add(readFrom(reader));
3055          }
3056
3057          filterComps = new Filter[comps.size()];
3058          comps.toArray(filterComps);
3059
3060          notComp        = null;
3061          attrName       = null;
3062          assertionValue = null;
3063          subInitial     = null;
3064          subAny         = NO_SUB_ANY;
3065          subFinal       = null;
3066          matchingRuleID = null;
3067          dnAttributes   = false;
3068          break;
3069
3070
3071        case FILTER_TYPE_NOT:
3072          final ASN1Element notFilterElement;
3073          try
3074          {
3075            final ASN1Element e = reader.readElement();
3076            notFilterElement = ASN1Element.decode(e.getValue());
3077          }
3078          catch (final ASN1Exception ae)
3079          {
3080            Debug.debugException(ae);
3081            throw new LDAPException(ResultCode.DECODING_ERROR,
3082                 ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(
3083                      StaticUtils.getExceptionMessage(ae)),
3084                 ae);
3085          }
3086          notComp = decode(notFilterElement);
3087
3088          filterComps    = NO_FILTERS;
3089          attrName       = null;
3090          assertionValue = null;
3091          subInitial     = null;
3092          subAny         = NO_SUB_ANY;
3093          subFinal       = null;
3094          matchingRuleID = null;
3095          dnAttributes   = false;
3096          break;
3097
3098
3099        case FILTER_TYPE_EQUALITY:
3100        case FILTER_TYPE_GREATER_OR_EQUAL:
3101        case FILTER_TYPE_LESS_OR_EQUAL:
3102        case FILTER_TYPE_APPROXIMATE_MATCH:
3103          reader.beginSequence();
3104          attrName = reader.readString();
3105          assertionValue = new ASN1OctetString(reader.readBytes());
3106
3107          filterComps    = NO_FILTERS;
3108          notComp        = null;
3109          subInitial     = null;
3110          subAny         = NO_SUB_ANY;
3111          subFinal       = null;
3112          matchingRuleID = null;
3113          dnAttributes   = false;
3114          break;
3115
3116
3117        case FILTER_TYPE_SUBSTRING:
3118          reader.beginSequence();
3119          attrName = reader.readString();
3120
3121          ASN1OctetString tempSubInitial = null;
3122          ASN1OctetString tempSubFinal   = null;
3123          final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1);
3124          final ASN1StreamReaderSequence subSequence = reader.beginSequence();
3125          while (subSequence.hasMoreElements())
3126          {
3127            final byte type = (byte) reader.peek();
3128            final ASN1OctetString s =
3129                 new ASN1OctetString(type, reader.readBytes());
3130            switch (type)
3131            {
3132              case SUBSTRING_TYPE_SUBINITIAL:
3133                tempSubInitial = s;
3134                break;
3135              case SUBSTRING_TYPE_SUBANY:
3136                subAnyList.add(s);
3137                break;
3138              case SUBSTRING_TYPE_SUBFINAL:
3139                tempSubFinal = s;
3140                break;
3141              default:
3142                throw new LDAPException(ResultCode.DECODING_ERROR,
3143                     ERR_FILTER_INVALID_SUBSTR_TYPE.get(
3144                          StaticUtils.toHex(type)));
3145            }
3146          }
3147
3148          subInitial = tempSubInitial;
3149          subFinal   = tempSubFinal;
3150
3151          subAny = new ASN1OctetString[subAnyList.size()];
3152          subAnyList.toArray(subAny);
3153
3154          filterComps    = NO_FILTERS;
3155          notComp        = null;
3156          assertionValue = null;
3157          matchingRuleID = null;
3158          dnAttributes   = false;
3159          break;
3160
3161
3162        case FILTER_TYPE_PRESENCE:
3163          attrName = reader.readString();
3164
3165          filterComps    = NO_FILTERS;
3166          notComp        = null;
3167          assertionValue = null;
3168          subInitial     = null;
3169          subAny         = NO_SUB_ANY;
3170          subFinal       = null;
3171          matchingRuleID = null;
3172          dnAttributes   = false;
3173          break;
3174
3175
3176        case FILTER_TYPE_EXTENSIBLE_MATCH:
3177          String          tempAttrName       = null;
3178          ASN1OctetString tempAssertionValue = null;
3179          String          tempMatchingRuleID = null;
3180          boolean         tempDNAttributes   = false;
3181
3182          final ASN1StreamReaderSequence emSequence = reader.beginSequence();
3183          while (emSequence.hasMoreElements())
3184          {
3185            final byte type = (byte) reader.peek();
3186            switch (type)
3187            {
3188              case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
3189                tempAttrName = reader.readString();
3190                break;
3191              case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
3192                tempMatchingRuleID = reader.readString();
3193                break;
3194              case EXTENSIBLE_TYPE_MATCH_VALUE:
3195                tempAssertionValue =
3196                     new ASN1OctetString(type, reader.readBytes());
3197                break;
3198              case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
3199                tempDNAttributes = reader.readBoolean();
3200                break;
3201              default:
3202                throw new LDAPException(ResultCode.DECODING_ERROR,
3203                     ERR_FILTER_EXTMATCH_INVALID_TYPE.get(
3204                          StaticUtils.toHex(type)));
3205            }
3206          }
3207
3208          if ((tempAttrName == null) && (tempMatchingRuleID == null))
3209          {
3210            throw new LDAPException(ResultCode.DECODING_ERROR,
3211                                    ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
3212          }
3213
3214          if (tempAssertionValue == null)
3215          {
3216            throw new LDAPException(ResultCode.DECODING_ERROR,
3217                                    ERR_FILTER_EXTMATCH_NO_VALUE.get());
3218          }
3219
3220          attrName       = tempAttrName;
3221          assertionValue = tempAssertionValue;
3222          matchingRuleID = tempMatchingRuleID;
3223          dnAttributes   = tempDNAttributes;
3224
3225          filterComps    = NO_FILTERS;
3226          notComp        = null;
3227          subInitial     = null;
3228          subAny         = NO_SUB_ANY;
3229          subFinal       = null;
3230          break;
3231
3232
3233        default:
3234          throw new LDAPException(ResultCode.DECODING_ERROR,
3235               ERR_FILTER_ELEMENT_INVALID_TYPE.get(
3236                    StaticUtils.toHex(filterType)));
3237      }
3238
3239      return new Filter(null, filterType, filterComps, notComp, attrName,
3240                        assertionValue, subInitial, subAny, subFinal,
3241                        matchingRuleID, dnAttributes);
3242    }
3243    catch (final LDAPException le)
3244    {
3245      Debug.debugException(le);
3246      throw le;
3247    }
3248    catch (final Exception e)
3249    {
3250      Debug.debugException(e);
3251      throw new LDAPException(ResultCode.DECODING_ERROR,
3252           ERR_FILTER_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e);
3253    }
3254  }
3255
3256
3257
3258  /**
3259   * Decodes the provided ASN.1 element as a search filter.
3260   *
3261   * @param  filterElement  The ASN.1 element containing the encoded search
3262   *                        filter.
3263   *
3264   * @return  The decoded search filter.
3265   *
3266   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
3267   *                         a search filter.
3268   */
3269  @NotNull()
3270  public static Filter decode(@NotNull final ASN1Element filterElement)
3271         throws LDAPException
3272  {
3273    final byte              filterType = filterElement.getType();
3274    final Filter[]          filterComps;
3275    final Filter            notComp;
3276    final String            attrName;
3277    final ASN1OctetString   assertionValue;
3278    final ASN1OctetString   subInitial;
3279    final ASN1OctetString[] subAny;
3280    final ASN1OctetString   subFinal;
3281    final String            matchingRuleID;
3282    final boolean           dnAttributes;
3283
3284    switch (filterType)
3285    {
3286      case FILTER_TYPE_AND:
3287      case FILTER_TYPE_OR:
3288        notComp        = null;
3289        attrName       = null;
3290        assertionValue = null;
3291        subInitial     = null;
3292        subAny         = NO_SUB_ANY;
3293        subFinal       = null;
3294        matchingRuleID = null;
3295        dnAttributes   = false;
3296
3297        final ASN1Set compSet;
3298        try
3299        {
3300          compSet = ASN1Set.decodeAsSet(filterElement);
3301        }
3302        catch (final ASN1Exception ae)
3303        {
3304          Debug.debugException(ae);
3305          throw new LDAPException(ResultCode.DECODING_ERROR,
3306               ERR_FILTER_CANNOT_DECODE_COMPS.get(
3307                    StaticUtils.getExceptionMessage(ae)),
3308               ae);
3309        }
3310
3311        final ASN1Element[] compElements = compSet.elements();
3312        filterComps = new Filter[compElements.length];
3313        for (int i=0; i < compElements.length; i++)
3314        {
3315          filterComps[i] = decode(compElements[i]);
3316        }
3317        break;
3318
3319
3320      case FILTER_TYPE_NOT:
3321        filterComps    = NO_FILTERS;
3322        attrName       = null;
3323        assertionValue = null;
3324        subInitial     = null;
3325        subAny         = NO_SUB_ANY;
3326        subFinal       = null;
3327        matchingRuleID = null;
3328        dnAttributes   = false;
3329
3330        final ASN1Element notFilterElement;
3331        try
3332        {
3333          notFilterElement = ASN1Element.decode(filterElement.getValue());
3334        }
3335        catch (final ASN1Exception ae)
3336        {
3337          Debug.debugException(ae);
3338          throw new LDAPException(ResultCode.DECODING_ERROR,
3339               ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(
3340                    StaticUtils.getExceptionMessage(ae)),
3341               ae);
3342        }
3343        notComp = decode(notFilterElement);
3344        break;
3345
3346
3347
3348      case FILTER_TYPE_EQUALITY:
3349      case FILTER_TYPE_GREATER_OR_EQUAL:
3350      case FILTER_TYPE_LESS_OR_EQUAL:
3351      case FILTER_TYPE_APPROXIMATE_MATCH:
3352        filterComps    = NO_FILTERS;
3353        notComp        = null;
3354        subInitial     = null;
3355        subAny         = NO_SUB_ANY;
3356        subFinal       = null;
3357        matchingRuleID = null;
3358        dnAttributes   = false;
3359
3360        final ASN1Sequence avaSequence;
3361        try
3362        {
3363          avaSequence = ASN1Sequence.decodeAsSequence(filterElement);
3364        }
3365        catch (final ASN1Exception ae)
3366        {
3367          Debug.debugException(ae);
3368          throw new LDAPException(ResultCode.DECODING_ERROR,
3369               ERR_FILTER_CANNOT_DECODE_AVA.get(
3370                    StaticUtils.getExceptionMessage(ae)),
3371               ae);
3372        }
3373
3374        final ASN1Element[] avaElements = avaSequence.elements();
3375        if (avaElements.length != 2)
3376        {
3377          throw new LDAPException(ResultCode.DECODING_ERROR,
3378                                  ERR_FILTER_INVALID_AVA_ELEMENT_COUNT.get(
3379                                       avaElements.length));
3380        }
3381
3382        attrName =
3383             ASN1OctetString.decodeAsOctetString(avaElements[0]).stringValue();
3384        assertionValue = ASN1OctetString.decodeAsOctetString(avaElements[1]);
3385        break;
3386
3387
3388      case FILTER_TYPE_SUBSTRING:
3389        filterComps    = NO_FILTERS;
3390        notComp        = null;
3391        assertionValue = null;
3392        matchingRuleID = null;
3393        dnAttributes   = false;
3394
3395        final ASN1Sequence subFilterSequence;
3396        try
3397        {
3398          subFilterSequence = ASN1Sequence.decodeAsSequence(filterElement);
3399        }
3400        catch (final ASN1Exception ae)
3401        {
3402          Debug.debugException(ae);
3403          throw new LDAPException(ResultCode.DECODING_ERROR,
3404               ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(
3405                    StaticUtils.getExceptionMessage(ae)),
3406               ae);
3407        }
3408
3409        final ASN1Element[] subFilterElements = subFilterSequence.elements();
3410        if (subFilterElements.length != 2)
3411        {
3412          throw new LDAPException(ResultCode.DECODING_ERROR,
3413                                  ERR_FILTER_INVALID_SUBSTR_ASSERTION_COUNT.get(
3414                                       subFilterElements.length));
3415        }
3416
3417        attrName = ASN1OctetString.decodeAsOctetString(
3418                        subFilterElements[0]).stringValue();
3419
3420        final ASN1Sequence subSequence;
3421        try
3422        {
3423          subSequence = ASN1Sequence.decodeAsSequence(subFilterElements[1]);
3424        }
3425        catch (final ASN1Exception ae)
3426        {
3427          Debug.debugException(ae);
3428          throw new LDAPException(ResultCode.DECODING_ERROR,
3429               ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(
3430                    StaticUtils.getExceptionMessage(ae)),
3431               ae);
3432        }
3433
3434        ASN1OctetString tempSubInitial = null;
3435        ASN1OctetString tempSubFinal   = null;
3436        final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1);
3437
3438        final ASN1Element[] subElements = subSequence.elements();
3439        for (final ASN1Element subElement : subElements)
3440        {
3441          switch (subElement.getType())
3442          {
3443            case SUBSTRING_TYPE_SUBINITIAL:
3444              if (tempSubInitial == null)
3445              {
3446                tempSubInitial =
3447                     ASN1OctetString.decodeAsOctetString(subElement);
3448              }
3449              else
3450              {
3451                throw new LDAPException(ResultCode.DECODING_ERROR,
3452                                        ERR_FILTER_MULTIPLE_SUBINITIAL.get());
3453              }
3454              break;
3455
3456            case SUBSTRING_TYPE_SUBANY:
3457              subAnyList.add(ASN1OctetString.decodeAsOctetString(subElement));
3458              break;
3459
3460            case SUBSTRING_TYPE_SUBFINAL:
3461              if (tempSubFinal == null)
3462              {
3463                tempSubFinal = ASN1OctetString.decodeAsOctetString(subElement);
3464              }
3465              else
3466              {
3467                throw new LDAPException(ResultCode.DECODING_ERROR,
3468                                        ERR_FILTER_MULTIPLE_SUBFINAL.get());
3469              }
3470              break;
3471
3472            default:
3473              throw new LDAPException(ResultCode.DECODING_ERROR,
3474                   ERR_FILTER_INVALID_SUBSTR_TYPE.get(
3475                        StaticUtils.toHex(subElement.getType())));
3476          }
3477        }
3478
3479        subInitial = tempSubInitial;
3480        subAny     = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
3481        subFinal   = tempSubFinal;
3482        break;
3483
3484
3485      case FILTER_TYPE_PRESENCE:
3486        filterComps    = NO_FILTERS;
3487        notComp        = null;
3488        assertionValue = null;
3489        subInitial     = null;
3490        subAny         = NO_SUB_ANY;
3491        subFinal       = null;
3492        matchingRuleID = null;
3493        dnAttributes   = false;
3494        attrName       =
3495             ASN1OctetString.decodeAsOctetString(filterElement).stringValue();
3496        break;
3497
3498
3499      case FILTER_TYPE_EXTENSIBLE_MATCH:
3500        filterComps    = NO_FILTERS;
3501        notComp        = null;
3502        subInitial     = null;
3503        subAny         = NO_SUB_ANY;
3504        subFinal       = null;
3505
3506        final ASN1Sequence emSequence;
3507        try
3508        {
3509          emSequence = ASN1Sequence.decodeAsSequence(filterElement);
3510        }
3511        catch (final ASN1Exception ae)
3512        {
3513          Debug.debugException(ae);
3514          throw new LDAPException(ResultCode.DECODING_ERROR,
3515               ERR_FILTER_CANNOT_DECODE_EXTMATCH.get(
3516                    StaticUtils.getExceptionMessage(ae)),
3517               ae);
3518        }
3519
3520        String          tempAttrName       = null;
3521        ASN1OctetString tempAssertionValue = null;
3522        String          tempMatchingRuleID = null;
3523        boolean         tempDNAttributes   = false;
3524        for (final ASN1Element e : emSequence.elements())
3525        {
3526          switch (e.getType())
3527          {
3528            case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
3529              if (tempAttrName == null)
3530              {
3531                tempAttrName =
3532                     ASN1OctetString.decodeAsOctetString(e).stringValue();
3533              }
3534              else
3535              {
3536                throw new LDAPException(ResultCode.DECODING_ERROR,
3537                               ERR_FILTER_EXTMATCH_MULTIPLE_ATTRS.get());
3538              }
3539              break;
3540
3541            case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
3542              if (tempMatchingRuleID == null)
3543              {
3544                tempMatchingRuleID  =
3545                     ASN1OctetString.decodeAsOctetString(e).stringValue();
3546              }
3547              else
3548              {
3549                throw new LDAPException(ResultCode.DECODING_ERROR,
3550                               ERR_FILTER_EXTMATCH_MULTIPLE_MRIDS.get());
3551              }
3552              break;
3553
3554            case EXTENSIBLE_TYPE_MATCH_VALUE:
3555              if (tempAssertionValue == null)
3556              {
3557                tempAssertionValue = ASN1OctetString.decodeAsOctetString(e);
3558              }
3559              else
3560              {
3561                throw new LDAPException(ResultCode.DECODING_ERROR,
3562                               ERR_FILTER_EXTMATCH_MULTIPLE_VALUES.get());
3563              }
3564              break;
3565
3566            case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
3567              try
3568              {
3569                if (tempDNAttributes)
3570                {
3571                  throw new LDAPException(ResultCode.DECODING_ERROR,
3572                                 ERR_FILTER_EXTMATCH_MULTIPLE_DNATTRS.get());
3573                }
3574                else
3575                {
3576                  tempDNAttributes =
3577                       ASN1Boolean.decodeAsBoolean(e).booleanValue();
3578                }
3579              }
3580              catch (final ASN1Exception ae)
3581              {
3582                Debug.debugException(ae);
3583                throw new LDAPException(ResultCode.DECODING_ERROR,
3584                     ERR_FILTER_EXTMATCH_DNATTRS_NOT_BOOLEAN.get(
3585                          StaticUtils.getExceptionMessage(ae)),
3586                     ae);
3587              }
3588              break;
3589
3590            default:
3591              throw new LDAPException(ResultCode.DECODING_ERROR,
3592                   ERR_FILTER_EXTMATCH_INVALID_TYPE.get(
3593                        StaticUtils.toHex(e.getType())));
3594          }
3595        }
3596
3597        if ((tempAttrName == null) && (tempMatchingRuleID == null))
3598        {
3599          throw new LDAPException(ResultCode.DECODING_ERROR,
3600                                  ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
3601        }
3602
3603        if (tempAssertionValue == null)
3604        {
3605          throw new LDAPException(ResultCode.DECODING_ERROR,
3606                                  ERR_FILTER_EXTMATCH_NO_VALUE.get());
3607        }
3608
3609        attrName       = tempAttrName;
3610        assertionValue = tempAssertionValue;
3611        matchingRuleID = tempMatchingRuleID;
3612        dnAttributes   = tempDNAttributes;
3613        break;
3614
3615
3616      default:
3617        throw new LDAPException(ResultCode.DECODING_ERROR,
3618             ERR_FILTER_ELEMENT_INVALID_TYPE.get(
3619                  StaticUtils.toHex(filterElement.getType())));
3620    }
3621
3622
3623    return new Filter(null, filterType, filterComps, notComp, attrName,
3624                      assertionValue, subInitial, subAny, subFinal,
3625                      matchingRuleID, dnAttributes);
3626  }
3627
3628
3629
3630  /**
3631   * Retrieves the filter type for this filter.
3632   *
3633   * @return  The filter type for this filter.
3634   */
3635  public byte getFilterType()
3636  {
3637    return filterType;
3638  }
3639
3640
3641
3642  /**
3643   * Retrieves the set of filter components used in this AND or OR filter.  This
3644   * is not applicable for any other filter type.
3645   *
3646   * @return  The set of filter components used in this AND or OR filter, or an
3647   *          empty array if this is some other type of filter or if there are
3648   *          no components (i.e., as in an LDAP TRUE or LDAP FALSE filter).
3649   */
3650  @NotNull()
3651  public Filter[] getComponents()
3652  {
3653    return filterComps;
3654  }
3655
3656
3657
3658  /**
3659   * Retrieves the filter component used in this NOT filter.  This is not
3660   * applicable for any other filter type.
3661   *
3662   * @return  The filter component used in this NOT filter, or {@code null} if
3663   *          this is some other type of filter.
3664   */
3665  @Nullable()
3666  public Filter getNOTComponent()
3667  {
3668    return notComp;
3669  }
3670
3671
3672
3673  /**
3674   * Retrieves the name of the attribute type for this search filter.  This is
3675   * applicable for the following types of filters:
3676   * <UL>
3677   *   <LI>Equality</LI>
3678   *   <LI>Substring</LI>
3679   *   <LI>Greater or Equal</LI>
3680   *   <LI>Less or Equal</LI>
3681   *   <LI>Presence</LI>
3682   *   <LI>Approximate Match</LI>
3683   *   <LI>Extensible Match</LI>
3684   * </UL>
3685   *
3686   * @return  The name of the attribute type for this search filter, or
3687   *          {@code null} if it is not applicable for this type of filter.
3688   */
3689  @Nullable()
3690  public String getAttributeName()
3691  {
3692    return attrName;
3693  }
3694
3695
3696
3697  /**
3698   * Retrieves the string representation of the assertion value for this search
3699   * filter.  This is applicable for the following types of filters:
3700   * <UL>
3701   *   <LI>Equality</LI>
3702   *   <LI>Greater or Equal</LI>
3703   *   <LI>Less or Equal</LI>
3704   *   <LI>Approximate Match</LI>
3705   *   <LI>Extensible Match</LI>
3706   * </UL>
3707   *
3708   * @return  The string representation of the assertion value for this search
3709   *          filter, or {@code null} if it is not applicable for this type of
3710   *          filter.
3711   */
3712  @Nullable()
3713  public String getAssertionValue()
3714  {
3715    if (assertionValue == null)
3716    {
3717      return null;
3718    }
3719    else
3720    {
3721      return assertionValue.stringValue();
3722    }
3723  }
3724
3725
3726
3727  /**
3728   * Retrieves the binary representation of the assertion value for this search
3729   * filter.  This is applicable for the following types of filters:
3730   * <UL>
3731   *   <LI>Equality</LI>
3732   *   <LI>Greater or Equal</LI>
3733   *   <LI>Less or Equal</LI>
3734   *   <LI>Approximate Match</LI>
3735   *   <LI>Extensible Match</LI>
3736   * </UL>
3737   *
3738   * @return  The binary representation of the assertion value for this search
3739   *          filter, or {@code null} if it is not applicable for this type of
3740   *          filter.
3741   */
3742  @Nullable()
3743  public byte[] getAssertionValueBytes()
3744  {
3745    if (assertionValue == null)
3746    {
3747      return null;
3748    }
3749    else
3750    {
3751      return assertionValue.getValue();
3752    }
3753  }
3754
3755
3756
3757  /**
3758   * Retrieves the raw assertion value for this search filter as an ASN.1
3759   * octet string.  This is applicable for the following types of filters:
3760   * <UL>
3761   *   <LI>Equality</LI>
3762   *   <LI>Greater or Equal</LI>
3763   *   <LI>Less or Equal</LI>
3764   *   <LI>Approximate Match</LI>
3765   *   <LI>Extensible Match</LI>
3766   * </UL>
3767   *
3768   * @return  The raw assertion value for this search filter as an ASN.1 octet
3769   *          string, or {@code null} if it is not applicable for this type of
3770   *          filter.
3771   */
3772  @Nullable()
3773  public ASN1OctetString getRawAssertionValue()
3774  {
3775    return assertionValue;
3776  }
3777
3778
3779
3780  /**
3781   * Retrieves the string representation of the subInitial element for this
3782   * substring filter.  This is not applicable for any other filter type.
3783   *
3784   * @return  The string representation of the subInitial element for this
3785   *          substring filter, or {@code null} if this is some other type of
3786   *          filter, or if it is a substring filter with no subInitial element.
3787   */
3788  @Nullable()
3789  public String getSubInitialString()
3790  {
3791    if (subInitial == null)
3792    {
3793      return null;
3794    }
3795    else
3796    {
3797      return subInitial.stringValue();
3798    }
3799  }
3800
3801
3802
3803  /**
3804   * Retrieves the binary representation of the subInitial element for this
3805   * substring filter.  This is not applicable for any other filter type.
3806   *
3807   * @return  The binary representation of the subInitial element for this
3808   *          substring filter, or {@code null} if this is some other type of
3809   *          filter, or if it is a substring filter with no subInitial element.
3810   */
3811  @Nullable()
3812  public byte[] getSubInitialBytes()
3813  {
3814    if (subInitial == null)
3815    {
3816      return null;
3817    }
3818    else
3819    {
3820      return subInitial.getValue();
3821    }
3822  }
3823
3824
3825
3826  /**
3827   * Retrieves the raw subInitial element for this filter as an ASN.1 octet
3828   * string.  This is not applicable for any other filter type.
3829   *
3830   * @return  The raw subInitial element for this filter as an ASN.1 octet
3831   *          string, or {@code null} if this is not a substring filter, or if
3832   *          it is a substring filter with no subInitial element.
3833   */
3834  @Nullable()
3835  public ASN1OctetString getRawSubInitialValue()
3836  {
3837    return subInitial;
3838  }
3839
3840
3841
3842  /**
3843   * Retrieves the string representations of the subAny elements for this
3844   * substring filter.  This is not applicable for any other filter type.
3845   *
3846   * @return  The string representations of the subAny elements for this
3847   *          substring filter, or an empty array if this is some other type of
3848   *          filter, or if it is a substring filter with no subFinal element.
3849   */
3850  @NotNull()
3851  public String[] getSubAnyStrings()
3852  {
3853    final String[] subAnyStrings = new String[subAny.length];
3854    for (int i=0; i < subAny.length; i++)
3855    {
3856      subAnyStrings[i] = subAny[i].stringValue();
3857    }
3858
3859    return subAnyStrings;
3860  }
3861
3862
3863
3864  /**
3865   * Retrieves the binary representations of the subAny elements for this
3866   * substring filter.  This is not applicable for any other filter type.
3867   *
3868   * @return  The binary representations of the subAny elements for this
3869   *          substring filter, or an empty array if this is some other type of
3870   *          filter, or if it is a substring filter with no subFinal element.
3871   */
3872  @NotNull()
3873  public byte[][] getSubAnyBytes()
3874  {
3875    final byte[][] subAnyBytes = new byte[subAny.length][];
3876    for (int i=0; i < subAny.length; i++)
3877    {
3878      subAnyBytes[i] = subAny[i].getValue();
3879    }
3880
3881    return subAnyBytes;
3882  }
3883
3884
3885
3886  /**
3887   * Retrieves the raw subAny values for this substring filter.  This is not
3888   * applicable for any other filter type.
3889   *
3890   * @return  The raw subAny values for this substring filter, or an empty array
3891   *          if this is some other type of filter, or if it is a substring
3892   *          filter with no subFinal element.
3893   */
3894  @NotNull()
3895  public ASN1OctetString[] getRawSubAnyValues()
3896  {
3897    return subAny;
3898  }
3899
3900
3901
3902  /**
3903   * Retrieves the string representation of the subFinal element for this
3904   * substring filter.  This is not applicable for any other filter type.
3905   *
3906   * @return  The string representation of the subFinal element for this
3907   *          substring filter, or {@code null} if this is some other type of
3908   *          filter, or if it is a substring filter with no subFinal element.
3909   */
3910  @Nullable()
3911  public String getSubFinalString()
3912  {
3913    if (subFinal == null)
3914    {
3915      return null;
3916    }
3917    else
3918    {
3919      return subFinal.stringValue();
3920    }
3921  }
3922
3923
3924
3925  /**
3926   * Retrieves the binary representation of the subFinal element for this
3927   * substring filter.  This is not applicable for any other filter type.
3928   *
3929   * @return  The binary representation of the subFinal element for this
3930   *          substring filter, or {@code null} if this is some other type of
3931   *          filter, or if it is a substring filter with no subFinal element.
3932   */
3933  @Nullable()
3934  public byte[] getSubFinalBytes()
3935  {
3936    if (subFinal == null)
3937    {
3938      return null;
3939    }
3940    else
3941    {
3942      return subFinal.getValue();
3943    }
3944  }
3945
3946
3947
3948  /**
3949   * Retrieves the raw subFinal element for this filter as an ASN.1 octet
3950   * string.  This is not applicable for any other filter type.
3951   *
3952   * @return  The raw subFinal element for this filter as an ASN.1 octet
3953   *          string, or {@code null} if this is not a substring filter, or if
3954   *          it is a substring filter with no subFinal element.
3955   */
3956  @Nullable()
3957  public ASN1OctetString getRawSubFinalValue()
3958  {
3959    return subFinal;
3960  }
3961
3962
3963
3964  /**
3965   * Retrieves the matching rule ID for this extensible match filter.  This is
3966   * not applicable for any other filter type.
3967   *
3968   * @return  The matching rule ID for this extensible match filter, or
3969   *          {@code null} if this is some other type of filter, or if this
3970   *          extensible match filter does not have a matching rule ID.
3971   */
3972  @Nullable()
3973  public String getMatchingRuleID()
3974  {
3975    return matchingRuleID;
3976  }
3977
3978
3979
3980  /**
3981   * Retrieves the dnAttributes flag for this extensible match filter.  This is
3982   * not applicable for any other filter type.
3983   *
3984   * @return  The dnAttributes flag for this extensible match filter.
3985   */
3986  public boolean getDNAttributes()
3987  {
3988    return dnAttributes;
3989  }
3990
3991
3992
3993  /**
3994   * Indicates whether this filter matches the provided entry.  Note that this
3995   * is a best-guess effort and may not be completely accurate in all cases.
3996   * All matching will be performed using case-ignore string matching, which may
3997   * yield an unexpected result for values that should not be treated as simple
3998   * strings.  For example:
3999   * <UL>
4000   *   <LI>Two DN values which are logically equivalent may not be considered
4001   *       matches if they have different spacing.</LI>
4002   *   <LI>Ordering comparisons against numeric values may yield unexpected
4003   *       results (e.g., "2" will be considered greater than "10" because the
4004   *       character "2" has a larger ASCII value than the character "1").</LI>
4005   * </UL>
4006   * <BR>
4007   * In addition to the above constraints, it should be noted that neither
4008   * approximate matching nor extensible matching are currently supported.
4009   *
4010   * @param  entry  The entry for which to make the determination.  It must not
4011   *                be {@code null}.
4012   *
4013   * @return  {@code true} if this filter appears to match the provided entry,
4014   *          or {@code false} if not.
4015   *
4016   * @throws  LDAPException  If a problem occurs while trying to make the
4017   *                         determination.
4018   */
4019  public boolean matchesEntry(@NotNull final Entry entry)
4020         throws LDAPException
4021  {
4022    return matchesEntry(entry, entry.getSchema());
4023  }
4024
4025
4026
4027  /**
4028   * Indicates whether this filter matches the provided entry.  Note that this
4029   * is a best-guess effort and may not be completely accurate in all cases.
4030   * If provided, the given schema will be used in an attempt to determine the
4031   * appropriate matching rule for making the determinations, but some corner
4032   * cases may not be handled accurately.  Neither approximate matching nor
4033   * extensible matching are currently supported.
4034   *
4035   * @param  entry   The entry for which to make the determination.  It must not
4036   *                 be {@code null}.
4037   * @param  schema  The schema to use when making the determination.  If this
4038   *                 is {@code null}, then all matching will be performed using
4039   *                 a case-ignore matching rule.
4040   *
4041   * @return  {@code true} if this filter appears to match the provided entry,
4042   *          or {@code false} if not.
4043   *
4044   * @throws  LDAPException  If a problem occurs while trying to make the
4045   *                         determination.
4046   */
4047  public boolean matchesEntry(@NotNull final Entry entry,
4048                              @Nullable final Schema schema)
4049         throws LDAPException
4050  {
4051    Validator.ensureNotNull(entry);
4052
4053    switch (filterType)
4054    {
4055      case FILTER_TYPE_AND:
4056        for (final Filter f : filterComps)
4057        {
4058          try
4059          {
4060            if (! f.matchesEntry(entry, schema))
4061            {
4062              return false;
4063            }
4064          }
4065          catch (final Exception e)
4066          {
4067            Debug.debugException(e);
4068            return false;
4069          }
4070        }
4071        return true;
4072
4073      case FILTER_TYPE_OR:
4074        for (final Filter f : filterComps)
4075        {
4076          try
4077          {
4078            if (f.matchesEntry(entry, schema))
4079            {
4080              return true;
4081            }
4082          }
4083          catch (final Exception e)
4084          {
4085            Debug.debugException(e);
4086          }
4087        }
4088        return false;
4089
4090      case FILTER_TYPE_NOT:
4091        return (! notComp.matchesEntry(entry, schema));
4092
4093      case FILTER_TYPE_EQUALITY:
4094        Attribute a = entry.getAttribute(attrName, schema);
4095        if (a == null)
4096        {
4097          return false;
4098        }
4099
4100        MatchingRule matchingRule =
4101             MatchingRule.selectEqualityMatchingRule(attrName, schema);
4102        return matchingRule.matchesAnyValue(assertionValue, a.getRawValues());
4103
4104      case FILTER_TYPE_SUBSTRING:
4105        a = entry.getAttribute(attrName, schema);
4106        if (a == null)
4107        {
4108          return false;
4109        }
4110
4111        matchingRule =
4112             MatchingRule.selectSubstringMatchingRule(attrName, schema);
4113        for (final ASN1OctetString v : a.getRawValues())
4114        {
4115          if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal))
4116          {
4117            return true;
4118          }
4119        }
4120        return false;
4121
4122      case FILTER_TYPE_GREATER_OR_EQUAL:
4123        a = entry.getAttribute(attrName, schema);
4124        if (a == null)
4125        {
4126          return false;
4127        }
4128
4129        matchingRule =
4130             MatchingRule.selectOrderingMatchingRule(attrName, schema);
4131        for (final ASN1OctetString v : a.getRawValues())
4132        {
4133          if (matchingRule.compareValues(v, assertionValue) >= 0)
4134          {
4135            return true;
4136          }
4137        }
4138        return false;
4139
4140      case FILTER_TYPE_LESS_OR_EQUAL:
4141        a = entry.getAttribute(attrName, schema);
4142        if (a == null)
4143        {
4144          return false;
4145        }
4146
4147        matchingRule =
4148             MatchingRule.selectOrderingMatchingRule(attrName, schema);
4149        for (final ASN1OctetString v : a.getRawValues())
4150        {
4151          if (matchingRule.compareValues(v, assertionValue) <= 0)
4152          {
4153            return true;
4154          }
4155        }
4156        return false;
4157
4158      case FILTER_TYPE_PRESENCE:
4159        return (entry.hasAttribute(attrName));
4160
4161      case FILTER_TYPE_APPROXIMATE_MATCH:
4162        throw new LDAPException(ResultCode.NOT_SUPPORTED,
4163             ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get());
4164
4165      case FILTER_TYPE_EXTENSIBLE_MATCH:
4166        return extensibleMatchFilterMatchesEntry(entry, schema);
4167
4168      default:
4169        throw new LDAPException(ResultCode.PARAM_ERROR,
4170                                ERR_FILTER_INVALID_TYPE.get());
4171    }
4172  }
4173
4174
4175
4176  /**
4177   * Indicates whether the provided extensible matching filter component matches
4178   * the provided entry.  This method provides very limited support for
4179   * extensible matching  It can only be used for filters that contain both an
4180   * attribute type and a matching rule ID, and when the matching rule ID is
4181   * one of the following:
4182   * <OL>
4183   *   <LI>jsonObjectFilterExtensibleMatch (or 1.3.6.1.4.1.30221.2.4.13)</LI>
4184   * </OL>
4185   *
4186   * @param  entry   The entry for which to make the determination.  It must not
4187   *                 be {@code null}.
4188   * @param  schema  The schema to use when making the determination.  If this
4189   *                 is {@code null}, then all matching will be performed using
4190   *                 a case-ignore matching rule.
4191   *
4192   * @return  {@code true} if this filter appears to match the provided entry,
4193   *          or {@code false} if not.
4194   *
4195   * @throws  LDAPException  If a problem occurs while trying to make the
4196   *                         determination.
4197   */
4198  private boolean extensibleMatchFilterMatchesEntry(@NotNull final Entry entry,
4199                       @Nullable final Schema schema)
4200          throws LDAPException
4201  {
4202    if ((attrName != null) && (matchingRuleID != null) && (! dnAttributes))
4203    {
4204      if (matchingRuleID.equalsIgnoreCase("jsonObjectFilterExtensibleMatch") ||
4205           matchingRuleID.equals("1.3.6.1.4.1.30221.2.4.13"))
4206      {
4207        final JSONObjectFilter jsonObjectFilter;
4208        try
4209        {
4210          final JSONObject jsonObject =
4211               new JSONObject(assertionValue.stringValue());
4212          jsonObjectFilter = JSONObjectFilter.decode(jsonObject);
4213        }
4214        catch (final Exception e)
4215        {
4216          Debug.debugException(e);
4217          throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING,
4218               ERR_FILTER_EXTENSIBLE_MATCH_MALFORMED_JSON_OBJECT_FILTER.get(
4219                    toString(), entry.getDN(),
4220                    StaticUtils.getExceptionMessage(e)),
4221               e);
4222        }
4223
4224        final Attribute attr = entry.getAttribute(attrName, schema);
4225        if (attr != null)
4226        {
4227          for (final ASN1OctetString v : attr.getRawValues())
4228          {
4229            try
4230            {
4231              final JSONObject jsonObject = new JSONObject(v.stringValue());
4232              if (jsonObjectFilter.matchesJSONObject(jsonObject))
4233              {
4234                return true;
4235              }
4236            }
4237            catch (final Exception e)
4238            {
4239              Debug.debugException(e);
4240            }
4241          }
4242        }
4243
4244        return false;
4245      }
4246    }
4247
4248    throw new LDAPException(ResultCode.NOT_SUPPORTED,
4249         ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get());
4250  }
4251
4252
4253
4254  /**
4255   * Attempts to simplify the provided filter to allow it to be more efficiently
4256   * processed by the server.  The simplifications it will make include:
4257   * <UL>
4258   *   <LI>Any AND or OR filter that contains only a single filter component
4259   *       will be converted to just that embedded filter component to eliminate
4260   *       the unnecessary AND or OR wrapper.  For example, the filter
4261   *       "(&amp;(uid=john.doe))" will be converted to just
4262   *       "(uid=john.doe)".</LI>
4263   *   <LI>Any AND components inside of an AND filter will be merged into the
4264   *       outer AND filter.  Any OR components inside of an OR filter will be
4265   *       merged into the outer OR filter.  For example, the filter
4266   *       "(&amp;(objectClass=person)(&amp;(givenName=John)(sn=Doe)))" will be
4267   *       converted to
4268   *       "(&amp;(objectClass=person)(givenName=John)(sn=Doe))".</LI>
4269   *   <LI>Any AND filter that contains an LDAP false filter will be converted
4270   *       to just an LDAP false filter.</LI>
4271   *   <LI>Any OR filter that contains an LDAP true filter will be converted
4272   *       to just an LDAP true filter.</LI>
4273   *   <LI>If {@code reOrderElements} is true, then this method will attempt to
4274   *       re-order the elements inside AND and OR filters in an attempt to
4275   *       ensure that the components which are likely to be the most efficient
4276   *       come earlier than those which are likely to be the least efficient.
4277   *       This can speed up processing in servers that process filter
4278   *       components in a left-to-right order.</LI>
4279   * </UL>
4280   * <BR><BR>
4281   * The simplification will happen recursively, in an attempt to generate a
4282   * filter that is as simple and efficient as possible.
4283   *
4284   * @param  filter           The filter to attempt to simplify.
4285   * @param  reOrderElements  Indicates whether this method may re-order the
4286   *                          elements in the filter so that, in a server that
4287   *                          evaluates the components in a left-to-right order,
4288   *                          the components which are likely to be more
4289   *                          efficient to process will be listed before those
4290   *                          which are likely to be less efficient.
4291   *
4292   * @return  The simplified filter, or the original filter if the provided
4293   *          filter is not one that can be simplified any further.
4294   */
4295  @NotNull()
4296  public static Filter simplifyFilter(@NotNull final Filter filter,
4297                                      final boolean reOrderElements)
4298  {
4299    final byte filterType = filter.filterType;
4300    switch (filterType)
4301    {
4302      case FILTER_TYPE_AND:
4303      case FILTER_TYPE_OR:
4304        // These will be handled below.
4305        break;
4306
4307      case FILTER_TYPE_NOT:
4308        // We may be able to simplify the filter component contained inside the
4309        // NOT.
4310        return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements));
4311
4312      default:
4313        // We can't simplify this filter, so just return what was provided.
4314        return filter;
4315    }
4316
4317
4318    // An AND filter with zero components is an LDAP true filter, and we can't
4319    // simplify that.  An OR filter with zero components is an LDAP false
4320    // filter, and we can't simplify that either.  The set of components
4321    // should never be null for an AND or OR filter, but if that happens to be
4322    // the case, then we'll return the original filter.
4323    final Filter[] components = filter.filterComps;
4324    if ((components == null) || (components.length == 0))
4325    {
4326      return filter;
4327    }
4328
4329
4330    // For either an AND or an OR filter with just a single component, then just
4331    // return that embedded component.  But simplify it first.
4332    if (components.length == 1)
4333    {
4334      return simplifyFilter(components[0], reOrderElements);
4335    }
4336
4337
4338    // If we've gotten here, then we have a filter with multiple components.
4339    // Simplify each of them to the extent possible, un-embed any ANDs
4340    // contained inside an AND or ORs contained inside an OR, and eliminate any
4341    // duplicate components in the resulting top-level filter.
4342    final LinkedHashSet<Filter> componentSet =
4343         new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
4344    for (final Filter f : components)
4345    {
4346      final Filter simplifiedFilter = simplifyFilter(f, reOrderElements);
4347      if (simplifiedFilter.filterType == FILTER_TYPE_AND)
4348      {
4349        if (filterType == FILTER_TYPE_AND)
4350        {
4351          // This is an AND nested inside an AND.  In that case, we'll just put
4352          // all the nested components inside the outer AND.
4353          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
4354        }
4355        else
4356        {
4357          componentSet.add(simplifiedFilter);
4358        }
4359      }
4360      else if (simplifiedFilter.filterType == FILTER_TYPE_OR)
4361      {
4362        if (filterType == FILTER_TYPE_OR)
4363        {
4364          // This is an OR nested inside an OR.  In that case, we'll just put
4365          // all the nested components inside the outer OR.
4366          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
4367        }
4368        else
4369        {
4370          componentSet.add(simplifiedFilter);
4371        }
4372      }
4373      else
4374      {
4375        componentSet.add(simplifiedFilter);
4376      }
4377    }
4378
4379
4380    // It's possible at this point that we are down to just a single component.
4381    // That can happen if the filter was an AND or an OR with a duplicate
4382    // element, like "(&(a=b)(a=b))".  In that case, just return that one
4383    // component.
4384    if (componentSet.size() == 1)
4385    {
4386      return componentSet.iterator().next();
4387    }
4388
4389
4390    // If we have an AND filter that contains an embedded LDAP false filter,
4391    // then just return the LDAP false filter.  If we have an OR filter that
4392    // contains an embedded LDAP true filter, then just return the LDAP true
4393    // filter.
4394    if (filterType == FILTER_TYPE_AND)
4395    {
4396      for (final Filter f : componentSet)
4397      {
4398        if ((f.filterType == FILTER_TYPE_OR) && (f.filterComps.length == 0))
4399        {
4400          return f;
4401        }
4402      }
4403    }
4404    else if (filterType == FILTER_TYPE_OR)
4405    {
4406      for (final Filter f : componentSet)
4407      {
4408        if ((f.filterType == FILTER_TYPE_AND) && (f.filterComps.length == 0))
4409        {
4410          return f;
4411        }
4412      }
4413    }
4414
4415
4416    // If we should re-order the components, then use the following priority
4417    // list:
4418    //
4419    // 1.  Equality components that target an attribute other than objectClass.
4420    //     These are most likely to require only a single database lookup to get
4421    //     the candidate list, and that candidate list will frequently be small.
4422    // 2.  Equality components that target the objectClass attribute.  These are
4423    //     likely to require only a single database lookup to get the candidate
4424    //     list, but the candidate list is more likely to be larger.
4425    // 3.  Approximate match components.  These are also likely to require only
4426    //     a single database lookup to get the candidate list, but that
4427    //     candidate list is likely to have a larger number of candidates.
4428    // 4.  Presence components that target an attribute other than objectClass.
4429    //     These are also likely to require only a single database lookup to get
4430    //     the candidate list, but are likely to have a large number of
4431    //     candidates.
4432    // 5.  Substring components that have a subInitial element.  These are
4433    //     generally the most efficient substring filters to process, requiring
4434    //     access to fewer database keys than substring filters with only subAny
4435    //     and/or subFinal components.
4436    // 6.  Substring components that only have subAny and/or subFinal elements.
4437    //     These will probably require a number of database lookups and will
4438    //     probably result in large candidate lists.
4439    // 7.  Greater-or-equal components and less-or-equal components.  These
4440    //     will probably require a number of database lookups and will probably
4441    //     result in large candidate lists.
4442    // 8.  Extensible match components.  Even if these are indexed, there isn't
4443    //     any good way to know how expensive they might be to process or how
4444    //     big the candidate list might be.
4445    // 9.  Presence components that target the objectClass attribute.  This is
4446    //     likely to require only a single database lookup to get the candidate
4447    //     list, but the candidate list will also be extremely large (if it's
4448    //     indexed at all) since it will match every entry.
4449    // 10. NOT components.  These are generally not possible to index and
4450    //     therefore cannot be used to create a candidate list.
4451    //
4452    // AND and OR components will be ordered according to the first of their
4453    // embedded components  Since the filter has already been simplified, then
4454    // the first element in the list will be the one we think will be the most
4455    // efficient to process.
4456    if (reOrderElements)
4457    {
4458      final TreeMap<Integer,LinkedHashSet<Filter>> m = new TreeMap<>();
4459      for (final Filter f : componentSet)
4460      {
4461        final Filter prioritizeComp;
4462        if ((f.filterType == FILTER_TYPE_AND) ||
4463            (f.filterType == FILTER_TYPE_OR))
4464        {
4465          if (f.filterComps.length > 0)
4466          {
4467            prioritizeComp = f.filterComps[0];
4468          }
4469          else
4470          {
4471            prioritizeComp = f;
4472          }
4473        }
4474        else
4475        {
4476          prioritizeComp = f;
4477        }
4478
4479        final Integer slot;
4480        switch (prioritizeComp.filterType)
4481        {
4482          case FILTER_TYPE_EQUALITY:
4483            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
4484            {
4485              slot = 2;
4486            }
4487            else
4488            {
4489              slot = 1;
4490            }
4491            break;
4492
4493          case FILTER_TYPE_APPROXIMATE_MATCH:
4494            slot = 3;
4495            break;
4496
4497          case FILTER_TYPE_PRESENCE:
4498            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
4499            {
4500              slot = 9;
4501            }
4502            else
4503            {
4504              slot = 4;
4505            }
4506            break;
4507
4508          case FILTER_TYPE_SUBSTRING:
4509            if (prioritizeComp.subInitial == null)
4510            {
4511              slot = 6;
4512            }
4513            else
4514            {
4515              slot = 5;
4516            }
4517            break;
4518
4519          case FILTER_TYPE_GREATER_OR_EQUAL:
4520          case FILTER_TYPE_LESS_OR_EQUAL:
4521            slot = 7;
4522            break;
4523
4524          case FILTER_TYPE_EXTENSIBLE_MATCH:
4525            slot = 8;
4526            break;
4527
4528          case FILTER_TYPE_NOT:
4529          default:
4530            slot = 10;
4531            break;
4532        }
4533
4534        LinkedHashSet<Filter> filterSet = m.get(slot-1);
4535        if (filterSet == null)
4536        {
4537          filterSet = new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
4538          m.put(slot-1, filterSet);
4539        }
4540        filterSet.add(f);
4541      }
4542
4543      componentSet.clear();
4544      for (final LinkedHashSet<Filter> filterSet : m.values())
4545      {
4546        componentSet.addAll(filterSet);
4547      }
4548    }
4549
4550
4551    // Return the new, possibly simplified filter.
4552    if (filterType == FILTER_TYPE_AND)
4553    {
4554      return createANDFilter(componentSet);
4555    }
4556    else
4557    {
4558      return createORFilter(componentSet);
4559    }
4560  }
4561
4562
4563
4564  /**
4565   * Generates a hash code for this search filter.
4566   *
4567   * @return  The generated hash code for this search filter.
4568   */
4569  @Override()
4570  public int hashCode()
4571  {
4572    final CaseIgnoreStringMatchingRule matchingRule =
4573         CaseIgnoreStringMatchingRule.getInstance();
4574    int hashCode = filterType;
4575
4576    switch (filterType)
4577    {
4578      case FILTER_TYPE_AND:
4579      case FILTER_TYPE_OR:
4580        for (final Filter f : filterComps)
4581        {
4582          hashCode += f.hashCode();
4583        }
4584        break;
4585
4586      case FILTER_TYPE_NOT:
4587        hashCode += notComp.hashCode();
4588        break;
4589
4590      case FILTER_TYPE_EQUALITY:
4591      case FILTER_TYPE_GREATER_OR_EQUAL:
4592      case FILTER_TYPE_LESS_OR_EQUAL:
4593      case FILTER_TYPE_APPROXIMATE_MATCH:
4594        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
4595        hashCode += matchingRule.normalize(assertionValue).hashCode();
4596        break;
4597
4598      case FILTER_TYPE_SUBSTRING:
4599        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
4600        if (subInitial != null)
4601        {
4602          hashCode += matchingRule.normalizeSubstring(subInitial,
4603                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode();
4604        }
4605        for (final ASN1OctetString s : subAny)
4606        {
4607          hashCode += matchingRule.normalizeSubstring(s,
4608                           MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode();
4609        }
4610        if (subFinal != null)
4611        {
4612          hashCode += matchingRule.normalizeSubstring(subFinal,
4613                           MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode();
4614        }
4615        break;
4616
4617      case FILTER_TYPE_PRESENCE:
4618        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
4619        break;
4620
4621      case FILTER_TYPE_EXTENSIBLE_MATCH:
4622        if (attrName != null)
4623        {
4624          hashCode += StaticUtils.toLowerCase(attrName).hashCode();
4625        }
4626
4627        if (matchingRuleID != null)
4628        {
4629          hashCode += StaticUtils.toLowerCase(matchingRuleID).hashCode();
4630        }
4631
4632        if (dnAttributes)
4633        {
4634          hashCode++;
4635        }
4636
4637        hashCode += matchingRule.normalize(assertionValue).hashCode();
4638        break;
4639    }
4640
4641    return hashCode;
4642  }
4643
4644
4645
4646  /**
4647   * Indicates whether the provided object is equal to this search filter.
4648   *
4649   * @param  o  The object for which to make the determination.
4650   *
4651   * @return  {@code true} if the provided object can be considered equal to
4652   *          this search filter, or {@code false} if not.
4653   */
4654  @Override()
4655  public boolean equals(@Nullable final Object o)
4656  {
4657    if (o == null)
4658    {
4659      return false;
4660    }
4661
4662    if (o == this)
4663    {
4664      return true;
4665    }
4666
4667    if (! (o instanceof Filter))
4668    {
4669      return false;
4670    }
4671
4672    final Filter f = (Filter) o;
4673    if (filterType != f.filterType)
4674    {
4675      return false;
4676    }
4677
4678    final CaseIgnoreStringMatchingRule matchingRule =
4679         CaseIgnoreStringMatchingRule.getInstance();
4680
4681    switch (filterType)
4682    {
4683      case FILTER_TYPE_AND:
4684      case FILTER_TYPE_OR:
4685        if (filterComps.length != f.filterComps.length)
4686        {
4687          return false;
4688        }
4689
4690        final HashSet<Filter> compSet =
4691             new HashSet<>(StaticUtils.computeMapCapacity(10));
4692        compSet.addAll(Arrays.asList(filterComps));
4693
4694        for (final Filter filterComp : f.filterComps)
4695        {
4696          if (! compSet.remove(filterComp))
4697          {
4698            return false;
4699          }
4700        }
4701
4702        return true;
4703
4704
4705    case FILTER_TYPE_NOT:
4706      return notComp.equals(f.notComp);
4707
4708
4709      case FILTER_TYPE_EQUALITY:
4710      case FILTER_TYPE_GREATER_OR_EQUAL:
4711      case FILTER_TYPE_LESS_OR_EQUAL:
4712      case FILTER_TYPE_APPROXIMATE_MATCH:
4713        return (attrName.equalsIgnoreCase(f.attrName) &&
4714                matchingRule.valuesMatch(assertionValue, f.assertionValue));
4715
4716
4717      case FILTER_TYPE_SUBSTRING:
4718        if (! attrName.equalsIgnoreCase(f.attrName))
4719        {
4720          return false;
4721        }
4722
4723        if (subAny.length != f.subAny.length)
4724        {
4725          return false;
4726        }
4727
4728        if (subInitial == null)
4729        {
4730          if (f.subInitial != null)
4731          {
4732            return false;
4733          }
4734        }
4735        else
4736        {
4737          if (f.subInitial == null)
4738          {
4739            return false;
4740          }
4741
4742          final ASN1OctetString si1 = matchingRule.normalizeSubstring(
4743               subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
4744          final ASN1OctetString si2 = matchingRule.normalizeSubstring(
4745               f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
4746          if (! si1.equals(si2))
4747          {
4748            return false;
4749          }
4750        }
4751
4752        for (int i=0; i < subAny.length; i++)
4753        {
4754          final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i],
4755               MatchingRule.SUBSTRING_TYPE_SUBANY);
4756          final ASN1OctetString sa2 = matchingRule.normalizeSubstring(
4757               f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY);
4758          if (! sa1.equals(sa2))
4759          {
4760            return false;
4761          }
4762        }
4763
4764        if (subFinal == null)
4765        {
4766          if (f.subFinal != null)
4767          {
4768            return false;
4769          }
4770        }
4771        else
4772        {
4773          if (f.subFinal == null)
4774          {
4775            return false;
4776          }
4777
4778          final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal,
4779               MatchingRule.SUBSTRING_TYPE_SUBFINAL);
4780          final ASN1OctetString sf2 = matchingRule.normalizeSubstring(
4781               f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL);
4782          if (! sf1.equals(sf2))
4783          {
4784            return false;
4785          }
4786        }
4787
4788        return true;
4789
4790
4791      case FILTER_TYPE_PRESENCE:
4792        return (attrName.equalsIgnoreCase(f.attrName));
4793
4794
4795      case FILTER_TYPE_EXTENSIBLE_MATCH:
4796        if (attrName == null)
4797        {
4798          if (f.attrName != null)
4799          {
4800            return false;
4801          }
4802        }
4803        else
4804        {
4805          if (f.attrName == null)
4806          {
4807            return false;
4808          }
4809          else
4810          {
4811            if (! attrName.equalsIgnoreCase(f.attrName))
4812            {
4813              return false;
4814            }
4815          }
4816        }
4817
4818        if (matchingRuleID == null)
4819        {
4820          if (f.matchingRuleID != null)
4821          {
4822            return false;
4823          }
4824        }
4825        else
4826        {
4827          if (f.matchingRuleID == null)
4828          {
4829            return false;
4830          }
4831          else
4832          {
4833            if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID))
4834            {
4835              return false;
4836            }
4837          }
4838        }
4839
4840        if (dnAttributes != f.dnAttributes)
4841        {
4842          return false;
4843        }
4844
4845        return matchingRule.valuesMatch(assertionValue, f.assertionValue);
4846
4847
4848      default:
4849        return false;
4850    }
4851  }
4852
4853
4854
4855  /**
4856   * Retrieves a string representation of this search filter.
4857   *
4858   * @return  A string representation of this search filter.
4859   */
4860  @Override()
4861  @NotNull()
4862  public String toString()
4863  {
4864    if (filterString == null)
4865    {
4866      final StringBuilder buffer = new StringBuilder();
4867      toString(buffer);
4868      filterString = buffer.toString();
4869    }
4870
4871    return filterString;
4872  }
4873
4874
4875
4876  /**
4877   * Appends a string representation of this search filter to the provided
4878   * buffer.
4879   *
4880   * @param  buffer  The buffer to which to append a string representation of
4881   *                 this search filter.
4882   */
4883  public void toString(@NotNull final StringBuilder buffer)
4884  {
4885    switch (filterType)
4886    {
4887      case FILTER_TYPE_AND:
4888        buffer.append("(&");
4889        for (final Filter f : filterComps)
4890        {
4891          f.toString(buffer);
4892        }
4893        buffer.append(')');
4894        break;
4895
4896      case FILTER_TYPE_OR:
4897        buffer.append("(|");
4898        for (final Filter f : filterComps)
4899        {
4900          f.toString(buffer);
4901        }
4902        buffer.append(')');
4903        break;
4904
4905      case FILTER_TYPE_NOT:
4906        buffer.append("(!");
4907        notComp.toString(buffer);
4908        buffer.append(')');
4909        break;
4910
4911      case FILTER_TYPE_EQUALITY:
4912        buffer.append('(');
4913        buffer.append(attrName);
4914        buffer.append('=');
4915        encodeValue(assertionValue, buffer);
4916        buffer.append(')');
4917        break;
4918
4919      case FILTER_TYPE_SUBSTRING:
4920        buffer.append('(');
4921        buffer.append(attrName);
4922        buffer.append('=');
4923        if (subInitial != null)
4924        {
4925          encodeValue(subInitial, buffer);
4926        }
4927        buffer.append('*');
4928        for (final ASN1OctetString s : subAny)
4929        {
4930          encodeValue(s, buffer);
4931          buffer.append('*');
4932        }
4933        if (subFinal != null)
4934        {
4935          encodeValue(subFinal, buffer);
4936        }
4937        buffer.append(')');
4938        break;
4939
4940      case FILTER_TYPE_GREATER_OR_EQUAL:
4941        buffer.append('(');
4942        buffer.append(attrName);
4943        buffer.append(">=");
4944        encodeValue(assertionValue, buffer);
4945        buffer.append(')');
4946        break;
4947
4948      case FILTER_TYPE_LESS_OR_EQUAL:
4949        buffer.append('(');
4950        buffer.append(attrName);
4951        buffer.append("<=");
4952        encodeValue(assertionValue, buffer);
4953        buffer.append(')');
4954        break;
4955
4956      case FILTER_TYPE_PRESENCE:
4957        buffer.append('(');
4958        buffer.append(attrName);
4959        buffer.append("=*)");
4960        break;
4961
4962      case FILTER_TYPE_APPROXIMATE_MATCH:
4963        buffer.append('(');
4964        buffer.append(attrName);
4965        buffer.append("~=");
4966        encodeValue(assertionValue, buffer);
4967        buffer.append(')');
4968        break;
4969
4970      case FILTER_TYPE_EXTENSIBLE_MATCH:
4971        buffer.append('(');
4972        if (attrName != null)
4973        {
4974          buffer.append(attrName);
4975        }
4976
4977        if (dnAttributes)
4978        {
4979          buffer.append(":dn");
4980        }
4981
4982        if (matchingRuleID != null)
4983        {
4984          buffer.append(':');
4985          buffer.append(matchingRuleID);
4986        }
4987
4988        buffer.append(":=");
4989        encodeValue(assertionValue, buffer);
4990        buffer.append(')');
4991        break;
4992    }
4993  }
4994
4995
4996
4997  /**
4998   * Retrieves a normalized string representation of this search filter.
4999   *
5000   * @return  A normalized string representation of this search filter.
5001   */
5002  @NotNull()
5003  public String toNormalizedString()
5004  {
5005    if (normalizedString == null)
5006    {
5007      final StringBuilder buffer = new StringBuilder();
5008      toNormalizedString(buffer);
5009      normalizedString = buffer.toString();
5010    }
5011
5012    return normalizedString;
5013  }
5014
5015
5016
5017  /**
5018   * Appends a normalized string representation of this search filter to the
5019   * provided buffer.
5020   *
5021   * @param  buffer  The buffer to which to append a normalized string
5022   *                 representation of this search filter.
5023   */
5024  public void toNormalizedString(@NotNull final StringBuilder buffer)
5025  {
5026    final CaseIgnoreStringMatchingRule mr =
5027         CaseIgnoreStringMatchingRule.getInstance();
5028
5029    switch (filterType)
5030    {
5031      case FILTER_TYPE_AND:
5032        buffer.append("(&");
5033        for (final Filter f : filterComps)
5034        {
5035          f.toNormalizedString(buffer);
5036        }
5037        buffer.append(')');
5038        break;
5039
5040      case FILTER_TYPE_OR:
5041        buffer.append("(|");
5042        for (final Filter f : filterComps)
5043        {
5044          f.toNormalizedString(buffer);
5045        }
5046        buffer.append(')');
5047        break;
5048
5049      case FILTER_TYPE_NOT:
5050        buffer.append("(!");
5051        notComp.toNormalizedString(buffer);
5052        buffer.append(')');
5053        break;
5054
5055      case FILTER_TYPE_EQUALITY:
5056        buffer.append('(');
5057        buffer.append(StaticUtils.toLowerCase(attrName));
5058        buffer.append('=');
5059        encodeValue(mr.normalize(assertionValue), buffer);
5060        buffer.append(')');
5061        break;
5062
5063      case FILTER_TYPE_SUBSTRING:
5064        buffer.append('(');
5065        buffer.append(StaticUtils.toLowerCase(attrName));
5066        buffer.append('=');
5067        if (subInitial != null)
5068        {
5069          encodeValue(mr.normalizeSubstring(subInitial,
5070                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer);
5071        }
5072        buffer.append('*');
5073        for (final ASN1OctetString s : subAny)
5074        {
5075          encodeValue(mr.normalizeSubstring(s,
5076                           MatchingRule.SUBSTRING_TYPE_SUBANY), buffer);
5077          buffer.append('*');
5078        }
5079        if (subFinal != null)
5080        {
5081          encodeValue(mr.normalizeSubstring(subFinal,
5082                           MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer);
5083        }
5084        buffer.append(')');
5085        break;
5086
5087      case FILTER_TYPE_GREATER_OR_EQUAL:
5088        buffer.append('(');
5089        buffer.append(StaticUtils.toLowerCase(attrName));
5090        buffer.append(">=");
5091        encodeValue(mr.normalize(assertionValue), buffer);
5092        buffer.append(')');
5093        break;
5094
5095      case FILTER_TYPE_LESS_OR_EQUAL:
5096        buffer.append('(');
5097        buffer.append(StaticUtils.toLowerCase(attrName));
5098        buffer.append("<=");
5099        encodeValue(mr.normalize(assertionValue), buffer);
5100        buffer.append(')');
5101        break;
5102
5103      case FILTER_TYPE_PRESENCE:
5104        buffer.append('(');
5105        buffer.append(StaticUtils.toLowerCase(attrName));
5106        buffer.append("=*)");
5107        break;
5108
5109      case FILTER_TYPE_APPROXIMATE_MATCH:
5110        buffer.append('(');
5111        buffer.append(StaticUtils.toLowerCase(attrName));
5112        buffer.append("~=");
5113        encodeValue(mr.normalize(assertionValue), buffer);
5114        buffer.append(')');
5115        break;
5116
5117      case FILTER_TYPE_EXTENSIBLE_MATCH:
5118        buffer.append('(');
5119        if (attrName != null)
5120        {
5121          buffer.append(StaticUtils.toLowerCase(attrName));
5122        }
5123
5124        if (dnAttributes)
5125        {
5126          buffer.append(":dn");
5127        }
5128
5129        if (matchingRuleID != null)
5130        {
5131          buffer.append(':');
5132          buffer.append(StaticUtils.toLowerCase(matchingRuleID));
5133        }
5134
5135        buffer.append(":=");
5136        encodeValue(mr.normalize(assertionValue), buffer);
5137        buffer.append(')');
5138        break;
5139    }
5140  }
5141
5142
5143
5144  /**
5145   * Encodes the provided value into a form suitable for use as the assertion
5146   * value in the string representation of a search filter.  Parentheses,
5147   * asterisks, backslashes, null characters, and any non-ASCII characters will
5148   * be escaped using a backslash before the hexadecimal representation of each
5149   * byte in the character to escape.
5150   *
5151   * @param  value  The value to be encoded.  It must not be {@code null}.
5152   *
5153   * @return  The encoded representation of the provided string.
5154   */
5155  @NotNull()
5156  public static String encodeValue(@NotNull final String value)
5157  {
5158    Validator.ensureNotNull(value);
5159
5160    final StringBuilder buffer = new StringBuilder();
5161    encodeValue(new ASN1OctetString(value), buffer);
5162    return buffer.toString();
5163  }
5164
5165
5166
5167  /**
5168   * Creates the string representation of a substring assertion with the
5169   * provided components.
5170   *
5171   * @param  subInitial  The subInitial component for this substring filter.  It
5172   *                     may be {@code null} if there is no subInitial
5173   *                     component, but it must not be empty.
5174   * @param  subAny      The set of subAny components for this substring filter.
5175   *                     It may be {@code null} or empty if there are no subAny
5176   *                     components.
5177   * @param  subFinal    The subFinal component for this substring filter.  It
5178   *                     may be {@code null} if there is no subFinal component,
5179   *                     but it must not be empty.
5180   *
5181   * @return  The string representation of a substring assertion with the
5182   *          provided components.
5183   */
5184  @NotNull()
5185  public static String createSubstringAssertion(
5186       @Nullable final String subInitial,
5187       @Nullable final String[] subAny,
5188       @Nullable final String subFinal)
5189  {
5190    final ASN1OctetString subInitialOS;
5191    if (subInitial == null)
5192    {
5193      subInitialOS = null;
5194    }
5195    else
5196    {
5197      subInitialOS = new ASN1OctetString(subInitial);
5198    }
5199
5200    final ASN1OctetString[] subAnyOS;
5201    if (subAny == null)
5202    {
5203      subAnyOS = NO_SUB_ANY;
5204    }
5205    else
5206    {
5207      subAnyOS = new ASN1OctetString[subAny.length];
5208      for (int i=0; i < subAny.length; i++)
5209      {
5210        subAnyOS[i] = new ASN1OctetString(subAny[i]);
5211      }
5212    }
5213
5214    final ASN1OctetString subFinalOS;
5215    if (subFinal == null)
5216    {
5217      subFinalOS = null;
5218    }
5219    else
5220    {
5221      subFinalOS = new ASN1OctetString(subFinal);
5222    }
5223
5224    return createSubstringAssertion(subInitialOS, subAnyOS, subFinalOS);
5225  }
5226
5227
5228
5229  /**
5230   * Creates the string representation of a substring assertion with the
5231   * provided components.
5232   *
5233   * @param  subInitial  The subInitial component for this substring filter.  It
5234   *                     may be {@code null} if there is no subInitial
5235   *                     component, but it must not be empty.
5236   * @param  subAny      The set of subAny components for this substring filter.
5237   *                     It may be {@code null} or empty if there are no subAny
5238   *                     components.
5239   * @param  subFinal    The subFinal component for this substring filter.  It
5240   *                     may be {@code null} if there is no subFinal component,
5241   *                     but it must not be empty.
5242   *
5243   * @return  The string representation of a substring assertion with the
5244   *          provided components.
5245   */
5246  @NotNull()
5247  public static String createSubstringAssertion(
5248       @Nullable final byte[] subInitial,
5249       @Nullable final byte[][] subAny,
5250       @Nullable final byte[] subFinal)
5251  {
5252    final ASN1OctetString subInitialOS;
5253    if (subInitial == null)
5254    {
5255      subInitialOS = null;
5256    }
5257    else
5258    {
5259      subInitialOS = new ASN1OctetString(subInitial);
5260    }
5261
5262    final ASN1OctetString[] subAnyOS;
5263    if (subAny == null)
5264    {
5265      subAnyOS = NO_SUB_ANY;
5266    }
5267    else
5268    {
5269      subAnyOS = new ASN1OctetString[subAny.length];
5270      for (int i=0; i < subAny.length; i++)
5271      {
5272        subAnyOS[i] = new ASN1OctetString(subAny[i]);
5273      }
5274    }
5275
5276    final ASN1OctetString subFinalOS;
5277    if (subFinal == null)
5278    {
5279      subFinalOS = null;
5280    }
5281    else
5282    {
5283      subFinalOS = new ASN1OctetString(subFinal);
5284    }
5285
5286    return createSubstringAssertion(subInitialOS, subAnyOS, subFinalOS);
5287  }
5288
5289
5290
5291  /**
5292   * Creates the string representation of a substring assertion with the
5293   * provided components.
5294   *
5295   * @param  subInitial  The subInitial component for this substring filter.  It
5296   *                     may be {@code null} if there is no subInitial
5297   *                     component, but it must not be empty.
5298   * @param  subAny      The set of subAny components for this substring filter.
5299   *                     It must not be {@code null}, but may be empty if there
5300   *                     are no subAny components.
5301   * @param  subFinal    The subFinal component for this substring filter.  It
5302   *                     may be {@code null} if there is no subFinal component,
5303   *                     but it must not be empty.
5304   *
5305   * @return  The string representation of a substring assertion with the
5306   *          provided components.
5307   */
5308  @NotNull()
5309  private static String createSubstringAssertion(
5310       @Nullable final ASN1OctetString subInitial,
5311       @Nullable final ASN1OctetString[] subAny,
5312       @Nullable final ASN1OctetString subFinal)
5313  {
5314    Validator.ensureTrue(
5315         (((subInitial != null) && (subInitial.getValueLength() > 0)) ||
5316              ((subAny != null) && (subAny.length > 0) &&
5317                   (subAny[0].getValueLength() > 0)) ||
5318              ((subFinal != null) && (subFinal.getValueLength() > 0))),
5319         "At least one substring filter component must be non-null and " +
5320              "non-empty");
5321
5322    final StringBuilder buffer = new StringBuilder();
5323    if (subInitial != null)
5324    {
5325      encodeValue(subInitial, buffer);
5326    }
5327    buffer.append('*');
5328    for (final ASN1OctetString s : subAny)
5329    {
5330      encodeValue(s, buffer);
5331      buffer.append('*');
5332    }
5333    if (subFinal != null)
5334    {
5335      encodeValue(subFinal, buffer);
5336    }
5337
5338    return buffer.toString();
5339  }
5340
5341
5342
5343  /**
5344   * Encodes the provided value into a form suitable for use as the assertion
5345   * value in the string representation of a search filter.  Parentheses,
5346   * asterisks, backslashes, null characters, and any non-ASCII characters will
5347   * be escaped using a backslash before the hexadecimal representation of each
5348   * byte in the character to escape.
5349   *
5350   * @param  value  The value to be encoded.  It must not be {@code null}.
5351   *
5352   * @return  The encoded representation of the provided string.
5353   */
5354  @NotNull()
5355  public static String encodeValue(@NotNull final byte[]value)
5356  {
5357    Validator.ensureNotNull(value);
5358
5359    final StringBuilder buffer = new StringBuilder();
5360    encodeValue(new ASN1OctetString(value), buffer);
5361    return buffer.toString();
5362  }
5363
5364
5365
5366  /**
5367   * Appends the assertion value for this filter to the provided buffer,
5368   * encoding any special characters as necessary.
5369   *
5370   * @param  value   The value to be encoded.
5371   * @param  buffer  The buffer to which the assertion value should be appended.
5372   */
5373  public static void encodeValue(@NotNull final ASN1OctetString value,
5374                                 @NotNull final StringBuilder buffer)
5375  {
5376    final byte[] valueBytes = value.getValue();
5377    for (int i=0; i < valueBytes.length; i++)
5378    {
5379      switch (StaticUtils.numBytesInUTF8CharacterWithFirstByte(valueBytes[i]))
5380      {
5381        case 1:
5382          // This character is ASCII, but might still need to be escaped.
5383          if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters.
5384              (valueBytes[i] == 0x28) || // Open parenthesis
5385              (valueBytes[i] == 0x29) || // Close parenthesis
5386              (valueBytes[i] == 0x2A) || // Asterisk
5387              (valueBytes[i] == 0x5C) || // Backslash
5388              (valueBytes[i] == 0x7F))   // DEL
5389          {
5390            buffer.append('\\');
5391            StaticUtils.toHex(valueBytes[i], buffer);
5392          }
5393          else
5394          {
5395            buffer.append((char) valueBytes[i]);
5396          }
5397          break;
5398
5399        case 2:
5400          // If there are at least two bytes left, then we'll hex-encode the
5401          // next two bytes.  Otherwise we'll hex-encode whatever is left.
5402          buffer.append('\\');
5403          StaticUtils.toHex(valueBytes[i++], buffer);
5404          if (i < valueBytes.length)
5405          {
5406            buffer.append('\\');
5407            StaticUtils.toHex(valueBytes[i], buffer);
5408          }
5409          break;
5410
5411        case 3:
5412          // If there are at least three bytes left, then we'll hex-encode the
5413          // next three bytes.  Otherwise we'll hex-encode whatever is left.
5414          buffer.append('\\');
5415          StaticUtils.toHex(valueBytes[i++], buffer);
5416          if (i < valueBytes.length)
5417          {
5418            buffer.append('\\');
5419            StaticUtils.toHex(valueBytes[i++], buffer);
5420          }
5421          if (i < valueBytes.length)
5422          {
5423            buffer.append('\\');
5424            StaticUtils.toHex(valueBytes[i], buffer);
5425          }
5426          break;
5427
5428        case 4:
5429          // If there are at least four bytes left, then we'll hex-encode the
5430          // next four bytes.  Otherwise we'll hex-encode whatever is left.
5431          buffer.append('\\');
5432          StaticUtils.toHex(valueBytes[i++], buffer);
5433          if (i < valueBytes.length)
5434          {
5435            buffer.append('\\');
5436            StaticUtils.toHex(valueBytes[i++], buffer);
5437          }
5438          if (i < valueBytes.length)
5439          {
5440            buffer.append('\\');
5441            StaticUtils.toHex(valueBytes[i++], buffer);
5442          }
5443          if (i < valueBytes.length)
5444          {
5445            buffer.append('\\');
5446            StaticUtils.toHex(valueBytes[i], buffer);
5447          }
5448          break;
5449
5450        default:
5451          // We'll hex-encode whatever is left in the buffer.
5452          while (i < valueBytes.length)
5453          {
5454            buffer.append('\\');
5455            StaticUtils.toHex(valueBytes[i++], buffer);
5456          }
5457          break;
5458      }
5459    }
5460  }
5461
5462
5463
5464  /**
5465   * Appends a number of lines comprising the Java source code that can be used
5466   * to recreate this filter to the given list.  Note that unless a first line
5467   * prefix and/or last line suffix are provided, this will just include the
5468   * code for the static method used to create the filter, starting with
5469   * "Filter.createXFilter(" and ending with the closing parenthesis for that
5470   * method call.
5471   *
5472   * @param  lineList         The list to which the source code lines should be
5473   *                          added.
5474   * @param  indentSpaces     The number of spaces that should be used to indent
5475   *                          the generated code.  It must not be negative.
5476   * @param  firstLinePrefix  An optional string that should precede the static
5477   *                          method call (e.g., it could be used for an
5478   *                          attribute assignment, like "Filter f = ").  It may
5479   *                          be {@code null} or empty if there should be no
5480   *                          first line prefix.
5481   * @param  lastLineSuffix   An optional suffix that should follow the closing
5482   *                          parenthesis of the static method call (e.g., it
5483   *                          could be a semicolon to represent the end of a
5484   *                          Java statement).  It may be {@code null} or empty
5485   *                          if there should be no last line suffix.
5486   */
5487  public void toCode(@NotNull final List<String> lineList,
5488                     final int indentSpaces,
5489                     @Nullable final String firstLinePrefix,
5490                     @Nullable final String lastLineSuffix)
5491  {
5492    // Generate a string with the appropriate indent.
5493    final StringBuilder buffer = new StringBuilder();
5494    for (int i = 0; i < indentSpaces; i++)
5495    {
5496      buffer.append(' ');
5497    }
5498    final String indent = buffer.toString();
5499
5500
5501    // Start the first line, including any appropriate prefix.
5502    buffer.setLength(0);
5503    buffer.append(indent);
5504    if (firstLinePrefix != null)
5505    {
5506      buffer.append(firstLinePrefix);
5507    }
5508
5509
5510    // Figure out what type of filter it is and create the appropriate code for
5511    // that type of filter.
5512    switch (filterType)
5513    {
5514      case FILTER_TYPE_AND:
5515      case FILTER_TYPE_OR:
5516        if (filterType == FILTER_TYPE_AND)
5517        {
5518          buffer.append("Filter.and(");
5519        }
5520        else
5521        {
5522          buffer.append("Filter.or(");
5523        }
5524        if (filterComps.length == 0)
5525        {
5526          buffer.append(')');
5527          if (lastLineSuffix != null)
5528          {
5529            buffer.append(lastLineSuffix);
5530          }
5531          lineList.add(buffer.toString());
5532          return;
5533        }
5534
5535        for (int i = 0; i < filterComps.length; i++)
5536        {
5537          String suffix;
5538          if (i == (filterComps.length - 1))
5539          {
5540            suffix = ")";
5541            if (lastLineSuffix != null)
5542            {
5543              suffix += lastLineSuffix;
5544            }
5545          }
5546          else
5547          {
5548            suffix = ",";
5549          }
5550
5551          filterComps[i].toCode(lineList, indentSpaces + 5, null, suffix);
5552        }
5553        return;
5554
5555
5556      case FILTER_TYPE_NOT:
5557        buffer.append("Filter.not(");
5558        lineList.add(buffer.toString());
5559
5560        final String suffix;
5561        if (lastLineSuffix == null)
5562        {
5563          suffix = ")";
5564        }
5565        else
5566        {
5567          suffix = ')' + lastLineSuffix;
5568        }
5569        notComp.toCode(lineList, indentSpaces + 5, null, suffix);
5570        return;
5571
5572      case FILTER_TYPE_PRESENCE:
5573        buffer.append("Filter.present(");
5574        lineList.add(buffer.toString());
5575
5576        buffer.setLength(0);
5577        buffer.append(indent);
5578        buffer.append("     \"");
5579        buffer.append(attrName);
5580        buffer.append("\")");
5581
5582        if (lastLineSuffix != null)
5583        {
5584          buffer.append(lastLineSuffix);
5585        }
5586
5587        lineList.add(buffer.toString());
5588        return;
5589
5590
5591      case FILTER_TYPE_EQUALITY:
5592      case FILTER_TYPE_GREATER_OR_EQUAL:
5593      case FILTER_TYPE_LESS_OR_EQUAL:
5594      case FILTER_TYPE_APPROXIMATE_MATCH:
5595        if (filterType == FILTER_TYPE_EQUALITY)
5596        {
5597          buffer.append("Filter.equals(");
5598        }
5599        else if (filterType == FILTER_TYPE_GREATER_OR_EQUAL)
5600        {
5601          buffer.append("Filter.greaterOrEqual(");
5602        }
5603        else if (filterType == FILTER_TYPE_LESS_OR_EQUAL)
5604        {
5605          buffer.append("Filter.lessOrEqual(");
5606        }
5607        else
5608        {
5609          buffer.append("Filter.approximateMatch(");
5610        }
5611        lineList.add(buffer.toString());
5612
5613        buffer.setLength(0);
5614        buffer.append(indent);
5615        buffer.append("     \"");
5616        buffer.append(attrName);
5617        buffer.append("\",");
5618        lineList.add(buffer.toString());
5619
5620        buffer.setLength(0);
5621        buffer.append(indent);
5622        buffer.append("     ");
5623        if (StaticUtils.isSensitiveToCodeAttribute(attrName))
5624        {
5625          buffer.append("\"---redacted-value---\"");
5626        }
5627        else if (StaticUtils.isPrintableString(assertionValue.getValue()))
5628        {
5629          buffer.append('"');
5630          buffer.append(assertionValue.stringValue());
5631          buffer.append('"');
5632        }
5633        else
5634        {
5635          StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
5636        }
5637
5638        buffer.append(')');
5639
5640        if (lastLineSuffix != null)
5641        {
5642          buffer.append(lastLineSuffix);
5643        }
5644
5645        lineList.add(buffer.toString());
5646        return;
5647
5648
5649      case FILTER_TYPE_SUBSTRING:
5650        buffer.append("Filter.substring(");
5651        lineList.add(buffer.toString());
5652
5653        buffer.setLength(0);
5654        buffer.append(indent);
5655        buffer.append("     \"");
5656        buffer.append(attrName);
5657        buffer.append("\",");
5658        lineList.add(buffer.toString());
5659
5660        final boolean isRedacted =
5661             StaticUtils.isSensitiveToCodeAttribute(attrName);
5662        boolean isPrintable = true;
5663        if (subInitial != null)
5664        {
5665          isPrintable = StaticUtils.isPrintableString(subInitial.getValue());
5666        }
5667
5668        if (isPrintable && (subAny != null))
5669        {
5670          for (final ASN1OctetString s : subAny)
5671          {
5672            if (! StaticUtils.isPrintableString(s.getValue()))
5673            {
5674              isPrintable = false;
5675              break;
5676            }
5677          }
5678        }
5679
5680        if (isPrintable && (subFinal != null))
5681        {
5682          isPrintable = StaticUtils.isPrintableString(subFinal.getValue());
5683        }
5684
5685        buffer.setLength(0);
5686        buffer.append(indent);
5687        buffer.append("     ");
5688        if (subInitial == null)
5689        {
5690          buffer.append("null");
5691        }
5692        else if (isRedacted)
5693        {
5694          buffer.append("\"---redacted-subInitial---\"");
5695        }
5696        else if (isPrintable)
5697        {
5698          buffer.append('"');
5699          buffer.append(subInitial.stringValue());
5700          buffer.append('"');
5701        }
5702        else
5703        {
5704          StaticUtils.byteArrayToCode(subInitial.getValue(), buffer);
5705        }
5706        buffer.append(',');
5707        lineList.add(buffer.toString());
5708
5709        buffer.setLength(0);
5710        buffer.append(indent);
5711        buffer.append("     ");
5712        if ((subAny == null) || (subAny.length == 0))
5713        {
5714          buffer.append("null,");
5715          lineList.add(buffer.toString());
5716        }
5717        else if (isRedacted)
5718        {
5719          buffer.append("new String[]");
5720          lineList.add(buffer.toString());
5721
5722          lineList.add(indent + "     {");
5723
5724          for (int i=0; i < subAny.length; i++)
5725          {
5726            buffer.setLength(0);
5727            buffer.append(indent);
5728            buffer.append("       \"---redacted-subAny-");
5729            buffer.append(i+1);
5730            buffer.append("---\"");
5731            if (i < (subAny.length-1))
5732            {
5733              buffer.append(',');
5734            }
5735            lineList.add(buffer.toString());
5736          }
5737
5738          lineList.add(indent + "     },");
5739        }
5740        else if (isPrintable)
5741        {
5742          buffer.append("new String[]");
5743          lineList.add(buffer.toString());
5744
5745          lineList.add(indent + "     {");
5746
5747          for (int i=0; i < subAny.length; i++)
5748          {
5749            buffer.setLength(0);
5750            buffer.append(indent);
5751            buffer.append("       \"");
5752            buffer.append(subAny[i].stringValue());
5753            buffer.append('"');
5754            if (i < (subAny.length-1))
5755            {
5756              buffer.append(',');
5757            }
5758            lineList.add(buffer.toString());
5759          }
5760
5761          lineList.add(indent + "     },");
5762        }
5763        else
5764        {
5765          buffer.append("new String[]");
5766          lineList.add(buffer.toString());
5767
5768          lineList.add(indent + "     {");
5769
5770          for (int i=0; i < subAny.length; i++)
5771          {
5772            buffer.setLength(0);
5773            buffer.append(indent);
5774            buffer.append("       ");
5775            StaticUtils.byteArrayToCode(subAny[i].getValue(), buffer);
5776            if (i < (subAny.length-1))
5777            {
5778              buffer.append(',');
5779            }
5780            lineList.add(buffer.toString());
5781          }
5782
5783          lineList.add(indent + "     },");
5784        }
5785
5786        buffer.setLength(0);
5787        buffer.append(indent);
5788        buffer.append("     ");
5789        if (subFinal == null)
5790        {
5791          buffer.append("null)");
5792        }
5793        else if (isRedacted)
5794        {
5795          buffer.append("\"---redacted-subFinal---\")");
5796        }
5797        else if (isPrintable)
5798        {
5799          buffer.append('"');
5800          buffer.append(subFinal.stringValue());
5801          buffer.append("\")");
5802        }
5803        else
5804        {
5805          StaticUtils.byteArrayToCode(subFinal.getValue(), buffer);
5806          buffer.append(')');
5807        }
5808        if (lastLineSuffix != null)
5809        {
5810          buffer.append(lastLineSuffix);
5811        }
5812        lineList.add(buffer.toString());
5813        return;
5814
5815
5816      case FILTER_TYPE_EXTENSIBLE_MATCH:
5817        buffer.append("Filter.extensibleMatch(");
5818        lineList.add(buffer.toString());
5819
5820        buffer.setLength(0);
5821        buffer.append(indent);
5822        buffer.append("     ");
5823        if (attrName == null)
5824        {
5825          buffer.append("null, // Attribute Description");
5826        }
5827        else
5828        {
5829          buffer.append('"');
5830          buffer.append(attrName);
5831          buffer.append("\",");
5832        }
5833        lineList.add(buffer.toString());
5834
5835        buffer.setLength(0);
5836        buffer.append(indent);
5837        buffer.append("     ");
5838        if (matchingRuleID == null)
5839        {
5840          buffer.append("null, // Matching Rule ID");
5841        }
5842        else
5843        {
5844          buffer.append('"');
5845          buffer.append(matchingRuleID);
5846          buffer.append("\",");
5847        }
5848        lineList.add(buffer.toString());
5849
5850        buffer.setLength(0);
5851        buffer.append(indent);
5852        buffer.append("     ");
5853        buffer.append(dnAttributes);
5854        buffer.append(", // DN Attributes");
5855        lineList.add(buffer.toString());
5856
5857        buffer.setLength(0);
5858        buffer.append(indent);
5859        buffer.append("     ");
5860        if ((attrName != null) &&
5861             StaticUtils.isSensitiveToCodeAttribute(attrName))
5862        {
5863          buffer.append("\"---redacted-value---\")");
5864        }
5865        else
5866        {
5867          if (StaticUtils.isPrintableString(assertionValue.getValue()))
5868          {
5869            buffer.append('"');
5870            buffer.append(assertionValue.stringValue());
5871            buffer.append("\")");
5872          }
5873          else
5874          {
5875            StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
5876            buffer.append(')');
5877          }
5878        }
5879
5880        if (lastLineSuffix != null)
5881        {
5882          buffer.append(lastLineSuffix);
5883        }
5884        lineList.add(buffer.toString());
5885        return;
5886    }
5887  }
5888}