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