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.Comparator;
043import java.util.List;
044
045import com.unboundid.asn1.ASN1OctetString;
046import com.unboundid.ldap.sdk.schema.Schema;
047import com.unboundid.util.Debug;
048import com.unboundid.util.NotMutable;
049import com.unboundid.util.StaticUtils;
050import com.unboundid.util.ThreadSafety;
051import com.unboundid.util.ThreadSafetyLevel;
052import com.unboundid.util.Validator;
053
054import static com.unboundid.ldap.sdk.LDAPMessages.*;
055
056
057
058/**
059 * This class provides a data structure for holding information about an LDAP
060 * distinguished name (DN).  A DN consists of a comma-delimited list of zero or
061 * more RDN components.  See
062 * <A HREF="http://www.ietf.org/rfc/rfc4514.txt">RFC 4514</A> for more
063 * information about representing DNs and RDNs as strings.
064 * <BR><BR>
065 * Examples of valid DNs (excluding the quotation marks, which are provided for
066 * clarity) include:
067 * <UL>
068 *   <LI>"" -- This is the zero-length DN (also called the null DN), which may
069 *       be used to refer to the directory server root DSE.</LI>
070 *   <LI>"{@code o=example.com}".  This is a DN with a single, single-valued
071 *       RDN.  The RDN attribute is "{@code o}" and the RDN value is
072 *       "{@code example.com}".</LI>
073 *   <LI>"{@code givenName=John+sn=Doe,ou=People,dc=example,dc=com}".  This is a
074 *       DN with four different RDNs ("{@code givenName=John+sn=Doe"},
075 *       "{@code ou=People}", "{@code dc=example}", and "{@code dc=com}".  The
076 *       first RDN is multivalued with attribute-value pairs of
077 *       "{@code givenName=John}" and "{@code sn=Doe}".</LI>
078 * </UL>
079 * Note that there is some inherent ambiguity in the string representations of
080 * distinguished names.  In particular, there may be differences in spacing
081 * (particularly around commas and equal signs, as well as plus signs in
082 * multivalued RDNs), and also differences in capitalization in attribute names
083 * and/or values.  For example, the strings
084 * "{@code uid=john.doe,ou=people,dc=example,dc=com}" and
085 * "{@code UID = JOHN.DOE , OU = PEOPLE , DC = EXAMPLE , DC = COM}" actually
086 * refer to the same distinguished name.  To deal with these differences, the
087 * normalized representation may be used.  The normalized representation is a
088 * standardized way of representing a DN, and it is obtained by eliminating any
089 * unnecessary spaces and converting all non-case-sensitive characters to
090 * lowercase.  The normalized representation of a DN may be obtained using the
091 * {@link DN#toNormalizedString} method, and two DNs may be compared to
092 * determine if they are equal using the standard {@link DN#equals} method.
093 * <BR><BR>
094 * Distinguished names are hierarchical.  The rightmost RDN refers to the root
095 * of the directory information tree (DIT), and each successive RDN to the left
096 * indicates the addition of another level of hierarchy.  For example, in the
097 * DN "{@code uid=john.doe,ou=People,o=example.com}", the entry
098 * "{@code o=example.com}" is at the root of the DIT, the entry
099 * "{@code ou=People,o=example.com}" is an immediate descendant of the
100 * "{@code o=example.com}" entry, and the
101 * "{@code uid=john.doe,ou=People,o=example.com}" entry is an immediate
102 * descendant of the "{@code ou=People,o=example.com}" entry.  Similarly, the
103 * entry "{@code uid=jane.doe,ou=People,o=example.com}" would be considered a
104 * peer of the "{@code uid=john.doe,ou=People,o=example.com}" entry because they
105 * have the same parent.
106 * <BR><BR>
107 * Note that in some cases, the root of the DIT may actually contain a DN with
108 * multiple RDNs.  For example, in the DN
109 * "{@code uid=john.doe,ou=People,dc=example,dc=com}", the directory server may
110 * or may not actually have a "{@code dc=com}" entry.  In many such cases, the
111 * base entry may actually be just "{@code dc=example,dc=com}".  The DNs of the
112 * entries that are at the base of the directory information tree are called
113 * "naming contexts" or "suffixes" and they are generally available in the
114 * {@code namingContexts} attribute of the root DSE.  See the {@link RootDSE}
115 * class for more information about interacting with the server root DSE.
116 * <BR><BR>
117 * This class provides methods for making determinations based on the
118 * hierarchical relationships of DNs.  For example, the
119 * {@link DN#isAncestorOf} and {@link DN#isDescendantOf} methods may be used to
120 * determine whether two DNs have a hierarchical relationship.  In addition,
121 * this class implements the {@link Comparable} and {@link Comparator}
122 * interfaces so that it may be used to easily sort DNs (ancestors will always
123 * be sorted before descendants, and peers will always be sorted
124 * lexicographically based on their normalized representations).
125 */
126@NotMutable()
127@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
128public final class DN
129       implements Comparable<DN>, Comparator<DN>, Serializable
130{
131  /**
132   * The RDN array that will be used for the null DN.
133   */
134  private static final RDN[] NO_RDNS = new RDN[0];
135
136
137
138  /**
139   * A pre-allocated DN object equivalent to the null DN.
140   */
141  public static final DN NULL_DN = new DN();
142
143
144
145  /**
146   * The serial version UID for this serializable class.
147   */
148  private static final long serialVersionUID = -5272968942085729346L;
149
150
151
152  // The set of RDN components that make up this DN.
153  private final RDN[] rdns;
154
155  // The schema to use to generate the normalized string representation of this
156  // DN, if any.
157  private final Schema schema;
158
159  // The string representation of this DN.
160  private final String dnString;
161
162  // The normalized string representation of this DN.
163  private volatile String normalizedString;
164
165
166
167  /**
168   * Creates a new DN with the provided set of RDNs.
169   *
170   * @param  rdns  The RDN components for this DN.  It must not be {@code null}.
171   */
172  public DN(final RDN... rdns)
173  {
174    Validator.ensureNotNull(rdns);
175
176    this.rdns = rdns;
177    if (rdns.length == 0)
178    {
179      dnString         = "";
180      normalizedString = "";
181      schema           = null;
182    }
183    else
184    {
185      Schema s = null;
186      final StringBuilder buffer = new StringBuilder();
187      for (final RDN rdn : rdns)
188      {
189        if (buffer.length() > 0)
190        {
191          buffer.append(',');
192        }
193        rdn.toString(buffer, false);
194
195        if (s == null)
196        {
197          s = rdn.getSchema();
198        }
199      }
200
201      dnString = buffer.toString();
202      schema   = s;
203    }
204  }
205
206
207
208  /**
209   * Creates a new DN with the provided set of RDNs.
210   *
211   * @param  rdns  The RDN components for this DN.  It must not be {@code null}.
212   */
213  public DN(final List<RDN> rdns)
214  {
215    Validator.ensureNotNull(rdns);
216
217    if (rdns.isEmpty())
218    {
219      this.rdns        = NO_RDNS;
220      dnString         = "";
221      normalizedString = "";
222      schema           = null;
223    }
224    else
225    {
226      this.rdns = rdns.toArray(new RDN[rdns.size()]);
227
228      Schema s = null;
229      final StringBuilder buffer = new StringBuilder();
230      for (final RDN rdn : this.rdns)
231      {
232        if (buffer.length() > 0)
233        {
234          buffer.append(',');
235        }
236        rdn.toString(buffer, false);
237
238        if (s == null)
239        {
240          s = rdn.getSchema();
241        }
242      }
243
244      dnString = buffer.toString();
245      schema   = s;
246    }
247  }
248
249
250
251  /**
252   * Creates a new DN below the provided parent DN with the given RDN.
253   *
254   * @param  rdn       The RDN for the new DN.  It must not be {@code null}.
255   * @param  parentDN  The parent DN for the new DN to create.  It must not be
256   *                   {@code null}.
257   */
258  public DN(final RDN rdn, final DN parentDN)
259  {
260    Validator.ensureNotNull(rdn, parentDN);
261
262    rdns = new RDN[parentDN.rdns.length + 1];
263    rdns[0] = rdn;
264    System.arraycopy(parentDN.rdns, 0, rdns, 1, parentDN.rdns.length);
265
266    Schema s = null;
267    final StringBuilder buffer = new StringBuilder();
268    for (final RDN r : rdns)
269    {
270      if (buffer.length() > 0)
271      {
272        buffer.append(',');
273      }
274      r.toString(buffer, false);
275
276      if (s == null)
277      {
278        s = r.getSchema();
279      }
280    }
281
282    dnString = buffer.toString();
283    schema   = s;
284  }
285
286
287
288  /**
289   * Creates a new DN from the provided string representation.
290   *
291   * @param  dnString  The string representation to use to create this DN.  It
292   *                   must not be {@code null}.
293   *
294   * @throws  LDAPException  If the provided string cannot be parsed as a valid
295   *                         DN.
296   */
297  public DN(final String dnString)
298         throws LDAPException
299  {
300    this(dnString, null, false);
301  }
302
303
304
305  /**
306   * Creates a new DN from the provided string representation.
307   *
308   * @param  dnString  The string representation to use to create this DN.  It
309   *                   must not be {@code null}.
310   * @param  schema    The schema to use to generate the normalized string
311   *                   representation of this DN.  It may be {@code null} if no
312   *                   schema is available.
313   *
314   * @throws  LDAPException  If the provided string cannot be parsed as a valid
315   *                         DN.
316   */
317  public DN(final String dnString, final Schema schema)
318         throws LDAPException
319  {
320    this(dnString, schema, false);
321  }
322
323
324
325  /**
326   * Creates a new DN from the provided string representation.
327   *
328   * @param  dnString            The string representation to use to create this
329   *                             DN.  It must not be {@code null}.
330   * @param  schema              The schema to use to generate the normalized
331   *                             string representation of this DN.  It may be
332   *                             {@code null} if no schema is available.
333   * @param  strictNameChecking  Indicates whether to verify that all attribute
334   *                             type names are valid as per RFC 4514.  If this
335   *                             is {@code false}, then some technically invalid
336   *                             characters may be accepted in attribute type
337   *                             names.  If this is {@code true}, then names
338   *                             must be strictly compliant.
339   *
340   * @throws  LDAPException  If the provided string cannot be parsed as a valid
341   *                         DN.
342   */
343  public DN(final String dnString, final Schema schema,
344            final boolean strictNameChecking)
345         throws LDAPException
346  {
347    Validator.ensureNotNull(dnString);
348
349    this.dnString = dnString;
350    this.schema   = schema;
351
352    final ArrayList<RDN> rdnList = new ArrayList<>(5);
353
354    final int length = dnString.length();
355    if (length == 0)
356    {
357      rdns             = NO_RDNS;
358      normalizedString = "";
359      return;
360    }
361
362    int pos = 0;
363    boolean expectMore = false;
364rdnLoop:
365    while (pos < length)
366    {
367      // Skip over any spaces before the attribute name.
368      while ((pos < length) && (dnString.charAt(pos) == ' '))
369      {
370        pos++;
371      }
372
373      if (pos >= length)
374      {
375        // This is only acceptable if we haven't read anything yet.
376        if (rdnList.isEmpty())
377        {
378          break;
379        }
380        else
381        {
382          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
383               ERR_DN_ENDS_WITH_COMMA.get(dnString));
384        }
385      }
386
387      // Read the attribute name, until we find a space or equal sign.
388      int rdnEndPos;
389      int attrStartPos = pos;
390      final int rdnStartPos = pos;
391      while (pos < length)
392      {
393        final char c = dnString.charAt(pos);
394        if ((c == ' ') || (c == '='))
395        {
396          break;
397        }
398        else if ((c == ',') || (c == ';'))
399        {
400          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
401               ERR_DN_UNEXPECTED_COMMA.get(dnString, pos));
402        }
403
404        pos++;
405      }
406
407      String attrName = dnString.substring(attrStartPos, pos);
408      if (attrName.isEmpty())
409      {
410        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
411             ERR_DN_NO_ATTR_IN_RDN.get(dnString));
412      }
413
414      if (strictNameChecking)
415      {
416        if (! (Attribute.nameIsValid(attrName) ||
417             StaticUtils.isNumericOID(attrName)))
418        {
419          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
420               ERR_DN_INVALID_ATTR_NAME.get(dnString, attrName));
421        }
422      }
423
424
425      // Skip over any spaces before the equal sign.
426      while ((pos < length) && (dnString.charAt(pos) == ' '))
427      {
428        pos++;
429      }
430
431      if ((pos >= length) || (dnString.charAt(pos) != '='))
432      {
433        // We didn't find an equal sign.
434        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
435             ERR_DN_NO_EQUAL_SIGN.get(dnString, attrName));
436      }
437
438      // Skip over the equal sign, and then any spaces leading up to the
439      // attribute value.
440      pos++;
441      while ((pos < length) && (dnString.charAt(pos) == ' '))
442      {
443        pos++;
444      }
445
446
447      // Read the value for this RDN component.
448      ASN1OctetString value;
449      if (pos >= length)
450      {
451        value = new ASN1OctetString();
452        rdnEndPos = pos;
453      }
454      else if (dnString.charAt(pos) == '#')
455      {
456        // It is a hex-encoded value, so we'll read until we find the end of the
457        // string or the first non-hex character, which must be a space, a
458        // comma, or a plus sign.  Then, parse the bytes of the hex-encoded
459        // value as a BER element, and take the value of that element.
460        final byte[] valueArray = RDN.readHexString(dnString, ++pos);
461
462        try
463        {
464          value = ASN1OctetString.decodeAsOctetString(valueArray);
465        }
466        catch (final Exception e)
467        {
468          Debug.debugException(e);
469          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
470               ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(dnString, attrName), e);
471        }
472
473        pos += (valueArray.length * 2);
474        rdnEndPos = pos;
475      }
476      else
477      {
478        // It is a string value, which potentially includes escaped characters.
479        final StringBuilder buffer = new StringBuilder();
480        pos = RDN.readValueString(dnString, pos, buffer);
481        value = new ASN1OctetString(buffer.toString());
482        rdnEndPos = pos;
483      }
484
485
486      // Skip over any spaces until we find a comma, a plus sign, or the end of
487      // the value.
488      while ((pos < length) && (dnString.charAt(pos) == ' '))
489      {
490        pos++;
491      }
492
493      if (pos >= length)
494      {
495        // It's a single-valued RDN, and we're at the end of the DN.
496        rdnList.add(new RDN(attrName, value, schema,
497             getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
498        expectMore = false;
499        break;
500      }
501
502      switch (dnString.charAt(pos))
503      {
504        case '+':
505          // It is a multivalued RDN, so we're not done reading either the DN
506          // or the RDN.
507          pos++;
508          break;
509
510        case ',':
511        case ';':
512          // We hit the end of the single-valued RDN, but there's still more of
513          // the DN to be read.
514          rdnList.add(new RDN(attrName, value, schema,
515               getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
516          pos++;
517          expectMore = true;
518          continue rdnLoop;
519
520        default:
521          // It's an illegal character.  This should never happen.
522          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
523               ERR_DN_UNEXPECTED_CHAR.get(dnString, dnString.charAt(pos), pos));
524      }
525
526      if (pos >= length)
527      {
528        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
529             ERR_DN_ENDS_WITH_PLUS.get(dnString));
530      }
531
532
533      // If we've gotten here, then we're dealing with a multivalued RDN.
534      // Create lists to hold the names and values, and then loop until we hit
535      // the end of the RDN.
536      final ArrayList<String> nameList = new ArrayList<>(5);
537      final ArrayList<ASN1OctetString> valueList = new ArrayList<>(5);
538      nameList.add(attrName);
539      valueList.add(value);
540
541      while (pos < length)
542      {
543        // Skip over any spaces before the attribute name.
544        while ((pos < length) && (dnString.charAt(pos) == ' '))
545        {
546          pos++;
547        }
548
549        if (pos >= length)
550        {
551          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
552               ERR_DN_ENDS_WITH_PLUS.get(dnString));
553        }
554
555        // Read the attribute name, until we find a space or equal sign.
556        attrStartPos = pos;
557        while (pos < length)
558        {
559          final char c = dnString.charAt(pos);
560          if ((c == ' ') || (c == '='))
561          {
562            break;
563          }
564          else if ((c == ',') || (c == ';'))
565          {
566            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
567                 ERR_DN_UNEXPECTED_COMMA.get(dnString, pos));
568          }
569
570          pos++;
571        }
572
573        attrName = dnString.substring(attrStartPos, pos);
574        if (attrName.isEmpty())
575        {
576          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
577               ERR_DN_NO_ATTR_IN_RDN.get(dnString));
578        }
579
580        if (strictNameChecking)
581        {
582          if (! (Attribute.nameIsValid(attrName) ||
583               StaticUtils.isNumericOID(attrName)))
584          {
585            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
586                 ERR_DN_INVALID_ATTR_NAME.get(dnString, attrName));
587          }
588        }
589
590
591        // Skip over any spaces before the equal sign.
592        while ((pos < length) && (dnString.charAt(pos) == ' '))
593        {
594          pos++;
595        }
596
597        if ((pos >= length) || (dnString.charAt(pos) != '='))
598        {
599          // We didn't find an equal sign.
600          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
601               ERR_DN_NO_EQUAL_SIGN.get(dnString, attrName));
602        }
603
604        // Skip over the equal sign, and then any spaces leading up to the
605        // attribute value.
606        pos++;
607        while ((pos < length) && (dnString.charAt(pos) == ' '))
608        {
609          pos++;
610        }
611
612
613        // Read the value for this RDN component.
614        if (pos >= length)
615        {
616          value = new ASN1OctetString();
617          rdnEndPos = pos;
618        }
619        else if (dnString.charAt(pos) == '#')
620        {
621          // It is a hex-encoded value, so we'll read until we find the end of
622          // the string or the first non-hex character, which must be a space, a
623          // comma, or a plus sign.  Then, parse the bytes of the hex-encoded
624          // value as a BER element, and take the value of that element.
625          final byte[] valueArray = RDN.readHexString(dnString, ++pos);
626
627          try
628          {
629            value = ASN1OctetString.decodeAsOctetString(valueArray);
630          }
631          catch (final Exception e)
632          {
633            Debug.debugException(e);
634            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
635                 ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(dnString, attrName), e);
636          }
637
638          pos += (valueArray.length * 2);
639          rdnEndPos = pos;
640        }
641        else
642        {
643          // It is a string value, which potentially includes escaped
644          // characters.
645          final StringBuilder buffer = new StringBuilder();
646          pos = RDN.readValueString(dnString, pos, buffer);
647          value = new ASN1OctetString(buffer.toString());
648          rdnEndPos = pos;
649        }
650
651
652        // Skip over any spaces until we find a comma, a plus sign, or the end
653        // of the value.
654        while ((pos < length) && (dnString.charAt(pos) == ' '))
655        {
656          pos++;
657        }
658
659        nameList.add(attrName);
660        valueList.add(value);
661
662        if (pos >= length)
663        {
664          // We've hit the end of the RDN and the end of the DN.
665          final String[] names = nameList.toArray(new String[nameList.size()]);
666          final ASN1OctetString[] values =
667               valueList.toArray(new ASN1OctetString[valueList.size()]);
668          rdnList.add(new RDN(names, values, schema,
669               getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
670          expectMore = false;
671          break rdnLoop;
672        }
673
674        switch (dnString.charAt(pos))
675        {
676          case '+':
677            // There are still more RDN components to be read, so we're not done
678            // yet.
679            pos++;
680
681            if (pos >= length)
682            {
683              throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
684                   ERR_DN_ENDS_WITH_PLUS.get(dnString));
685            }
686            break;
687
688          case ',':
689          case ';':
690            // We've hit the end of the RDN, but there is still more of the DN
691            // to be read.
692            final String[] names =
693                 nameList.toArray(new String[nameList.size()]);
694            final ASN1OctetString[] values =
695                 valueList.toArray(new ASN1OctetString[valueList.size()]);
696            rdnList.add(new RDN(names, values, schema,
697                 getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
698            pos++;
699            expectMore = true;
700            continue rdnLoop;
701
702          default:
703            // It's an illegal character.  This should never happen.
704            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
705                 ERR_DN_UNEXPECTED_CHAR.get(dnString, dnString.charAt(pos),
706                      pos));
707        }
708      }
709    }
710
711    // If we are expecting more information to be provided, then it means that
712    // the string ended with a comma or semicolon.
713    if (expectMore)
714    {
715      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
716                              ERR_DN_ENDS_WITH_COMMA.get(dnString));
717    }
718
719    // At this point, we should have all of the RDNs to use to create this DN.
720    rdns = new RDN[rdnList.size()];
721    rdnList.toArray(rdns);
722  }
723
724
725
726  /**
727   * Retrieves a trimmed version of the string representation of the RDN in the
728   * specified portion of the provided DN string.  Only non-escaped trailing
729   * spaces will be removed.
730   *
731   * @param  dnString  The string representation of the DN from which to extract
732   *                   the string representation of the RDN.
733   * @param  start     The position of the first character in the RDN.
734   * @param  end       The position marking the end of the RDN.
735   *
736   * @return  A properly-trimmed string representation of the RDN.
737   */
738  private static String getTrimmedRDN(final String dnString, final int start,
739                                      final int end)
740  {
741    final String rdnString = dnString.substring(start, end);
742    if (! rdnString.endsWith(" "))
743    {
744      return rdnString;
745    }
746
747    final StringBuilder buffer = new StringBuilder(rdnString);
748    while ((buffer.charAt(buffer.length() - 1) == ' ') &&
749           (buffer.charAt(buffer.length() - 2) != '\\'))
750    {
751      buffer.setLength(buffer.length() - 1);
752    }
753
754    return buffer.toString();
755  }
756
757
758
759  /**
760   * Indicates whether the provided string represents a valid DN.
761   *
762   * @param  s  The string for which to make the determination.  It must not be
763   *            {@code null}.
764   *
765   * @return  {@code true} if the provided string represents a valid DN, or
766   *          {@code false} if not.
767   */
768  public static boolean isValidDN(final String s)
769  {
770    return isValidDN(s, false);
771  }
772
773
774
775  /**
776   * Indicates whether the provided string represents a valid DN.
777   *
778   * @param  s                   The string for which to make the determination.
779   *                             It must not be {@code null}.
780   * @param  strictNameChecking  Indicates whether to verify that all attribute
781   *                             type names are valid as per RFC 4514.  If this
782   *                             is {@code false}, then some technically invalid
783   *                             characters may be accepted in attribute type
784   *                             names.  If this is {@code true}, then names
785   *                             must be strictly compliant.
786   *
787   * @return  {@code true} if the provided string represents a valid DN, or
788   *          {@code false} if not.
789   */
790  public static boolean isValidDN(final String s,
791                                  final boolean strictNameChecking)
792  {
793    try
794    {
795      new DN(s, null, strictNameChecking);
796      return true;
797    }
798    catch (final LDAPException le)
799    {
800      Debug.debugException(le);
801      return false;
802    }
803  }
804
805
806
807  /**
808   * Retrieves the leftmost (i.e., furthest from the naming context) RDN
809   * component for this DN.
810   *
811   * @return  The leftmost RDN component for this DN, or {@code null} if this DN
812   *          does not have any RDNs (i.e., it is the null DN).
813   */
814  public RDN getRDN()
815  {
816    if (rdns.length == 0)
817    {
818      return null;
819    }
820    else
821    {
822      return rdns[0];
823    }
824  }
825
826
827
828  /**
829   * Retrieves the string representation of the leftmost (i.e., furthest from
830   * the naming context) RDN component for this DN.
831   *
832   * @return  The string representation of the leftmost RDN component for this
833   *          DN, or {@code null} if this DN does not have any RDNs (i.e., it is
834   *          the null DN).
835   */
836  public String getRDNString()
837  {
838    if (rdns.length == 0)
839    {
840      return null;
841    }
842    else
843    {
844      return rdns[0].toString();
845    }
846  }
847
848
849
850  /**
851   * Retrieves the string representation of the leftmost (i.e., furthest from
852   * the naming context) RDN component for the DN with the provided string
853   * representation.
854   *
855   * @param  s  The string representation of the DN to process.  It must not be
856   *            {@code null}.
857   *
858   * @return  The string representation of the leftmost RDN component for this
859   *          DN, or {@code null} if this DN does not have any RDNs (i.e., it is
860   *          the null DN).
861   *
862   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
863   */
864  public static String getRDNString(final String s)
865         throws LDAPException
866  {
867    return new DN(s).getRDNString();
868  }
869
870
871
872  /**
873   * Retrieves the set of RDNs that comprise this DN.
874   *
875   * @return  The set of RDNs that comprise this DN.
876   */
877  public RDN[] getRDNs()
878  {
879    return rdns;
880  }
881
882
883
884  /**
885   * Retrieves the set of RDNs that comprise the DN with the provided string
886   * representation.
887   *
888   * @param  s  The string representation of the DN for which to retrieve the
889   *            RDNs.  It must not be {@code null}.
890   *
891   * @return  The set of RDNs that comprise the DN with the provided string
892   *          representation.
893   *
894   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
895   */
896  public static RDN[] getRDNs(final String s)
897         throws LDAPException
898  {
899    return new DN(s).getRDNs();
900  }
901
902
903
904  /**
905   * Retrieves the set of string representations of the RDNs that comprise this
906   * DN.
907   *
908   * @return  The set of string representations of the RDNs that comprise this
909   *          DN.
910   */
911  public String[] getRDNStrings()
912  {
913    final String[] rdnStrings = new String[rdns.length];
914    for (int i=0; i < rdns.length; i++)
915    {
916      rdnStrings[i] = rdns[i].toString();
917    }
918    return rdnStrings;
919  }
920
921
922
923  /**
924   * Retrieves the set of string representations of the RDNs that comprise this
925   * DN.
926   *
927   * @param  s  The string representation of the DN for which to retrieve the
928   *            RDN strings.  It must not be {@code null}.
929   *
930   * @return  The set of string representations of the RDNs that comprise this
931   *          DN.
932   *
933   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
934   */
935  public static String[] getRDNStrings(final String s)
936         throws LDAPException
937  {
938    return new DN(s).getRDNStrings();
939  }
940
941
942
943  /**
944   * Indicates whether this DN represents the null DN, which does not have any
945   * RDN components.
946   *
947   * @return  {@code true} if this DN represents the null DN, or {@code false}
948   *          if not.
949   */
950  public boolean isNullDN()
951  {
952    return (rdns.length == 0);
953  }
954
955
956
957  /**
958   * Retrieves the DN that is the parent for this DN.  Note that neither the
959   * null DN nor DNs consisting of a single RDN component will be considered to
960   * have parent DNs.
961   *
962   * @return  The DN that is the parent for this DN, or {@code null} if there
963   *          is no parent.
964   */
965  public DN getParent()
966  {
967    switch (rdns.length)
968    {
969      case 0:
970      case 1:
971        return null;
972
973      case 2:
974        return new DN(rdns[1]);
975
976      case 3:
977        return new DN(rdns[1], rdns[2]);
978
979      case 4:
980        return new DN(rdns[1], rdns[2], rdns[3]);
981
982      case 5:
983        return new DN(rdns[1], rdns[2], rdns[3], rdns[4]);
984
985      default:
986        final RDN[] parentRDNs = new RDN[rdns.length - 1];
987        System.arraycopy(rdns, 1, parentRDNs, 0, parentRDNs.length);
988        return new DN(parentRDNs);
989    }
990  }
991
992
993
994  /**
995   * Retrieves the DN that is the parent for the DN with the provided string
996   * representation.  Note that neither the null DN nor DNs consisting of a
997   * single RDN component will be considered to have parent DNs.
998   *
999   * @param  s  The string representation of the DN for which to retrieve the
1000   *            parent.  It must not be {@code null}.
1001   *
1002   * @return  The DN that is the parent for this DN, or {@code null} if there
1003   *          is no parent.
1004   *
1005   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1006   */
1007  public static DN getParent(final String s)
1008         throws LDAPException
1009  {
1010    return new DN(s).getParent();
1011  }
1012
1013
1014
1015  /**
1016   * Retrieves the string representation of the DN that is the parent for this
1017   * DN.  Note that neither the null DN nor DNs consisting of a single RDN
1018   * component will be considered to have parent DNs.
1019   *
1020   * @return  The DN that is the parent for this DN, or {@code null} if there
1021   *          is no parent.
1022   */
1023  public String getParentString()
1024  {
1025    final DN parentDN = getParent();
1026    if (parentDN == null)
1027    {
1028      return null;
1029    }
1030    else
1031    {
1032      return parentDN.toString();
1033    }
1034  }
1035
1036
1037
1038  /**
1039   * Retrieves the string representation of the DN that is the parent for the
1040   * DN with the provided string representation.  Note that neither the null DN
1041   * nor DNs consisting of a single RDN component will be considered to have
1042   * parent DNs.
1043   *
1044   * @param  s  The string representation of the DN for which to retrieve the
1045   *            parent.  It must not be {@code null}.
1046   *
1047   * @return  The DN that is the parent for this DN, or {@code null} if there
1048   *          is no parent.
1049   *
1050   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1051   */
1052  public static String getParentString(final String s)
1053         throws LDAPException
1054  {
1055    return new DN(s).getParentString();
1056  }
1057
1058
1059
1060  /**
1061   * Indicates whether this DN is an ancestor of the provided DN.  It will be
1062   * considered an ancestor of the provided DN if the array of RDN components
1063   * for the provided DN ends with the elements that comprise the array of RDN
1064   * components for this DN (i.e., if the provided DN is subordinate to, or
1065   * optionally equal to, this DN).  The null DN will be considered an ancestor
1066   * for all other DNs (with the exception of the null DN if {@code allowEquals}
1067   * is {@code false}).
1068   *
1069   * @param  dn           The DN for which to make the determination.
1070   * @param  allowEquals  Indicates whether a DN should be considered an
1071   *                      ancestor of itself.
1072   *
1073   * @return  {@code true} if this DN may be considered an ancestor of the
1074   *          provided DN, or {@code false} if not.
1075   */
1076  public boolean isAncestorOf(final DN dn, final boolean allowEquals)
1077  {
1078    int thisPos = rdns.length - 1;
1079    int thatPos = dn.rdns.length - 1;
1080
1081    if (thisPos < 0)
1082    {
1083      // This DN must be the null DN, which is an ancestor for all other DNs
1084      // (and equal to the null DN, which we may still classify as being an
1085      // ancestor).
1086      return (allowEquals || (thatPos >= 0));
1087    }
1088
1089    if ((thisPos > thatPos) || ((thisPos == thatPos) && (! allowEquals)))
1090    {
1091      // This DN has more RDN components than the provided DN, so it can't
1092      // possibly be an ancestor, or has the same number of components and equal
1093      // DNs shouldn't be considered ancestors.
1094      return false;
1095    }
1096
1097    while (thisPos >= 0)
1098    {
1099      if (! rdns[thisPos--].equals(dn.rdns[thatPos--]))
1100      {
1101        return false;
1102      }
1103    }
1104
1105    // If we've gotten here, then we can consider this DN to be an ancestor of
1106    // the provided DN.
1107    return true;
1108  }
1109
1110
1111
1112  /**
1113   * Indicates whether this DN is an ancestor of the DN with the provided string
1114   * representation.  It will be considered an ancestor of the provided DN if
1115   * the array of RDN components for the provided DN ends with the elements that
1116   * comprise the array of RDN components for this DN (i.e., if the provided DN
1117   * is subordinate to, or optionally equal to, this DN).  The null DN will be
1118   * considered an ancestor for all other DNs (with the exception of the null DN
1119   * if {@code allowEquals} is {@code false}).
1120   *
1121   * @param  s            The string representation of the DN for which to make
1122   *                      the determination.
1123   * @param  allowEquals  Indicates whether a DN should be considered an
1124   *                      ancestor of itself.
1125   *
1126   * @return  {@code true} if this DN may be considered an ancestor of the
1127   *          provided DN, or {@code false} if not.
1128   *
1129   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1130   */
1131  public boolean isAncestorOf(final String s, final boolean allowEquals)
1132         throws LDAPException
1133  {
1134    return isAncestorOf(new DN(s), allowEquals);
1135  }
1136
1137
1138
1139  /**
1140   * Indicates whether the DN represented by the first string is an ancestor of
1141   * the DN represented by the second string.  The first DN will be considered
1142   * an ancestor of the second DN if the array of RDN components for the first
1143   * DN ends with the elements that comprise the array of RDN components for the
1144   * second DN (i.e., if the first DN is subordinate to, or optionally equal to,
1145   * the second DN).  The null DN will be considered an ancestor for all other
1146   * DNs (with the exception of the null DN if {@code allowEquals} is
1147   * {@code false}).
1148   *
1149   * @param  s1           The string representation of the first DN for which to
1150   *                      make the determination.
1151   * @param  s2           The string representation of the second DN for which
1152   *                      to make the determination.
1153   * @param  allowEquals  Indicates whether a DN should be considered an
1154   *                      ancestor of itself.
1155   *
1156   * @return  {@code true} if the first DN may be considered an ancestor of the
1157   *          second DN, or {@code false} if not.
1158   *
1159   * @throws  LDAPException  If either of the provided strings cannot be parsed
1160   *                         as a DN.
1161   */
1162  public static boolean isAncestorOf(final String s1, final String s2,
1163                                     final boolean allowEquals)
1164         throws LDAPException
1165  {
1166    return new DN(s1).isAncestorOf(new DN(s2), allowEquals);
1167  }
1168
1169
1170
1171  /**
1172   * Indicates whether this DN is a descendant of the provided DN.  It will be
1173   * considered a descendant of the provided DN if the array of RDN components
1174   * for this DN ends with the elements that comprise the RDN components for the
1175   * provided DN (i.e., if this DN is subordinate to, or optionally equal to,
1176   * the provided DN).  The null DN will not be considered a descendant for any
1177   * other DNs (with the exception of the null DN if {@code allowEquals} is
1178   * {@code true}).
1179   *
1180   * @param  dn           The DN for which to make the determination.
1181   * @param  allowEquals  Indicates whether a DN should be considered a
1182   *                      descendant of itself.
1183   *
1184   * @return  {@code true} if this DN may be considered a descendant of the
1185   *          provided DN, or {@code false} if not.
1186   */
1187  public boolean isDescendantOf(final DN dn, final boolean allowEquals)
1188  {
1189    int thisPos = rdns.length - 1;
1190    int thatPos = dn.rdns.length - 1;
1191
1192    if (thatPos < 0)
1193    {
1194      // The provided DN must be the null DN, which will be considered an
1195      // ancestor for all other DNs (and equal to the null DN), making this DN
1196      // considered a descendant for that DN.
1197      return (allowEquals || (thisPos >= 0));
1198    }
1199
1200    if ((thisPos < thatPos) || ((thisPos == thatPos) && (! allowEquals)))
1201    {
1202      // This DN has fewer DN components than the provided DN, so it can't
1203      // possibly be a descendant, or it has the same number of components and
1204      // equal DNs shouldn't be considered descendants.
1205      return false;
1206    }
1207
1208    while (thatPos >= 0)
1209    {
1210      if (! rdns[thisPos--].equals(dn.rdns[thatPos--]))
1211      {
1212        return false;
1213      }
1214    }
1215
1216    // If we've gotten here, then we can consider this DN to be a descendant of
1217    // the provided DN.
1218    return true;
1219  }
1220
1221
1222
1223  /**
1224   * Indicates whether this DN is a descendant of the DN with the provided
1225   * string representation.  It will be considered a descendant of the provided
1226   * DN if the array of RDN components for this DN ends with the elements that
1227   * comprise the RDN components for the provided DN (i.e., if this DN is
1228   * subordinate to, or optionally equal to, the provided DN).  The null DN will
1229   * not be considered a descendant for any other DNs (with the exception of the
1230   * null DN if {@code allowEquals} is {@code true}).
1231   *
1232   * @param  s            The string representation of the DN for which to make
1233   *                      the determination.
1234   * @param  allowEquals  Indicates whether a DN should be considered a
1235   *                      descendant of itself.
1236   *
1237   * @return  {@code true} if this DN may be considered a descendant of the
1238   *          provided DN, or {@code false} if not.
1239   *
1240   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1241   */
1242  public boolean isDescendantOf(final String s, final boolean allowEquals)
1243         throws LDAPException
1244  {
1245    return isDescendantOf(new DN(s), allowEquals);
1246  }
1247
1248
1249
1250  /**
1251   * Indicates whether the DN represented by the first string is a descendant of
1252   * the DN represented by the second string.  The first DN will be considered a
1253   * descendant of the second DN if the array of RDN components for the first DN
1254   * ends with the elements that comprise the RDN components for the second DN
1255   * (i.e., if the first DN is subordinate to, or optionally equal to, the
1256   * second DN).  The null DN will not be considered a descendant for any other
1257   * DNs (with the exception of the null DN if {@code allowEquals} is
1258   * {@code true}).
1259   *
1260   * @param  s1           The string representation of the first DN for which to
1261   *                      make the determination.
1262   * @param  s2           The string representation of the second DN for which
1263   *                      to make the determination.
1264   * @param  allowEquals  Indicates whether a DN should be considered an
1265   *                      ancestor of itself.
1266   *
1267   * @return  {@code true} if this DN may be considered a descendant of the
1268   *          provided DN, or {@code false} if not.
1269   *
1270   * @throws  LDAPException  If either of the provided strings cannot be parsed
1271   *                         as a DN.
1272   */
1273  public static boolean isDescendantOf(final String s1, final String s2,
1274                                       final boolean allowEquals)
1275         throws LDAPException
1276  {
1277    return new DN(s1).isDescendantOf(new DN(s2), allowEquals);
1278  }
1279
1280
1281
1282  /**
1283   * Indicates whether this DN falls within the range of the provided search
1284   * base DN and scope.
1285   *
1286   * @param  baseDN  The base DN for which to make the determination.  It must
1287   *                 not be {@code null}.
1288   * @param  scope   The scope for which to make the determination.  It must not
1289   *                 be {@code null}.
1290   *
1291   * @return  {@code true} if this DN is within the range of the provided base
1292   *          and scope, or {@code false} if not.
1293   *
1294   * @throws  LDAPException  If a problem occurs while making the determination.
1295   */
1296  public boolean matchesBaseAndScope(final String baseDN,
1297                                     final SearchScope scope)
1298         throws LDAPException
1299  {
1300    return matchesBaseAndScope(new DN(baseDN), scope);
1301  }
1302
1303
1304
1305  /**
1306   * Indicates whether this DN falls within the range of the provided search
1307   * base DN and scope.
1308   *
1309   * @param  baseDN  The base DN for which to make the determination.  It must
1310   *                 not be {@code null}.
1311   * @param  scope   The scope for which to make the determination.  It must not
1312   *                 be {@code null}.
1313   *
1314   * @return  {@code true} if this DN is within the range of the provided base
1315   *          and scope, or {@code false} if not.
1316   *
1317   * @throws  LDAPException  If a problem occurs while making the determination.
1318   */
1319  public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope)
1320         throws LDAPException
1321  {
1322    Validator.ensureNotNull(baseDN, scope);
1323
1324    switch (scope.intValue())
1325    {
1326      case SearchScope.BASE_INT_VALUE:
1327        return equals(baseDN);
1328
1329      case SearchScope.ONE_INT_VALUE:
1330        return baseDN.equals(getParent());
1331
1332      case SearchScope.SUB_INT_VALUE:
1333        return isDescendantOf(baseDN, true);
1334
1335      case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
1336        return isDescendantOf(baseDN, false);
1337
1338      default:
1339        throw new LDAPException(ResultCode.PARAM_ERROR,
1340             ERR_DN_MATCHES_UNSUPPORTED_SCOPE.get(dnString,
1341                  String.valueOf(scope)));
1342    }
1343  }
1344
1345
1346
1347  /**
1348   * Generates a hash code for this DN.
1349   *
1350   * @return  The generated hash code for this DN.
1351   */
1352  @Override() public int hashCode()
1353  {
1354    return toNormalizedString().hashCode();
1355  }
1356
1357
1358
1359  /**
1360   * Indicates whether the provided object is equal to this DN.  In order for
1361   * the provided object to be considered equal, it must be a non-null DN with
1362   * the same set of RDN components.
1363   *
1364   * @param  o  The object for which to make the determination.
1365   *
1366   * @return  {@code true} if the provided object is considered equal to this
1367   *          DN, or {@code false} if not.
1368   */
1369  @Override()
1370  public boolean equals(final Object o)
1371  {
1372    if (o == null)
1373    {
1374      return false;
1375    }
1376
1377    if (this == o)
1378    {
1379      return true;
1380    }
1381
1382    if (! (o instanceof DN))
1383    {
1384      return false;
1385    }
1386
1387    final DN dn = (DN) o;
1388    return (toNormalizedString().equals(dn.toNormalizedString()));
1389  }
1390
1391
1392
1393  /**
1394   * Indicates whether the DN with the provided string representation is equal
1395   * to this DN.
1396   *
1397   * @param  s  The string representation of the DN to compare with this DN.
1398   *
1399   * @return  {@code true} if the DN with the provided string representation is
1400   *          equal to this DN, or {@code false} if not.
1401   *
1402   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1403   */
1404  public boolean equals(final String s)
1405         throws LDAPException
1406  {
1407    if (s == null)
1408    {
1409      return false;
1410    }
1411
1412    return equals(new DN(s));
1413  }
1414
1415
1416
1417  /**
1418   * Indicates whether the two provided strings represent the same DN.
1419   *
1420   * @param  s1  The string representation of the first DN for which to make the
1421   *             determination.  It must not be {@code null}.
1422   * @param  s2  The string representation of the second DN for which to make
1423   *             the determination.  It must not be {@code null}.
1424   *
1425   * @return  {@code true} if the provided strings represent the same DN, or
1426   *          {@code false} if not.
1427   *
1428   * @throws  LDAPException  If either of the provided strings cannot be parsed
1429   *                         as a DN.
1430   */
1431  public static boolean equals(final String s1, final String s2)
1432         throws LDAPException
1433  {
1434    return new DN(s1).equals(new DN(s2));
1435  }
1436
1437
1438
1439  /**
1440   * Indicates whether the two provided strings represent the same DN.
1441   *
1442   * @param  s1      The string representation of the first DN for which to make
1443   *                 the determination.  It must not be {@code null}.
1444   * @param  s2      The string representation of the second DN for which to
1445   *                 make the determination.  It must not be {@code null}.
1446   * @param  schema  The schema to use while making the determination.  It may
1447   *                 be {@code null} if no schema is available.
1448   *
1449   * @return  {@code true} if the provided strings represent the same DN, or
1450   *          {@code false} if not.
1451   *
1452   * @throws  LDAPException  If either of the provided strings cannot be parsed
1453   *                         as a DN.
1454   */
1455  public static boolean equals(final String s1, final String s2,
1456                               final Schema schema)
1457         throws LDAPException
1458  {
1459    return new DN(s1, schema).equals(new DN(s2, schema));
1460  }
1461
1462
1463
1464  /**
1465   * Retrieves a string representation of this DN.
1466   *
1467   * @return  A string representation of this DN.
1468   */
1469  @Override()
1470  public String toString()
1471  {
1472    return dnString;
1473  }
1474
1475
1476
1477  /**
1478   * Retrieves a string representation of this DN with minimal encoding for
1479   * special characters.  Only those characters specified in RFC 4514 section
1480   * 2.4 will be escaped.  No escaping will be used for non-ASCII characters or
1481   * non-printable ASCII characters.
1482   *
1483   * @return  A string representation of this DN with minimal encoding for
1484   *          special characters.
1485   */
1486  public String toMinimallyEncodedString()
1487  {
1488    final StringBuilder buffer = new StringBuilder();
1489    toString(buffer, true);
1490    return buffer.toString();
1491  }
1492
1493
1494
1495  /**
1496   * Appends a string representation of this DN to the provided buffer.
1497   *
1498   * @param  buffer  The buffer to which to append the string representation of
1499   *                 this DN.
1500   */
1501  public void toString(final StringBuilder buffer)
1502  {
1503    toString(buffer, false);
1504  }
1505
1506
1507
1508  /**
1509   * Appends a string representation of this DN to the provided buffer.
1510   *
1511   * @param  buffer            The buffer to which the string representation is
1512   *                           to be appended.
1513   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1514   *                           special characters to the bare minimum required
1515   *                           by LDAP (as per RFC 4514 section 2.4).  If this
1516   *                           is {@code true}, then only leading and trailing
1517   *                           spaces, double quotes, plus signs, commas,
1518   *                           semicolons, greater-than, less-than, and
1519   *                           backslash characters will be encoded.
1520   */
1521  public void toString(final StringBuilder buffer,
1522                       final boolean minimizeEncoding)
1523  {
1524    for (int i=0; i < rdns.length; i++)
1525    {
1526      if (i > 0)
1527      {
1528        buffer.append(',');
1529      }
1530
1531      rdns[i].toString(buffer, minimizeEncoding);
1532    }
1533  }
1534
1535
1536
1537  /**
1538   * Retrieves a normalized string representation of this DN.
1539   *
1540   * @return  A normalized string representation of this DN.
1541   */
1542  public String toNormalizedString()
1543  {
1544    if (normalizedString == null)
1545    {
1546      final StringBuilder buffer = new StringBuilder();
1547      toNormalizedString(buffer);
1548      normalizedString = buffer.toString();
1549    }
1550
1551    return normalizedString;
1552  }
1553
1554
1555
1556  /**
1557   * Appends a normalized string representation of this DN to the provided
1558   * buffer.
1559   *
1560   * @param  buffer  The buffer to which to append the normalized string
1561   *                 representation of this DN.
1562   */
1563  public void toNormalizedString(final StringBuilder buffer)
1564  {
1565    for (int i=0; i < rdns.length; i++)
1566    {
1567      if (i > 0)
1568      {
1569        buffer.append(',');
1570      }
1571
1572      buffer.append(rdns[i].toNormalizedString());
1573    }
1574  }
1575
1576
1577
1578  /**
1579   * Retrieves a normalized representation of the DN with the provided string
1580   * representation.
1581   *
1582   * @param  s  The string representation of the DN to normalize.  It must not
1583   *            be {@code null}.
1584   *
1585   * @return  The normalized representation of the DN with the provided string
1586   *          representation.
1587   *
1588   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1589   */
1590  public static String normalize(final String s)
1591         throws LDAPException
1592  {
1593    return normalize(s, null);
1594  }
1595
1596
1597
1598  /**
1599   * Retrieves a normalized representation of the DN with the provided string
1600   * representation.
1601   *
1602   * @param  s       The string representation of the DN to normalize.  It must
1603   *                 not be {@code null}.
1604   * @param  schema  The schema to use to generate the normalized string
1605   *                 representation of the DN.  It may be {@code null} if no
1606   *                 schema is available.
1607   *
1608   * @return  The normalized representation of the DN with the provided string
1609   *          representation.
1610   *
1611   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1612   */
1613  public static String normalize(final String s, final Schema schema)
1614         throws LDAPException
1615  {
1616    return new DN(s, schema).toNormalizedString();
1617  }
1618
1619
1620
1621  /**
1622   * Compares the provided DN to this DN to determine their relative order in
1623   * a sorted list.
1624   *
1625   * @param  dn  The DN to compare against this DN.  It must not be
1626   *             {@code null}.
1627   *
1628   * @return  A negative integer if this DN should come before the provided DN
1629   *          in a sorted list, a positive integer if this DN should come after
1630   *          the provided DN in a sorted list, or zero if the provided DN can
1631   *          be considered equal to this DN.
1632   */
1633  @Override()
1634  public int compareTo(final DN dn)
1635  {
1636    return compare(this, dn);
1637  }
1638
1639
1640
1641  /**
1642   * Compares the provided DN values to determine their relative order in a
1643   * sorted list.
1644   *
1645   * @param  dn1  The first DN to be compared.  It must not be {@code null}.
1646   * @param  dn2  The second DN to be compared.  It must not be {@code null}.
1647   *
1648   * @return  A negative integer if the first DN should come before the second
1649   *          DN in a sorted list, a positive integer if the first DN should
1650   *          come after the second DN in a sorted list, or zero if the two DN
1651   *          values can be considered equal.
1652   */
1653  @Override()
1654  public int compare(final DN dn1, final DN dn2)
1655  {
1656    Validator.ensureNotNull(dn1, dn2);
1657
1658    // We want the comparison to be in reverse order, so that DNs will be sorted
1659    // hierarchically.
1660    int pos1 = dn1.rdns.length - 1;
1661    int pos2 = dn2.rdns.length - 1;
1662    if (pos1 < 0)
1663    {
1664      if (pos2 < 0)
1665      {
1666        // Both DNs are the null DN, so they are equal.
1667        return 0;
1668      }
1669      else
1670      {
1671        // The first DN is the null DN and the second isn't, so the first DN
1672        // comes first.
1673        return -1;
1674      }
1675    }
1676    else if (pos2 < 0)
1677    {
1678      // The second DN is the null DN, which always comes first.
1679      return 1;
1680    }
1681
1682
1683    while ((pos1 >= 0) && (pos2 >= 0))
1684    {
1685      final int compValue = dn1.rdns[pos1].compareTo(dn2.rdns[pos2]);
1686      if (compValue != 0)
1687      {
1688        return compValue;
1689      }
1690
1691      pos1--;
1692      pos2--;
1693    }
1694
1695
1696    // If we've gotten here, then one of the DNs is equal to or a descendant of
1697    // the other.
1698    if (pos1 < 0)
1699    {
1700      if (pos2 < 0)
1701      {
1702        // They're both the same length, so they should be considered equal.
1703        return 0;
1704      }
1705      else
1706      {
1707        // The first is shorter than the second, so it should come first.
1708        return -1;
1709      }
1710    }
1711    else
1712    {
1713      // The second RDN is shorter than the first, so it should come first.
1714      return 1;
1715    }
1716  }
1717
1718
1719
1720  /**
1721   * Compares the DNs with the provided string representations to determine
1722   * their relative order in a sorted list.
1723   *
1724   * @param  s1  The string representation for the first DN to be compared.  It
1725   *             must not be {@code null}.
1726   * @param  s2  The string representation for the second DN to be compared.  It
1727   *             must not be {@code null}.
1728   *
1729   * @return  A negative integer if the first DN should come before the second
1730   *          DN in a sorted list, a positive integer if the first DN should
1731   *          come after the second DN in a sorted list, or zero if the two DN
1732   *          values can be considered equal.
1733   *
1734   * @throws  LDAPException  If either of the provided strings cannot be parsed
1735   *                         as a DN.
1736   */
1737  public static int compare(final String s1, final String s2)
1738         throws LDAPException
1739  {
1740    return compare(s1, s2, null);
1741  }
1742
1743
1744
1745  /**
1746   * Compares the DNs with the provided string representations to determine
1747   * their relative order in a sorted list.
1748   *
1749   * @param  s1      The string representation for the first DN to be compared.
1750   *                 It must not be {@code null}.
1751   * @param  s2      The string representation for the second DN to be compared.
1752   *                 It must not be {@code null}.
1753   * @param  schema  The schema to use to generate the normalized string
1754   *                 representations of the DNs.  It may be {@code null} if no
1755   *                 schema is available.
1756   *
1757   * @return  A negative integer if the first DN should come before the second
1758   *          DN in a sorted list, a positive integer if the first DN should
1759   *          come after the second DN in a sorted list, or zero if the two DN
1760   *          values can be considered equal.
1761   *
1762   * @throws  LDAPException  If either of the provided strings cannot be parsed
1763   *                         as a DN.
1764   */
1765  public static int compare(final String s1, final String s2,
1766                            final Schema schema)
1767         throws LDAPException
1768  {
1769    return new DN(s1, schema).compareTo(new DN(s2, schema));
1770  }
1771}