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