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