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