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