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