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                if (Character.isHighSurrogate(c))
1689                {
1690                  if (l <= r)
1691                  {
1692                    final char c2 = filterString.charAt(l);
1693                    if (Character.isLowSurrogate(c2))
1694                    {
1695                      l++;
1696                      final int codePoint = Character.toCodePoint(c, c2);
1697                      buffer.append(new String(new int[] { codePoint }, 0, 1));
1698                      break;
1699                    }
1700                  }
1701                }
1702
1703                buffer.append(c);
1704                break;
1705            }
1706          }
1707
1708          if ((tempFilterType == FILTER_TYPE_SUBSTRING) &&
1709              (buffer.length() > 0))
1710          {
1711            // The buffer must contain the subFinal portion.
1712            tempSubFinal = new ASN1OctetString(buffer.toByteArray());
1713          }
1714
1715          subInitial = tempSubInitial;
1716          subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
1717          subFinal = tempSubFinal;
1718
1719          if (tempFilterType == FILTER_TYPE_SUBSTRING)
1720          {
1721            assertionValue = null;
1722          }
1723          else
1724          {
1725            assertionValue = new ASN1OctetString(buffer.toByteArray());
1726          }
1727        }
1728
1729        filterType = tempFilterType;
1730        break;
1731    }
1732
1733
1734    if (startPos == 0)
1735    {
1736      return new Filter(filterString, filterType, filterComps, notComp,
1737                        attrName, assertionValue, subInitial, subAny, subFinal,
1738                        matchingRuleID, dnAttributes);
1739    }
1740    else
1741    {
1742      return new Filter(filterString.substring(startPos, endPos+1), filterType,
1743                        filterComps, notComp, attrName, assertionValue,
1744                        subInitial, subAny, subFinal, matchingRuleID,
1745                        dnAttributes);
1746    }
1747  }
1748
1749
1750
1751  /**
1752   * Parses the specified portion of the provided filter string to obtain a set
1753   * of filter components for use in an AND or OR filter.
1754   *
1755   * @param  filterString  The string representation for the set of filters.
1756   * @param  startPos      The position of the first character to consider as
1757   *                       part of the first filter.
1758   * @param  endPos        The position of the last character to consider as
1759   *                       part of the last filter.
1760   * @param  depth         The current nesting depth for this filter.  It should
1761   *                       be increased by one for each AND, OR, or NOT filter
1762   *                       encountered, in order to prevent stack overflow
1763   *                       errors from excessive recursion.
1764   *
1765   * @return  The decoded set of search filters.
1766   *
1767   * @throws  LDAPException  If the provided string cannot be decoded as a set
1768   *                         of LDAP search filters.
1769   */
1770  private static Filter[] parseFilterComps(final String filterString,
1771                                           final int startPos, final int endPos,
1772                                           final int depth)
1773          throws LDAPException
1774  {
1775    if (startPos > endPos)
1776    {
1777      // This is acceptable, since it can represent an LDAP TRUE or FALSE filter
1778      // as described in RFC 4526.
1779      return NO_FILTERS;
1780    }
1781
1782
1783    // The set of filters must start with an opening parenthesis, and end with a
1784    // closing parenthesis.
1785    if (filterString.charAt(startPos) != '(')
1786    {
1787      throw new LDAPException(ResultCode.FILTER_ERROR,
1788           ERR_FILTER_EXPECTED_OPEN_PAREN.get(filterString, startPos));
1789    }
1790    if (filterString.charAt(endPos) != ')')
1791    {
1792      throw new LDAPException(ResultCode.FILTER_ERROR,
1793           ERR_FILTER_EXPECTED_CLOSE_PAREN.get(filterString, startPos));
1794    }
1795
1796
1797    // Iterate through the specified portion of the filter string and count
1798    // opening and closing parentheses to figure out where one filter ends and
1799    // another begins.
1800    final ArrayList<Filter> filterList = new ArrayList<Filter>(5);
1801    int filterStartPos = startPos;
1802    int pos = startPos;
1803    int numOpen = 0;
1804    while (pos <= endPos)
1805    {
1806      final char c = filterString.charAt(pos++);
1807      if (c == '(')
1808      {
1809        numOpen++;
1810      }
1811      else if (c == ')')
1812      {
1813        numOpen--;
1814        if (numOpen == 0)
1815        {
1816          filterList.add(create(filterString, filterStartPos, pos-1, depth));
1817          filterStartPos = pos;
1818        }
1819      }
1820    }
1821
1822    if (numOpen != 0)
1823    {
1824      throw new LDAPException(ResultCode.FILTER_ERROR,
1825           ERR_FILTER_MISMATCHED_PARENS.get(filterString, startPos, endPos));
1826    }
1827
1828    return filterList.toArray(new Filter[filterList.size()]);
1829  }
1830
1831
1832
1833  /**
1834   * Reads one or more hex-encoded bytes from the specified portion of the
1835   * filter string.
1836   *
1837   * @param  filterString  The string from which the data is to be read.
1838   * @param  startPos      The position at which to start reading.  This should
1839   *                       be the position of first hex character immediately
1840   *                       after the initial backslash.
1841   * @param  buffer        The buffer to which the decoded string portion should
1842   *                       be appended.
1843   *
1844   * @return  The position at which the caller may resume parsing.
1845   *
1846   * @throws  LDAPException  If a problem occurs while reading hex-encoded
1847   *                         bytes.
1848   */
1849  private static int readEscapedHexString(final String filterString,
1850                                          final int startPos,
1851                                          final ByteStringBuffer buffer)
1852          throws LDAPException
1853  {
1854    final byte b;
1855    switch (filterString.charAt(startPos))
1856    {
1857      case '0':
1858        b = 0x00;
1859        break;
1860      case '1':
1861        b = 0x10;
1862        break;
1863      case '2':
1864        b = 0x20;
1865        break;
1866      case '3':
1867        b = 0x30;
1868        break;
1869      case '4':
1870        b = 0x40;
1871        break;
1872      case '5':
1873        b = 0x50;
1874        break;
1875      case '6':
1876        b = 0x60;
1877        break;
1878      case '7':
1879        b = 0x70;
1880        break;
1881      case '8':
1882        b = (byte) 0x80;
1883        break;
1884      case '9':
1885        b = (byte) 0x90;
1886        break;
1887      case 'a':
1888      case 'A':
1889        b = (byte) 0xA0;
1890        break;
1891      case 'b':
1892      case 'B':
1893        b = (byte) 0xB0;
1894        break;
1895      case 'c':
1896      case 'C':
1897        b = (byte) 0xC0;
1898        break;
1899      case 'd':
1900      case 'D':
1901        b = (byte) 0xD0;
1902        break;
1903      case 'e':
1904      case 'E':
1905        b = (byte) 0xE0;
1906        break;
1907      case 'f':
1908      case 'F':
1909        b = (byte) 0xF0;
1910        break;
1911      default:
1912        throw new LDAPException(ResultCode.FILTER_ERROR,
1913             ERR_FILTER_INVALID_HEX_CHAR.get(filterString,
1914                  filterString.charAt(startPos), startPos));
1915    }
1916
1917    switch (filterString.charAt(startPos+1))
1918    {
1919      case '0':
1920        buffer.append(b);
1921        break;
1922      case '1':
1923        buffer.append((byte) (b | 0x01));
1924        break;
1925      case '2':
1926        buffer.append((byte) (b | 0x02));
1927        break;
1928      case '3':
1929        buffer.append((byte) (b | 0x03));
1930        break;
1931      case '4':
1932        buffer.append((byte) (b | 0x04));
1933        break;
1934      case '5':
1935        buffer.append((byte) (b | 0x05));
1936        break;
1937      case '6':
1938        buffer.append((byte) (b | 0x06));
1939        break;
1940      case '7':
1941        buffer.append((byte) (b | 0x07));
1942        break;
1943      case '8':
1944        buffer.append((byte) (b | 0x08));
1945        break;
1946      case '9':
1947        buffer.append((byte) (b | 0x09));
1948        break;
1949      case 'a':
1950      case 'A':
1951        buffer.append((byte) (b | 0x0A));
1952        break;
1953      case 'b':
1954      case 'B':
1955        buffer.append((byte) (b | 0x0B));
1956        break;
1957      case 'c':
1958      case 'C':
1959        buffer.append((byte) (b | 0x0C));
1960        break;
1961      case 'd':
1962      case 'D':
1963        buffer.append((byte) (b | 0x0D));
1964        break;
1965      case 'e':
1966      case 'E':
1967        buffer.append((byte) (b | 0x0E));
1968        break;
1969      case 'f':
1970      case 'F':
1971        buffer.append((byte) (b | 0x0F));
1972        break;
1973      default:
1974        throw new LDAPException(ResultCode.FILTER_ERROR,
1975             ERR_FILTER_INVALID_HEX_CHAR.get(filterString,
1976                  filterString.charAt(startPos+1), (startPos+1)));
1977    }
1978
1979    return startPos+2;
1980  }
1981
1982
1983
1984  /**
1985   * Writes an ASN.1-encoded representation of this filter to the provided ASN.1
1986   * buffer.
1987   *
1988   * @param  buffer  The ASN.1 buffer to which the encoded representation should
1989   *                 be written.
1990   */
1991  public void writeTo(final ASN1Buffer buffer)
1992  {
1993    switch (filterType)
1994    {
1995      case FILTER_TYPE_AND:
1996      case FILTER_TYPE_OR:
1997        final ASN1BufferSet compSet = buffer.beginSet(filterType);
1998        for (final Filter f : filterComps)
1999        {
2000          f.writeTo(buffer);
2001        }
2002        compSet.end();
2003        break;
2004
2005      case FILTER_TYPE_NOT:
2006        buffer.addElement(
2007             new ASN1Element(filterType, notComp.encode().encode()));
2008        break;
2009
2010      case FILTER_TYPE_EQUALITY:
2011      case FILTER_TYPE_GREATER_OR_EQUAL:
2012      case FILTER_TYPE_LESS_OR_EQUAL:
2013      case FILTER_TYPE_APPROXIMATE_MATCH:
2014        final ASN1BufferSequence avaSequence = buffer.beginSequence(filterType);
2015        buffer.addOctetString(attrName);
2016        buffer.addElement(assertionValue);
2017        avaSequence.end();
2018        break;
2019
2020      case FILTER_TYPE_SUBSTRING:
2021        final ASN1BufferSequence subFilterSequence =
2022             buffer.beginSequence(filterType);
2023        buffer.addOctetString(attrName);
2024
2025        final ASN1BufferSequence valueSequence = buffer.beginSequence();
2026        if (subInitial != null)
2027        {
2028          buffer.addOctetString(SUBSTRING_TYPE_SUBINITIAL,
2029                                subInitial.getValue());
2030        }
2031
2032        for (final ASN1OctetString s : subAny)
2033        {
2034          buffer.addOctetString(SUBSTRING_TYPE_SUBANY, s.getValue());
2035        }
2036
2037        if (subFinal != null)
2038        {
2039          buffer.addOctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue());
2040        }
2041        valueSequence.end();
2042        subFilterSequence.end();
2043        break;
2044
2045      case FILTER_TYPE_PRESENCE:
2046        buffer.addOctetString(filterType, attrName);
2047        break;
2048
2049      case FILTER_TYPE_EXTENSIBLE_MATCH:
2050        final ASN1BufferSequence mrSequence = buffer.beginSequence(filterType);
2051        if (matchingRuleID != null)
2052        {
2053          buffer.addOctetString(EXTENSIBLE_TYPE_MATCHING_RULE_ID,
2054                                matchingRuleID);
2055        }
2056
2057        if (attrName != null)
2058        {
2059          buffer.addOctetString(EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName);
2060        }
2061
2062        buffer.addOctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2063                              assertionValue.getValue());
2064
2065        if (dnAttributes)
2066        {
2067          buffer.addBoolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true);
2068        }
2069        mrSequence.end();
2070        break;
2071    }
2072  }
2073
2074
2075
2076  /**
2077   * Encodes this search filter to an ASN.1 element suitable for inclusion in an
2078   * LDAP search request protocol op.
2079   *
2080   * @return  An ASN.1 element containing the encoded search filter.
2081   */
2082  public ASN1Element encode()
2083  {
2084    switch (filterType)
2085    {
2086      case FILTER_TYPE_AND:
2087      case FILTER_TYPE_OR:
2088        final ASN1Element[] filterElements =
2089             new ASN1Element[filterComps.length];
2090        for (int i=0; i < filterComps.length; i++)
2091        {
2092          filterElements[i] = filterComps[i].encode();
2093        }
2094        return new ASN1Set(filterType, filterElements);
2095
2096
2097      case FILTER_TYPE_NOT:
2098        return new ASN1Element(filterType, notComp.encode().encode());
2099
2100
2101      case FILTER_TYPE_EQUALITY:
2102      case FILTER_TYPE_GREATER_OR_EQUAL:
2103      case FILTER_TYPE_LESS_OR_EQUAL:
2104      case FILTER_TYPE_APPROXIMATE_MATCH:
2105        final ASN1OctetString[] attrValueAssertionElements =
2106        {
2107          new ASN1OctetString(attrName),
2108          assertionValue
2109        };
2110        return new ASN1Sequence(filterType, attrValueAssertionElements);
2111
2112
2113      case FILTER_TYPE_SUBSTRING:
2114        final ArrayList<ASN1OctetString> subList =
2115             new ArrayList<ASN1OctetString>(2 + subAny.length);
2116        if (subInitial != null)
2117        {
2118          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBINITIAL,
2119                                          subInitial.getValue()));
2120        }
2121
2122        for (final ASN1Element subAnyElement : subAny)
2123        {
2124          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBANY,
2125                                          subAnyElement.getValue()));
2126        }
2127
2128
2129        if (subFinal != null)
2130        {
2131          subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBFINAL,
2132                                          subFinal.getValue()));
2133        }
2134
2135        final ASN1Element[] subFilterElements =
2136        {
2137          new ASN1OctetString(attrName),
2138          new ASN1Sequence(subList)
2139        };
2140        return new ASN1Sequence(filterType, subFilterElements);
2141
2142
2143      case FILTER_TYPE_PRESENCE:
2144        return new ASN1OctetString(filterType, attrName);
2145
2146
2147      case FILTER_TYPE_EXTENSIBLE_MATCH:
2148        final ArrayList<ASN1Element> emElementList =
2149             new ArrayList<ASN1Element>(4);
2150        if (matchingRuleID != null)
2151        {
2152          emElementList.add(new ASN1OctetString(
2153               EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID));
2154        }
2155
2156        if (attrName != null)
2157        {
2158          emElementList.add(new ASN1OctetString(
2159               EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName));
2160        }
2161
2162        emElementList.add(new ASN1OctetString(EXTENSIBLE_TYPE_MATCH_VALUE,
2163             assertionValue.getValue()));
2164
2165        if (dnAttributes)
2166        {
2167          emElementList.add(new ASN1Boolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES,
2168                                            true));
2169        }
2170
2171        return new ASN1Sequence(filterType, emElementList);
2172
2173
2174      default:
2175        throw new AssertionError(ERR_FILTER_INVALID_TYPE.get(
2176                                      toHex(filterType)));
2177    }
2178  }
2179
2180
2181
2182  /**
2183   * Reads and decodes a search filter from the provided ASN.1 stream reader.
2184   *
2185   * @param  reader  The ASN.1 stream reader from which to read the filter.
2186   *
2187   * @return  The decoded search filter.
2188   *
2189   * @throws  LDAPException  If an error occurs while reading or parsing the
2190   *                         search filter.
2191   */
2192  public static Filter readFrom(final ASN1StreamReader reader)
2193         throws LDAPException
2194  {
2195    try
2196    {
2197      final Filter[]          filterComps;
2198      final Filter            notComp;
2199      final String            attrName;
2200      final ASN1OctetString   assertionValue;
2201      final ASN1OctetString   subInitial;
2202      final ASN1OctetString[] subAny;
2203      final ASN1OctetString   subFinal;
2204      final String            matchingRuleID;
2205      final boolean           dnAttributes;
2206
2207      final byte filterType = (byte) reader.peek();
2208
2209      switch (filterType)
2210      {
2211        case FILTER_TYPE_AND:
2212        case FILTER_TYPE_OR:
2213          final ArrayList<Filter> comps = new ArrayList<Filter>(5);
2214          final ASN1StreamReaderSet elementSet = reader.beginSet();
2215          while (elementSet.hasMoreElements())
2216          {
2217            comps.add(readFrom(reader));
2218          }
2219
2220          filterComps = new Filter[comps.size()];
2221          comps.toArray(filterComps);
2222
2223          notComp        = null;
2224          attrName       = null;
2225          assertionValue = null;
2226          subInitial     = null;
2227          subAny         = NO_SUB_ANY;
2228          subFinal       = null;
2229          matchingRuleID = null;
2230          dnAttributes   = false;
2231          break;
2232
2233
2234        case FILTER_TYPE_NOT:
2235          final ASN1Element notFilterElement;
2236          try
2237          {
2238            final ASN1Element e = reader.readElement();
2239            notFilterElement = ASN1Element.decode(e.getValue());
2240          }
2241          catch (final ASN1Exception ae)
2242          {
2243            debugException(ae);
2244            throw new LDAPException(ResultCode.DECODING_ERROR,
2245                 ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(getExceptionMessage(ae)),
2246                 ae);
2247          }
2248          notComp = decode(notFilterElement);
2249
2250          filterComps    = NO_FILTERS;
2251          attrName       = null;
2252          assertionValue = null;
2253          subInitial     = null;
2254          subAny         = NO_SUB_ANY;
2255          subFinal       = null;
2256          matchingRuleID = null;
2257          dnAttributes   = false;
2258          break;
2259
2260
2261        case FILTER_TYPE_EQUALITY:
2262        case FILTER_TYPE_GREATER_OR_EQUAL:
2263        case FILTER_TYPE_LESS_OR_EQUAL:
2264        case FILTER_TYPE_APPROXIMATE_MATCH:
2265          reader.beginSequence();
2266          attrName = reader.readString();
2267          assertionValue = new ASN1OctetString(reader.readBytes());
2268
2269          filterComps    = NO_FILTERS;
2270          notComp        = null;
2271          subInitial     = null;
2272          subAny         = NO_SUB_ANY;
2273          subFinal       = null;
2274          matchingRuleID = null;
2275          dnAttributes   = false;
2276          break;
2277
2278
2279        case FILTER_TYPE_SUBSTRING:
2280          reader.beginSequence();
2281          attrName = reader.readString();
2282
2283          ASN1OctetString tempSubInitial = null;
2284          ASN1OctetString tempSubFinal   = null;
2285          final ArrayList<ASN1OctetString> subAnyList =
2286               new ArrayList<ASN1OctetString>(1);
2287          final ASN1StreamReaderSequence subSequence = reader.beginSequence();
2288          while (subSequence.hasMoreElements())
2289          {
2290            final byte type = (byte) reader.peek();
2291            final ASN1OctetString s =
2292                 new ASN1OctetString(type, reader.readBytes());
2293            switch (type)
2294            {
2295              case SUBSTRING_TYPE_SUBINITIAL:
2296                tempSubInitial = s;
2297                break;
2298              case SUBSTRING_TYPE_SUBANY:
2299                subAnyList.add(s);
2300                break;
2301              case SUBSTRING_TYPE_SUBFINAL:
2302                tempSubFinal = s;
2303                break;
2304              default:
2305                throw new LDAPException(ResultCode.DECODING_ERROR,
2306                     ERR_FILTER_INVALID_SUBSTR_TYPE.get(toHex(type)));
2307            }
2308          }
2309
2310          subInitial = tempSubInitial;
2311          subFinal   = tempSubFinal;
2312
2313          subAny = new ASN1OctetString[subAnyList.size()];
2314          subAnyList.toArray(subAny);
2315
2316          filterComps    = NO_FILTERS;
2317          notComp        = null;
2318          assertionValue = null;
2319          matchingRuleID = null;
2320          dnAttributes   = false;
2321          break;
2322
2323
2324        case FILTER_TYPE_PRESENCE:
2325          attrName = reader.readString();
2326
2327          filterComps    = NO_FILTERS;
2328          notComp        = null;
2329          assertionValue = null;
2330          subInitial     = null;
2331          subAny         = NO_SUB_ANY;
2332          subFinal       = null;
2333          matchingRuleID = null;
2334          dnAttributes   = false;
2335          break;
2336
2337
2338        case FILTER_TYPE_EXTENSIBLE_MATCH:
2339          String          tempAttrName       = null;
2340          ASN1OctetString tempAssertionValue = null;
2341          String          tempMatchingRuleID = null;
2342          boolean         tempDNAttributes   = false;
2343
2344          final ASN1StreamReaderSequence emSequence = reader.beginSequence();
2345          while (emSequence.hasMoreElements())
2346          {
2347            final byte type = (byte) reader.peek();
2348            switch (type)
2349            {
2350              case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
2351                tempAttrName = reader.readString();
2352                break;
2353              case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
2354                tempMatchingRuleID = reader.readString();
2355                break;
2356              case EXTENSIBLE_TYPE_MATCH_VALUE:
2357                tempAssertionValue =
2358                     new ASN1OctetString(type, reader.readBytes());
2359                break;
2360              case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
2361                tempDNAttributes = reader.readBoolean();
2362                break;
2363              default:
2364                throw new LDAPException(ResultCode.DECODING_ERROR,
2365                     ERR_FILTER_EXTMATCH_INVALID_TYPE.get(toHex(type)));
2366            }
2367          }
2368
2369          if ((tempAttrName == null) && (tempMatchingRuleID == null))
2370          {
2371            throw new LDAPException(ResultCode.DECODING_ERROR,
2372                                    ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
2373          }
2374
2375          if (tempAssertionValue == null)
2376          {
2377            throw new LDAPException(ResultCode.DECODING_ERROR,
2378                                    ERR_FILTER_EXTMATCH_NO_VALUE.get());
2379          }
2380
2381          attrName       = tempAttrName;
2382          assertionValue = tempAssertionValue;
2383          matchingRuleID = tempMatchingRuleID;
2384          dnAttributes   = tempDNAttributes;
2385
2386          filterComps    = NO_FILTERS;
2387          notComp        = null;
2388          subInitial     = null;
2389          subAny         = NO_SUB_ANY;
2390          subFinal       = null;
2391          break;
2392
2393
2394        default:
2395          throw new LDAPException(ResultCode.DECODING_ERROR,
2396               ERR_FILTER_ELEMENT_INVALID_TYPE.get(toHex(filterType)));
2397      }
2398
2399      return new Filter(null, filterType, filterComps, notComp, attrName,
2400                        assertionValue, subInitial, subAny, subFinal,
2401                        matchingRuleID, dnAttributes);
2402    }
2403    catch (final LDAPException le)
2404    {
2405      debugException(le);
2406      throw le;
2407    }
2408    catch (final Exception e)
2409    {
2410      debugException(e);
2411      throw new LDAPException(ResultCode.DECODING_ERROR,
2412           ERR_FILTER_CANNOT_DECODE.get(getExceptionMessage(e)), e);
2413    }
2414  }
2415
2416
2417
2418  /**
2419   * Decodes the provided ASN.1 element as a search filter.
2420   *
2421   * @param  filterElement  The ASN.1 element containing the encoded search
2422   *                        filter.
2423   *
2424   * @return  The decoded search filter.
2425   *
2426   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
2427   *                         a search filter.
2428   */
2429  public static Filter decode(final ASN1Element filterElement)
2430         throws LDAPException
2431  {
2432    final byte              filterType = filterElement.getType();
2433    final Filter[]          filterComps;
2434    final Filter            notComp;
2435    final String            attrName;
2436    final ASN1OctetString   assertionValue;
2437    final ASN1OctetString   subInitial;
2438    final ASN1OctetString[] subAny;
2439    final ASN1OctetString   subFinal;
2440    final String            matchingRuleID;
2441    final boolean           dnAttributes;
2442
2443    switch (filterType)
2444    {
2445      case FILTER_TYPE_AND:
2446      case FILTER_TYPE_OR:
2447        notComp        = null;
2448        attrName       = null;
2449        assertionValue = null;
2450        subInitial     = null;
2451        subAny         = NO_SUB_ANY;
2452        subFinal       = null;
2453        matchingRuleID = null;
2454        dnAttributes   = false;
2455
2456        final ASN1Set compSet;
2457        try
2458        {
2459          compSet = ASN1Set.decodeAsSet(filterElement);
2460        }
2461        catch (final ASN1Exception ae)
2462        {
2463          debugException(ae);
2464          throw new LDAPException(ResultCode.DECODING_ERROR,
2465               ERR_FILTER_CANNOT_DECODE_COMPS.get(getExceptionMessage(ae)), ae);
2466        }
2467
2468        final ASN1Element[] compElements = compSet.elements();
2469        filterComps = new Filter[compElements.length];
2470        for (int i=0; i < compElements.length; i++)
2471        {
2472          filterComps[i] = decode(compElements[i]);
2473        }
2474        break;
2475
2476
2477      case FILTER_TYPE_NOT:
2478        filterComps    = NO_FILTERS;
2479        attrName       = null;
2480        assertionValue = null;
2481        subInitial     = null;
2482        subAny         = NO_SUB_ANY;
2483        subFinal       = null;
2484        matchingRuleID = null;
2485        dnAttributes   = false;
2486
2487        final ASN1Element notFilterElement;
2488        try
2489        {
2490          notFilterElement = ASN1Element.decode(filterElement.getValue());
2491        }
2492        catch (final ASN1Exception ae)
2493        {
2494          debugException(ae);
2495          throw new LDAPException(ResultCode.DECODING_ERROR,
2496               ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(getExceptionMessage(ae)),
2497               ae);
2498        }
2499        notComp = decode(notFilterElement);
2500        break;
2501
2502
2503
2504      case FILTER_TYPE_EQUALITY:
2505      case FILTER_TYPE_GREATER_OR_EQUAL:
2506      case FILTER_TYPE_LESS_OR_EQUAL:
2507      case FILTER_TYPE_APPROXIMATE_MATCH:
2508        filterComps    = NO_FILTERS;
2509        notComp        = null;
2510        subInitial     = null;
2511        subAny         = NO_SUB_ANY;
2512        subFinal       = null;
2513        matchingRuleID = null;
2514        dnAttributes   = false;
2515
2516        final ASN1Sequence avaSequence;
2517        try
2518        {
2519          avaSequence = ASN1Sequence.decodeAsSequence(filterElement);
2520        }
2521        catch (final ASN1Exception ae)
2522        {
2523          debugException(ae);
2524          throw new LDAPException(ResultCode.DECODING_ERROR,
2525               ERR_FILTER_CANNOT_DECODE_AVA.get(getExceptionMessage(ae)), ae);
2526        }
2527
2528        final ASN1Element[] avaElements = avaSequence.elements();
2529        if (avaElements.length != 2)
2530        {
2531          throw new LDAPException(ResultCode.DECODING_ERROR,
2532                                  ERR_FILTER_INVALID_AVA_ELEMENT_COUNT.get(
2533                                       avaElements.length));
2534        }
2535
2536        attrName =
2537             ASN1OctetString.decodeAsOctetString(avaElements[0]).stringValue();
2538        assertionValue = ASN1OctetString.decodeAsOctetString(avaElements[1]);
2539        break;
2540
2541
2542      case FILTER_TYPE_SUBSTRING:
2543        filterComps    = NO_FILTERS;
2544        notComp        = null;
2545        assertionValue = null;
2546        matchingRuleID = null;
2547        dnAttributes   = false;
2548
2549        final ASN1Sequence subFilterSequence;
2550        try
2551        {
2552          subFilterSequence = ASN1Sequence.decodeAsSequence(filterElement);
2553        }
2554        catch (final ASN1Exception ae)
2555        {
2556          debugException(ae);
2557          throw new LDAPException(ResultCode.DECODING_ERROR,
2558               ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(getExceptionMessage(ae)),
2559               ae);
2560        }
2561
2562        final ASN1Element[] subFilterElements = subFilterSequence.elements();
2563        if (subFilterElements.length != 2)
2564        {
2565          throw new LDAPException(ResultCode.DECODING_ERROR,
2566                                  ERR_FILTER_INVALID_SUBSTR_ASSERTION_COUNT.get(
2567                                       subFilterElements.length));
2568        }
2569
2570        attrName = ASN1OctetString.decodeAsOctetString(
2571                        subFilterElements[0]).stringValue();
2572
2573        final ASN1Sequence subSequence;
2574        try
2575        {
2576          subSequence = ASN1Sequence.decodeAsSequence(subFilterElements[1]);
2577        }
2578        catch (final ASN1Exception ae)
2579        {
2580          debugException(ae);
2581          throw new LDAPException(ResultCode.DECODING_ERROR,
2582               ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(getExceptionMessage(ae)),
2583               ae);
2584        }
2585
2586        ASN1OctetString tempSubInitial = null;
2587        ASN1OctetString tempSubFinal   = null;
2588        final ArrayList<ASN1OctetString> subAnyList =
2589             new ArrayList<ASN1OctetString>(1);
2590
2591        final ASN1Element[] subElements = subSequence.elements();
2592        for (final ASN1Element subElement : subElements)
2593        {
2594          switch (subElement.getType())
2595          {
2596            case SUBSTRING_TYPE_SUBINITIAL:
2597              if (tempSubInitial == null)
2598              {
2599                tempSubInitial =
2600                     ASN1OctetString.decodeAsOctetString(subElement);
2601              }
2602              else
2603              {
2604                throw new LDAPException(ResultCode.DECODING_ERROR,
2605                                        ERR_FILTER_MULTIPLE_SUBINITIAL.get());
2606              }
2607              break;
2608
2609            case SUBSTRING_TYPE_SUBANY:
2610              subAnyList.add(ASN1OctetString.decodeAsOctetString(subElement));
2611              break;
2612
2613            case SUBSTRING_TYPE_SUBFINAL:
2614              if (tempSubFinal == null)
2615              {
2616                tempSubFinal = ASN1OctetString.decodeAsOctetString(subElement);
2617              }
2618              else
2619              {
2620                throw new LDAPException(ResultCode.DECODING_ERROR,
2621                                        ERR_FILTER_MULTIPLE_SUBFINAL.get());
2622              }
2623              break;
2624
2625            default:
2626              throw new LDAPException(ResultCode.DECODING_ERROR,
2627                                      ERR_FILTER_INVALID_SUBSTR_TYPE.get(
2628                                           toHex(subElement.getType())));
2629          }
2630        }
2631
2632        subInitial = tempSubInitial;
2633        subAny     = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]);
2634        subFinal   = tempSubFinal;
2635        break;
2636
2637
2638      case FILTER_TYPE_PRESENCE:
2639        filterComps    = NO_FILTERS;
2640        notComp        = null;
2641        assertionValue = null;
2642        subInitial     = null;
2643        subAny         = NO_SUB_ANY;
2644        subFinal       = null;
2645        matchingRuleID = null;
2646        dnAttributes   = false;
2647        attrName       =
2648             ASN1OctetString.decodeAsOctetString(filterElement).stringValue();
2649        break;
2650
2651
2652      case FILTER_TYPE_EXTENSIBLE_MATCH:
2653        filterComps    = NO_FILTERS;
2654        notComp        = null;
2655        subInitial     = null;
2656        subAny         = NO_SUB_ANY;
2657        subFinal       = null;
2658
2659        final ASN1Sequence emSequence;
2660        try
2661        {
2662          emSequence = ASN1Sequence.decodeAsSequence(filterElement);
2663        }
2664        catch (final ASN1Exception ae)
2665        {
2666          debugException(ae);
2667          throw new LDAPException(ResultCode.DECODING_ERROR,
2668               ERR_FILTER_CANNOT_DECODE_EXTMATCH.get(getExceptionMessage(ae)),
2669               ae);
2670        }
2671
2672        String          tempAttrName       = null;
2673        ASN1OctetString tempAssertionValue = null;
2674        String          tempMatchingRuleID = null;
2675        boolean         tempDNAttributes   = false;
2676        for (final ASN1Element e : emSequence.elements())
2677        {
2678          switch (e.getType())
2679          {
2680            case EXTENSIBLE_TYPE_ATTRIBUTE_NAME:
2681              if (tempAttrName == null)
2682              {
2683                tempAttrName =
2684                     ASN1OctetString.decodeAsOctetString(e).stringValue();
2685              }
2686              else
2687              {
2688                throw new LDAPException(ResultCode.DECODING_ERROR,
2689                               ERR_FILTER_EXTMATCH_MULTIPLE_ATTRS.get());
2690              }
2691              break;
2692
2693            case EXTENSIBLE_TYPE_MATCHING_RULE_ID:
2694              if (tempMatchingRuleID == null)
2695              {
2696                tempMatchingRuleID  =
2697                     ASN1OctetString.decodeAsOctetString(e).stringValue();
2698              }
2699              else
2700              {
2701                throw new LDAPException(ResultCode.DECODING_ERROR,
2702                               ERR_FILTER_EXTMATCH_MULTIPLE_MRIDS.get());
2703              }
2704              break;
2705
2706            case EXTENSIBLE_TYPE_MATCH_VALUE:
2707              if (tempAssertionValue == null)
2708              {
2709                tempAssertionValue = ASN1OctetString.decodeAsOctetString(e);
2710              }
2711              else
2712              {
2713                throw new LDAPException(ResultCode.DECODING_ERROR,
2714                               ERR_FILTER_EXTMATCH_MULTIPLE_VALUES.get());
2715              }
2716              break;
2717
2718            case EXTENSIBLE_TYPE_DN_ATTRIBUTES:
2719              try
2720              {
2721                if (tempDNAttributes)
2722                {
2723                  throw new LDAPException(ResultCode.DECODING_ERROR,
2724                                 ERR_FILTER_EXTMATCH_MULTIPLE_DNATTRS.get());
2725                }
2726                else
2727                {
2728                  tempDNAttributes =
2729                       ASN1Boolean.decodeAsBoolean(e).booleanValue();
2730                }
2731              }
2732              catch (final ASN1Exception ae)
2733              {
2734                debugException(ae);
2735                throw new LDAPException(ResultCode.DECODING_ERROR,
2736                               ERR_FILTER_EXTMATCH_DNATTRS_NOT_BOOLEAN.get(
2737                                    getExceptionMessage(ae)),
2738                               ae);
2739              }
2740              break;
2741
2742            default:
2743              throw new LDAPException(ResultCode.DECODING_ERROR,
2744                                      ERR_FILTER_EXTMATCH_INVALID_TYPE.get(
2745                                           toHex(e.getType())));
2746          }
2747        }
2748
2749        if ((tempAttrName == null) && (tempMatchingRuleID == null))
2750        {
2751          throw new LDAPException(ResultCode.DECODING_ERROR,
2752                                  ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get());
2753        }
2754
2755        if (tempAssertionValue == null)
2756        {
2757          throw new LDAPException(ResultCode.DECODING_ERROR,
2758                                  ERR_FILTER_EXTMATCH_NO_VALUE.get());
2759        }
2760
2761        attrName       = tempAttrName;
2762        assertionValue = tempAssertionValue;
2763        matchingRuleID = tempMatchingRuleID;
2764        dnAttributes   = tempDNAttributes;
2765        break;
2766
2767
2768      default:
2769        throw new LDAPException(ResultCode.DECODING_ERROR,
2770                                ERR_FILTER_ELEMENT_INVALID_TYPE.get(
2771                                     toHex(filterElement.getType())));
2772    }
2773
2774
2775    return new Filter(null, filterType, filterComps, notComp, attrName,
2776                      assertionValue, subInitial, subAny, subFinal,
2777                      matchingRuleID, dnAttributes);
2778  }
2779
2780
2781
2782  /**
2783   * Retrieves the filter type for this filter.
2784   *
2785   * @return  The filter type for this filter.
2786   */
2787  public byte getFilterType()
2788  {
2789    return filterType;
2790  }
2791
2792
2793
2794  /**
2795   * Retrieves the set of filter components used in this AND or OR filter.  This
2796   * is not applicable for any other filter type.
2797   *
2798   * @return  The set of filter components used in this AND or OR filter, or an
2799   *          empty array if this is some other type of filter or if there are
2800   *          no components (i.e., as in an LDAP TRUE or LDAP FALSE filter).
2801   */
2802  public Filter[] getComponents()
2803  {
2804    return filterComps;
2805  }
2806
2807
2808
2809  /**
2810   * Retrieves the filter component used in this NOT filter.  This is not
2811   * applicable for any other filter type.
2812   *
2813   * @return  The filter component used in this NOT filter, or {@code null} if
2814   *          this is some other type of filter.
2815   */
2816  public Filter getNOTComponent()
2817  {
2818    return notComp;
2819  }
2820
2821
2822
2823  /**
2824   * Retrieves the name of the attribute type for this search filter.  This is
2825   * applicable for the following types of filters:
2826   * <UL>
2827   *   <LI>Equality</LI>
2828   *   <LI>Substring</LI>
2829   *   <LI>Greater or Equal</LI>
2830   *   <LI>Less or Equal</LI>
2831   *   <LI>Presence</LI>
2832   *   <LI>Approximate Match</LI>
2833   *   <LI>Extensible Match</LI>
2834   * </UL>
2835   *
2836   * @return  The name of the attribute type for this search filter, or
2837   *          {@code null} if it is not applicable for this type of filter.
2838   */
2839  public String getAttributeName()
2840  {
2841    return attrName;
2842  }
2843
2844
2845
2846  /**
2847   * Retrieves the string representation of the assertion value for this search
2848   * filter.  This is applicable for the following types of filters:
2849   * <UL>
2850   *   <LI>Equality</LI>
2851   *   <LI>Greater or Equal</LI>
2852   *   <LI>Less or Equal</LI>
2853   *   <LI>Approximate Match</LI>
2854   *   <LI>Extensible Match</LI>
2855   * </UL>
2856   *
2857   * @return  The string representation of the assertion value for this search
2858   *          filter, or {@code null} if it is not applicable for this type of
2859   *          filter.
2860   */
2861  public String getAssertionValue()
2862  {
2863    if (assertionValue == null)
2864    {
2865      return null;
2866    }
2867    else
2868    {
2869      return assertionValue.stringValue();
2870    }
2871  }
2872
2873
2874
2875  /**
2876   * Retrieves the binary representation of the assertion value for this search
2877   * filter.  This is applicable for the following types of filters:
2878   * <UL>
2879   *   <LI>Equality</LI>
2880   *   <LI>Greater or Equal</LI>
2881   *   <LI>Less or Equal</LI>
2882   *   <LI>Approximate Match</LI>
2883   *   <LI>Extensible Match</LI>
2884   * </UL>
2885   *
2886   * @return  The binary representation of the assertion value for this search
2887   *          filter, or {@code null} if it is not applicable for this type of
2888   *          filter.
2889   */
2890  public byte[] getAssertionValueBytes()
2891  {
2892    if (assertionValue == null)
2893    {
2894      return null;
2895    }
2896    else
2897    {
2898      return assertionValue.getValue();
2899    }
2900  }
2901
2902
2903
2904  /**
2905   * Retrieves the raw assertion value for this search filter as an ASN.1
2906   * octet string.  This is applicable for the following types of filters:
2907   * <UL>
2908   *   <LI>Equality</LI>
2909   *   <LI>Greater or Equal</LI>
2910   *   <LI>Less or Equal</LI>
2911   *   <LI>Approximate Match</LI>
2912   *   <LI>Extensible Match</LI>
2913   * </UL>
2914   *
2915   * @return  The raw assertion value for this search filter as an ASN.1 octet
2916   *          string, or {@code null} if it is not applicable for this type of
2917   *          filter.
2918   */
2919  public ASN1OctetString getRawAssertionValue()
2920  {
2921    return assertionValue;
2922  }
2923
2924
2925
2926  /**
2927   * Retrieves the string representation of the subInitial element for this
2928   * substring filter.  This is not applicable for any other filter type.
2929   *
2930   * @return  The string representation of the subInitial element for this
2931   *          substring filter, or {@code null} if this is some other type of
2932   *          filter, or if it is a substring filter with no subInitial element.
2933   */
2934  public String getSubInitialString()
2935  {
2936    if (subInitial == null)
2937    {
2938      return null;
2939    }
2940    else
2941    {
2942      return subInitial.stringValue();
2943    }
2944  }
2945
2946
2947
2948  /**
2949   * Retrieves the binary representation of the subInitial element for this
2950   * substring filter.  This is not applicable for any other filter type.
2951   *
2952   * @return  The binary representation of the subInitial element for this
2953   *          substring filter, or {@code null} if this is some other type of
2954   *          filter, or if it is a substring filter with no subInitial element.
2955   */
2956  public byte[] getSubInitialBytes()
2957  {
2958    if (subInitial == null)
2959    {
2960      return null;
2961    }
2962    else
2963    {
2964      return subInitial.getValue();
2965    }
2966  }
2967
2968
2969
2970  /**
2971   * Retrieves the raw subInitial element for this filter as an ASN.1 octet
2972   * string.  This is not applicable for any other filter type.
2973   *
2974   * @return  The raw subInitial element for this filter as an ASN.1 octet
2975   *          string, or {@code null} if this is not a substring filter, or if
2976   *          it is a substring filter with no subInitial element.
2977   */
2978  public ASN1OctetString getRawSubInitialValue()
2979  {
2980    return subInitial;
2981  }
2982
2983
2984
2985  /**
2986   * Retrieves the string representations of the subAny elements for this
2987   * substring filter.  This is not applicable for any other filter type.
2988   *
2989   * @return  The string representations of the subAny elements for this
2990   *          substring filter, or an empty array if this is some other type of
2991   *          filter, or if it is a substring filter with no subFinal element.
2992   */
2993  public String[] getSubAnyStrings()
2994  {
2995    final String[] subAnyStrings = new String[subAny.length];
2996    for (int i=0; i < subAny.length; i++)
2997    {
2998      subAnyStrings[i] = subAny[i].stringValue();
2999    }
3000
3001    return subAnyStrings;
3002  }
3003
3004
3005
3006  /**
3007   * Retrieves the binary representations of the subAny elements for this
3008   * substring filter.  This is not applicable for any other filter type.
3009   *
3010   * @return  The binary representations of the subAny elements for this
3011   *          substring filter, or an empty array if this is some other type of
3012   *          filter, or if it is a substring filter with no subFinal element.
3013   */
3014  public byte[][] getSubAnyBytes()
3015  {
3016    final byte[][] subAnyBytes = new byte[subAny.length][];
3017    for (int i=0; i < subAny.length; i++)
3018    {
3019      subAnyBytes[i] = subAny[i].getValue();
3020    }
3021
3022    return subAnyBytes;
3023  }
3024
3025
3026
3027  /**
3028   * Retrieves the raw subAny values for this substring filter.  This is not
3029   * applicable for any other filter type.
3030   *
3031   * @return  The raw subAny values for this substring filter, or an empty array
3032   *          if this is some other type of filter, or if it is a substring
3033   *          filter with no subFinal element.
3034   */
3035  public ASN1OctetString[] getRawSubAnyValues()
3036  {
3037    return subAny;
3038  }
3039
3040
3041
3042  /**
3043   * Retrieves the string representation of the subFinal element for this
3044   * substring filter.  This is not applicable for any other filter type.
3045   *
3046   * @return  The string representation of the subFinal element for this
3047   *          substring filter, or {@code null} if this is some other type of
3048   *          filter, or if it is a substring filter with no subFinal element.
3049   */
3050  public String getSubFinalString()
3051  {
3052    if (subFinal == null)
3053    {
3054      return null;
3055    }
3056    else
3057    {
3058      return subFinal.stringValue();
3059    }
3060  }
3061
3062
3063
3064  /**
3065   * Retrieves the binary representation of the subFinal element for this
3066   * substring filter.  This is not applicable for any other filter type.
3067   *
3068   * @return  The binary representation of the subFinal element for this
3069   *          substring filter, or {@code null} if this is some other type of
3070   *          filter, or if it is a substring filter with no subFinal element.
3071   */
3072  public byte[] getSubFinalBytes()
3073  {
3074    if (subFinal == null)
3075    {
3076      return null;
3077    }
3078    else
3079    {
3080      return subFinal.getValue();
3081    }
3082  }
3083
3084
3085
3086  /**
3087   * Retrieves the raw subFinal element for this filter as an ASN.1 octet
3088   * string.  This is not applicable for any other filter type.
3089   *
3090   * @return  The raw subFinal element for this filter as an ASN.1 octet
3091   *          string, or {@code null} if this is not a substring filter, or if
3092   *          it is a substring filter with no subFinal element.
3093   */
3094  public ASN1OctetString getRawSubFinalValue()
3095  {
3096    return subFinal;
3097  }
3098
3099
3100
3101  /**
3102   * Retrieves the matching rule ID for this extensible match filter.  This is
3103   * not applicable for any other filter type.
3104   *
3105   * @return  The matching rule ID for this extensible match filter, or
3106   *          {@code null} if this is some other type of filter, or if this
3107   *          extensible match filter does not have a matching rule ID.
3108   */
3109  public String getMatchingRuleID()
3110  {
3111    return matchingRuleID;
3112  }
3113
3114
3115
3116  /**
3117   * Retrieves the dnAttributes flag for this extensible match filter.  This is
3118   * not applicable for any other filter type.
3119   *
3120   * @return  The dnAttributes flag for this extensible match filter.
3121   */
3122  public boolean getDNAttributes()
3123  {
3124    return dnAttributes;
3125  }
3126
3127
3128
3129  /**
3130   * Indicates whether this filter matches the provided entry.  Note that this
3131   * is a best-guess effort and may not be completely accurate in all cases.
3132   * All matching will be performed using case-ignore string matching, which may
3133   * yield an unexpected result for values that should not be treated as simple
3134   * strings.  For example:
3135   * <UL>
3136   *   <LI>Two DN values which are logically equivalent may not be considered
3137   *       matches if they have different spacing.</LI>
3138   *   <LI>Ordering comparisons against numeric values may yield unexpected
3139   *       results (e.g., "2" will be considered greater than "10" because the
3140   *       character "2" has a larger ASCII value than the character "1").</LI>
3141   * </UL>
3142   * <BR>
3143   * In addition to the above constraints, it should be noted that neither
3144   * approximate matching nor extensible matching are currently supported.
3145   *
3146   * @param  entry  The entry for which to make the determination.  It must not
3147   *                be {@code null}.
3148   *
3149   * @return  {@code true} if this filter appears to match the provided entry,
3150   *          or {@code false} if not.
3151   *
3152   * @throws  LDAPException  If a problem occurs while trying to make the
3153   *                         determination.
3154   */
3155  public boolean matchesEntry(final Entry entry)
3156         throws LDAPException
3157  {
3158    return matchesEntry(entry, entry.getSchema());
3159  }
3160
3161
3162
3163  /**
3164   * Indicates whether this filter matches the provided entry.  Note that this
3165   * is a best-guess effort and may not be completely accurate in all cases.
3166   * If provided, the given schema will be used in an attempt to determine the
3167   * appropriate matching rule for making the determinations, but some corner
3168   * cases may not be handled accurately.  Neither approximate matching nor
3169   * extensible matching are currently supported.
3170   *
3171   * @param  entry   The entry for which to make the determination.  It must not
3172   *                 be {@code null}.
3173   * @param  schema  The schema to use when making the determination.  If this
3174   *                 is {@code null}, then all matching will be performed using
3175   *                 a case-ignore matching rule.
3176   *
3177   * @return  {@code true} if this filter appears to match the provided entry,
3178   *          or {@code false} if not.
3179   *
3180   * @throws  LDAPException  If a problem occurs while trying to make the
3181   *                         determination.
3182   */
3183  public boolean matchesEntry(final Entry entry, final Schema schema)
3184         throws LDAPException
3185  {
3186    ensureNotNull(entry);
3187
3188    switch (filterType)
3189    {
3190      case FILTER_TYPE_AND:
3191        for (final Filter f : filterComps)
3192        {
3193          if (! f.matchesEntry(entry, schema))
3194          {
3195            return false;
3196          }
3197        }
3198        return true;
3199
3200      case FILTER_TYPE_OR:
3201        for (final Filter f : filterComps)
3202        {
3203          if (f.matchesEntry(entry, schema))
3204          {
3205            return true;
3206          }
3207        }
3208        return false;
3209
3210      case FILTER_TYPE_NOT:
3211        return (! notComp.matchesEntry(entry, schema));
3212
3213      case FILTER_TYPE_EQUALITY:
3214        Attribute a = entry.getAttribute(attrName, schema);
3215        if (a == null)
3216        {
3217          return false;
3218        }
3219
3220        MatchingRule matchingRule =
3221             MatchingRule.selectEqualityMatchingRule(attrName, schema);
3222        return matchingRule.matchesAnyValue(assertionValue, a.getRawValues());
3223
3224      case FILTER_TYPE_SUBSTRING:
3225        a = entry.getAttribute(attrName, schema);
3226        if (a == null)
3227        {
3228          return false;
3229        }
3230
3231        matchingRule =
3232             MatchingRule.selectSubstringMatchingRule(attrName, schema);
3233        for (final ASN1OctetString v : a.getRawValues())
3234        {
3235          if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal))
3236          {
3237            return true;
3238          }
3239        }
3240        return false;
3241
3242      case FILTER_TYPE_GREATER_OR_EQUAL:
3243        a = entry.getAttribute(attrName, schema);
3244        if (a == null)
3245        {
3246          return false;
3247        }
3248
3249        matchingRule =
3250             MatchingRule.selectOrderingMatchingRule(attrName, schema);
3251        for (final ASN1OctetString v : a.getRawValues())
3252        {
3253          if (matchingRule.compareValues(v, assertionValue) >= 0)
3254          {
3255            return true;
3256          }
3257        }
3258        return false;
3259
3260      case FILTER_TYPE_LESS_OR_EQUAL:
3261        a = entry.getAttribute(attrName, schema);
3262        if (a == null)
3263        {
3264          return false;
3265        }
3266
3267        matchingRule =
3268             MatchingRule.selectOrderingMatchingRule(attrName, schema);
3269        for (final ASN1OctetString v : a.getRawValues())
3270        {
3271          if (matchingRule.compareValues(v, assertionValue) <= 0)
3272          {
3273            return true;
3274          }
3275        }
3276        return false;
3277
3278      case FILTER_TYPE_PRESENCE:
3279        return (entry.hasAttribute(attrName));
3280
3281      case FILTER_TYPE_APPROXIMATE_MATCH:
3282        throw new LDAPException(ResultCode.NOT_SUPPORTED,
3283             ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get());
3284
3285      case FILTER_TYPE_EXTENSIBLE_MATCH:
3286        throw new LDAPException(ResultCode.NOT_SUPPORTED,
3287             ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get());
3288
3289      default:
3290        throw new LDAPException(ResultCode.PARAM_ERROR,
3291                                ERR_FILTER_INVALID_TYPE.get());
3292    }
3293  }
3294
3295
3296
3297  /**
3298   * Attempts to simplify the provided filter to allow it to be more efficiently
3299   * processed by the server.  The simplifications it will make include:
3300   * <UL>
3301   *   <LI>Any AND or OR filter that contains only a single filter component
3302   *       will be converted to just that embedded filter component to eliminate
3303   *       the unnecessary AND or OR wrapper.  For example, the filter
3304   *       "(&amp;(uid=john.doe))" will be converted to just
3305   *       "(uid=john.doe)".</LI>
3306   *   <LI>Any AND components inside of an AND filter will be merged into the
3307   *       outer AND filter.  Any OR components inside of an OR filter will be
3308   *       merged into the outer OR filter.  For example, the filter
3309   *       "(&amp;(objectClass=person)(&amp;(givenName=John)(sn=Doe)))" will be
3310   *       converted to
3311   *       "(&amp;(objectClass=person)(givenName=John)(sn=Doe))".</LI>
3312   *   <LI>If {@code reOrderElements} is true, then this method will attempt to
3313   *       re-order the elements inside AND and OR filters in an attempt to
3314   *       ensure that the components which are likely to be the most efficient
3315   *       come earlier than those which are likely to be the least efficient.
3316   *       This can speed up processing in servers that process filter
3317   *       components in a left-to-right order.</LI>
3318   * </UL>
3319   * <BR><BR>
3320   * The simplification will happen recursively, in an attempt to generate a
3321   * filter that is as simple and efficient as possible.
3322   *
3323   * @param  filter           The filter to attempt to simplify.
3324   * @param  reOrderElements  Indicates whether this method may re-order the
3325   *                          elements in the filter so that, in a server that
3326   *                          evaluates the components in a left-to-right order,
3327   *                          the components which are likely to be more
3328   *                          efficient to process will be listed before those
3329   *                          which are likely to be less efficient.
3330   *
3331   * @return  The simplified filter, or the original filter if the provided
3332   *          filter is not one that can be simplified any further.
3333   */
3334  public static Filter simplifyFilter(final Filter filter,
3335                                      final boolean reOrderElements)
3336  {
3337    final byte filterType = filter.filterType;
3338    switch (filterType)
3339    {
3340      case FILTER_TYPE_AND:
3341      case FILTER_TYPE_OR:
3342        // These will be handled below.
3343        break;
3344
3345      case FILTER_TYPE_NOT:
3346        // We may be able to simplify the filter component contained inside the
3347        // NOT.
3348        return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements));
3349
3350      default:
3351        // We can't simplify this filter, so just return what was provided.
3352        return filter;
3353    }
3354
3355
3356    // An AND filter with zero components is an LDAP true filter, and we can't
3357    // simplify that.  An OR filter with zero components is an LDAP false
3358    // filter, and we can't simplify that either.  The set of components
3359    // should never be null for an AND or OR filter, but if that happens to be
3360    // the case, then we'll return the original filter.
3361    final Filter[] components = filter.filterComps;
3362    if ((components == null) || (components.length == 0))
3363    {
3364      return filter;
3365    }
3366
3367
3368    // For either an AND or an OR filter with just a single component, then just
3369    // return that embedded component.  But simplify it first.
3370    if (components.length == 1)
3371    {
3372      return simplifyFilter(components[0], reOrderElements);
3373    }
3374
3375
3376    // If we've gotten here, then we have a filter with multiple components.
3377    // Simplify each of them to the extent possible, un-embed any ANDs
3378    // contained inside an AND or ORs contained inside an OR, and eliminate any
3379    // duplicate components in the resulting top-level filter.
3380    final LinkedHashSet<Filter> componentSet = new LinkedHashSet<Filter>(10);
3381    for (final Filter f : components)
3382    {
3383      final Filter simplifiedFilter = simplifyFilter(f, reOrderElements);
3384      if (simplifiedFilter.filterType == FILTER_TYPE_AND)
3385      {
3386        if (filterType == FILTER_TYPE_AND)
3387        {
3388          // This is an AND nested inside an AND.  In that case, we'll just put
3389          // all the nested components inside the outer AND.
3390          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3391        }
3392        else
3393        {
3394          componentSet.add(simplifiedFilter);
3395        }
3396      }
3397      else if (simplifiedFilter.filterType == FILTER_TYPE_OR)
3398      {
3399        if (filterType == FILTER_TYPE_OR)
3400        {
3401          // This is an OR nested inside an OR.  In that case, we'll just put
3402          // all the nested components inside the outer OR.
3403          componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps));
3404        }
3405        else
3406        {
3407          componentSet.add(simplifiedFilter);
3408        }
3409      }
3410      else
3411      {
3412        componentSet.add(simplifiedFilter);
3413      }
3414    }
3415
3416
3417    // It's possible at this point that we are down to just a single component.
3418    // That can happen if the filter was an AND or an OR with a duplicate
3419    // element, like "(&(a=b)(a=b))".  In that case, just return that one
3420    // component.
3421    if (componentSet.size() == 1)
3422    {
3423      return componentSet.iterator().next();
3424    }
3425
3426
3427    // If we should re-order the components, then use the following priority
3428    // list:
3429    //
3430    // 1.  Equality components that target an attribute other than objectClass.
3431    //     These are most likely to require only a single database lookup to get
3432    //     the candidate list, and that candidate list will frequently be small.
3433    // 2.  Equality components that target the objectClass attribute.  These are
3434    //     likely to require only a single database lookup to get the candidate
3435    //     list, but the candidate list is more likely to be larger.
3436    // 3.  Approximate match components.  These are also likely to require only
3437    //     a single database lookup to get the candidate list, but that
3438    //     candidate list is likely to have a larger number of candidates.
3439    // 4.  Presence components that target an attribute other than objectClass.
3440    //     These are also likely to require only a single database lookup to get
3441    //     the candidate list, but are likely to have a large number of
3442    //     candidates.
3443    // 5.  Substring components that have a subInitial element.  These are
3444    //     generally the most efficient substring filters to process, requiring
3445    //     access to fewer database keys than substring filters with only subAny
3446    //     and/or subFinal components.
3447    // 6.  Substring components that only have subAny and/or subFinal elements.
3448    //     These will probably require a number of database lookups and will
3449    //     probably result in large candidate lists.
3450    // 7.  Greater-or-equal components and less-or-equal components.  These
3451    //     will probably require a number of database lookups and will probably
3452    //     result in large candidate lists.
3453    // 8.  Extensible match components.  Even if these are indexed, there isn't
3454    //     any good way to know how expensive they might be to process or how
3455    //     big the candidate list might be.
3456    // 9.  Presence components that target the objectClass attribute.  This is
3457    //     likely to require only a single database lookup to get the candidate
3458    //     list, but the candidate list will also be extremely large (if it's
3459    //     indexed at all) since it will match every entry.
3460    // 10. NOT components.  These are generally not possible to index and
3461    //     therefore cannot be used to create a candidate list.
3462    //
3463    // AND and OR components will be ordered according to the first of their
3464    // embedded components  Since the filter has already been simplified, then
3465    // the first element in the list will be the one we think will be the most
3466    // efficient to process.
3467    if (reOrderElements)
3468    {
3469      final TreeMap<Integer,LinkedHashSet<Filter>> m =
3470           new TreeMap<Integer,LinkedHashSet<Filter>>();
3471      for (final Filter f : componentSet)
3472      {
3473        final Filter prioritizeComp;
3474        if ((f.filterType == FILTER_TYPE_AND) ||
3475            (f.filterType == FILTER_TYPE_OR))
3476        {
3477          if (f.filterComps.length > 0)
3478          {
3479            prioritizeComp = f.filterComps[0];
3480          }
3481          else
3482          {
3483            prioritizeComp = f;
3484          }
3485        }
3486        else
3487        {
3488          prioritizeComp = f;
3489        }
3490
3491        final Integer slot;
3492        switch (prioritizeComp.filterType)
3493        {
3494          case FILTER_TYPE_EQUALITY:
3495            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3496            {
3497              slot = 2;
3498            }
3499            else
3500            {
3501              slot = 1;
3502            }
3503            break;
3504
3505          case FILTER_TYPE_APPROXIMATE_MATCH:
3506            slot = 3;
3507            break;
3508
3509          case FILTER_TYPE_PRESENCE:
3510            if (prioritizeComp.attrName.equalsIgnoreCase("objectClass"))
3511            {
3512              slot = 9;
3513            }
3514            else
3515            {
3516              slot = 4;
3517            }
3518            break;
3519
3520          case FILTER_TYPE_SUBSTRING:
3521            if (prioritizeComp.subInitial == null)
3522            {
3523              slot = 6;
3524            }
3525            else
3526            {
3527              slot = 5;
3528            }
3529            break;
3530
3531          case FILTER_TYPE_GREATER_OR_EQUAL:
3532          case FILTER_TYPE_LESS_OR_EQUAL:
3533            slot = 7;
3534            break;
3535
3536          case FILTER_TYPE_EXTENSIBLE_MATCH:
3537            slot = 8;
3538            break;
3539
3540          case FILTER_TYPE_NOT:
3541          default:
3542            slot = 10;
3543            break;
3544        }
3545
3546        LinkedHashSet<Filter> filterSet = m.get(slot-1);
3547        if (filterSet == null)
3548        {
3549          filterSet = new LinkedHashSet<Filter>(10);
3550          m.put(slot-1, filterSet);
3551        }
3552        filterSet.add(f);
3553      }
3554
3555      componentSet.clear();
3556      for (final LinkedHashSet<Filter> filterSet : m.values())
3557      {
3558        componentSet.addAll(filterSet);
3559      }
3560    }
3561
3562
3563    // Return the new, possibly simplified filter.
3564    if (filterType == FILTER_TYPE_AND)
3565    {
3566      return createANDFilter(componentSet);
3567    }
3568    else
3569    {
3570      return createORFilter(componentSet);
3571    }
3572  }
3573
3574
3575
3576  /**
3577   * Generates a hash code for this search filter.
3578   *
3579   * @return  The generated hash code for this search filter.
3580   */
3581  @Override()
3582  public int hashCode()
3583  {
3584    final CaseIgnoreStringMatchingRule matchingRule =
3585         CaseIgnoreStringMatchingRule.getInstance();
3586    int hashCode = filterType;
3587
3588    switch (filterType)
3589    {
3590      case FILTER_TYPE_AND:
3591      case FILTER_TYPE_OR:
3592        for (final Filter f : filterComps)
3593        {
3594          hashCode += f.hashCode();
3595        }
3596        break;
3597
3598      case FILTER_TYPE_NOT:
3599        hashCode += notComp.hashCode();
3600        break;
3601
3602      case FILTER_TYPE_EQUALITY:
3603      case FILTER_TYPE_GREATER_OR_EQUAL:
3604      case FILTER_TYPE_LESS_OR_EQUAL:
3605      case FILTER_TYPE_APPROXIMATE_MATCH:
3606        hashCode += toLowerCase(attrName).hashCode();
3607        hashCode += matchingRule.normalize(assertionValue).hashCode();
3608        break;
3609
3610      case FILTER_TYPE_SUBSTRING:
3611        hashCode += toLowerCase(attrName).hashCode();
3612        if (subInitial != null)
3613        {
3614          hashCode += matchingRule.normalizeSubstring(subInitial,
3615                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode();
3616        }
3617        for (final ASN1OctetString s : subAny)
3618        {
3619          hashCode += matchingRule.normalizeSubstring(s,
3620                           MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode();
3621        }
3622        if (subFinal != null)
3623        {
3624          hashCode += matchingRule.normalizeSubstring(subFinal,
3625                           MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode();
3626        }
3627        break;
3628
3629      case FILTER_TYPE_PRESENCE:
3630        hashCode += toLowerCase(attrName).hashCode();
3631        break;
3632
3633      case FILTER_TYPE_EXTENSIBLE_MATCH:
3634        if (attrName != null)
3635        {
3636          hashCode += toLowerCase(attrName).hashCode();
3637        }
3638
3639        if (matchingRuleID != null)
3640        {
3641          hashCode += toLowerCase(matchingRuleID).hashCode();
3642        }
3643
3644        if (dnAttributes)
3645        {
3646          hashCode++;
3647        }
3648
3649        hashCode += matchingRule.normalize(assertionValue).hashCode();
3650        break;
3651    }
3652
3653    return hashCode;
3654  }
3655
3656
3657
3658  /**
3659   * Indicates whether the provided object is equal to this search filter.
3660   *
3661   * @param  o  The object for which to make the determination.
3662   *
3663   * @return  {@code true} if the provided object can be considered equal to
3664   *          this search filter, or {@code false} if not.
3665   */
3666  @Override()
3667  public boolean equals(final Object o)
3668  {
3669    if (o == null)
3670    {
3671      return false;
3672    }
3673
3674    if (o == this)
3675    {
3676      return true;
3677    }
3678
3679    if (! (o instanceof Filter))
3680    {
3681      return false;
3682    }
3683
3684    final Filter f = (Filter) o;
3685    if (filterType != f.filterType)
3686    {
3687      return false;
3688    }
3689
3690    final CaseIgnoreStringMatchingRule matchingRule =
3691         CaseIgnoreStringMatchingRule.getInstance();
3692
3693    switch (filterType)
3694    {
3695      case FILTER_TYPE_AND:
3696      case FILTER_TYPE_OR:
3697        if (filterComps.length != f.filterComps.length)
3698        {
3699          return false;
3700        }
3701
3702        final HashSet<Filter> compSet = new HashSet<Filter>();
3703        compSet.addAll(Arrays.asList(filterComps));
3704
3705        for (final Filter filterComp : f.filterComps)
3706        {
3707          if (! compSet.remove(filterComp))
3708          {
3709            return false;
3710          }
3711        }
3712
3713        return true;
3714
3715
3716    case FILTER_TYPE_NOT:
3717      return notComp.equals(f.notComp);
3718
3719
3720      case FILTER_TYPE_EQUALITY:
3721      case FILTER_TYPE_GREATER_OR_EQUAL:
3722      case FILTER_TYPE_LESS_OR_EQUAL:
3723      case FILTER_TYPE_APPROXIMATE_MATCH:
3724        return (attrName.equalsIgnoreCase(f.attrName) &&
3725                matchingRule.valuesMatch(assertionValue, f.assertionValue));
3726
3727
3728      case FILTER_TYPE_SUBSTRING:
3729        if (! attrName.equalsIgnoreCase(f.attrName))
3730        {
3731          return false;
3732        }
3733
3734        if (subAny.length != f.subAny.length)
3735        {
3736          return false;
3737        }
3738
3739        if (subInitial == null)
3740        {
3741          if (f.subInitial != null)
3742          {
3743            return false;
3744          }
3745        }
3746        else
3747        {
3748          if (f.subInitial == null)
3749          {
3750            return false;
3751          }
3752
3753          final ASN1OctetString si1 = matchingRule.normalizeSubstring(
3754               subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
3755          final ASN1OctetString si2 = matchingRule.normalizeSubstring(
3756               f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL);
3757          if (! si1.equals(si2))
3758          {
3759            return false;
3760          }
3761        }
3762
3763        for (int i=0; i < subAny.length; i++)
3764        {
3765          final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i],
3766               MatchingRule.SUBSTRING_TYPE_SUBANY);
3767          final ASN1OctetString sa2 = matchingRule.normalizeSubstring(
3768               f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY);
3769          if (! sa1.equals(sa2))
3770          {
3771            return false;
3772          }
3773        }
3774
3775        if (subFinal == null)
3776        {
3777          if (f.subFinal != null)
3778          {
3779            return false;
3780          }
3781        }
3782        else
3783        {
3784          if (f.subFinal == null)
3785          {
3786            return false;
3787          }
3788
3789          final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal,
3790               MatchingRule.SUBSTRING_TYPE_SUBFINAL);
3791          final ASN1OctetString sf2 = matchingRule.normalizeSubstring(
3792               f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL);
3793          if (! sf1.equals(sf2))
3794          {
3795            return false;
3796          }
3797        }
3798
3799        return true;
3800
3801
3802      case FILTER_TYPE_PRESENCE:
3803        return (attrName.equalsIgnoreCase(f.attrName));
3804
3805
3806      case FILTER_TYPE_EXTENSIBLE_MATCH:
3807        if (attrName == null)
3808        {
3809          if (f.attrName != null)
3810          {
3811            return false;
3812          }
3813        }
3814        else
3815        {
3816          if (f.attrName == null)
3817          {
3818            return false;
3819          }
3820          else
3821          {
3822            if (! attrName.equalsIgnoreCase(f.attrName))
3823            {
3824              return false;
3825            }
3826          }
3827        }
3828
3829        if (matchingRuleID == null)
3830        {
3831          if (f.matchingRuleID != null)
3832          {
3833            return false;
3834          }
3835        }
3836        else
3837        {
3838          if (f.matchingRuleID == null)
3839          {
3840            return false;
3841          }
3842          else
3843          {
3844            if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID))
3845            {
3846              return false;
3847            }
3848          }
3849        }
3850
3851        if (dnAttributes != f.dnAttributes)
3852        {
3853          return false;
3854        }
3855
3856        return matchingRule.valuesMatch(assertionValue, f.assertionValue);
3857
3858
3859      default:
3860        return false;
3861    }
3862  }
3863
3864
3865
3866  /**
3867   * Retrieves a string representation of this search filter.
3868   *
3869   * @return  A string representation of this search filter.
3870   */
3871  @Override()
3872  public String toString()
3873  {
3874    if (filterString == null)
3875    {
3876      final StringBuilder buffer = new StringBuilder();
3877      toString(buffer);
3878      filterString = buffer.toString();
3879    }
3880
3881    return filterString;
3882  }
3883
3884
3885
3886  /**
3887   * Appends a string representation of this search filter to the provided
3888   * buffer.
3889   *
3890   * @param  buffer  The buffer to which to append a string representation of
3891   *                 this search filter.
3892   */
3893  public void toString(final StringBuilder buffer)
3894  {
3895    switch (filterType)
3896    {
3897      case FILTER_TYPE_AND:
3898        buffer.append("(&");
3899        for (final Filter f : filterComps)
3900        {
3901          f.toString(buffer);
3902        }
3903        buffer.append(')');
3904        break;
3905
3906      case FILTER_TYPE_OR:
3907        buffer.append("(|");
3908        for (final Filter f : filterComps)
3909        {
3910          f.toString(buffer);
3911        }
3912        buffer.append(')');
3913        break;
3914
3915      case FILTER_TYPE_NOT:
3916        buffer.append("(!");
3917        notComp.toString(buffer);
3918        buffer.append(')');
3919        break;
3920
3921      case FILTER_TYPE_EQUALITY:
3922        buffer.append('(');
3923        buffer.append(attrName);
3924        buffer.append('=');
3925        encodeValue(assertionValue, buffer);
3926        buffer.append(')');
3927        break;
3928
3929      case FILTER_TYPE_SUBSTRING:
3930        buffer.append('(');
3931        buffer.append(attrName);
3932        buffer.append('=');
3933        if (subInitial != null)
3934        {
3935          encodeValue(subInitial, buffer);
3936        }
3937        buffer.append('*');
3938        for (final ASN1OctetString s : subAny)
3939        {
3940          encodeValue(s, buffer);
3941          buffer.append('*');
3942        }
3943        if (subFinal != null)
3944        {
3945          encodeValue(subFinal, buffer);
3946        }
3947        buffer.append(')');
3948        break;
3949
3950      case FILTER_TYPE_GREATER_OR_EQUAL:
3951        buffer.append('(');
3952        buffer.append(attrName);
3953        buffer.append(">=");
3954        encodeValue(assertionValue, buffer);
3955        buffer.append(')');
3956        break;
3957
3958      case FILTER_TYPE_LESS_OR_EQUAL:
3959        buffer.append('(');
3960        buffer.append(attrName);
3961        buffer.append("<=");
3962        encodeValue(assertionValue, buffer);
3963        buffer.append(')');
3964        break;
3965
3966      case FILTER_TYPE_PRESENCE:
3967        buffer.append('(');
3968        buffer.append(attrName);
3969        buffer.append("=*)");
3970        break;
3971
3972      case FILTER_TYPE_APPROXIMATE_MATCH:
3973        buffer.append('(');
3974        buffer.append(attrName);
3975        buffer.append("~=");
3976        encodeValue(assertionValue, buffer);
3977        buffer.append(')');
3978        break;
3979
3980      case FILTER_TYPE_EXTENSIBLE_MATCH:
3981        buffer.append('(');
3982        if (attrName != null)
3983        {
3984          buffer.append(attrName);
3985        }
3986
3987        if (dnAttributes)
3988        {
3989          buffer.append(":dn");
3990        }
3991
3992        if (matchingRuleID != null)
3993        {
3994          buffer.append(':');
3995          buffer.append(matchingRuleID);
3996        }
3997
3998        buffer.append(":=");
3999        encodeValue(assertionValue, buffer);
4000        buffer.append(')');
4001        break;
4002    }
4003  }
4004
4005
4006
4007  /**
4008   * Retrieves a normalized string representation of this search filter.
4009   *
4010   * @return  A normalized string representation of this search filter.
4011   */
4012  public String toNormalizedString()
4013  {
4014    if (normalizedString == null)
4015    {
4016      final StringBuilder buffer = new StringBuilder();
4017      toNormalizedString(buffer);
4018      normalizedString = buffer.toString();
4019    }
4020
4021    return normalizedString;
4022  }
4023
4024
4025
4026  /**
4027   * Appends a normalized string representation of this search filter to the
4028   * provided buffer.
4029   *
4030   * @param  buffer  The buffer to which to append a normalized string
4031   *                 representation of this search filter.
4032   */
4033  public void toNormalizedString(final StringBuilder buffer)
4034  {
4035    final CaseIgnoreStringMatchingRule mr =
4036         CaseIgnoreStringMatchingRule.getInstance();
4037
4038    switch (filterType)
4039    {
4040      case FILTER_TYPE_AND:
4041        buffer.append("(&");
4042        for (final Filter f : filterComps)
4043        {
4044          f.toNormalizedString(buffer);
4045        }
4046        buffer.append(')');
4047        break;
4048
4049      case FILTER_TYPE_OR:
4050        buffer.append("(|");
4051        for (final Filter f : filterComps)
4052        {
4053          f.toNormalizedString(buffer);
4054        }
4055        buffer.append(')');
4056        break;
4057
4058      case FILTER_TYPE_NOT:
4059        buffer.append("(!");
4060        notComp.toNormalizedString(buffer);
4061        buffer.append(')');
4062        break;
4063
4064      case FILTER_TYPE_EQUALITY:
4065        buffer.append('(');
4066        buffer.append(toLowerCase(attrName));
4067        buffer.append('=');
4068        encodeValue(mr.normalize(assertionValue), buffer);
4069        buffer.append(')');
4070        break;
4071
4072      case FILTER_TYPE_SUBSTRING:
4073        buffer.append('(');
4074        buffer.append(toLowerCase(attrName));
4075        buffer.append('=');
4076        if (subInitial != null)
4077        {
4078          encodeValue(mr.normalizeSubstring(subInitial,
4079                           MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer);
4080        }
4081        buffer.append('*');
4082        for (final ASN1OctetString s : subAny)
4083        {
4084          encodeValue(mr.normalizeSubstring(s,
4085                           MatchingRule.SUBSTRING_TYPE_SUBANY), buffer);
4086          buffer.append('*');
4087        }
4088        if (subFinal != null)
4089        {
4090          encodeValue(mr.normalizeSubstring(subFinal,
4091                           MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer);
4092        }
4093        buffer.append(')');
4094        break;
4095
4096      case FILTER_TYPE_GREATER_OR_EQUAL:
4097        buffer.append('(');
4098        buffer.append(toLowerCase(attrName));
4099        buffer.append(">=");
4100        encodeValue(mr.normalize(assertionValue), buffer);
4101        buffer.append(')');
4102        break;
4103
4104      case FILTER_TYPE_LESS_OR_EQUAL:
4105        buffer.append('(');
4106        buffer.append(toLowerCase(attrName));
4107        buffer.append("<=");
4108        encodeValue(mr.normalize(assertionValue), buffer);
4109        buffer.append(')');
4110        break;
4111
4112      case FILTER_TYPE_PRESENCE:
4113        buffer.append('(');
4114        buffer.append(toLowerCase(attrName));
4115        buffer.append("=*)");
4116        break;
4117
4118      case FILTER_TYPE_APPROXIMATE_MATCH:
4119        buffer.append('(');
4120        buffer.append(toLowerCase(attrName));
4121        buffer.append("~=");
4122        encodeValue(mr.normalize(assertionValue), buffer);
4123        buffer.append(')');
4124        break;
4125
4126      case FILTER_TYPE_EXTENSIBLE_MATCH:
4127        buffer.append('(');
4128        if (attrName != null)
4129        {
4130          buffer.append(toLowerCase(attrName));
4131        }
4132
4133        if (dnAttributes)
4134        {
4135          buffer.append(":dn");
4136        }
4137
4138        if (matchingRuleID != null)
4139        {
4140          buffer.append(':');
4141          buffer.append(toLowerCase(matchingRuleID));
4142        }
4143
4144        buffer.append(":=");
4145        encodeValue(mr.normalize(assertionValue), buffer);
4146        buffer.append(')');
4147        break;
4148    }
4149  }
4150
4151
4152
4153  /**
4154   * Encodes the provided value into a form suitable for use as the assertion
4155   * value in the string representation of a search filter.  Parentheses,
4156   * asterisks, backslashes, null characters, and any non-ASCII characters will
4157   * be escaped using a backslash before the hexadecimal representation of each
4158   * byte in the character to escape.
4159   *
4160   * @param  value  The value to be encoded.  It must not be {@code null}.
4161   *
4162   * @return  The encoded representation of the provided string.
4163   */
4164  public static String encodeValue(final String value)
4165  {
4166    ensureNotNull(value);
4167
4168    final StringBuilder buffer = new StringBuilder();
4169    encodeValue(new ASN1OctetString(value), buffer);
4170    return buffer.toString();
4171  }
4172
4173
4174
4175  /**
4176   * Encodes the provided value into a form suitable for use as the assertion
4177   * value in the string representation of a search filter.  Parentheses,
4178   * asterisks, backslashes, null characters, and any non-ASCII characters will
4179   * be escaped using a backslash before the hexadecimal representation of each
4180   * byte in the character to escape.
4181   *
4182   * @param  value  The value to be encoded.  It must not be {@code null}.
4183   *
4184   * @return  The encoded representation of the provided string.
4185   */
4186  public static String encodeValue(final byte[]value)
4187  {
4188    ensureNotNull(value);
4189
4190    final StringBuilder buffer = new StringBuilder();
4191    encodeValue(new ASN1OctetString(value), buffer);
4192    return buffer.toString();
4193  }
4194
4195
4196
4197  /**
4198   * Appends the assertion value for this filter to the provided buffer,
4199   * encoding any special characters as necessary.
4200   *
4201   * @param  value   The value to be encoded.
4202   * @param  buffer  The buffer to which the assertion value should be appended.
4203   */
4204  public static void encodeValue(final ASN1OctetString value,
4205                                 final StringBuilder buffer)
4206  {
4207    final byte[] valueBytes = value.getValue();
4208    for (int i=0; i < valueBytes.length; i++)
4209    {
4210      switch (numBytesInUTF8CharacterWithFirstByte(valueBytes[i]))
4211      {
4212        case 1:
4213          // This character is ASCII, but might still need to be escaped.
4214          if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters.
4215              (valueBytes[i] == 0x28) || // Open parenthesis
4216              (valueBytes[i] == 0x29) || // Close parenthesis
4217              (valueBytes[i] == 0x2A) || // Asterisk
4218              (valueBytes[i] == 0x5C) || // Backslash
4219              (valueBytes[i] == 0x7F))   // DEL
4220          {
4221            buffer.append('\\');
4222            toHex(valueBytes[i], buffer);
4223          }
4224          else
4225          {
4226            buffer.append((char) valueBytes[i]);
4227          }
4228          break;
4229
4230        case 2:
4231          // If there are at least two bytes left, then we'll hex-encode the
4232          // next two bytes.  Otherwise we'll hex-encode whatever is left.
4233          buffer.append('\\');
4234          toHex(valueBytes[i++], buffer);
4235          if (i < valueBytes.length)
4236          {
4237            buffer.append('\\');
4238            toHex(valueBytes[i], buffer);
4239          }
4240          break;
4241
4242        case 3:
4243          // If there are at least three bytes left, then we'll hex-encode the
4244          // next three bytes.  Otherwise we'll hex-encode whatever is left.
4245          buffer.append('\\');
4246          toHex(valueBytes[i++], buffer);
4247          if (i < valueBytes.length)
4248          {
4249            buffer.append('\\');
4250            toHex(valueBytes[i++], buffer);
4251          }
4252          if (i < valueBytes.length)
4253          {
4254            buffer.append('\\');
4255            toHex(valueBytes[i], buffer);
4256          }
4257          break;
4258
4259        case 4:
4260          // If there are at least four bytes left, then we'll hex-encode the
4261          // next four bytes.  Otherwise we'll hex-encode whatever is left.
4262          buffer.append('\\');
4263          toHex(valueBytes[i++], buffer);
4264          if (i < valueBytes.length)
4265          {
4266            buffer.append('\\');
4267            toHex(valueBytes[i++], buffer);
4268          }
4269          if (i < valueBytes.length)
4270          {
4271            buffer.append('\\');
4272            toHex(valueBytes[i++], buffer);
4273          }
4274          if (i < valueBytes.length)
4275          {
4276            buffer.append('\\');
4277            toHex(valueBytes[i], buffer);
4278          }
4279          break;
4280
4281        default:
4282          // We'll hex-encode whatever is left in the buffer.
4283          while (i < valueBytes.length)
4284          {
4285            buffer.append('\\');
4286            toHex(valueBytes[i++], buffer);
4287          }
4288          break;
4289      }
4290    }
4291  }
4292
4293
4294
4295  /**
4296   * Appends a number of lines comprising the Java source code that can be used
4297   * to recreate this filter to the given list.  Note that unless a first line
4298   * prefix and/or last line suffix are provided, this will just include the
4299   * code for the static method used to create the filter, starting with
4300   * "Filter.createXFilter(" and ending with the closing parenthesis for that
4301   * method call.
4302   *
4303   * @param  lineList         The list to which the source code lines should be
4304   *                          added.
4305   * @param  indentSpaces     The number of spaces that should be used to indent
4306   *                          the generated code.  It must not be negative.
4307   * @param  firstLinePrefix  An optional string that should precede the static
4308   *                          method call (e.g., it could be used for an
4309   *                          attribute assignment, like "Filter f = ").  It may
4310   *                          be {@code null} or empty if there should be no
4311   *                          first line prefix.
4312   * @param  lastLineSuffix   An optional suffix that should follow the closing
4313   *                          parenthesis of the static method call (e.g., it
4314   *                          could be a semicolon to represent the end of a
4315   *                          Java statement).  It may be {@code null} or empty
4316   *                          if there should be no last line suffix.
4317   */
4318  public void toCode(final List<String> lineList, final int indentSpaces,
4319                     final String firstLinePrefix, final String lastLineSuffix)
4320  {
4321    // Generate a string with the appropriate indent.
4322    final StringBuilder buffer = new StringBuilder();
4323    for (int i = 0; i < indentSpaces; i++)
4324    {
4325      buffer.append(' ');
4326    }
4327    final String indent = buffer.toString();
4328
4329
4330    // Start the first line, including any appropriate prefix.
4331    buffer.setLength(0);
4332    buffer.append(indent);
4333    if (firstLinePrefix != null)
4334    {
4335      buffer.append(firstLinePrefix);
4336    }
4337
4338
4339    // Figure out what type of filter it is and create the appropriate code for
4340    // that type of filter.
4341    switch (filterType)
4342    {
4343      case FILTER_TYPE_AND:
4344      case FILTER_TYPE_OR:
4345        if (filterType == FILTER_TYPE_AND)
4346        {
4347          buffer.append("Filter.createANDFilter(");
4348        }
4349        else
4350        {
4351          buffer.append("Filter.createORFilter(");
4352        }
4353        if (filterComps.length == 0)
4354        {
4355          buffer.append(')');
4356          if (lastLineSuffix != null)
4357          {
4358            buffer.append(lastLineSuffix);
4359          }
4360          lineList.add(buffer.toString());
4361          return;
4362        }
4363
4364        for (int i = 0; i < filterComps.length; i++)
4365        {
4366          String suffix;
4367          if (i == (filterComps.length - 1))
4368          {
4369            suffix = ")";
4370            if (lastLineSuffix != null)
4371            {
4372              suffix += lastLineSuffix;
4373            }
4374          }
4375          else
4376          {
4377            suffix = ",";
4378          }
4379
4380          filterComps[i].toCode(lineList, indentSpaces + 5, null, suffix);
4381        }
4382        return;
4383
4384
4385      case FILTER_TYPE_NOT:
4386        buffer.append("Filter.createNOTFilter(");
4387        lineList.add(buffer.toString());
4388
4389        final String suffix;
4390        if (lastLineSuffix == null)
4391        {
4392          suffix = ")";
4393        }
4394        else
4395        {
4396          suffix = ')' + lastLineSuffix;
4397        }
4398        notComp.toCode(lineList, indentSpaces + 5, null, suffix);
4399        return;
4400
4401      case FILTER_TYPE_PRESENCE:
4402        buffer.append("Filter.createPresenceFilter(");
4403        lineList.add(buffer.toString());
4404
4405        buffer.setLength(0);
4406        buffer.append(indent);
4407        buffer.append("     \"");
4408        buffer.append(attrName);
4409        buffer.append("\")");
4410
4411        if (lastLineSuffix != null)
4412        {
4413          buffer.append(lastLineSuffix);
4414        }
4415
4416        lineList.add(buffer.toString());
4417        return;
4418
4419
4420      case FILTER_TYPE_EQUALITY:
4421      case FILTER_TYPE_GREATER_OR_EQUAL:
4422      case FILTER_TYPE_LESS_OR_EQUAL:
4423      case FILTER_TYPE_APPROXIMATE_MATCH:
4424        if (filterType == FILTER_TYPE_EQUALITY)
4425        {
4426          buffer.append("Filter.createEqualityFilter(");
4427        }
4428        else if (filterType == FILTER_TYPE_GREATER_OR_EQUAL)
4429        {
4430          buffer.append("Filter.createGreaterOrEqualFilter(");
4431        }
4432        else if (filterType == FILTER_TYPE_LESS_OR_EQUAL)
4433        {
4434          buffer.append("Filter.createLessOrEqualFilter(");
4435        }
4436        else
4437        {
4438          buffer.append("Filter.createApproximateMatchFilter(");
4439        }
4440        lineList.add(buffer.toString());
4441
4442        buffer.setLength(0);
4443        buffer.append(indent);
4444        buffer.append("     \"");
4445        buffer.append(attrName);
4446        buffer.append("\",");
4447        lineList.add(buffer.toString());
4448
4449        buffer.setLength(0);
4450        buffer.append(indent);
4451        buffer.append("     ");
4452        if (isSensitiveToCodeAttribute(attrName))
4453        {
4454          buffer.append("\"---redacted-value---\"");
4455        }
4456        else if (isPrintableString(assertionValue.getValue()))
4457        {
4458          buffer.append('"');
4459          buffer.append(assertionValue.stringValue());
4460          buffer.append('"');
4461        }
4462        else
4463        {
4464          byteArrayToCode(assertionValue.getValue(), buffer);
4465        }
4466
4467        buffer.append(')');
4468
4469        if (lastLineSuffix != null)
4470        {
4471          buffer.append(lastLineSuffix);
4472        }
4473
4474        lineList.add(buffer.toString());
4475        return;
4476
4477
4478      case FILTER_TYPE_SUBSTRING:
4479        buffer.append("Filter.createSubstringFilter(");
4480        lineList.add(buffer.toString());
4481
4482        buffer.setLength(0);
4483        buffer.append(indent);
4484        buffer.append("     \"");
4485        buffer.append(attrName);
4486        buffer.append("\",");
4487        lineList.add(buffer.toString());
4488
4489        final boolean isRedacted = isSensitiveToCodeAttribute(attrName);
4490        boolean isPrintable = true;
4491        if (subInitial != null)
4492        {
4493          isPrintable = isPrintableString(subInitial.getValue());
4494        }
4495
4496        if (isPrintable && (subAny != null))
4497        {
4498          for (final ASN1OctetString s : subAny)
4499          {
4500            if (! isPrintableString(s.getValue()))
4501            {
4502              isPrintable = false;
4503              break;
4504            }
4505          }
4506        }
4507
4508        if (isPrintable && (subFinal != null))
4509        {
4510          isPrintable = isPrintableString(subFinal.getValue());
4511        }
4512
4513        buffer.setLength(0);
4514        buffer.append(indent);
4515        buffer.append("     ");
4516        if (subInitial == null)
4517        {
4518          buffer.append("null");
4519        }
4520        else if (isRedacted)
4521        {
4522          buffer.append("\"---redacted-subInitial---\"");
4523        }
4524        else if (isPrintable)
4525        {
4526          buffer.append('"');
4527          buffer.append(subInitial.stringValue());
4528          buffer.append('"');
4529        }
4530        else
4531        {
4532          byteArrayToCode(subInitial.getValue(), buffer);
4533        }
4534        buffer.append(',');
4535        lineList.add(buffer.toString());
4536
4537        buffer.setLength(0);
4538        buffer.append(indent);
4539        buffer.append("     ");
4540        if ((subAny == null) || (subAny.length == 0))
4541        {
4542          buffer.append("null,");
4543          lineList.add(buffer.toString());
4544        }
4545        else if (isRedacted)
4546        {
4547          buffer.append("new String[]");
4548          lineList.add(buffer.toString());
4549
4550          lineList.add(indent + "     {");
4551
4552          for (int i=0; i < subAny.length; i++)
4553          {
4554            buffer.setLength(0);
4555            buffer.append(indent);
4556            buffer.append("       \"---redacted-subAny-");
4557            buffer.append(i+1);
4558            buffer.append("---\"");
4559            if (i < (subAny.length-1))
4560            {
4561              buffer.append(',');
4562            }
4563            lineList.add(buffer.toString());
4564          }
4565
4566          lineList.add(indent + "     },");
4567        }
4568        else if (isPrintable)
4569        {
4570          buffer.append("new String[]");
4571          lineList.add(buffer.toString());
4572
4573          lineList.add(indent + "     {");
4574
4575          for (int i=0; i < subAny.length; i++)
4576          {
4577            buffer.setLength(0);
4578            buffer.append(indent);
4579            buffer.append("       \"");
4580            buffer.append(subAny[i].stringValue());
4581            buffer.append('"');
4582            if (i < (subAny.length-1))
4583            {
4584              buffer.append(',');
4585            }
4586            lineList.add(buffer.toString());
4587          }
4588
4589          lineList.add(indent + "     },");
4590        }
4591        else
4592        {
4593          buffer.append("new String[]");
4594          lineList.add(buffer.toString());
4595
4596          lineList.add(indent + "     {");
4597
4598          for (int i=0; i < subAny.length; i++)
4599          {
4600            buffer.setLength(0);
4601            buffer.append(indent);
4602            buffer.append("       ");
4603            byteArrayToCode(subAny[i].getValue(), buffer);
4604            if (i < (subAny.length-1))
4605            {
4606              buffer.append(',');
4607            }
4608            lineList.add(buffer.toString());
4609          }
4610
4611          lineList.add(indent + "     },");
4612        }
4613
4614        buffer.setLength(0);
4615        buffer.append(indent);
4616        buffer.append("     ");
4617        if (subFinal == null)
4618        {
4619          buffer.append("null)");
4620        }
4621        else if (isRedacted)
4622        {
4623          buffer.append("\"---redacted-subFinal---\")");
4624        }
4625        else if (isPrintable)
4626        {
4627          buffer.append('"');
4628          buffer.append(subFinal.stringValue());
4629          buffer.append("\")");
4630        }
4631        else
4632        {
4633          byteArrayToCode(subFinal.getValue(), buffer);
4634          buffer.append(')');
4635        }
4636        if (lastLineSuffix != null)
4637        {
4638          buffer.append(lastLineSuffix);
4639        }
4640        lineList.add(buffer.toString());
4641        return;
4642
4643
4644      case FILTER_TYPE_EXTENSIBLE_MATCH:
4645        buffer.append("Filter.createExtensibleMatchFilter(");
4646        lineList.add(buffer.toString());
4647
4648        buffer.setLength(0);
4649        buffer.append(indent);
4650        buffer.append("     ");
4651        if (attrName == null)
4652        {
4653          buffer.append("null, // Attribute Description");
4654        }
4655        else
4656        {
4657          buffer.append('"');
4658          buffer.append(attrName);
4659          buffer.append("\",");
4660        }
4661        lineList.add(buffer.toString());
4662
4663        buffer.setLength(0);
4664        buffer.append(indent);
4665        buffer.append("     ");
4666        if (matchingRuleID == null)
4667        {
4668          buffer.append("null, // Matching Rule ID");
4669        }
4670        else
4671        {
4672          buffer.append('"');
4673          buffer.append(matchingRuleID);
4674          buffer.append("\",");
4675        }
4676        lineList.add(buffer.toString());
4677
4678        buffer.setLength(0);
4679        buffer.append(indent);
4680        buffer.append("     ");
4681        buffer.append(dnAttributes);
4682        buffer.append(", // DN Attributes");
4683        lineList.add(buffer.toString());
4684
4685        buffer.setLength(0);
4686        buffer.append(indent);
4687        buffer.append("     ");
4688        if ((attrName != null) && isSensitiveToCodeAttribute(attrName))
4689        {
4690          buffer.append("\"---redacted-value---\")");
4691        }
4692        else
4693        {
4694          if (isPrintableString(assertionValue.getValue()))
4695          {
4696            buffer.append('"');
4697            buffer.append(assertionValue.stringValue());
4698            buffer.append("\")");
4699          }
4700          else
4701          {
4702            byteArrayToCode(assertionValue.getValue(), buffer);
4703            buffer.append(')');
4704          }
4705        }
4706
4707        if (lastLineSuffix != null)
4708        {
4709          buffer.append(lastLineSuffix);
4710        }
4711        lineList.add(buffer.toString());
4712        return;
4713    }
4714  }
4715}