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