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>If {@code reOrderElements} is true, then this method will attempt to
3435   *       re-order the elements inside AND and OR filters in an attempt to
3436   *       ensure that the components which are likely to be the most efficient
3437   *       come earlier than those which are likely to be the least efficient.
3438   *       This can speed up processing in servers that process filter
3439   *       components in a left-to-right order.</LI>
3440   * </UL>
3441   * <BR><BR>
3442   * The simplification will happen recursively, in an attempt to generate a
3443   * filter that is as simple and efficient as possible.
3444   *
3445   * @param  filter           The filter to attempt to simplify.
3446   * @param  reOrderElements  Indicates whether this method may re-order the
3447   *                          elements in the filter so that, in a server that
3448   *                          evaluates the components in a left-to-right order,
3449   *                          the components which are likely to be more
3450   *                          efficient to process will be listed before those
3451   *                          which are likely to be less efficient.
3452   *
3453   * @return  The simplified filter, or the original filter if the provided
3454   *          filter is not one that can be simplified any further.
3455   */
3456  public static Filter simplifyFilter(final Filter filter,
3457                                      final boolean reOrderElements)
3458  {
3459    final byte filterType = filter.filterType;
3460    switch (filterType)
3461    {
3462      case FILTER_TYPE_AND:
3463      case FILTER_TYPE_OR:
3464        // These will be handled below.
3465        break;
3466
3467      case FILTER_TYPE_NOT:
3468        // We may be able to simplify the filter component contained inside the
3469        // NOT.
3470        return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements));
3471
3472      default:
3473        // We can't simplify this filter, so just return what was provided.
3474        return filter;
3475    }
3476
3477
3478    // An AND filter with zero components is an LDAP true filter, and we can't
3479    // simplify that.  An OR filter with zero components is an LDAP false
3480    // filter, and we can't simplify that either.  The set of components
3481    // should never be null for an AND or OR filter, but if that happens to be
3482    // the case, then we'll return the original filter.
3483    final Filter[] components = filter.filterComps;
3484    if ((components == null) || (components.length == 0))
3485    {
3486      return filter;
3487    }
3488
3489
3490    // For either an AND or an OR filter with just a single component, then just
3491    // return that embedded component.  But simplify it first.
3492    if (components.length == 1)
3493    {
3494      return simplifyFilter(components[0], reOrderElements);
3495    }
3496
3497
3498    // If we've gotten here, then we have a filter with multiple components.
3499    // Simplify each of them to the extent possible, un-embed any ANDs
3500    // contained inside an AND or ORs contained inside an OR, and eliminate any
3501    // duplicate components in the resulting top-level filter.
3502    final LinkedHashSet<Filter> componentSet =
3503         new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
3504    for (final Filter f : components)
3505    {
3506      final Filter simplifiedFilter = simplifyFilter(f, reOrderElements);
3507      if (simplifiedFilter.filterType == FILTER_TYPE_AND)
3508      {
3509        if (filterType == FILTER_TYPE_AND)
3510        {
3511          // This is an AND nested inside an AND.  In that case, we'll just put
3512          // all the nested components inside the outer AND.
3513          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3514        }
3515        else
3516        {
3517          componentSet.add(simplifiedFilter);
3518        }
3519      }
3520      else if (simplifiedFilter.filterType == FILTER_TYPE_OR)
3521      {
3522        if (filterType == FILTER_TYPE_OR)
3523        {
3524          // This is an OR nested inside an OR.  In that case, we'll just put
3525          // all the nested components inside the outer OR.
3526          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3527        }
3528        else
3529        {
3530          componentSet.add(simplifiedFilter);
3531        }
3532      }
3533      else
3534      {
3535        componentSet.add(simplifiedFilter);
3536      }
3537    }
3538
3539
3540    // It's possible at this point that we are down to just a single component.
3541    // That can happen if the filter was an AND or an OR with a duplicate
3542    // element, like "(&(a=b)(a=b))".  In that case, just return that one
3543    // component.
3544    if (componentSet.size() == 1)
3545    {
3546      return componentSet.iterator().next();
3547    }
3548
3549
3550    // If we should re-order the components, then use the following priority
3551    // list:
3552    //
3553    // 1.  Equality components that target an attribute other than objectClass.
3554    //     These are most likely to require only a single database lookup to get
3555    //     the candidate list, and that candidate list will frequently be small.
3556    // 2.  Equality components that target the objectClass attribute.  These are
3557    //     likely to require only a single database lookup to get the candidate
3558    //     list, but the candidate list is more likely to be larger.
3559    // 3.  Approximate match components.  These are also likely to require only
3560    //     a single database lookup to get the candidate list, but that
3561    //     candidate list is likely to have a larger number of candidates.
3562    // 4.  Presence components that target an attribute other than objectClass.
3563    //     These are also likely to require only a single database lookup to get
3564    //     the candidate list, but are likely to have a large number of
3565    //     candidates.
3566    // 5.  Substring components that have a subInitial element.  These are
3567    //     generally the most efficient substring filters to process, requiring
3568    //     access to fewer database keys than substring filters with only subAny
3569    //     and/or subFinal components.
3570    // 6.  Substring components that only have subAny and/or subFinal elements.
3571    //     These will probably require a number of database lookups and will
3572    //     probably result in large candidate lists.
3573    // 7.  Greater-or-equal components and less-or-equal components.  These
3574    //     will probably require a number of database lookups and will probably
3575    //     result in large candidate lists.
3576    // 8.  Extensible match components.  Even if these are indexed, there isn't
3577    //     any good way to know how expensive they might be to process or how
3578    //     big the candidate list might be.
3579    // 9.  Presence components that target the objectClass attribute.  This is
3580    //     likely to require only a single database lookup to get the candidate
3581    //     list, but the candidate list will also be extremely large (if it's
3582    //     indexed at all) since it will match every entry.
3583    // 10. NOT components.  These are generally not possible to index and
3584    //     therefore cannot be used to create a candidate list.
3585    //
3586    // AND and OR components will be ordered according to the first of their
3587    // embedded components  Since the filter has already been simplified, then
3588    // the first element in the list will be the one we think will be the most
3589    // efficient to process.
3590    if (reOrderElements)
3591    {
3592      final TreeMap<Integer,LinkedHashSet<Filter>> m = new TreeMap<>();
3593      for (final Filter f : componentSet)
3594      {
3595        final Filter prioritizeComp;
3596        if ((f.filterType == FILTER_TYPE_AND) ||
3597            (f.filterType == FILTER_TYPE_OR))
3598        {
3599          if (f.filterComps.length > 0)
3600          {
3601            prioritizeComp = f.filterComps[0];
3602          }
3603          else
3604          {
3605            prioritizeComp = f;
3606          }
3607        }
3608        else
3609        {
3610          prioritizeComp = f;
3611        }
3612
3613        final Integer slot;
3614        switch (prioritizeComp.filterType)
3615        {
3616          case FILTER_TYPE_EQUALITY:
3617            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3618            {
3619              slot = 2;
3620            }
3621            else
3622            {
3623              slot = 1;
3624            }
3625            break;
3626
3627          case FILTER_TYPE_APPROXIMATE_MATCH:
3628            slot = 3;
3629            break;
3630
3631          case FILTER_TYPE_PRESENCE:
3632            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3633            {
3634              slot = 9;
3635            }
3636            else
3637            {
3638              slot = 4;
3639            }
3640            break;
3641
3642          case FILTER_TYPE_SUBSTRING:
3643            if (prioritizeComp.subInitial == null)
3644            {
3645              slot = 6;
3646            }
3647            else
3648            {
3649              slot = 5;
3650            }
3651            break;
3652
3653          case FILTER_TYPE_GREATER_OR_EQUAL:
3654          case FILTER_TYPE_LESS_OR_EQUAL:
3655            slot = 7;
3656            break;
3657
3658          case FILTER_TYPE_EXTENSIBLE_MATCH:
3659            slot = 8;
3660            break;
3661
3662          case FILTER_TYPE_NOT:
3663          default:
3664            slot = 10;
3665            break;
3666        }
3667
3668        LinkedHashSet<Filter> filterSet = m.get(slot-1);
3669        if (filterSet == null)
3670        {
3671          filterSet = new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
3672          m.put(slot-1, filterSet);
3673        }
3674        filterSet.add(f);
3675      }
3676
3677      componentSet.clear();
3678      for (final LinkedHashSet<Filter> filterSet : m.values())
3679      {
3680        componentSet.addAll(filterSet);
3681      }
3682    }
3683
3684
3685    // Return the new, possibly simplified filter.
3686    if (filterType == FILTER_TYPE_AND)
3687    {
3688      return createANDFilter(componentSet);
3689    }
3690    else
3691    {
3692      return createORFilter(componentSet);
3693    }
3694  }
3695
3696
3697
3698  /**
3699   * Generates a hash code for this search filter.
3700   *
3701   * @return  The generated hash code for this search filter.
3702   */
3703  @Override()
3704  public int hashCode()
3705  {
3706    final CaseIgnoreStringMatchingRule matchingRule =
3707         CaseIgnoreStringMatchingRule.getInstance();
3708    int hashCode = filterType;
3709
3710    switch (filterType)
3711    {
3712      case FILTER_TYPE_AND:
3713      case FILTER_TYPE_OR:
3714        for (final Filter f : filterComps)
3715        {
3716          hashCode += f.hashCode();
3717        }
3718        break;
3719
3720      case FILTER_TYPE_NOT:
3721        hashCode += notComp.hashCode();
3722        break;
3723
3724      case FILTER_TYPE_EQUALITY:
3725      case FILTER_TYPE_GREATER_OR_EQUAL:
3726      case FILTER_TYPE_LESS_OR_EQUAL:
3727      case FILTER_TYPE_APPROXIMATE_MATCH:
3728        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3729        hashCode += matchingRule.normalize(assertionValue).hashCode();
3730        break;
3731
3732      case FILTER_TYPE_SUBSTRING:
3733        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3734        if (subInitial != null)
3735        {
3736          hashCode += matchingRule.normalizeSubstring(subInitial,
3737                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode();
3738        }
3739        for (final ASN1OctetString s : subAny)
3740        {
3741          hashCode += matchingRule.normalizeSubstring(s,
3742                           MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode();
3743        }
3744        if (subFinal != null)
3745        {
3746          hashCode += matchingRule.normalizeSubstring(subFinal,
3747                           MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode();
3748        }
3749        break;
3750
3751      case FILTER_TYPE_PRESENCE:
3752        hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3753        break;
3754
3755      case FILTER_TYPE_EXTENSIBLE_MATCH:
3756        if (attrName != null)
3757        {
3758          hashCode += StaticUtils.toLowerCase(attrName).hashCode();
3759        }
3760
3761        if (matchingRuleID != null)
3762        {
3763          hashCode += StaticUtils.toLowerCase(matchingRuleID).hashCode();
3764        }
3765
3766        if (dnAttributes)
3767        {
3768          hashCode++;
3769        }
3770
3771        hashCode += matchingRule.normalize(assertionValue).hashCode();
3772        break;
3773    }
3774
3775    return hashCode;
3776  }
3777
3778
3779
3780  /**
3781   * Indicates whether the provided object is equal to this search filter.
3782   *
3783   * @param  o  The object for which to make the determination.
3784   *
3785   * @return  {@code true} if the provided object can be considered equal to
3786   *          this search filter, or {@code false} if not.
3787   */
3788  @Override()
3789  public boolean equals(final Object o)
3790  {
3791    if (o == null)
3792    {
3793      return false;
3794    }
3795
3796    if (o == this)
3797    {
3798      return true;
3799    }
3800
3801    if (! (o instanceof Filter))
3802    {
3803      return false;
3804    }
3805
3806    final Filter f = (Filter) o;
3807    if (filterType != f.filterType)
3808    {
3809      return false;
3810    }
3811
3812    final CaseIgnoreStringMatchingRule matchingRule =
3813         CaseIgnoreStringMatchingRule.getInstance();
3814
3815    switch (filterType)
3816    {
3817      case FILTER_TYPE_AND:
3818      case FILTER_TYPE_OR:
3819        if (filterComps.length != f.filterComps.length)
3820        {
3821          return false;
3822        }
3823
3824        final HashSet<Filter> compSet =
3825             new HashSet<>(StaticUtils.computeMapCapacity(10));
3826        compSet.addAll(Arrays.asList(filterComps));
3827
3828        for (final Filter filterComp : f.filterComps)
3829        {
3830          if (! compSet.remove(filterComp))
3831          {
3832            return false;
3833          }
3834        }
3835
3836        return true;
3837
3838
3839    case FILTER_TYPE_NOT:
3840      return notComp.equals(f.notComp);
3841
3842
3843      case FILTER_TYPE_EQUALITY:
3844      case FILTER_TYPE_GREATER_OR_EQUAL:
3845      case FILTER_TYPE_LESS_OR_EQUAL:
3846      case FILTER_TYPE_APPROXIMATE_MATCH:
3847        return (attrName.equalsIgnoreCase(f.attrName) &&
3848                matchingRule.valuesMatch(assertionValue, f.assertionValue));
3849
3850
3851      case FILTER_TYPE_SUBSTRING:
3852        if (! attrName.equalsIgnoreCase(f.attrName))
3853        {
3854          return false;
3855        }
3856
3857        if (subAny.length != f.subAny.length)
3858        {
3859          return false;
3860        }
3861
3862        if (subInitial == null)
3863        {
3864          if (f.subInitial != null)
3865          {
3866            return false;
3867          }
3868        }
3869        else
3870        {
3871          if (f.subInitial == null)
3872          {
3873            return false;
3874          }
3875
3876          final ASN1OctetString si1 = matchingRule.normalizeSubstring(
3877               subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
3878          final ASN1OctetString si2 = matchingRule.normalizeSubstring(
3879               f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
3880          if (! si1.equals(si2))
3881          {
3882            return false;
3883          }
3884        }
3885
3886        for (int i=0; i < subAny.length; i++)
3887        {
3888          final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i],
3889               MatchingRule.SUBSTRING_TYPE_SUBANY);
3890          final ASN1OctetString sa2 = matchingRule.normalizeSubstring(
3891               f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY);
3892          if (! sa1.equals(sa2))
3893          {
3894            return false;
3895          }
3896        }
3897
3898        if (subFinal == null)
3899        {
3900          if (f.subFinal != null)
3901          {
3902            return false;
3903          }
3904        }
3905        else
3906        {
3907          if (f.subFinal == null)
3908          {
3909            return false;
3910          }
3911
3912          final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal,
3913               MatchingRule.SUBSTRING_TYPE_SUBFINAL);
3914          final ASN1OctetString sf2 = matchingRule.normalizeSubstring(
3915               f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL);
3916          if (! sf1.equals(sf2))
3917          {
3918            return false;
3919          }
3920        }
3921
3922        return true;
3923
3924
3925      case FILTER_TYPE_PRESENCE:
3926        return (attrName.equalsIgnoreCase(f.attrName));
3927
3928
3929      case FILTER_TYPE_EXTENSIBLE_MATCH:
3930        if (attrName == null)
3931        {
3932          if (f.attrName != null)
3933          {
3934            return false;
3935          }
3936        }
3937        else
3938        {
3939          if (f.attrName == null)
3940          {
3941            return false;
3942          }
3943          else
3944          {
3945            if (! attrName.equalsIgnoreCase(f.attrName))
3946            {
3947              return false;
3948            }
3949          }
3950        }
3951
3952        if (matchingRuleID == null)
3953        {
3954          if (f.matchingRuleID != null)
3955          {
3956            return false;
3957          }
3958        }
3959        else
3960        {
3961          if (f.matchingRuleID == null)
3962          {
3963            return false;
3964          }
3965          else
3966          {
3967            if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID))
3968            {
3969              return false;
3970            }
3971          }
3972        }
3973
3974        if (dnAttributes != f.dnAttributes)
3975        {
3976          return false;
3977        }
3978
3979        return matchingRule.valuesMatch(assertionValue, f.assertionValue);
3980
3981
3982      default:
3983        return false;
3984    }
3985  }
3986
3987
3988
3989  /**
3990   * Retrieves a string representation of this search filter.
3991   *
3992   * @return  A string representation of this search filter.
3993   */
3994  @Override()
3995  public String toString()
3996  {
3997    if (filterString == null)
3998    {
3999      final StringBuilder buffer = new StringBuilder();
4000      toString(buffer);
4001      filterString = buffer.toString();
4002    }
4003
4004    return filterString;
4005  }
4006
4007
4008
4009  /**
4010   * Appends a string representation of this search filter to the provided
4011   * buffer.
4012   *
4013   * @param  buffer  The buffer to which to append a string representation of
4014   *                 this search filter.
4015   */
4016  public void toString(final StringBuilder buffer)
4017  {
4018    switch (filterType)
4019    {
4020      case FILTER_TYPE_AND:
4021        buffer.append("(&");
4022        for (final Filter f : filterComps)
4023        {
4024          f.toString(buffer);
4025        }
4026        buffer.append(')');
4027        break;
4028
4029      case FILTER_TYPE_OR:
4030        buffer.append("(|");
4031        for (final Filter f : filterComps)
4032        {
4033          f.toString(buffer);
4034        }
4035        buffer.append(')');
4036        break;
4037
4038      case FILTER_TYPE_NOT:
4039        buffer.append("(!");
4040        notComp.toString(buffer);
4041        buffer.append(')');
4042        break;
4043
4044      case FILTER_TYPE_EQUALITY:
4045        buffer.append('(');
4046        buffer.append(attrName);
4047        buffer.append('=');
4048        encodeValue(assertionValue, buffer);
4049        buffer.append(')');
4050        break;
4051
4052      case FILTER_TYPE_SUBSTRING:
4053        buffer.append('(');
4054        buffer.append(attrName);
4055        buffer.append('=');
4056        if (subInitial != null)
4057        {
4058          encodeValue(subInitial, buffer);
4059        }
4060        buffer.append('*');
4061        for (final ASN1OctetString s : subAny)
4062        {
4063          encodeValue(s, buffer);
4064          buffer.append('*');
4065        }
4066        if (subFinal != null)
4067        {
4068          encodeValue(subFinal, buffer);
4069        }
4070        buffer.append(')');
4071        break;
4072
4073      case FILTER_TYPE_GREATER_OR_EQUAL:
4074        buffer.append('(');
4075        buffer.append(attrName);
4076        buffer.append(">=");
4077        encodeValue(assertionValue, buffer);
4078        buffer.append(')');
4079        break;
4080
4081      case FILTER_TYPE_LESS_OR_EQUAL:
4082        buffer.append('(');
4083        buffer.append(attrName);
4084        buffer.append("<=");
4085        encodeValue(assertionValue, buffer);
4086        buffer.append(')');
4087        break;
4088
4089      case FILTER_TYPE_PRESENCE:
4090        buffer.append('(');
4091        buffer.append(attrName);
4092        buffer.append("=*)");
4093        break;
4094
4095      case FILTER_TYPE_APPROXIMATE_MATCH:
4096        buffer.append('(');
4097        buffer.append(attrName);
4098        buffer.append("~=");
4099        encodeValue(assertionValue, buffer);
4100        buffer.append(')');
4101        break;
4102
4103      case FILTER_TYPE_EXTENSIBLE_MATCH:
4104        buffer.append('(');
4105        if (attrName != null)
4106        {
4107          buffer.append(attrName);
4108        }
4109
4110        if (dnAttributes)
4111        {
4112          buffer.append(":dn");
4113        }
4114
4115        if (matchingRuleID != null)
4116        {
4117          buffer.append(':');
4118          buffer.append(matchingRuleID);
4119        }
4120
4121        buffer.append(":=");
4122        encodeValue(assertionValue, buffer);
4123        buffer.append(')');
4124        break;
4125    }
4126  }
4127
4128
4129
4130  /**
4131   * Retrieves a normalized string representation of this search filter.
4132   *
4133   * @return  A normalized string representation of this search filter.
4134   */
4135  public String toNormalizedString()
4136  {
4137    if (normalizedString == null)
4138    {
4139      final StringBuilder buffer = new StringBuilder();
4140      toNormalizedString(buffer);
4141      normalizedString = buffer.toString();
4142    }
4143
4144    return normalizedString;
4145  }
4146
4147
4148
4149  /**
4150   * Appends a normalized string representation of this search filter to the
4151   * provided buffer.
4152   *
4153   * @param  buffer  The buffer to which to append a normalized string
4154   *                 representation of this search filter.
4155   */
4156  public void toNormalizedString(final StringBuilder buffer)
4157  {
4158    final CaseIgnoreStringMatchingRule mr =
4159         CaseIgnoreStringMatchingRule.getInstance();
4160
4161    switch (filterType)
4162    {
4163      case FILTER_TYPE_AND:
4164        buffer.append("(&");
4165        for (final Filter f : filterComps)
4166        {
4167          f.toNormalizedString(buffer);
4168        }
4169        buffer.append(')');
4170        break;
4171
4172      case FILTER_TYPE_OR:
4173        buffer.append("(|");
4174        for (final Filter f : filterComps)
4175        {
4176          f.toNormalizedString(buffer);
4177        }
4178        buffer.append(')');
4179        break;
4180
4181      case FILTER_TYPE_NOT:
4182        buffer.append("(!");
4183        notComp.toNormalizedString(buffer);
4184        buffer.append(')');
4185        break;
4186
4187      case FILTER_TYPE_EQUALITY:
4188        buffer.append('(');
4189        buffer.append(StaticUtils.toLowerCase(attrName));
4190        buffer.append('=');
4191        encodeValue(mr.normalize(assertionValue), buffer);
4192        buffer.append(')');
4193        break;
4194
4195      case FILTER_TYPE_SUBSTRING:
4196        buffer.append('(');
4197        buffer.append(StaticUtils.toLowerCase(attrName));
4198        buffer.append('=');
4199        if (subInitial != null)
4200        {
4201          encodeValue(mr.normalizeSubstring(subInitial,
4202                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer);
4203        }
4204        buffer.append('*');
4205        for (final ASN1OctetString s : subAny)
4206        {
4207          encodeValue(mr.normalizeSubstring(s,
4208                           MatchingRule.SUBSTRING_TYPE_SUBANY), buffer);
4209          buffer.append('*');
4210        }
4211        if (subFinal != null)
4212        {
4213          encodeValue(mr.normalizeSubstring(subFinal,
4214                           MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer);
4215        }
4216        buffer.append(')');
4217        break;
4218
4219      case FILTER_TYPE_GREATER_OR_EQUAL:
4220        buffer.append('(');
4221        buffer.append(StaticUtils.toLowerCase(attrName));
4222        buffer.append(">=");
4223        encodeValue(mr.normalize(assertionValue), buffer);
4224        buffer.append(')');
4225        break;
4226
4227      case FILTER_TYPE_LESS_OR_EQUAL:
4228        buffer.append('(');
4229        buffer.append(StaticUtils.toLowerCase(attrName));
4230        buffer.append("<=");
4231        encodeValue(mr.normalize(assertionValue), buffer);
4232        buffer.append(')');
4233        break;
4234
4235      case FILTER_TYPE_PRESENCE:
4236        buffer.append('(');
4237        buffer.append(StaticUtils.toLowerCase(attrName));
4238        buffer.append("=*)");
4239        break;
4240
4241      case FILTER_TYPE_APPROXIMATE_MATCH:
4242        buffer.append('(');
4243        buffer.append(StaticUtils.toLowerCase(attrName));
4244        buffer.append("~=");
4245        encodeValue(mr.normalize(assertionValue), buffer);
4246        buffer.append(')');
4247        break;
4248
4249      case FILTER_TYPE_EXTENSIBLE_MATCH:
4250        buffer.append('(');
4251        if (attrName != null)
4252        {
4253          buffer.append(StaticUtils.toLowerCase(attrName));
4254        }
4255
4256        if (dnAttributes)
4257        {
4258          buffer.append(":dn");
4259        }
4260
4261        if (matchingRuleID != null)
4262        {
4263          buffer.append(':');
4264          buffer.append(StaticUtils.toLowerCase(matchingRuleID));
4265        }
4266
4267        buffer.append(":=");
4268        encodeValue(mr.normalize(assertionValue), buffer);
4269        buffer.append(')');
4270        break;
4271    }
4272  }
4273
4274
4275
4276  /**
4277   * Encodes the provided value into a form suitable for use as the assertion
4278   * value in the string representation of a search filter.  Parentheses,
4279   * asterisks, backslashes, null characters, and any non-ASCII characters will
4280   * be escaped using a backslash before the hexadecimal representation of each
4281   * byte in the character to escape.
4282   *
4283   * @param  value  The value to be encoded.  It must not be {@code null}.
4284   *
4285   * @return  The encoded representation of the provided string.
4286   */
4287  public static String encodeValue(final String value)
4288  {
4289    Validator.ensureNotNull(value);
4290
4291    final StringBuilder buffer = new StringBuilder();
4292    encodeValue(new ASN1OctetString(value), buffer);
4293    return buffer.toString();
4294  }
4295
4296
4297
4298  /**
4299   * Encodes the provided value into a form suitable for use as the assertion
4300   * value in the string representation of a search filter.  Parentheses,
4301   * asterisks, backslashes, null characters, and any non-ASCII characters will
4302   * be escaped using a backslash before the hexadecimal representation of each
4303   * byte in the character to escape.
4304   *
4305   * @param  value  The value to be encoded.  It must not be {@code null}.
4306   *
4307   * @return  The encoded representation of the provided string.
4308   */
4309  public static String encodeValue(final byte[]value)
4310  {
4311    Validator.ensureNotNull(value);
4312
4313    final StringBuilder buffer = new StringBuilder();
4314    encodeValue(new ASN1OctetString(value), buffer);
4315    return buffer.toString();
4316  }
4317
4318
4319
4320  /**
4321   * Appends the assertion value for this filter to the provided buffer,
4322   * encoding any special characters as necessary.
4323   *
4324   * @param  value   The value to be encoded.
4325   * @param  buffer  The buffer to which the assertion value should be appended.
4326   */
4327  public static void encodeValue(final ASN1OctetString value,
4328                                 final StringBuilder buffer)
4329  {
4330    final byte[] valueBytes = value.getValue();
4331    for (int i=0; i < valueBytes.length; i++)
4332    {
4333      switch (StaticUtils.numBytesInUTF8CharacterWithFirstByte(valueBytes[i]))
4334      {
4335        case 1:
4336          // This character is ASCII, but might still need to be escaped.
4337          if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters.
4338              (valueBytes[i] == 0x28) || // Open parenthesis
4339              (valueBytes[i] == 0x29) || // Close parenthesis
4340              (valueBytes[i] == 0x2A) || // Asterisk
4341              (valueBytes[i] == 0x5C) || // Backslash
4342              (valueBytes[i] == 0x7F))   // DEL
4343          {
4344            buffer.append('\\');
4345            StaticUtils.toHex(valueBytes[i], buffer);
4346          }
4347          else
4348          {
4349            buffer.append((char) valueBytes[i]);
4350          }
4351          break;
4352
4353        case 2:
4354          // If there are at least two bytes left, then we'll hex-encode the
4355          // next two bytes.  Otherwise we'll hex-encode whatever is left.
4356          buffer.append('\\');
4357          StaticUtils.toHex(valueBytes[i++], buffer);
4358          if (i < valueBytes.length)
4359          {
4360            buffer.append('\\');
4361            StaticUtils.toHex(valueBytes[i], buffer);
4362          }
4363          break;
4364
4365        case 3:
4366          // If there are at least three bytes left, then we'll hex-encode the
4367          // next three bytes.  Otherwise we'll hex-encode whatever is left.
4368          buffer.append('\\');
4369          StaticUtils.toHex(valueBytes[i++], buffer);
4370          if (i < valueBytes.length)
4371          {
4372            buffer.append('\\');
4373            StaticUtils.toHex(valueBytes[i++], buffer);
4374          }
4375          if (i < valueBytes.length)
4376          {
4377            buffer.append('\\');
4378            StaticUtils.toHex(valueBytes[i], buffer);
4379          }
4380          break;
4381
4382        case 4:
4383          // If there are at least four bytes left, then we'll hex-encode the
4384          // next four bytes.  Otherwise we'll hex-encode whatever is left.
4385          buffer.append('\\');
4386          StaticUtils.toHex(valueBytes[i++], buffer);
4387          if (i < valueBytes.length)
4388          {
4389            buffer.append('\\');
4390            StaticUtils.toHex(valueBytes[i++], buffer);
4391          }
4392          if (i < valueBytes.length)
4393          {
4394            buffer.append('\\');
4395            StaticUtils.toHex(valueBytes[i++], buffer);
4396          }
4397          if (i < valueBytes.length)
4398          {
4399            buffer.append('\\');
4400            StaticUtils.toHex(valueBytes[i], buffer);
4401          }
4402          break;
4403
4404        default:
4405          // We'll hex-encode whatever is left in the buffer.
4406          while (i < valueBytes.length)
4407          {
4408            buffer.append('\\');
4409            StaticUtils.toHex(valueBytes[i++], buffer);
4410          }
4411          break;
4412      }
4413    }
4414  }
4415
4416
4417
4418  /**
4419   * Appends a number of lines comprising the Java source code that can be used
4420   * to recreate this filter to the given list.  Note that unless a first line
4421   * prefix and/or last line suffix are provided, this will just include the
4422   * code for the static method used to create the filter, starting with
4423   * "Filter.createXFilter(" and ending with the closing parenthesis for that
4424   * method call.
4425   *
4426   * @param  lineList         The list to which the source code lines should be
4427   *                          added.
4428   * @param  indentSpaces     The number of spaces that should be used to indent
4429   *                          the generated code.  It must not be negative.
4430   * @param  firstLinePrefix  An optional string that should precede the static
4431   *                          method call (e.g., it could be used for an
4432   *                          attribute assignment, like "Filter f = ").  It may
4433   *                          be {@code null} or empty if there should be no
4434   *                          first line prefix.
4435   * @param  lastLineSuffix   An optional suffix that should follow the closing
4436   *                          parenthesis of the static method call (e.g., it
4437   *                          could be a semicolon to represent the end of a
4438   *                          Java statement).  It may be {@code null} or empty
4439   *                          if there should be no last line suffix.
4440   */
4441  public void toCode(final List<String> lineList, final int indentSpaces,
4442                     final String firstLinePrefix, final String lastLineSuffix)
4443  {
4444    // Generate a string with the appropriate indent.
4445    final StringBuilder buffer = new StringBuilder();
4446    for (int i = 0; i < indentSpaces; i++)
4447    {
4448      buffer.append(' ');
4449    }
4450    final String indent = buffer.toString();
4451
4452
4453    // Start the first line, including any appropriate prefix.
4454    buffer.setLength(0);
4455    buffer.append(indent);
4456    if (firstLinePrefix != null)
4457    {
4458      buffer.append(firstLinePrefix);
4459    }
4460
4461
4462    // Figure out what type of filter it is and create the appropriate code for
4463    // that type of filter.
4464    switch (filterType)
4465    {
4466      case FILTER_TYPE_AND:
4467      case FILTER_TYPE_OR:
4468        if (filterType == FILTER_TYPE_AND)
4469        {
4470          buffer.append("Filter.createANDFilter(");
4471        }
4472        else
4473        {
4474          buffer.append("Filter.createORFilter(");
4475        }
4476        if (filterComps.length == 0)
4477        {
4478          buffer.append(')');
4479          if (lastLineSuffix != null)
4480          {
4481            buffer.append(lastLineSuffix);
4482          }
4483          lineList.add(buffer.toString());
4484          return;
4485        }
4486
4487        for (int i = 0; i < filterComps.length; i++)
4488        {
4489          String suffix;
4490          if (i == (filterComps.length - 1))
4491          {
4492            suffix = ")";
4493            if (lastLineSuffix != null)
4494            {
4495              suffix += lastLineSuffix;
4496            }
4497          }
4498          else
4499          {
4500            suffix = ",";
4501          }
4502
4503          filterComps[i].toCode(lineList, indentSpaces + 5, null, suffix);
4504        }
4505        return;
4506
4507
4508      case FILTER_TYPE_NOT:
4509        buffer.append("Filter.createNOTFilter(");
4510        lineList.add(buffer.toString());
4511
4512        final String suffix;
4513        if (lastLineSuffix == null)
4514        {
4515          suffix = ")";
4516        }
4517        else
4518        {
4519          suffix = ')' + lastLineSuffix;
4520        }
4521        notComp.toCode(lineList, indentSpaces + 5, null, suffix);
4522        return;
4523
4524      case FILTER_TYPE_PRESENCE:
4525        buffer.append("Filter.createPresenceFilter(");
4526        lineList.add(buffer.toString());
4527
4528        buffer.setLength(0);
4529        buffer.append(indent);
4530        buffer.append("     \"");
4531        buffer.append(attrName);
4532        buffer.append("\")");
4533
4534        if (lastLineSuffix != null)
4535        {
4536          buffer.append(lastLineSuffix);
4537        }
4538
4539        lineList.add(buffer.toString());
4540        return;
4541
4542
4543      case FILTER_TYPE_EQUALITY:
4544      case FILTER_TYPE_GREATER_OR_EQUAL:
4545      case FILTER_TYPE_LESS_OR_EQUAL:
4546      case FILTER_TYPE_APPROXIMATE_MATCH:
4547        if (filterType == FILTER_TYPE_EQUALITY)
4548        {
4549          buffer.append("Filter.createEqualityFilter(");
4550        }
4551        else if (filterType == FILTER_TYPE_GREATER_OR_EQUAL)
4552        {
4553          buffer.append("Filter.createGreaterOrEqualFilter(");
4554        }
4555        else if (filterType == FILTER_TYPE_LESS_OR_EQUAL)
4556        {
4557          buffer.append("Filter.createLessOrEqualFilter(");
4558        }
4559        else
4560        {
4561          buffer.append("Filter.createApproximateMatchFilter(");
4562        }
4563        lineList.add(buffer.toString());
4564
4565        buffer.setLength(0);
4566        buffer.append(indent);
4567        buffer.append("     \"");
4568        buffer.append(attrName);
4569        buffer.append("\",");
4570        lineList.add(buffer.toString());
4571
4572        buffer.setLength(0);
4573        buffer.append(indent);
4574        buffer.append("     ");
4575        if (StaticUtils.isSensitiveToCodeAttribute(attrName))
4576        {
4577          buffer.append("\"---redacted-value---\"");
4578        }
4579        else if (StaticUtils.isPrintableString(assertionValue.getValue()))
4580        {
4581          buffer.append('"');
4582          buffer.append(assertionValue.stringValue());
4583          buffer.append('"');
4584        }
4585        else
4586        {
4587          StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
4588        }
4589
4590        buffer.append(')');
4591
4592        if (lastLineSuffix != null)
4593        {
4594          buffer.append(lastLineSuffix);
4595        }
4596
4597        lineList.add(buffer.toString());
4598        return;
4599
4600
4601      case FILTER_TYPE_SUBSTRING:
4602        buffer.append("Filter.createSubstringFilter(");
4603        lineList.add(buffer.toString());
4604
4605        buffer.setLength(0);
4606        buffer.append(indent);
4607        buffer.append("     \"");
4608        buffer.append(attrName);
4609        buffer.append("\",");
4610        lineList.add(buffer.toString());
4611
4612        final boolean isRedacted =
4613             StaticUtils.isSensitiveToCodeAttribute(attrName);
4614        boolean isPrintable = true;
4615        if (subInitial != null)
4616        {
4617          isPrintable = StaticUtils.isPrintableString(subInitial.getValue());
4618        }
4619
4620        if (isPrintable && (subAny != null))
4621        {
4622          for (final ASN1OctetString s : subAny)
4623          {
4624            if (! StaticUtils.isPrintableString(s.getValue()))
4625            {
4626              isPrintable = false;
4627              break;
4628            }
4629          }
4630        }
4631
4632        if (isPrintable && (subFinal != null))
4633        {
4634          isPrintable = StaticUtils.isPrintableString(subFinal.getValue());
4635        }
4636
4637        buffer.setLength(0);
4638        buffer.append(indent);
4639        buffer.append("     ");
4640        if (subInitial == null)
4641        {
4642          buffer.append("null");
4643        }
4644        else if (isRedacted)
4645        {
4646          buffer.append("\"---redacted-subInitial---\"");
4647        }
4648        else if (isPrintable)
4649        {
4650          buffer.append('"');
4651          buffer.append(subInitial.stringValue());
4652          buffer.append('"');
4653        }
4654        else
4655        {
4656          StaticUtils.byteArrayToCode(subInitial.getValue(), buffer);
4657        }
4658        buffer.append(',');
4659        lineList.add(buffer.toString());
4660
4661        buffer.setLength(0);
4662        buffer.append(indent);
4663        buffer.append("     ");
4664        if ((subAny == null) || (subAny.length == 0))
4665        {
4666          buffer.append("null,");
4667          lineList.add(buffer.toString());
4668        }
4669        else if (isRedacted)
4670        {
4671          buffer.append("new String[]");
4672          lineList.add(buffer.toString());
4673
4674          lineList.add(indent + "     {");
4675
4676          for (int i=0; i < subAny.length; i++)
4677          {
4678            buffer.setLength(0);
4679            buffer.append(indent);
4680            buffer.append("       \"---redacted-subAny-");
4681            buffer.append(i+1);
4682            buffer.append("---\"");
4683            if (i < (subAny.length-1))
4684            {
4685              buffer.append(',');
4686            }
4687            lineList.add(buffer.toString());
4688          }
4689
4690          lineList.add(indent + "     },");
4691        }
4692        else if (isPrintable)
4693        {
4694          buffer.append("new String[]");
4695          lineList.add(buffer.toString());
4696
4697          lineList.add(indent + "     {");
4698
4699          for (int i=0; i < subAny.length; i++)
4700          {
4701            buffer.setLength(0);
4702            buffer.append(indent);
4703            buffer.append("       \"");
4704            buffer.append(subAny[i].stringValue());
4705            buffer.append('"');
4706            if (i < (subAny.length-1))
4707            {
4708              buffer.append(',');
4709            }
4710            lineList.add(buffer.toString());
4711          }
4712
4713          lineList.add(indent + "     },");
4714        }
4715        else
4716        {
4717          buffer.append("new String[]");
4718          lineList.add(buffer.toString());
4719
4720          lineList.add(indent + "     {");
4721
4722          for (int i=0; i < subAny.length; i++)
4723          {
4724            buffer.setLength(0);
4725            buffer.append(indent);
4726            buffer.append("       ");
4727            StaticUtils.byteArrayToCode(subAny[i].getValue(), buffer);
4728            if (i < (subAny.length-1))
4729            {
4730              buffer.append(',');
4731            }
4732            lineList.add(buffer.toString());
4733          }
4734
4735          lineList.add(indent + "     },");
4736        }
4737
4738        buffer.setLength(0);
4739        buffer.append(indent);
4740        buffer.append("     ");
4741        if (subFinal == null)
4742        {
4743          buffer.append("null)");
4744        }
4745        else if (isRedacted)
4746        {
4747          buffer.append("\"---redacted-subFinal---\")");
4748        }
4749        else if (isPrintable)
4750        {
4751          buffer.append('"');
4752          buffer.append(subFinal.stringValue());
4753          buffer.append("\")");
4754        }
4755        else
4756        {
4757          StaticUtils.byteArrayToCode(subFinal.getValue(), buffer);
4758          buffer.append(')');
4759        }
4760        if (lastLineSuffix != null)
4761        {
4762          buffer.append(lastLineSuffix);
4763        }
4764        lineList.add(buffer.toString());
4765        return;
4766
4767
4768      case FILTER_TYPE_EXTENSIBLE_MATCH:
4769        buffer.append("Filter.createExtensibleMatchFilter(");
4770        lineList.add(buffer.toString());
4771
4772        buffer.setLength(0);
4773        buffer.append(indent);
4774        buffer.append("     ");
4775        if (attrName == null)
4776        {
4777          buffer.append("null, // Attribute Description");
4778        }
4779        else
4780        {
4781          buffer.append('"');
4782          buffer.append(attrName);
4783          buffer.append("\",");
4784        }
4785        lineList.add(buffer.toString());
4786
4787        buffer.setLength(0);
4788        buffer.append(indent);
4789        buffer.append("     ");
4790        if (matchingRuleID == null)
4791        {
4792          buffer.append("null, // Matching Rule ID");
4793        }
4794        else
4795        {
4796          buffer.append('"');
4797          buffer.append(matchingRuleID);
4798          buffer.append("\",");
4799        }
4800        lineList.add(buffer.toString());
4801
4802        buffer.setLength(0);
4803        buffer.append(indent);
4804        buffer.append("     ");
4805        buffer.append(dnAttributes);
4806        buffer.append(", // DN Attributes");
4807        lineList.add(buffer.toString());
4808
4809        buffer.setLength(0);
4810        buffer.append(indent);
4811        buffer.append("     ");
4812        if ((attrName != null) &&
4813             StaticUtils.isSensitiveToCodeAttribute(attrName))
4814        {
4815          buffer.append("\"---redacted-value---\")");
4816        }
4817        else
4818        {
4819          if (StaticUtils.isPrintableString(assertionValue.getValue()))
4820          {
4821            buffer.append('"');
4822            buffer.append(assertionValue.stringValue());
4823            buffer.append("\")");
4824          }
4825          else
4826          {
4827            StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer);
4828            buffer.append(')');
4829          }
4830        }
4831
4832        if (lastLineSuffix != null)
4833        {
4834          buffer.append(lastLineSuffix);
4835        }
4836        lineList.add(buffer.toString());
4837        return;
4838    }
4839  }
4840}