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