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