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