001    /*
002     * Copyright 2007-2014 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2014 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.io.Serializable;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collection;
029    import java.util.HashSet;
030    import java.util.LinkedHashSet;
031    import java.util.List;
032    import java.util.TreeMap;
033    
034    import com.unboundid.asn1.ASN1Boolean;
035    import com.unboundid.asn1.ASN1Buffer;
036    import com.unboundid.asn1.ASN1BufferSequence;
037    import com.unboundid.asn1.ASN1BufferSet;
038    import com.unboundid.asn1.ASN1Element;
039    import com.unboundid.asn1.ASN1Exception;
040    import com.unboundid.asn1.ASN1OctetString;
041    import com.unboundid.asn1.ASN1Sequence;
042    import com.unboundid.asn1.ASN1Set;
043    import com.unboundid.asn1.ASN1StreamReader;
044    import com.unboundid.asn1.ASN1StreamReaderSequence;
045    import com.unboundid.asn1.ASN1StreamReaderSet;
046    import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
047    import com.unboundid.ldap.matchingrules.MatchingRule;
048    import com.unboundid.ldap.sdk.schema.Schema;
049    import com.unboundid.util.ByteStringBuffer;
050    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    attrNameLoop:
1364            while (l <= r)
1365            {
1366              final char c = filterString.charAt(l++);
1367              switch (c)
1368              {
1369                case ':':
1370                  tempFilterType = FILTER_TYPE_EXTENSIBLE_MATCH;
1371                  filterTypeKnown = true;
1372                  attrEndPos = l - 1;
1373                  break attrNameLoop;
1374    
1375                case '>':
1376                  tempFilterType = FILTER_TYPE_GREATER_OR_EQUAL;
1377                  filterTypeKnown = true;
1378                  attrEndPos = l - 1;
1379    
1380                  if (l <= r)
1381                  {
1382                    if (filterString.charAt(l++) != '=')
1383                    {
1384                      throw new LDAPException(ResultCode.FILTER_ERROR,
1385                                     ERR_FILTER_UNEXPECTED_CHAR_AFTER_GT.get(
1386                                          startPos, filterString.charAt(l-1)));
1387                    }
1388                  }
1389                  else
1390                  {
1391                    throw new LDAPException(ResultCode.FILTER_ERROR,
1392                                            ERR_FILTER_END_AFTER_GT.get(startPos));
1393                  }
1394                  break attrNameLoop;
1395    
1396                case '<':
1397                  tempFilterType = FILTER_TYPE_LESS_OR_EQUAL;
1398                  filterTypeKnown = true;
1399                  attrEndPos = l - 1;
1400    
1401                  if (l <= r)
1402                  {
1403                    if (filterString.charAt(l++) != '=')
1404                    {
1405                      throw new LDAPException(ResultCode.FILTER_ERROR,
1406                                     ERR_FILTER_UNEXPECTED_CHAR_AFTER_LT.get(
1407                                          startPos, filterString.charAt(l-1)));
1408                    }
1409                  }
1410                  else
1411                  {
1412                    throw new LDAPException(ResultCode.FILTER_ERROR,
1413                                            ERR_FILTER_END_AFTER_LT.get(startPos));
1414                  }
1415                  break attrNameLoop;
1416    
1417                case '~':
1418                  tempFilterType = FILTER_TYPE_APPROXIMATE_MATCH;
1419                  filterTypeKnown = true;
1420                  attrEndPos = l - 1;
1421    
1422                  if (l <= r)
1423                  {
1424                    if (filterString.charAt(l++) != '=')
1425                    {
1426                      throw new LDAPException(ResultCode.FILTER_ERROR,
1427                                     ERR_FILTER_UNEXPECTED_CHAR_AFTER_TILDE.get(
1428                                          startPos, filterString.charAt(l-1)));
1429                    }
1430                  }
1431                  else
1432                  {
1433                    throw new LDAPException(ResultCode.FILTER_ERROR,
1434                                            ERR_FILTER_END_AFTER_TILDE.get(
1435                                                 startPos));
1436                  }
1437                  break attrNameLoop;
1438    
1439                case '=':
1440                  // It could be either an equality, presence, or substring filter.
1441                  // We'll need to look at the value to determine that.
1442                  attrEndPos = l - 1;
1443                  break attrNameLoop;
1444              }
1445            }
1446    
1447            if (attrEndPos <= attrStartPos)
1448            {
1449              throw new LDAPException(ResultCode.FILTER_ERROR,
1450                                      ERR_FILTER_EMPTY_ATTR_NAME.get(startPos));
1451            }
1452            attrName = filterString.substring(attrStartPos, attrEndPos);
1453    
1454    
1455            // See if we're dealing with an extensible match filter.  If so, then
1456            // we may still need to do additional parsing to get the matching rule
1457            // ID and/or the dnAttributes flag.  Otherwise, we can rule out any
1458            // variables that are specific to extensible matching filters.
1459            if (filterTypeKnown && (tempFilterType == FILTER_TYPE_EXTENSIBLE_MATCH))
1460            {
1461              if (l > r)
1462              {
1463                throw new LDAPException(ResultCode.FILTER_ERROR,
1464                                        ERR_FILTER_NO_EQUALS.get(startPos));
1465              }
1466    
1467              final char c = filterString.charAt(l++);
1468              if (c == '=')
1469              {
1470                matchingRuleID = null;
1471                dnAttributes   = false;
1472              }
1473              else
1474              {
1475                // We have either a matching rule ID or a dnAttributes flag, or
1476                // both.  Iterate through the filter until we find the equal sign,
1477                // and then figure out what we have from that.
1478                boolean equalFound = false;
1479                final int substrStartPos = l - 1;
1480                while (l <= r)
1481                {
1482                  if (filterString.charAt(l++) == '=')
1483                  {
1484                    equalFound = true;
1485                    break;
1486                  }
1487                }
1488    
1489                if (! equalFound)
1490                {
1491                  throw new LDAPException(ResultCode.FILTER_ERROR,
1492                                          ERR_FILTER_NO_EQUALS.get(startPos));
1493                }
1494    
1495                final String substr = filterString.substring(substrStartPos, l-1);
1496                final String lowerSubstr = toLowerCase(substr);
1497                if (! substr.endsWith(":"))
1498                {
1499                  throw new LDAPException(ResultCode.FILTER_ERROR,
1500                                          ERR_FILTER_CANNOT_PARSE_MRID.get(
1501                                               startPos));
1502                }
1503    
1504                if (lowerSubstr.equals("dn:"))
1505                {
1506                  matchingRuleID = null;
1507                  dnAttributes   = true;
1508                }
1509                else if (lowerSubstr.startsWith("dn:"))
1510                {
1511                  matchingRuleID = substr.substring(3, substr.length() - 1);
1512                  if (matchingRuleID.length() == 0)
1513                  {
1514                    throw new LDAPException(ResultCode.FILTER_ERROR,
1515                                            ERR_FILTER_EMPTY_MRID.get(startPos));
1516                  }
1517    
1518                  dnAttributes   = true;
1519                }
1520                else
1521                {
1522                  matchingRuleID = substr.substring(0, substr.length() - 1);
1523                  dnAttributes   = false;
1524    
1525                  if (matchingRuleID.length() == 0)
1526                  {
1527                    throw new LDAPException(ResultCode.FILTER_ERROR,
1528                                            ERR_FILTER_EMPTY_MRID.get(startPos));
1529                  }
1530                }
1531              }
1532            }
1533            else
1534            {
1535              matchingRuleID = null;
1536              dnAttributes   = false;
1537            }
1538    
1539    
1540            // At this point, we're ready to read the value.  If we still don't
1541            // know what type of filter we're dealing with, then we can tell that
1542            // based on asterisks in the value.
1543            if (l > r)
1544            {
1545              assertionValue = new ASN1OctetString();
1546              if (! filterTypeKnown)
1547              {
1548                tempFilterType = FILTER_TYPE_EQUALITY;
1549              }
1550    
1551              subInitial = null;
1552              subAny     = NO_SUB_ANY;
1553              subFinal   = null;
1554            }
1555            else if (l == r)
1556            {
1557              if (filterTypeKnown)
1558              {
1559                switch (filterString.charAt(l))
1560                {
1561                  case '*':
1562                  case '(':
1563                  case ')':
1564                  case '\\':
1565                    throw new LDAPException(ResultCode.FILTER_ERROR,
1566                                            ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(
1567                                                 filterString.charAt(l), startPos));
1568                }
1569    
1570                assertionValue =
1571                     new ASN1OctetString(filterString.substring(l, l+1));
1572              }
1573              else
1574              {
1575                final char c = filterString.charAt(l);
1576                switch (c)
1577                {
1578                  case '*':
1579                    tempFilterType = FILTER_TYPE_PRESENCE;
1580                    assertionValue = null;
1581                    break;
1582    
1583                  case '\\':
1584                  case '(':
1585                  case ')':
1586                    throw new LDAPException(ResultCode.FILTER_ERROR,
1587                                            ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(
1588                                                 filterString.charAt(l), startPos));
1589    
1590                  default:
1591                    tempFilterType = FILTER_TYPE_EQUALITY;
1592                    assertionValue =
1593                         new ASN1OctetString(filterString.substring(l, l+1));
1594                    break;
1595                }
1596              }
1597    
1598              subInitial     = null;
1599              subAny         = NO_SUB_ANY;
1600              subFinal       = null;
1601            }
1602            else
1603            {
1604              if (! filterTypeKnown)
1605              {
1606                tempFilterType = FILTER_TYPE_EQUALITY;
1607              }
1608    
1609              final int valueStartPos = l;
1610              ASN1OctetString tempSubInitial = null;
1611              ASN1OctetString tempSubFinal   = null;
1612              final ArrayList<ASN1OctetString> subAnyList =
1613                   new ArrayList<ASN1OctetString>(1);
1614              ByteStringBuffer buffer = new ByteStringBuffer(r - l + 1);
1615              while (l <= r)
1616              {
1617                final char c = filterString.charAt(l++);
1618                switch (c)
1619                {
1620                  case '*':
1621                    if (filterTypeKnown)
1622                    {
1623                      throw new LDAPException(ResultCode.FILTER_ERROR,
1624                                              ERR_FILTER_UNEXPECTED_ASTERISK.get(
1625                                                   startPos));
1626                    }
1627                    else
1628                    {
1629                      if ((l-1) == valueStartPos)
1630                      {
1631                        // The first character is an asterisk, so there is no
1632                        // subInitial.
1633                      }
1634                      else
1635                      {
1636                        if (tempFilterType == FILTER_TYPE_SUBSTRING)
1637                        {
1638                          // We already know that it's a substring filter, so this
1639                          // must be a subAny portion.  However, if the buffer is
1640                          // empty, then that means that there were two asterisks
1641                          // right next to each other, which is invalid.
1642                          if (buffer.length() == 0)
1643                          {
1644                            throw new LDAPException(ResultCode.FILTER_ERROR,
1645                                 ERR_FILTER_UNEXPECTED_DOUBLE_ASTERISK.get(
1646                                      startPos));
1647                          }
1648                          else
1649                          {
1650                            subAnyList.add(
1651                                 new ASN1OctetString(buffer.toByteArray()));
1652                            buffer = new ByteStringBuffer(r - l + 1);
1653                          }
1654                        }
1655                        else
1656                        {
1657                          // We haven't yet set the filter type, so the buffer must
1658                          // contain the subInitial portion.  We also know it's not
1659                          // empty because of an earlier check.
1660                          tempSubInitial =
1661                               new ASN1OctetString(buffer.toByteArray());
1662                          buffer = new ByteStringBuffer(r - l + 1);
1663                        }
1664                      }
1665    
1666                      tempFilterType = FILTER_TYPE_SUBSTRING;
1667                    }
1668                    break;
1669    
1670                  case '\\':
1671                    l = readEscapedHexString(filterString, l, buffer);
1672                    break;
1673    
1674                  case '(':
1675                    throw new LDAPException(ResultCode.FILTER_ERROR,
1676                                            ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(
1677                                                 l));
1678    
1679                  case ')':
1680                    throw new LDAPException(ResultCode.FILTER_ERROR,
1681                                            ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(
1682                                                 l));
1683    
1684                  default:
1685                    buffer.append(c);
1686                    break;
1687                }
1688              }
1689    
1690              if ((tempFilterType == FILTER_TYPE_SUBSTRING) &&
1691                  (buffer.length() > 0))
1692              {
1693                // The buffer must contain the subFinal portion.
1694                tempSubFinal = new ASN1OctetString(buffer.toByteArray());
1695              }
1696    
1697              subInitial = tempSubInitial;
1698              subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
1699              subFinal = tempSubFinal;
1700    
1701              if (tempFilterType == FILTER_TYPE_SUBSTRING)
1702              {
1703                assertionValue = null;
1704              }
1705              else
1706              {
1707                assertionValue = new ASN1OctetString(buffer.toByteArray());
1708              }
1709            }
1710    
1711            filterType = tempFilterType;
1712            break;
1713        }
1714    
1715    
1716        if (startPos == 0)
1717        {
1718          return new Filter(filterString, filterType, filterComps, notComp,
1719                            attrName, assertionValue, subInitial, subAny, subFinal,
1720                            matchingRuleID, dnAttributes);
1721        }
1722        else
1723        {
1724          return new Filter(filterString.substring(startPos, endPos+1), filterType,
1725                            filterComps, notComp, attrName, assertionValue,
1726                            subInitial, subAny, subFinal, matchingRuleID,
1727                            dnAttributes);
1728        }
1729      }
1730    
1731    
1732    
1733      /**
1734       * Parses the specified portion of the provided filter string to obtain a set
1735       * of filter components for use in an AND or OR filter.
1736       *
1737       * @param  filterString  The string representation for the set of filters.
1738       * @param  startPos      The position of the first character to consider as
1739       *                       part of the first filter.
1740       * @param  endPos        The position of the last character to consider as
1741       *                       part of the last filter.
1742       * @param  depth         The current nesting depth for this filter.  It should
1743       *                       be increased by one for each AND, OR, or NOT filter
1744       *                       encountered, in order to prevent stack overflow
1745       *                       errors from excessive recursion.
1746       *
1747       * @return  The decoded set of search filters.
1748       *
1749       * @throws  LDAPException  If the provided string cannot be decoded as a set
1750       *                         of LDAP search filters.
1751       */
1752      private static Filter[] parseFilterComps(final String filterString,
1753                                               final int startPos, final int endPos,
1754                                               final int depth)
1755              throws LDAPException
1756      {
1757        if (startPos > endPos)
1758        {
1759          // This is acceptable, since it can represent an LDAP TRUE or FALSE filter
1760          // as described in RFC 4526.
1761          return NO_FILTERS;
1762        }
1763    
1764    
1765        // The set of filters must start with an opening parenthesis, and end with a
1766        // closing parenthesis.
1767        if (filterString.charAt(startPos) != '(')
1768        {
1769          throw new LDAPException(ResultCode.FILTER_ERROR,
1770                                  ERR_FILTER_EXPECTED_OPEN_PAREN.get(startPos));
1771        }
1772        if (filterString.charAt(endPos) != ')')
1773        {
1774          throw new LDAPException(ResultCode.FILTER_ERROR,
1775                                  ERR_FILTER_EXPECTED_CLOSE_PAREN.get(startPos));
1776        }
1777    
1778    
1779        // Iterate through the specified portion of the filter string and count
1780        // opening and closing parentheses to figure out where one filter ends and
1781        // another begins.
1782        final ArrayList<Filter> filterList = new ArrayList<Filter>(5);
1783        int filterStartPos = startPos;
1784        int pos = startPos;
1785        int numOpen = 0;
1786        while (pos <= endPos)
1787        {
1788          final char c = filterString.charAt(pos++);
1789          if (c == '(')
1790          {
1791            numOpen++;
1792          }
1793          else if (c == ')')
1794          {
1795            numOpen--;
1796            if (numOpen == 0)
1797            {
1798              filterList.add(create(filterString, filterStartPos, pos-1, depth));
1799              filterStartPos = pos;
1800            }
1801          }
1802        }
1803    
1804        if (numOpen != 0)
1805        {
1806          throw new LDAPException(ResultCode.FILTER_ERROR,
1807                                  ERR_FILTER_MISMATCHED_PARENS.get(startPos,
1808                                                                   endPos));
1809        }
1810    
1811        return filterList.toArray(new Filter[filterList.size()]);
1812      }
1813    
1814    
1815    
1816      /**
1817       * Reads one or more hex-encoded bytes from the specified portion of the
1818       * filter string.
1819       *
1820       * @param  filterString  The string from which the data is to be read.
1821       * @param  startPos      The position at which to start reading.  This should
1822       *                       be the position of first hex character immediately
1823       *                       after the initial backslash.
1824       * @param  buffer        The buffer to which the decoded string portion should
1825       *                       be appended.
1826       *
1827       * @return  The position at which the caller may resume parsing.
1828       *
1829       * @throws  LDAPException  If a problem occurs while reading hex-encoded
1830       *                         bytes.
1831       */
1832      private static int readEscapedHexString(final String filterString,
1833                                              final int startPos,
1834                                              final ByteStringBuffer buffer)
1835              throws LDAPException
1836      {
1837        byte b;
1838        switch (filterString.charAt(startPos))
1839        {
1840          case '0':
1841            b = 0x00;
1842            break;
1843          case '1':
1844            b = 0x10;
1845            break;
1846          case '2':
1847            b = 0x20;
1848            break;
1849          case '3':
1850            b = 0x30;
1851            break;
1852          case '4':
1853            b = 0x40;
1854            break;
1855          case '5':
1856            b = 0x50;
1857            break;
1858          case '6':
1859            b = 0x60;
1860            break;
1861          case '7':
1862            b = 0x70;
1863            break;
1864          case '8':
1865            b = (byte) 0x80;
1866            break;
1867          case '9':
1868            b = (byte) 0x90;
1869            break;
1870          case 'a':
1871          case 'A':
1872            b = (byte) 0xA0;
1873            break;
1874          case 'b':
1875          case 'B':
1876            b = (byte) 0xB0;
1877            break;
1878          case 'c':
1879          case 'C':
1880            b = (byte) 0xC0;
1881            break;
1882          case 'd':
1883          case 'D':
1884            b = (byte) 0xD0;
1885            break;
1886          case 'e':
1887          case 'E':
1888            b = (byte) 0xE0;
1889            break;
1890          case 'f':
1891          case 'F':
1892            b = (byte) 0xF0;
1893            break;
1894          default:
1895            throw new LDAPException(ResultCode.FILTER_ERROR,
1896                 ERR_FILTER_INVALID_HEX_CHAR.get(filterString.charAt(startPos),
1897                      startPos));
1898        }
1899    
1900        switch (filterString.charAt(startPos+1))
1901        {
1902          case '0':
1903            // No action is required.
1904            break;
1905          case '1':
1906            b |= 0x01;
1907            break;
1908          case '2':
1909            b |= 0x02;
1910            break;
1911          case '3':
1912            b |= 0x03;
1913            break;
1914          case '4':
1915            b |= 0x04;
1916            break;
1917          case '5':
1918            b |= 0x05;
1919            break;
1920          case '6':
1921            b |= 0x06;
1922            break;
1923          case '7':
1924            b |= 0x07;
1925            break;
1926          case '8':
1927            b |= 0x08;
1928            break;
1929          case '9':
1930            b |= 0x09;
1931            break;
1932          case 'a':
1933          case 'A':
1934            b |= 0x0A;
1935            break;
1936          case 'b':
1937          case 'B':
1938            b |= 0x0B;
1939            break;
1940          case 'c':
1941          case 'C':
1942            b |= 0x0C;
1943            break;
1944          case 'd':
1945          case 'D':
1946            b |= 0x0D;
1947            break;
1948          case 'e':
1949          case 'E':
1950            b |= 0x0E;
1951            break;
1952          case 'f':
1953          case 'F':
1954            b |= 0x0F;
1955            break;
1956          default:
1957            throw new LDAPException(ResultCode.FILTER_ERROR,
1958                 ERR_FILTER_INVALID_HEX_CHAR.get(filterString.charAt(startPos+1),
1959                      (startPos+1)));
1960        }
1961    
1962        buffer.append(b);
1963        return startPos+2;
1964      }
1965    
1966    
1967    
1968      /**
1969       * Writes an ASN.1-encoded representation of this filter to the provided ASN.1
1970       * buffer.
1971       *
1972       * @param  buffer  The ASN.1 buffer to which the encoded representation should
1973       *                 be written.
1974       */
1975      public void writeTo(final ASN1Buffer buffer)
1976      {
1977        switch (filterType)
1978        {
1979          case FILTER_TYPE_AND:
1980          case FILTER_TYPE_OR:
1981            final ASN1BufferSet compSet = buffer.beginSet(filterType);
1982            for (final Filter f : filterComps)
1983            {
1984              f.writeTo(buffer);
1985            }
1986            compSet.end();
1987            break;
1988    
1989          case FILTER_TYPE_NOT:
1990            buffer.addElement(
1991                 new ASN1Element(filterType, notComp.encode().encode()));
1992            break;
1993    
1994          case FILTER_TYPE_EQUALITY:
1995          case FILTER_TYPE_GREATER_OR_EQUAL:
1996          case FILTER_TYPE_LESS_OR_EQUAL:
1997          case FILTER_TYPE_APPROXIMATE_MATCH:
1998            final ASN1BufferSequence avaSequence = buffer.beginSequence(filterType);
1999            buffer.addOctetString(attrName);
2000            buffer.addElement(assertionValue);
2001            avaSequence.end();
2002            break;
2003    
2004          case FILTER_TYPE_SUBSTRING:
2005            final ASN1BufferSequence subFilterSequence =
2006                 buffer.beginSequence(filterType);
2007            buffer.addOctetString(attrName);
2008    
2009            final ASN1BufferSequence valueSequence = buffer.beginSequence();
2010            if (subInitial != null)
2011            {
2012              buffer.addOctetString(SUBSTRING_TYPE_SUBINITIAL,
2013                                    subInitial.getValue());
2014            }
2015    
2016            for (final ASN1OctetString s : subAny)
2017            {
2018              buffer.addOctetString(SUBSTRING_TYPE_SUBANY, s.getValue());
2019            }
2020    
2021            if (subFinal != null)
2022            {
2023              buffer.addOctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue());
2024            }
2025            valueSequence.end();
2026            subFilterSequence.end();
2027            break;
2028    
2029          case FILTER_TYPE_PRESENCE:
2030            buffer.addOctetString(filterType, attrName);
2031            break;
2032    
2033          case FILTER_TYPE_EXTENSIBLE_MATCH:
2034            final ASN1BufferSequence mrSequence = buffer.beginSequence(filterType);
2035            if (matchingRuleID != null)
2036            {
2037              buffer.addOctetString(EXTENSIBLE_TYPE_MATCHING_RULE_ID,
2038                                    matchingRuleID);
2039            }
2040    
2041            if (attrName != null)
2042            {
2043              buffer.addOctetString(EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName);
2044            }
2045    
2046            buffer.addOctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2047                                  assertionValue.getValue());
2048    
2049            if (dnAttributes)
2050            {
2051              buffer.addBoolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true);
2052            }
2053            mrSequence.end();
2054            break;
2055        }
2056      }
2057    
2058    
2059    
2060      /**
2061       * Encodes this search filter to an ASN.1 element suitable for inclusion in an
2062       * LDAP search request protocol op.
2063       *
2064       * @return  An ASN.1 element containing the encoded search filter.
2065       */
2066      public ASN1Element encode()
2067      {
2068        switch (filterType)
2069        {
2070          case FILTER_TYPE_AND:
2071          case FILTER_TYPE_OR:
2072            final ASN1Element[] filterElements =
2073                 new ASN1Element[filterComps.length];
2074            for (int i=0; i < filterComps.length; i++)
2075            {
2076              filterElements[i] = filterComps[i].encode();
2077            }
2078            return new ASN1Set(filterType, filterElements);
2079    
2080    
2081          case FILTER_TYPE_NOT:
2082            return new ASN1Element(filterType, notComp.encode().encode());
2083    
2084    
2085          case FILTER_TYPE_EQUALITY:
2086          case FILTER_TYPE_GREATER_OR_EQUAL:
2087          case FILTER_TYPE_LESS_OR_EQUAL:
2088          case FILTER_TYPE_APPROXIMATE_MATCH:
2089            final ASN1OctetString[] attrValueAssertionElements =
2090            {
2091              new ASN1OctetString(attrName),
2092              assertionValue
2093            };
2094            return new ASN1Sequence(filterType, attrValueAssertionElements);
2095    
2096    
2097          case FILTER_TYPE_SUBSTRING:
2098            final ArrayList<ASN1OctetString> subList =
2099                 new ArrayList<ASN1OctetString>(2 + subAny.length);
2100            if (subInitial != null)
2101            {
2102              subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBINITIAL,
2103                                              subInitial.getValue()));
2104            }
2105    
2106            for (final ASN1Element subAnyElement : subAny)
2107            {
2108              subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBANY,
2109                                              subAnyElement.getValue()));
2110            }
2111    
2112    
2113            if (subFinal != null)
2114            {
2115              subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBFINAL,
2116                                              subFinal.getValue()));
2117            }
2118    
2119            final ASN1Element[] subFilterElements =
2120            {
2121              new ASN1OctetString(attrName),
2122              new ASN1Sequence(subList)
2123            };
2124            return new ASN1Sequence(filterType, subFilterElements);
2125    
2126    
2127          case FILTER_TYPE_PRESENCE:
2128            return new ASN1OctetString(filterType, attrName);
2129    
2130    
2131          case FILTER_TYPE_EXTENSIBLE_MATCH:
2132            final ArrayList<ASN1Element> emElementList =
2133                 new ArrayList<ASN1Element>(4);
2134            if (matchingRuleID != null)
2135            {
2136              emElementList.add(new ASN1OctetString(
2137                   EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID));
2138            }
2139    
2140            if (attrName != null)
2141            {
2142              emElementList.add(new ASN1OctetString(
2143                   EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName));
2144            }
2145    
2146            emElementList.add(new ASN1OctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2147                                                  assertionValue.getValue()));
2148    
2149            if (dnAttributes)
2150            {
2151              emElementList.add(new ASN1Boolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES,
2152                                                true));
2153            }
2154    
2155            return new ASN1Sequence(filterType, emElementList);
2156    
2157    
2158          default:
2159            throw new AssertionError(ERR_FILTER_INVALID_TYPE.get(
2160                                          toHex(filterType)));
2161        }
2162      }
2163    
2164    
2165    
2166      /**
2167       * Reads and decodes a search filter from the provided ASN.1 stream reader.
2168       *
2169       * @param  reader  The ASN.1 stream reader from which to read the filter.
2170       *
2171       * @return  The decoded search filter.
2172       *
2173       * @throws  LDAPException  If an error occurs while reading or parsing the
2174       *                         search filter.
2175       */
2176      public static Filter readFrom(final ASN1StreamReader reader)
2177             throws LDAPException
2178      {
2179        try
2180        {
2181          final Filter[]          filterComps;
2182          final Filter            notComp;
2183          final String            attrName;
2184          final ASN1OctetString   assertionValue;
2185          final ASN1OctetString   subInitial;
2186          final ASN1OctetString[] subAny;
2187          final ASN1OctetString   subFinal;
2188          final String            matchingRuleID;
2189          final boolean           dnAttributes;
2190    
2191          final byte filterType = (byte) reader.peek();
2192    
2193          switch (filterType)
2194          {
2195            case FILTER_TYPE_AND:
2196            case FILTER_TYPE_OR:
2197              final ArrayList<Filter> comps = new ArrayList<Filter>(5);
2198              final ASN1StreamReaderSet elementSet = reader.beginSet();
2199              while (elementSet.hasMoreElements())
2200              {
2201                comps.add(readFrom(reader));
2202              }
2203    
2204              filterComps = new Filter[comps.size()];
2205              comps.toArray(filterComps);
2206    
2207              notComp        = null;
2208              attrName       = null;
2209              assertionValue = null;
2210              subInitial     = null;
2211              subAny         = NO_SUB_ANY;
2212              subFinal       = null;
2213              matchingRuleID = null;
2214              dnAttributes   = false;
2215              break;
2216    
2217    
2218            case FILTER_TYPE_NOT:
2219              final ASN1Element notFilterElement;
2220              try
2221              {
2222                final ASN1Element e = reader.readElement();
2223                notFilterElement = ASN1Element.decode(e.getValue());
2224              }
2225              catch (final ASN1Exception ae)
2226              {
2227                debugException(ae);
2228                throw new LDAPException(ResultCode.DECODING_ERROR,
2229                     ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(getExceptionMessage(ae)),
2230                     ae);
2231              }
2232              notComp = decode(notFilterElement);
2233    
2234              filterComps    = NO_FILTERS;
2235              attrName       = null;
2236              assertionValue = null;
2237              subInitial     = null;
2238              subAny         = NO_SUB_ANY;
2239              subFinal       = null;
2240              matchingRuleID = null;
2241              dnAttributes   = false;
2242              break;
2243    
2244    
2245            case FILTER_TYPE_EQUALITY:
2246            case FILTER_TYPE_GREATER_OR_EQUAL:
2247            case FILTER_TYPE_LESS_OR_EQUAL:
2248            case FILTER_TYPE_APPROXIMATE_MATCH:
2249              reader.beginSequence();
2250              attrName = reader.readString();
2251              assertionValue = new ASN1OctetString(reader.readBytes());
2252    
2253              filterComps    = NO_FILTERS;
2254              notComp        = null;
2255              subInitial     = null;
2256              subAny         = NO_SUB_ANY;
2257              subFinal       = null;
2258              matchingRuleID = null;
2259              dnAttributes   = false;
2260              break;
2261    
2262    
2263            case FILTER_TYPE_SUBSTRING:
2264              reader.beginSequence();
2265              attrName = reader.readString();
2266    
2267              ASN1OctetString tempSubInitial = null;
2268              ASN1OctetString tempSubFinal   = null;
2269              final ArrayList<ASN1OctetString> subAnyList =
2270                   new ArrayList<ASN1OctetString>(1);
2271              final ASN1StreamReaderSequence subSequence = reader.beginSequence();
2272              while (subSequence.hasMoreElements())
2273              {
2274                final byte type = (byte) reader.peek();
2275                final ASN1OctetString s =
2276                     new ASN1OctetString(type, reader.readBytes());
2277                switch (type)
2278                {
2279                  case SUBSTRING_TYPE_SUBINITIAL:
2280                    tempSubInitial = s;
2281                    break;
2282                  case SUBSTRING_TYPE_SUBANY:
2283                    subAnyList.add(s);
2284                    break;
2285                  case SUBSTRING_TYPE_SUBFINAL:
2286                    tempSubFinal = s;
2287                    break;
2288                  default:
2289                    throw new LDAPException(ResultCode.DECODING_ERROR,
2290                         ERR_FILTER_INVALID_SUBSTR_TYPE.get(toHex(type)));
2291                }
2292              }
2293    
2294              subInitial = tempSubInitial;
2295              subFinal   = tempSubFinal;
2296    
2297              subAny = new ASN1OctetString[subAnyList.size()];
2298              subAnyList.toArray(subAny);
2299    
2300              filterComps    = NO_FILTERS;
2301              notComp        = null;
2302              assertionValue = null;
2303              matchingRuleID = null;
2304              dnAttributes   = false;
2305              break;
2306    
2307    
2308            case FILTER_TYPE_PRESENCE:
2309              attrName = reader.readString();
2310    
2311              filterComps    = NO_FILTERS;
2312              notComp        = null;
2313              assertionValue = null;
2314              subInitial     = null;
2315              subAny         = NO_SUB_ANY;
2316              subFinal       = null;
2317              matchingRuleID = null;
2318              dnAttributes   = false;
2319              break;
2320    
2321    
2322            case FILTER_TYPE_EXTENSIBLE_MATCH:
2323              String          tempAttrName       = null;
2324              ASN1OctetString tempAssertionValue = null;
2325              String          tempMatchingRuleID = null;
2326              boolean         tempDNAttributes   = false;
2327    
2328              final ASN1StreamReaderSequence emSequence = reader.beginSequence();
2329              while (emSequence.hasMoreElements())
2330              {
2331                final byte type = (byte) reader.peek();
2332                switch (type)
2333                {
2334                  case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
2335                    tempAttrName = reader.readString();
2336                    break;
2337                  case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
2338                    tempMatchingRuleID = reader.readString();
2339                    break;
2340                  case EXTENSIBLE_TYPE_MATCH_VALUE:
2341                    tempAssertionValue =
2342                         new ASN1OctetString(type, reader.readBytes());
2343                    break;
2344                  case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
2345                    tempDNAttributes = reader.readBoolean();
2346                    break;
2347                  default:
2348                    throw new LDAPException(ResultCode.DECODING_ERROR,
2349                         ERR_FILTER_EXTMATCH_INVALID_TYPE.get(toHex(type)));
2350                }
2351              }
2352    
2353              if ((tempAttrName == null) && (tempMatchingRuleID == null))
2354              {
2355                throw new LDAPException(ResultCode.DECODING_ERROR,
2356                                        ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
2357              }
2358    
2359              if (tempAssertionValue == null)
2360              {
2361                throw new LDAPException(ResultCode.DECODING_ERROR,
2362                                        ERR_FILTER_EXTMATCH_NO_VALUE.get());
2363              }
2364    
2365              attrName       = tempAttrName;
2366              assertionValue = tempAssertionValue;
2367              matchingRuleID = tempMatchingRuleID;
2368              dnAttributes   = tempDNAttributes;
2369    
2370              filterComps    = NO_FILTERS;
2371              notComp        = null;
2372              subInitial     = null;
2373              subAny         = NO_SUB_ANY;
2374              subFinal       = null;
2375              break;
2376    
2377    
2378            default:
2379              throw new LDAPException(ResultCode.DECODING_ERROR,
2380                   ERR_FILTER_ELEMENT_INVALID_TYPE.get(toHex(filterType)));
2381          }
2382    
2383          return new Filter(null, filterType, filterComps, notComp, attrName,
2384                            assertionValue, subInitial, subAny, subFinal,
2385                            matchingRuleID, dnAttributes);
2386        }
2387        catch (LDAPException le)
2388        {
2389          debugException(le);
2390          throw le;
2391        }
2392        catch (Exception e)
2393        {
2394          debugException(e);
2395          throw new LDAPException(ResultCode.DECODING_ERROR,
2396               ERR_FILTER_CANNOT_DECODE.get(getExceptionMessage(e)), e);
2397        }
2398      }
2399    
2400    
2401    
2402      /**
2403       * Decodes the provided ASN.1 element as a search filter.
2404       *
2405       * @param  filterElement  The ASN.1 element containing the encoded search
2406       *                        filter.
2407       *
2408       * @return  The decoded search filter.
2409       *
2410       * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
2411       *                         a search filter.
2412       */
2413      public static Filter decode(final ASN1Element filterElement)
2414             throws LDAPException
2415      {
2416        final byte              filterType = filterElement.getType();
2417        final Filter[]          filterComps;
2418        final Filter            notComp;
2419        final String            attrName;
2420        final ASN1OctetString   assertionValue;
2421        final ASN1OctetString   subInitial;
2422        final ASN1OctetString[] subAny;
2423        final ASN1OctetString   subFinal;
2424        final String            matchingRuleID;
2425        final boolean           dnAttributes;
2426    
2427        switch (filterType)
2428        {
2429          case FILTER_TYPE_AND:
2430          case FILTER_TYPE_OR:
2431            notComp        = null;
2432            attrName       = null;
2433            assertionValue = null;
2434            subInitial     = null;
2435            subAny         = NO_SUB_ANY;
2436            subFinal       = null;
2437            matchingRuleID = null;
2438            dnAttributes   = false;
2439    
2440            final ASN1Set compSet;
2441            try
2442            {
2443              compSet = ASN1Set.decodeAsSet(filterElement);
2444            }
2445            catch (final ASN1Exception ae)
2446            {
2447              debugException(ae);
2448              throw new LDAPException(ResultCode.DECODING_ERROR,
2449                   ERR_FILTER_CANNOT_DECODE_COMPS.get(getExceptionMessage(ae)), ae);
2450            }
2451    
2452            final ASN1Element[] compElements = compSet.elements();
2453            filterComps = new Filter[compElements.length];
2454            for (int i=0; i < compElements.length; i++)
2455            {
2456              filterComps[i] = decode(compElements[i]);
2457            }
2458            break;
2459    
2460    
2461          case FILTER_TYPE_NOT:
2462            filterComps    = NO_FILTERS;
2463            attrName       = null;
2464            assertionValue = null;
2465            subInitial     = null;
2466            subAny         = NO_SUB_ANY;
2467            subFinal       = null;
2468            matchingRuleID = null;
2469            dnAttributes   = false;
2470    
2471            final ASN1Element notFilterElement;
2472            try
2473            {
2474              notFilterElement = ASN1Element.decode(filterElement.getValue());
2475            }
2476            catch (final ASN1Exception ae)
2477            {
2478              debugException(ae);
2479              throw new LDAPException(ResultCode.DECODING_ERROR,
2480                   ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(getExceptionMessage(ae)),
2481                   ae);
2482            }
2483            notComp = decode(notFilterElement);
2484            break;
2485    
2486    
2487    
2488          case FILTER_TYPE_EQUALITY:
2489          case FILTER_TYPE_GREATER_OR_EQUAL:
2490          case FILTER_TYPE_LESS_OR_EQUAL:
2491          case FILTER_TYPE_APPROXIMATE_MATCH:
2492            filterComps    = NO_FILTERS;
2493            notComp        = null;
2494            subInitial     = null;
2495            subAny         = NO_SUB_ANY;
2496            subFinal       = null;
2497            matchingRuleID = null;
2498            dnAttributes   = false;
2499    
2500            final ASN1Sequence avaSequence;
2501            try
2502            {
2503              avaSequence = ASN1Sequence.decodeAsSequence(filterElement);
2504            }
2505            catch (final ASN1Exception ae)
2506            {
2507              debugException(ae);
2508              throw new LDAPException(ResultCode.DECODING_ERROR,
2509                   ERR_FILTER_CANNOT_DECODE_AVA.get(getExceptionMessage(ae)), ae);
2510            }
2511    
2512            final ASN1Element[] avaElements = avaSequence.elements();
2513            if (avaElements.length != 2)
2514            {
2515              throw new LDAPException(ResultCode.DECODING_ERROR,
2516                                      ERR_FILTER_INVALID_AVA_ELEMENT_COUNT.get(
2517                                           avaElements.length));
2518            }
2519    
2520            attrName =
2521                 ASN1OctetString.decodeAsOctetString(avaElements[0]).stringValue();
2522            assertionValue = ASN1OctetString.decodeAsOctetString(avaElements[1]);
2523            break;
2524    
2525    
2526          case FILTER_TYPE_SUBSTRING:
2527            filterComps    = NO_FILTERS;
2528            notComp        = null;
2529            assertionValue = null;
2530            matchingRuleID = null;
2531            dnAttributes   = false;
2532    
2533            final ASN1Sequence subFilterSequence;
2534            try
2535            {
2536              subFilterSequence = ASN1Sequence.decodeAsSequence(filterElement);
2537            }
2538            catch (final ASN1Exception ae)
2539            {
2540              debugException(ae);
2541              throw new LDAPException(ResultCode.DECODING_ERROR,
2542                   ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(getExceptionMessage(ae)),
2543                   ae);
2544            }
2545    
2546            final ASN1Element[] subFilterElements = subFilterSequence.elements();
2547            if (subFilterElements.length != 2)
2548            {
2549              throw new LDAPException(ResultCode.DECODING_ERROR,
2550                                      ERR_FILTER_INVALID_SUBSTR_ASSERTION_COUNT.get(
2551                                           subFilterElements.length));
2552            }
2553    
2554            attrName = ASN1OctetString.decodeAsOctetString(
2555                            subFilterElements[0]).stringValue();
2556    
2557            final ASN1Sequence subSequence;
2558            try
2559            {
2560              subSequence = ASN1Sequence.decodeAsSequence(subFilterElements[1]);
2561            }
2562            catch (ASN1Exception ae)
2563            {
2564              debugException(ae);
2565              throw new LDAPException(ResultCode.DECODING_ERROR,
2566                   ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(getExceptionMessage(ae)),
2567                   ae);
2568            }
2569    
2570            ASN1OctetString tempSubInitial = null;
2571            ASN1OctetString tempSubFinal   = null;
2572            final ArrayList<ASN1OctetString> subAnyList =
2573                 new ArrayList<ASN1OctetString>(1);
2574    
2575            final ASN1Element[] subElements = subSequence.elements();
2576            for (final ASN1Element subElement : subElements)
2577            {
2578              switch (subElement.getType())
2579              {
2580                case SUBSTRING_TYPE_SUBINITIAL:
2581                  if (tempSubInitial == null)
2582                  {
2583                    tempSubInitial =
2584                         ASN1OctetString.decodeAsOctetString(subElement);
2585                  }
2586                  else
2587                  {
2588                    throw new LDAPException(ResultCode.DECODING_ERROR,
2589                                            ERR_FILTER_MULTIPLE_SUBINITIAL.get());
2590                  }
2591                  break;
2592    
2593                case SUBSTRING_TYPE_SUBANY:
2594                  subAnyList.add(ASN1OctetString.decodeAsOctetString(subElement));
2595                  break;
2596    
2597                case SUBSTRING_TYPE_SUBFINAL:
2598                  if (tempSubFinal == null)
2599                  {
2600                    tempSubFinal = ASN1OctetString.decodeAsOctetString(subElement);
2601                  }
2602                  else
2603                  {
2604                    throw new LDAPException(ResultCode.DECODING_ERROR,
2605                                            ERR_FILTER_MULTIPLE_SUBFINAL.get());
2606                  }
2607                  break;
2608    
2609                default:
2610                  throw new LDAPException(ResultCode.DECODING_ERROR,
2611                                          ERR_FILTER_INVALID_SUBSTR_TYPE.get(
2612                                               toHex(subElement.getType())));
2613              }
2614            }
2615    
2616            subInitial = tempSubInitial;
2617            subAny     = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
2618            subFinal   = tempSubFinal;
2619            break;
2620    
2621    
2622          case FILTER_TYPE_PRESENCE:
2623            filterComps    = NO_FILTERS;
2624            notComp        = null;
2625            assertionValue = null;
2626            subInitial     = null;
2627            subAny         = NO_SUB_ANY;
2628            subFinal       = null;
2629            matchingRuleID = null;
2630            dnAttributes   = false;
2631            attrName       =
2632                 ASN1OctetString.decodeAsOctetString(filterElement).stringValue();
2633            break;
2634    
2635    
2636          case FILTER_TYPE_EXTENSIBLE_MATCH:
2637            filterComps    = NO_FILTERS;
2638            notComp        = null;
2639            subInitial     = null;
2640            subAny         = NO_SUB_ANY;
2641            subFinal       = null;
2642    
2643            final ASN1Sequence emSequence;
2644            try
2645            {
2646              emSequence = ASN1Sequence.decodeAsSequence(filterElement);
2647            }
2648            catch (ASN1Exception ae)
2649            {
2650              debugException(ae);
2651              throw new LDAPException(ResultCode.DECODING_ERROR,
2652                   ERR_FILTER_CANNOT_DECODE_EXTMATCH.get(getExceptionMessage(ae)),
2653                   ae);
2654            }
2655    
2656            String          tempAttrName       = null;
2657            ASN1OctetString tempAssertionValue = null;
2658            String          tempMatchingRuleID = null;
2659            boolean         tempDNAttributes   = false;
2660            for (final ASN1Element e : emSequence.elements())
2661            {
2662              switch (e.getType())
2663              {
2664                case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
2665                  if (tempAttrName == null)
2666                  {
2667                    tempAttrName =
2668                         ASN1OctetString.decodeAsOctetString(e).stringValue();
2669                  }
2670                  else
2671                  {
2672                    throw new LDAPException(ResultCode.DECODING_ERROR,
2673                                   ERR_FILTER_EXTMATCH_MULTIPLE_ATTRS.get());
2674                  }
2675                  break;
2676    
2677                case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
2678                  if (tempMatchingRuleID == null)
2679                  {
2680                    tempMatchingRuleID  =
2681                         ASN1OctetString.decodeAsOctetString(e).stringValue();
2682                  }
2683                  else
2684                  {
2685                    throw new LDAPException(ResultCode.DECODING_ERROR,
2686                                   ERR_FILTER_EXTMATCH_MULTIPLE_MRIDS.get());
2687                  }
2688                  break;
2689    
2690                case EXTENSIBLE_TYPE_MATCH_VALUE:
2691                  if (tempAssertionValue == null)
2692                  {
2693                    tempAssertionValue = ASN1OctetString.decodeAsOctetString(e);
2694                  }
2695                  else
2696                  {
2697                    throw new LDAPException(ResultCode.DECODING_ERROR,
2698                                   ERR_FILTER_EXTMATCH_MULTIPLE_VALUES.get());
2699                  }
2700                  break;
2701    
2702                case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
2703                  try
2704                  {
2705                    if (tempDNAttributes)
2706                    {
2707                      throw new LDAPException(ResultCode.DECODING_ERROR,
2708                                     ERR_FILTER_EXTMATCH_MULTIPLE_DNATTRS.get());
2709                    }
2710                    else
2711                    {
2712                      tempDNAttributes =
2713                           ASN1Boolean.decodeAsBoolean(e).booleanValue();
2714                    }
2715                  }
2716                  catch (ASN1Exception ae)
2717                  {
2718                    debugException(ae);
2719                    throw new LDAPException(ResultCode.DECODING_ERROR,
2720                                   ERR_FILTER_EXTMATCH_DNATTRS_NOT_BOOLEAN.get(
2721                                        getExceptionMessage(ae)),
2722                                   ae);
2723                  }
2724                  break;
2725    
2726                default:
2727                  throw new LDAPException(ResultCode.DECODING_ERROR,
2728                                          ERR_FILTER_EXTMATCH_INVALID_TYPE.get(
2729                                               toHex(e.getType())));
2730              }
2731            }
2732    
2733            if ((tempAttrName == null) && (tempMatchingRuleID == null))
2734            {
2735              throw new LDAPException(ResultCode.DECODING_ERROR,
2736                                      ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
2737            }
2738    
2739            if (tempAssertionValue == null)
2740            {
2741              throw new LDAPException(ResultCode.DECODING_ERROR,
2742                                      ERR_FILTER_EXTMATCH_NO_VALUE.get());
2743            }
2744    
2745            attrName       = tempAttrName;
2746            assertionValue = tempAssertionValue;
2747            matchingRuleID = tempMatchingRuleID;
2748            dnAttributes   = tempDNAttributes;
2749            break;
2750    
2751    
2752          default:
2753            throw new LDAPException(ResultCode.DECODING_ERROR,
2754                                    ERR_FILTER_ELEMENT_INVALID_TYPE.get(
2755                                         toHex(filterElement.getType())));
2756        }
2757    
2758    
2759        return new Filter(null, filterType, filterComps, notComp, attrName,
2760                          assertionValue, subInitial, subAny, subFinal,
2761                          matchingRuleID, dnAttributes);
2762      }
2763    
2764    
2765    
2766      /**
2767       * Retrieves the filter type for this filter.
2768       *
2769       * @return  The filter type for this filter.
2770       */
2771      public byte getFilterType()
2772      {
2773        return filterType;
2774      }
2775    
2776    
2777    
2778      /**
2779       * Retrieves the set of filter components used in this AND or OR filter.  This
2780       * is not applicable for any other filter type.
2781       *
2782       * @return  The set of filter components used in this AND or OR filter, or an
2783       *          empty array if this is some other type of filter or if there are
2784       *          no components (i.e., as in an LDAP TRUE or LDAP FALSE filter).
2785       */
2786      public Filter[] getComponents()
2787      {
2788        return filterComps;
2789      }
2790    
2791    
2792    
2793      /**
2794       * Retrieves the filter component used in this NOT filter.  This is not
2795       * applicable for any other filter type.
2796       *
2797       * @return  The filter component used in this NOT filter, or {@code null} if
2798       *          this is some other type of filter.
2799       */
2800      public Filter getNOTComponent()
2801      {
2802        return notComp;
2803      }
2804    
2805    
2806    
2807      /**
2808       * Retrieves the name of the attribute type for this search filter.  This is
2809       * applicable for the following types of filters:
2810       * <UL>
2811       *   <LI>Equality</LI>
2812       *   <LI>Substring</LI>
2813       *   <LI>Greater or Equal</LI>
2814       *   <LI>Less or Equal</LI>
2815       *   <LI>Presence</LI>
2816       *   <LI>Approximate Match</LI>
2817       *   <LI>Extensible Match</LI>
2818       * </UL>
2819       *
2820       * @return  The name of the attribute type for this search filter, or
2821       *          {@code null} if it is not applicable for this type of filter.
2822       */
2823      public String getAttributeName()
2824      {
2825        return attrName;
2826      }
2827    
2828    
2829    
2830      /**
2831       * Retrieves the string representation of the assertion value for this search
2832       * filter.  This is applicable for the following types of filters:
2833       * <UL>
2834       *   <LI>Equality</LI>
2835       *   <LI>Greater or Equal</LI>
2836       *   <LI>Less or Equal</LI>
2837       *   <LI>Approximate Match</LI>
2838       *   <LI>Extensible Match</LI>
2839       * </UL>
2840       *
2841       * @return  The string representation of the assertion value for this search
2842       *          filter, or {@code null} if it is not applicable for this type of
2843       *          filter.
2844       */
2845      public String getAssertionValue()
2846      {
2847        if (assertionValue == null)
2848        {
2849          return null;
2850        }
2851        else
2852        {
2853          return assertionValue.stringValue();
2854        }
2855      }
2856    
2857    
2858    
2859      /**
2860       * Retrieves the binary representation of the assertion value for this search
2861       * filter.  This is applicable for the following types of filters:
2862       * <UL>
2863       *   <LI>Equality</LI>
2864       *   <LI>Greater or Equal</LI>
2865       *   <LI>Less or Equal</LI>
2866       *   <LI>Approximate Match</LI>
2867       *   <LI>Extensible Match</LI>
2868       * </UL>
2869       *
2870       * @return  The binary representation of the assertion value for this search
2871       *          filter, or {@code null} if it is not applicable for this type of
2872       *          filter.
2873       */
2874      public byte[] getAssertionValueBytes()
2875      {
2876        if (assertionValue == null)
2877        {
2878          return null;
2879        }
2880        else
2881        {
2882          return assertionValue.getValue();
2883        }
2884      }
2885    
2886    
2887    
2888      /**
2889       * Retrieves the raw assertion value for this search filter as an ASN.1
2890       * octet string.  This is applicable for the following types of filters:
2891       * <UL>
2892       *   <LI>Equality</LI>
2893       *   <LI>Greater or Equal</LI>
2894       *   <LI>Less or Equal</LI>
2895       *   <LI>Approximate Match</LI>
2896       *   <LI>Extensible Match</LI>
2897       * </UL>
2898       *
2899       * @return  The raw assertion value for this search filter as an ASN.1 octet
2900       *          string, or {@code null} if it is not applicable for this type of
2901       *          filter.
2902       */
2903      public ASN1OctetString getRawAssertionValue()
2904      {
2905        return assertionValue;
2906      }
2907    
2908    
2909    
2910      /**
2911       * Retrieves the string representation of the subInitial element for this
2912       * substring filter.  This is not applicable for any other filter type.
2913       *
2914       * @return  The string representation of the subInitial element for this
2915       *          substring filter, or {@code null} if this is some other type of
2916       *          filter, or if it is a substring filter with no subInitial element.
2917       */
2918      public String getSubInitialString()
2919      {
2920        if (subInitial == null)
2921        {
2922          return null;
2923        }
2924        else
2925        {
2926          return subInitial.stringValue();
2927        }
2928      }
2929    
2930    
2931    
2932      /**
2933       * Retrieves the binary representation of the subInitial element for this
2934       * substring filter.  This is not applicable for any other filter type.
2935       *
2936       * @return  The binary representation of the subInitial element for this
2937       *          substring filter, or {@code null} if this is some other type of
2938       *          filter, or if it is a substring filter with no subInitial element.
2939       */
2940      public byte[] getSubInitialBytes()
2941      {
2942        if (subInitial == null)
2943        {
2944          return null;
2945        }
2946        else
2947        {
2948          return subInitial.getValue();
2949        }
2950      }
2951    
2952    
2953    
2954      /**
2955       * Retrieves the raw subInitial element for this filter as an ASN.1 octet
2956       * string.  This is not applicable for any other filter type.
2957       *
2958       * @return  The raw subInitial element for this filter as an ASN.1 octet
2959       *          string, or {@code null} if this is not a substring filter, or if
2960       *          it is a substring filter with no subInitial element.
2961       */
2962      public ASN1OctetString getRawSubInitialValue()
2963      {
2964        return subInitial;
2965      }
2966    
2967    
2968    
2969      /**
2970       * Retrieves the string representations of the subAny elements for this
2971       * substring filter.  This is not applicable for any other filter type.
2972       *
2973       * @return  The string representations of the subAny elements for this
2974       *          substring filter, or an empty array if this is some other type of
2975       *          filter, or if it is a substring filter with no subFinal element.
2976       */
2977      public String[] getSubAnyStrings()
2978      {
2979        final String[] subAnyStrings = new String[subAny.length];
2980        for (int i=0; i < subAny.length; i++)
2981        {
2982          subAnyStrings[i] = subAny[i].stringValue();
2983        }
2984    
2985        return subAnyStrings;
2986      }
2987    
2988    
2989    
2990      /**
2991       * Retrieves the binary representations of the subAny elements for this
2992       * substring filter.  This is not applicable for any other filter type.
2993       *
2994       * @return  The binary representations of the subAny elements for this
2995       *          substring filter, or an empty array if this is some other type of
2996       *          filter, or if it is a substring filter with no subFinal element.
2997       */
2998      public byte[][] getSubAnyBytes()
2999      {
3000        final byte[][] subAnyBytes = new byte[subAny.length][];
3001        for (int i=0; i < subAny.length; i++)
3002        {
3003          subAnyBytes[i] = subAny[i].getValue();
3004        }
3005    
3006        return subAnyBytes;
3007      }
3008    
3009    
3010    
3011      /**
3012       * Retrieves the raw subAny values for this substring filter.  This is not
3013       * applicable for any other filter type.
3014       *
3015       * @return  The raw subAny values for this substring filter, or an empty array
3016       *          if this is some other type of filter, or if it is a substring
3017       *          filter with no subFinal element.
3018       */
3019      public ASN1OctetString[] getRawSubAnyValues()
3020      {
3021        return subAny;
3022      }
3023    
3024    
3025    
3026      /**
3027       * Retrieves the string representation of the subFinal element for this
3028       * substring filter.  This is not applicable for any other filter type.
3029       *
3030       * @return  The string representation of the subFinal element for this
3031       *          substring filter, or {@code null} if this is some other type of
3032       *          filter, or if it is a substring filter with no subFinal element.
3033       */
3034      public String getSubFinalString()
3035      {
3036        if (subFinal == null)
3037        {
3038          return null;
3039        }
3040        else
3041        {
3042          return subFinal.stringValue();
3043        }
3044      }
3045    
3046    
3047    
3048      /**
3049       * Retrieves the binary representation of the subFinal element for this
3050       * substring filter.  This is not applicable for any other filter type.
3051       *
3052       * @return  The binary representation of the subFinal element for this
3053       *          substring filter, or {@code null} if this is some other type of
3054       *          filter, or if it is a substring filter with no subFinal element.
3055       */
3056      public byte[] getSubFinalBytes()
3057      {
3058        if (subFinal == null)
3059        {
3060          return null;
3061        }
3062        else
3063        {
3064          return subFinal.getValue();
3065        }
3066      }
3067    
3068    
3069    
3070      /**
3071       * Retrieves the raw subFinal element for this filter as an ASN.1 octet
3072       * string.  This is not applicable for any other filter type.
3073       *
3074       * @return  The raw subFinal element for this filter as an ASN.1 octet
3075       *          string, or {@code null} if this is not a substring filter, or if
3076       *          it is a substring filter with no subFinal element.
3077       */
3078      public ASN1OctetString getRawSubFinalValue()
3079      {
3080        return subFinal;
3081      }
3082    
3083    
3084    
3085      /**
3086       * Retrieves the matching rule ID for this extensible match filter.  This is
3087       * not applicable for any other filter type.
3088       *
3089       * @return  The matching rule ID for this extensible match filter, or
3090       *          {@code null} if this is some other type of filter, or if this
3091       *          extensible match filter does not have a matching rule ID.
3092       */
3093      public String getMatchingRuleID()
3094      {
3095        return matchingRuleID;
3096      }
3097    
3098    
3099    
3100      /**
3101       * Retrieves the dnAttributes flag for this extensible match filter.  This is
3102       * not applicable for any other filter type.
3103       *
3104       * @return  The dnAttributes flag for this extensible match filter.
3105       */
3106      public boolean getDNAttributes()
3107      {
3108        return dnAttributes;
3109      }
3110    
3111    
3112    
3113      /**
3114       * Indicates whether this filter matches the provided entry.  Note that this
3115       * is a best-guess effort and may not be completely accurate in all cases.
3116       * All matching will be performed using case-ignore string matching, which may
3117       * yield an unexpected result for values that should not be treated as simple
3118       * strings.  For example:
3119       * <UL>
3120       *   <LI>Two DN values which are logically equivalent may not be considered
3121       *       matches if they have different spacing.</LI>
3122       *   <LI>Ordering comparisons against numeric values may yield unexpected
3123       *       results (e.g., "2" will be considered greater than "10" because the
3124       *       character "2" has a larger ASCII value than the character "1").</LI>
3125       * </UL>
3126       * <BR>
3127       * In addition to the above constraints, it should be noted that neither
3128       * approximate matching nor extensible matching are currently supported.
3129       *
3130       * @param  entry  The entry for which to make the determination.  It must not
3131       *                be {@code null}.
3132       *
3133       * @return  {@code true} if this filter appears to match the provided entry,
3134       *          or {@code false} if not.
3135       *
3136       * @throws  LDAPException  If a problem occurs while trying to make the
3137       *                         determination.
3138       */
3139      public boolean matchesEntry(final Entry entry)
3140             throws LDAPException
3141      {
3142        return matchesEntry(entry, entry.getSchema());
3143      }
3144    
3145    
3146    
3147      /**
3148       * Indicates whether this filter matches the provided entry.  Note that this
3149       * is a best-guess effort and may not be completely accurate in all cases.
3150       * If provided, the given schema will be used in an attempt to determine the
3151       * appropriate matching rule for making the determinations, but some corner
3152       * cases may not be handled accurately.  Neither approximate matching nor
3153       * extensible matching are currently supported.
3154       *
3155       * @param  entry   The entry for which to make the determination.  It must not
3156       *                 be {@code null}.
3157       * @param  schema  The schema to use when making the determination.  If this
3158       *                 is {@code null}, then all matching will be performed using
3159       *                 a case-ignore matching rule.
3160       *
3161       * @return  {@code true} if this filter appears to match the provided entry,
3162       *          or {@code false} if not.
3163       *
3164       * @throws  LDAPException  If a problem occurs while trying to make the
3165       *                         determination.
3166       */
3167      public boolean matchesEntry(final Entry entry, final Schema schema)
3168             throws LDAPException
3169      {
3170        ensureNotNull(entry);
3171    
3172        switch (filterType)
3173        {
3174          case FILTER_TYPE_AND:
3175            for (final Filter f : filterComps)
3176            {
3177              if (! f.matchesEntry(entry, schema))
3178              {
3179                return false;
3180              }
3181            }
3182            return true;
3183    
3184          case FILTER_TYPE_OR:
3185            for (final Filter f : filterComps)
3186            {
3187              if (f.matchesEntry(entry, schema))
3188              {
3189                return true;
3190              }
3191            }
3192            return false;
3193    
3194          case FILTER_TYPE_NOT:
3195            return (! notComp.matchesEntry(entry, schema));
3196    
3197          case FILTER_TYPE_EQUALITY:
3198            Attribute a = entry.getAttribute(attrName, schema);
3199            if (a == null)
3200            {
3201              return false;
3202            }
3203    
3204            MatchingRule matchingRule =
3205                 MatchingRule.selectEqualityMatchingRule(attrName, schema);
3206            for (final ASN1OctetString v : a.getRawValues())
3207            {
3208              if (matchingRule.valuesMatch(v, assertionValue))
3209              {
3210                return true;
3211              }
3212            }
3213            return false;
3214    
3215          case FILTER_TYPE_SUBSTRING:
3216            a = entry.getAttribute(attrName, schema);
3217            if (a == null)
3218            {
3219              return false;
3220            }
3221    
3222            matchingRule =
3223                 MatchingRule.selectSubstringMatchingRule(attrName, schema);
3224            for (final ASN1OctetString v : a.getRawValues())
3225            {
3226              if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal))
3227              {
3228                return true;
3229              }
3230            }
3231            return false;
3232    
3233          case FILTER_TYPE_GREATER_OR_EQUAL:
3234            a = entry.getAttribute(attrName, schema);
3235            if (a == null)
3236            {
3237              return false;
3238            }
3239    
3240            matchingRule =
3241                 MatchingRule.selectOrderingMatchingRule(attrName, schema);
3242            for (final ASN1OctetString v : a.getRawValues())
3243            {
3244              if (matchingRule.compareValues(v, assertionValue) >= 0)
3245              {
3246                return true;
3247              }
3248            }
3249            return false;
3250    
3251          case FILTER_TYPE_LESS_OR_EQUAL:
3252            a = entry.getAttribute(attrName, schema);
3253            if (a == null)
3254            {
3255              return false;
3256            }
3257    
3258            matchingRule =
3259                 MatchingRule.selectOrderingMatchingRule(attrName, schema);
3260            for (final ASN1OctetString v : a.getRawValues())
3261            {
3262              if (matchingRule.compareValues(v, assertionValue) <= 0)
3263              {
3264                return true;
3265              }
3266            }
3267            return false;
3268    
3269          case FILTER_TYPE_PRESENCE:
3270            return (entry.hasAttribute(attrName));
3271    
3272          case FILTER_TYPE_APPROXIMATE_MATCH:
3273            throw new LDAPException(ResultCode.NOT_SUPPORTED,
3274                 ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get());
3275    
3276          case FILTER_TYPE_EXTENSIBLE_MATCH:
3277            throw new LDAPException(ResultCode.NOT_SUPPORTED,
3278                 ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get());
3279    
3280          default:
3281            throw new LDAPException(ResultCode.PARAM_ERROR,
3282                                    ERR_FILTER_INVALID_TYPE.get());
3283        }
3284      }
3285    
3286    
3287    
3288      /**
3289       * Attempts to simplify the provided filter to allow it to be more efficiently
3290       * processed by the server.  The simplifications it will make include:
3291       * <UL>
3292       *   <LI>Any AND or OR filter that contains only a single filter component
3293       *       will be converted to just that embedded filter component to eliminate
3294       *       the unnecessary AND or OR wrapper.  For example, the filter
3295       *       "(&(uid=john.doe))" will be converted to just "(uid=john.doe)".</LI>
3296       *   <LI>Any AND components inside of an AND filter will be merged into the
3297       *       outer AND filter.  Any OR components inside of an OR filter will be
3298       *       merged into the outer OR filter.  For example, the filter
3299       *       "(&(objectClass=person)(&(givenName=John)(sn=Doe)))" will be
3300       *       converted to "(&(objectClass=person)(givenName=John)(sn=Doe))".</LI>
3301       *   <LI>If {@code reOrderElements} is true, then this method will attempt to
3302       *       re-order the elements inside AND and OR filters in an attempt to
3303       *       ensure that the components which are likely to be the most efficient
3304       *       come earlier than those which are likely to be the least efficient.
3305       *       This can speed up processing in servers that process filter
3306       *       components in a left-to-right order.</LI>
3307       * </UL>
3308       * <BR><BR>
3309       * The simplification will happen recursively, in an attempt to generate a
3310       * filter that is as simple and efficient as possible.
3311       *
3312       * @param  filter           The filter to attempt to simplify.
3313       * @param  reOrderElements  Indicates whether this method may re-order the
3314       *                          elements in the filter so that, in a server that
3315       *                          evaluates the components in a left-to-right order,
3316       *                          the components which are likely to be more
3317       *                          efficient to process will be listed before those
3318       *                          which are likely to be less efficient.
3319       *
3320       * @return  The simplified filter, or the original filter if the provided
3321       *          filter is not one that can be simplified any further.
3322       */
3323      public static Filter simplifyFilter(final Filter filter,
3324                                          final boolean reOrderElements)
3325      {
3326        final byte filterType = filter.filterType;
3327        switch (filterType)
3328        {
3329          case FILTER_TYPE_AND:
3330          case FILTER_TYPE_OR:
3331            // These will be handled below.
3332            break;
3333    
3334          case FILTER_TYPE_NOT:
3335            // We may be able to simplify the filter component contained inside the
3336            // NOT.
3337            return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements));
3338    
3339          default:
3340            // We can't simplify this filter, so just return what was provided.
3341            return filter;
3342        }
3343    
3344    
3345        // An AND filter with zero components is an LDAP true filter, and we can't
3346        // simplify that.  An OR filter with zero components is an LDAP false
3347        // filter, and we can't simplify that either.  The set of components
3348        // should never be null for an AND or OR filter, but if that happens to be
3349        // the case, then we'll return the original filter.
3350        final Filter[] components = filter.filterComps;
3351        if ((components == null) || (components.length == 0))
3352        {
3353          return filter;
3354        }
3355    
3356    
3357        // For either an AND or an OR filter with just a single component, then just
3358        // return that embedded component.  But simplify it first.
3359        if (components.length == 1)
3360        {
3361          return simplifyFilter(components[0], reOrderElements);
3362        }
3363    
3364    
3365        // If we've gotten here, then we have a filter with multiple components.
3366        // Simplify each of them to the extent possible, un-embed any ANDs
3367        // contained inside an AND or ORs contained inside an OR, and eliminate any
3368        // duplicate components in the resulting top-level filter.
3369        final LinkedHashSet<Filter> componentSet = new LinkedHashSet<Filter>(10);
3370        for (final Filter f : components)
3371        {
3372          final Filter simplifiedFilter = simplifyFilter(f, reOrderElements);
3373          if (simplifiedFilter.filterType == FILTER_TYPE_AND)
3374          {
3375            if (filterType == FILTER_TYPE_AND)
3376            {
3377              // This is an AND nested inside an AND.  In that case, we'll just put
3378              // all the nested components inside the outer AND.
3379              componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3380            }
3381            else
3382            {
3383              componentSet.add(simplifiedFilter);
3384            }
3385          }
3386          else if (simplifiedFilter.filterType == FILTER_TYPE_OR)
3387          {
3388            if (filterType == FILTER_TYPE_OR)
3389            {
3390              // This is an OR nested inside an OR.  In that case, we'll just put
3391              // all the nested components inside the outer OR.
3392              componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3393            }
3394            else
3395            {
3396              componentSet.add(simplifiedFilter);
3397            }
3398          }
3399          else
3400          {
3401            componentSet.add(simplifiedFilter);
3402          }
3403        }
3404    
3405    
3406        // It's possible at this point that we are down to just a single component.
3407        // That can happen if the filter was an AND or an OR with a duplicate
3408        // element, like "(&(a=b)(a=b))".  In that case, just return that one
3409        // component.
3410        if (componentSet.size() == 1)
3411        {
3412          return componentSet.iterator().next();
3413        }
3414    
3415    
3416        // If we should re-order the components, then use the following priority
3417        // list:
3418        //
3419        // 1.  Equality components that target an attribute other than objectClass.
3420        //     These are most likely to require only a single database lookup to get
3421        //     the candidate list, and that candidate list will frequently be small.
3422        // 2.  Equality components that target the objectClass attribute.  These are
3423        //     likely to require only a single database lookup to get the candidate
3424        //     list, but the candidate list is more likely to be larger.
3425        // 3.  Approximate match components.  These are also likely to require only
3426        //     a single database lookup to get the candidate list, but that
3427        //     candidate list is likely to have a larger number of candidates.
3428        // 4.  Presence components that target an attribute other than objectClass.
3429        //     These are also likely to require only a single database lookup to get
3430        //     the candidate list, but are likely to have a large number of
3431        //     candidates.
3432        // 5.  Substring components that have a subInitial element.  These are
3433        //     generally the most efficient substring filters to process, requiring
3434        //     access to fewer database keys than substring filters with only subAny
3435        //     and/or subFinal components.
3436        // 6.  Substring components that only have subAny and/or subFinal elements.
3437        //     These will probably require a number of database lookups and will
3438        //     probably result in large candidate lists.
3439        // 7.  Greater-or-equal components and less-or-equal components.  These
3440        //     will probably require a number of database lookups and will probably
3441        //     result in large candidate lists.
3442        // 8.  Extensible match components.  Even if these are indexed, there isn't
3443        //     any good way to know how expensive they might be to process or how
3444        //     big the candidate list might be.
3445        // 9.  Presence components that target the objectClass attribute.  This is
3446        //     likely to require only a single database lookup to get the candidate
3447        //     list, but the candidate list will also be extremely large (if it's
3448        //     indexed at all) since it will match every entry.
3449        // 10. NOT components.  These are generally not possible to index and
3450        //     therefore cannot be used to create a candidate list.
3451        //
3452        // AND and OR components will be ordered according to the first of their
3453        // embedded components  Since the filter has already been simplified, then
3454        // the first element in the list will be the one we think will be the most
3455        // efficient to process.
3456        if (reOrderElements)
3457        {
3458          final TreeMap<Integer,LinkedHashSet<Filter>> m =
3459               new TreeMap<Integer,LinkedHashSet<Filter>>();
3460          for (final Filter f : componentSet)
3461          {
3462            final Filter prioritizeComp;
3463            if ((f.filterType == FILTER_TYPE_AND) ||
3464                (f.filterType == FILTER_TYPE_OR))
3465            {
3466              if (f.filterComps.length > 0)
3467              {
3468                prioritizeComp = f.filterComps[0];
3469              }
3470              else
3471              {
3472                prioritizeComp = f;
3473              }
3474            }
3475            else
3476            {
3477              prioritizeComp = f;
3478            }
3479    
3480            final Integer slot;
3481            switch (prioritizeComp.filterType)
3482            {
3483              case FILTER_TYPE_EQUALITY:
3484                if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3485                {
3486                  slot = 2;
3487                }
3488                else
3489                {
3490                  slot = 1;
3491                }
3492                break;
3493    
3494              case FILTER_TYPE_APPROXIMATE_MATCH:
3495                slot = 3;
3496                break;
3497    
3498              case FILTER_TYPE_PRESENCE:
3499                if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3500                {
3501                  slot = 9;
3502                }
3503                else
3504                {
3505                  slot = 4;
3506                }
3507                break;
3508    
3509              case FILTER_TYPE_SUBSTRING:
3510                if (prioritizeComp.subInitial == null)
3511                {
3512                  slot = 6;
3513                }
3514                else
3515                {
3516                  slot = 5;
3517                }
3518                break;
3519    
3520              case FILTER_TYPE_GREATER_OR_EQUAL:
3521              case FILTER_TYPE_LESS_OR_EQUAL:
3522                slot = 7;
3523                break;
3524    
3525              case FILTER_TYPE_EXTENSIBLE_MATCH:
3526                slot = 8;
3527                break;
3528    
3529              case FILTER_TYPE_NOT:
3530              default:
3531                slot = 10;
3532                break;
3533            }
3534    
3535            LinkedHashSet<Filter> filterSet = m.get(slot-1);
3536            if (filterSet == null)
3537            {
3538              filterSet = new LinkedHashSet<Filter>(10);
3539              m.put(slot-1, filterSet);
3540            }
3541            filterSet.add(f);
3542          }
3543    
3544          componentSet.clear();
3545          for (final LinkedHashSet<Filter> filterSet : m.values())
3546          {
3547            componentSet.addAll(filterSet);
3548          }
3549        }
3550    
3551    
3552        // Return the new, possibly simplified filter.
3553        if (filterType == FILTER_TYPE_AND)
3554        {
3555          return createANDFilter(componentSet);
3556        }
3557        else
3558        {
3559          return createORFilter(componentSet);
3560        }
3561      }
3562    
3563    
3564    
3565      /**
3566       * Generates a hash code for this search filter.
3567       *
3568       * @return  The generated hash code for this search filter.
3569       */
3570      @Override()
3571      public int hashCode()
3572      {
3573        final CaseIgnoreStringMatchingRule matchingRule =
3574             CaseIgnoreStringMatchingRule.getInstance();
3575        int hashCode = filterType;
3576    
3577        switch (filterType)
3578        {
3579          case FILTER_TYPE_AND:
3580          case FILTER_TYPE_OR:
3581            for (final Filter f : filterComps)
3582            {
3583              hashCode += f.hashCode();
3584            }
3585            break;
3586    
3587          case FILTER_TYPE_NOT:
3588            hashCode += notComp.hashCode();
3589            break;
3590    
3591          case FILTER_TYPE_EQUALITY:
3592          case FILTER_TYPE_GREATER_OR_EQUAL:
3593          case FILTER_TYPE_LESS_OR_EQUAL:
3594          case FILTER_TYPE_APPROXIMATE_MATCH:
3595            hashCode += toLowerCase(attrName).hashCode();
3596            hashCode += matchingRule.normalize(assertionValue).hashCode();
3597            break;
3598    
3599          case FILTER_TYPE_SUBSTRING:
3600            hashCode += toLowerCase(attrName).hashCode();
3601            if (subInitial != null)
3602            {
3603              hashCode += matchingRule.normalizeSubstring(subInitial,
3604                               MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode();
3605            }
3606            for (final ASN1OctetString s : subAny)
3607            {
3608              hashCode += matchingRule.normalizeSubstring(s,
3609                               MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode();
3610            }
3611            if (subFinal != null)
3612            {
3613              hashCode += matchingRule.normalizeSubstring(subFinal,
3614                               MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode();
3615            }
3616            break;
3617    
3618          case FILTER_TYPE_PRESENCE:
3619            hashCode += toLowerCase(attrName).hashCode();
3620            break;
3621    
3622          case FILTER_TYPE_EXTENSIBLE_MATCH:
3623            if (attrName != null)
3624            {
3625              hashCode += toLowerCase(attrName).hashCode();
3626            }
3627    
3628            if (matchingRuleID != null)
3629            {
3630              hashCode += toLowerCase(matchingRuleID).hashCode();
3631            }
3632    
3633            if (dnAttributes)
3634            {
3635              hashCode++;
3636            }
3637    
3638            hashCode += matchingRule.normalize(assertionValue).hashCode();
3639            break;
3640        }
3641    
3642        return hashCode;
3643      }
3644    
3645    
3646    
3647      /**
3648       * Indicates whether the provided object is equal to this search filter.
3649       *
3650       * @param  o  The object for which to make the determination.
3651       *
3652       * @return  {@code true} if the provided object can be considered equal to
3653       *          this search filter, or {@code false} if not.
3654       */
3655      @Override()
3656      public boolean equals(final Object o)
3657      {
3658        if (o == null)
3659        {
3660          return false;
3661        }
3662    
3663        if (o == this)
3664        {
3665          return true;
3666        }
3667    
3668        if (! (o instanceof Filter))
3669        {
3670          return false;
3671        }
3672    
3673        final Filter f = (Filter) o;
3674        if (filterType != f.filterType)
3675        {
3676          return false;
3677        }
3678    
3679        final CaseIgnoreStringMatchingRule matchingRule =
3680             CaseIgnoreStringMatchingRule.getInstance();
3681    
3682        switch (filterType)
3683        {
3684          case FILTER_TYPE_AND:
3685          case FILTER_TYPE_OR:
3686            if (filterComps.length != f.filterComps.length)
3687            {
3688              return false;
3689            }
3690    
3691            final HashSet<Filter> compSet = new HashSet<Filter>();
3692            compSet.addAll(Arrays.asList(filterComps));
3693    
3694            for (final Filter filterComp : f.filterComps)
3695            {
3696              if (! compSet.remove(filterComp))
3697              {
3698                return false;
3699              }
3700            }
3701    
3702            return true;
3703    
3704    
3705        case FILTER_TYPE_NOT:
3706          return notComp.equals(f.notComp);
3707    
3708    
3709          case FILTER_TYPE_EQUALITY:
3710          case FILTER_TYPE_GREATER_OR_EQUAL:
3711          case FILTER_TYPE_LESS_OR_EQUAL:
3712          case FILTER_TYPE_APPROXIMATE_MATCH:
3713            return (attrName.equalsIgnoreCase(f.attrName) &&
3714                    matchingRule.valuesMatch(assertionValue, f.assertionValue));
3715    
3716    
3717          case FILTER_TYPE_SUBSTRING:
3718            if (! attrName.equalsIgnoreCase(f.attrName))
3719            {
3720              return false;
3721            }
3722    
3723            if (subAny.length != f.subAny.length)
3724            {
3725              return false;
3726            }
3727    
3728            if (subInitial == null)
3729            {
3730              if (f.subInitial != null)
3731              {
3732                return false;
3733              }
3734            }
3735            else
3736            {
3737              if (f.subInitial == null)
3738              {
3739                return false;
3740              }
3741    
3742              final ASN1OctetString si1 = matchingRule.normalizeSubstring(
3743                   subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
3744              final ASN1OctetString si2 = matchingRule.normalizeSubstring(
3745                   f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
3746              if (! si1.equals(si2))
3747              {
3748                return false;
3749              }
3750            }
3751    
3752            for (int i=0; i < subAny.length; i++)
3753            {
3754              final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i],
3755                   MatchingRule.SUBSTRING_TYPE_SUBANY);
3756              final ASN1OctetString sa2 = matchingRule.normalizeSubstring(
3757                   f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY);
3758              if (! sa1.equals(sa2))
3759              {
3760                return false;
3761              }
3762            }
3763    
3764            if (subFinal == null)
3765            {
3766              if (f.subFinal != null)
3767              {
3768                return false;
3769              }
3770            }
3771            else
3772            {
3773              if (f.subFinal == null)
3774              {
3775                return false;
3776              }
3777    
3778              final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal,
3779                   MatchingRule.SUBSTRING_TYPE_SUBFINAL);
3780              final ASN1OctetString sf2 = matchingRule.normalizeSubstring(
3781                   f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL);
3782              if (! sf1.equals(sf2))
3783              {
3784                return false;
3785              }
3786            }
3787    
3788            return true;
3789    
3790    
3791          case FILTER_TYPE_PRESENCE:
3792            return (attrName.equalsIgnoreCase(f.attrName));
3793    
3794    
3795          case FILTER_TYPE_EXTENSIBLE_MATCH:
3796            if (attrName == null)
3797            {
3798              if (f.attrName != null)
3799              {
3800                return false;
3801              }
3802            }
3803            else
3804            {
3805              if (f.attrName == null)
3806              {
3807                return false;
3808              }
3809              else
3810              {
3811                if (! attrName.equalsIgnoreCase(f.attrName))
3812                {
3813                  return false;
3814                }
3815              }
3816            }
3817    
3818            if (matchingRuleID == null)
3819            {
3820              if (f.matchingRuleID != null)
3821              {
3822                return false;
3823              }
3824            }
3825            else
3826            {
3827              if (f.matchingRuleID == null)
3828              {
3829                return false;
3830              }
3831              else
3832              {
3833                if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID))
3834                {
3835                  return false;
3836                }
3837              }
3838            }
3839    
3840            if (dnAttributes != f.dnAttributes)
3841            {
3842              return false;
3843            }
3844    
3845            return matchingRule.valuesMatch(assertionValue, f.assertionValue);
3846    
3847    
3848          default:
3849            return false;
3850        }
3851      }
3852    
3853    
3854    
3855      /**
3856       * Retrieves a string representation of this search filter.
3857       *
3858       * @return  A string representation of this search filter.
3859       */
3860      @Override()
3861      public String toString()
3862      {
3863        if (filterString == null)
3864        {
3865          final StringBuilder buffer = new StringBuilder();
3866          toString(buffer);
3867          filterString = buffer.toString();
3868        }
3869    
3870        return filterString;
3871      }
3872    
3873    
3874    
3875      /**
3876       * Appends a string representation of this search filter to the provided
3877       * buffer.
3878       *
3879       * @param  buffer  The buffer to which to append a string representation of
3880       *                 this search filter.
3881       */
3882      public void toString(final StringBuilder buffer)
3883      {
3884        switch (filterType)
3885        {
3886          case FILTER_TYPE_AND:
3887            buffer.append("(&");
3888            for (final Filter f : filterComps)
3889            {
3890              f.toString(buffer);
3891            }
3892            buffer.append(')');
3893            break;
3894    
3895          case FILTER_TYPE_OR:
3896            buffer.append("(|");
3897            for (final Filter f : filterComps)
3898            {
3899              f.toString(buffer);
3900            }
3901            buffer.append(')');
3902            break;
3903    
3904          case FILTER_TYPE_NOT:
3905            buffer.append("(!");
3906            notComp.toString(buffer);
3907            buffer.append(')');
3908            break;
3909    
3910          case FILTER_TYPE_EQUALITY:
3911            buffer.append('(');
3912            buffer.append(attrName);
3913            buffer.append('=');
3914            encodeValue(assertionValue, buffer);
3915            buffer.append(')');
3916            break;
3917    
3918          case FILTER_TYPE_SUBSTRING:
3919            buffer.append('(');
3920            buffer.append(attrName);
3921            buffer.append('=');
3922            if (subInitial != null)
3923            {
3924              encodeValue(subInitial, buffer);
3925            }
3926            buffer.append('*');
3927            for (final ASN1OctetString s : subAny)
3928            {
3929              encodeValue(s, buffer);
3930              buffer.append('*');
3931            }
3932            if (subFinal != null)
3933            {
3934              encodeValue(subFinal, buffer);
3935            }
3936            buffer.append(')');
3937            break;
3938    
3939          case FILTER_TYPE_GREATER_OR_EQUAL:
3940            buffer.append('(');
3941            buffer.append(attrName);
3942            buffer.append(">=");
3943            encodeValue(assertionValue, buffer);
3944            buffer.append(')');
3945            break;
3946    
3947          case FILTER_TYPE_LESS_OR_EQUAL:
3948            buffer.append('(');
3949            buffer.append(attrName);
3950            buffer.append("<=");
3951            encodeValue(assertionValue, buffer);
3952            buffer.append(')');
3953            break;
3954    
3955          case FILTER_TYPE_PRESENCE:
3956            buffer.append('(');
3957            buffer.append(attrName);
3958            buffer.append("=*)");
3959            break;
3960    
3961          case FILTER_TYPE_APPROXIMATE_MATCH:
3962            buffer.append('(');
3963            buffer.append(attrName);
3964            buffer.append("~=");
3965            encodeValue(assertionValue, buffer);
3966            buffer.append(')');
3967            break;
3968    
3969          case FILTER_TYPE_EXTENSIBLE_MATCH:
3970            buffer.append('(');
3971            if (attrName != null)
3972            {
3973              buffer.append(attrName);
3974            }
3975    
3976            if (dnAttributes)
3977            {
3978              buffer.append(":dn");
3979            }
3980    
3981            if (matchingRuleID != null)
3982            {
3983              buffer.append(':');
3984              buffer.append(matchingRuleID);
3985            }
3986    
3987            buffer.append(":=");
3988            encodeValue(assertionValue, buffer);
3989            buffer.append(')');
3990            break;
3991        }
3992      }
3993    
3994    
3995    
3996      /**
3997       * Retrieves a normalized string representation of this search filter.
3998       *
3999       * @return  A normalized string representation of this search filter.
4000       */
4001      public String toNormalizedString()
4002      {
4003        if (normalizedString == null)
4004        {
4005          final StringBuilder buffer = new StringBuilder();
4006          toNormalizedString(buffer);
4007          normalizedString = buffer.toString();
4008        }
4009    
4010        return normalizedString;
4011      }
4012    
4013    
4014    
4015      /**
4016       * Appends a normalized string representation of this search filter to the
4017       * provided buffer.
4018       *
4019       * @param  buffer  The buffer to which to append a normalized string
4020       *                 representation of this search filter.
4021       */
4022      public void toNormalizedString(final StringBuilder buffer)
4023      {
4024        final CaseIgnoreStringMatchingRule mr =
4025             CaseIgnoreStringMatchingRule.getInstance();
4026    
4027        switch (filterType)
4028        {
4029          case FILTER_TYPE_AND:
4030            buffer.append("(&");
4031            for (final Filter f : filterComps)
4032            {
4033              f.toNormalizedString(buffer);
4034            }
4035            buffer.append(')');
4036            break;
4037    
4038          case FILTER_TYPE_OR:
4039            buffer.append("(|");
4040            for (final Filter f : filterComps)
4041            {
4042              f.toNormalizedString(buffer);
4043            }
4044            buffer.append(')');
4045            break;
4046    
4047          case FILTER_TYPE_NOT:
4048            buffer.append("(!");
4049            notComp.toNormalizedString(buffer);
4050            buffer.append(')');
4051            break;
4052    
4053          case FILTER_TYPE_EQUALITY:
4054            buffer.append('(');
4055            buffer.append(toLowerCase(attrName));
4056            buffer.append('=');
4057            encodeValue(mr.normalize(assertionValue), buffer);
4058            buffer.append(')');
4059            break;
4060    
4061          case FILTER_TYPE_SUBSTRING:
4062            buffer.append('(');
4063            buffer.append(toLowerCase(attrName));
4064            buffer.append('=');
4065            if (subInitial != null)
4066            {
4067              encodeValue(mr.normalizeSubstring(subInitial,
4068                               MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer);
4069            }
4070            buffer.append('*');
4071            for (final ASN1OctetString s : subAny)
4072            {
4073              encodeValue(mr.normalizeSubstring(s,
4074                               MatchingRule.SUBSTRING_TYPE_SUBANY), buffer);
4075              buffer.append('*');
4076            }
4077            if (subFinal != null)
4078            {
4079              encodeValue(mr.normalizeSubstring(subFinal,
4080                               MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer);
4081            }
4082            buffer.append(')');
4083            break;
4084    
4085          case FILTER_TYPE_GREATER_OR_EQUAL:
4086            buffer.append('(');
4087            buffer.append(toLowerCase(attrName));
4088            buffer.append(">=");
4089            encodeValue(mr.normalize(assertionValue), buffer);
4090            buffer.append(')');
4091            break;
4092    
4093          case FILTER_TYPE_LESS_OR_EQUAL:
4094            buffer.append('(');
4095            buffer.append(toLowerCase(attrName));
4096            buffer.append("<=");
4097            encodeValue(mr.normalize(assertionValue), buffer);
4098            buffer.append(')');
4099            break;
4100    
4101          case FILTER_TYPE_PRESENCE:
4102            buffer.append('(');
4103            buffer.append(toLowerCase(attrName));
4104            buffer.append("=*)");
4105            break;
4106    
4107          case FILTER_TYPE_APPROXIMATE_MATCH:
4108            buffer.append('(');
4109            buffer.append(toLowerCase(attrName));
4110            buffer.append("~=");
4111            encodeValue(mr.normalize(assertionValue), buffer);
4112            buffer.append(')');
4113            break;
4114    
4115          case FILTER_TYPE_EXTENSIBLE_MATCH:
4116            buffer.append('(');
4117            if (attrName != null)
4118            {
4119              buffer.append(toLowerCase(attrName));
4120            }
4121    
4122            if (dnAttributes)
4123            {
4124              buffer.append(":dn");
4125            }
4126    
4127            if (matchingRuleID != null)
4128            {
4129              buffer.append(':');
4130              buffer.append(toLowerCase(matchingRuleID));
4131            }
4132    
4133            buffer.append(":=");
4134            encodeValue(mr.normalize(assertionValue), buffer);
4135            buffer.append(')');
4136            break;
4137        }
4138      }
4139    
4140    
4141    
4142      /**
4143       * Encodes the provided value into a form suitable for use as the assertion
4144       * value in the string representation of a search filter.  Parentheses,
4145       * asterisks, backslashes, null characters, and any non-ASCII characters will
4146       * be escaped using a backslash before the hexadecimal representation of each
4147       * byte in the character to escape.
4148       *
4149       * @param  value  The value to be encoded.  It must not be {@code null}.
4150       *
4151       * @return  The encoded representation of the provided string.
4152       */
4153      public static String encodeValue(final String value)
4154      {
4155        ensureNotNull(value);
4156    
4157        final StringBuilder buffer = new StringBuilder();
4158        encodeValue(new ASN1OctetString(value), buffer);
4159        return buffer.toString();
4160      }
4161    
4162    
4163    
4164      /**
4165       * Encodes the provided value into a form suitable for use as the assertion
4166       * value in the string representation of a search filter.  Parentheses,
4167       * asterisks, backslashes, null characters, and any non-ASCII characters will
4168       * be escaped using a backslash before the hexadecimal representation of each
4169       * byte in the character to escape.
4170       *
4171       * @param  value  The value to be encoded.  It must not be {@code null}.
4172       *
4173       * @return  The encoded representation of the provided string.
4174       */
4175      public static String encodeValue(final byte[]value)
4176      {
4177        ensureNotNull(value);
4178    
4179        final StringBuilder buffer = new StringBuilder();
4180        encodeValue(new ASN1OctetString(value), buffer);
4181        return buffer.toString();
4182      }
4183    
4184    
4185    
4186      /**
4187       * Appends the assertion value for this filter to the provided buffer,
4188       * encoding any special characters as necessary.
4189       *
4190       * @param  value   The value to be encoded.
4191       * @param  buffer  The buffer to which the assertion value should be appended.
4192       */
4193      private static void encodeValue(final ASN1OctetString value,
4194                                      final StringBuilder buffer)
4195      {
4196        final byte[] valueBytes = value.getValue();
4197        for (int i=0; i < valueBytes.length; i++)
4198        {
4199          switch (numBytesInUTF8CharacterWithFirstByte(valueBytes[i]))
4200          {
4201            case 1:
4202              // This character is ASCII, but might still need to be escaped.  We'll
4203              // escape anything
4204              if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters.
4205                  (valueBytes[i] == 0x28) || // Open parenthesis
4206                  (valueBytes[i] == 0x29) || // Close parenthesis
4207                  (valueBytes[i] == 0x2A) || // Asterisk
4208                  (valueBytes[i] == 0x5C) || // Backslash
4209                  (valueBytes[i] == 0x7F))   // DEL
4210              {
4211                buffer.append('\\');
4212                toHex(valueBytes[i], buffer);
4213              }
4214              else
4215              {
4216                buffer.append((char) valueBytes[i]);
4217              }
4218              break;
4219    
4220            case 2:
4221              // If there are at least two bytes left, then we'll hex-encode the
4222              // next two bytes.  Otherwise we'll hex-encode whatever is left.
4223              buffer.append('\\');
4224              toHex(valueBytes[i++], buffer);
4225              if (i < valueBytes.length)
4226              {
4227                buffer.append('\\');
4228                toHex(valueBytes[i], buffer);
4229              }
4230              break;
4231    
4232            case 3:
4233              // If there are at least three bytes left, then we'll hex-encode the
4234              // next three bytes.  Otherwise we'll hex-encode whatever is left.
4235              buffer.append('\\');
4236              toHex(valueBytes[i++], buffer);
4237              if (i < valueBytes.length)
4238              {
4239                buffer.append('\\');
4240                toHex(valueBytes[i++], buffer);
4241              }
4242              if (i < valueBytes.length)
4243              {
4244                buffer.append('\\');
4245                toHex(valueBytes[i], buffer);
4246              }
4247              break;
4248    
4249            case 4:
4250              // If there are at least four bytes left, then we'll hex-encode the
4251              // next four bytes.  Otherwise we'll hex-encode whatever is left.
4252              buffer.append('\\');
4253              toHex(valueBytes[i++], buffer);
4254              if (i < valueBytes.length)
4255              {
4256                buffer.append('\\');
4257                toHex(valueBytes[i++], buffer);
4258              }
4259              if (i < valueBytes.length)
4260              {
4261                buffer.append('\\');
4262                toHex(valueBytes[i++], buffer);
4263              }
4264              if (i < valueBytes.length)
4265              {
4266                buffer.append('\\');
4267                toHex(valueBytes[i], buffer);
4268              }
4269              break;
4270    
4271            default:
4272              // We'll hex-encode whatever is left in the buffer.
4273              while (i < valueBytes.length)
4274              {
4275                buffer.append('\\');
4276                toHex(valueBytes[i++], buffer);
4277              }
4278              break;
4279          }
4280        }
4281      }
4282    }