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