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