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