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