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;
036
037import static com.unboundid.ldap.sdk.LDAPMessages.*;
038import static com.unboundid.util.Validator.*;
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    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    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    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    ensureNotNull(dnString);
305
306    this.dnString = dnString;
307    this.schema   = schema;
308
309    final ArrayList<RDN> rdnList = new ArrayList<RDN>(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.length() == 0)
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<String>(5);
484      final ArrayList<ASN1OctetString> valueList =
485           new ArrayList<ASN1OctetString>(5);
486      nameList.add(attrName);
487      valueList.add(value);
488
489      while (pos < length)
490      {
491        // Skip over any spaces before the attribute name.
492        while ((pos < length) && (dnString.charAt(pos) == ' '))
493        {
494          pos++;
495        }
496
497        if (pos >= length)
498        {
499          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
500               ERR_DN_ENDS_WITH_PLUS.get(dnString));
501        }
502
503        // Read the attribute name, until we find a space or equal sign.
504        attrStartPos = pos;
505        while (pos < length)
506        {
507          final char c = dnString.charAt(pos);
508          if ((c == ' ') || (c == '='))
509          {
510            break;
511          }
512          else if ((c == ',') || (c == ';'))
513          {
514            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
515                 ERR_DN_UNEXPECTED_COMMA.get(dnString, pos));
516          }
517
518          pos++;
519        }
520
521        attrName = dnString.substring(attrStartPos, pos);
522        if (attrName.length() == 0)
523        {
524          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
525               ERR_DN_NO_ATTR_IN_RDN.get(dnString));
526        }
527
528
529        // Skip over any spaces before the equal sign.
530        while ((pos < length) && (dnString.charAt(pos) == ' '))
531        {
532          pos++;
533        }
534
535        if ((pos >= length) || (dnString.charAt(pos) != '='))
536        {
537          // We didn't find an equal sign.
538          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
539               ERR_DN_NO_EQUAL_SIGN.get(dnString, attrName));
540        }
541
542        // Skip over the equal sign, and then any spaces leading up to the
543        // attribute value.
544        pos++;
545        while ((pos < length) && (dnString.charAt(pos) == ' '))
546        {
547          pos++;
548        }
549
550
551        // Read the value for this RDN component.
552        if (pos >= length)
553        {
554          value = new ASN1OctetString();
555          rdnEndPos = pos;
556        }
557        else if (dnString.charAt(pos) == '#')
558        {
559          // It is a hex-encoded value, so we'll read until we find the end of
560          // the string or the first non-hex character, which must be a space, a
561          // comma, or a plus sign.  Then, parse the bytes of the hex-encoded
562          // value as a BER element, and take the value of that element.
563          final byte[] valueArray = RDN.readHexString(dnString, ++pos);
564
565          try
566          {
567            value = ASN1OctetString.decodeAsOctetString(valueArray);
568          }
569          catch (final Exception e)
570          {
571            Debug.debugException(e);
572            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
573                 ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(dnString, attrName), e);
574          }
575
576          pos += (valueArray.length * 2);
577          rdnEndPos = pos;
578        }
579        else
580        {
581          // It is a string value, which potentially includes escaped
582          // characters.
583          final StringBuilder buffer = new StringBuilder();
584          pos = RDN.readValueString(dnString, pos, buffer);
585          value = new ASN1OctetString(buffer.toString());
586          rdnEndPos = pos;
587        }
588
589
590        // Skip over any spaces until we find a comma, a plus sign, or the end
591        // of the value.
592        while ((pos < length) && (dnString.charAt(pos) == ' '))
593        {
594          pos++;
595        }
596
597        nameList.add(attrName);
598        valueList.add(value);
599
600        if (pos >= length)
601        {
602          // We've hit the end of the RDN and the end of the DN.
603          final String[] names = nameList.toArray(new String[nameList.size()]);
604          final ASN1OctetString[] values =
605               valueList.toArray(new ASN1OctetString[valueList.size()]);
606          rdnList.add(new RDN(names, values, schema,
607               getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
608          expectMore = false;
609          break rdnLoop;
610        }
611
612        switch (dnString.charAt(pos))
613        {
614          case '+':
615            // There are still more RDN components to be read, so we're not done
616            // yet.
617            pos++;
618
619            if (pos >= length)
620            {
621              throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
622                   ERR_DN_ENDS_WITH_PLUS.get(dnString));
623            }
624            break;
625
626          case ',':
627          case ';':
628            // We've hit the end of the RDN, but there is still more of the DN
629            // to be read.
630            final String[] names =
631                 nameList.toArray(new String[nameList.size()]);
632            final ASN1OctetString[] values =
633                 valueList.toArray(new ASN1OctetString[valueList.size()]);
634            rdnList.add(new RDN(names, values, schema,
635                 getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
636            pos++;
637            expectMore = true;
638            continue rdnLoop;
639
640          default:
641            // It's an illegal character.  This should never happen.
642            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
643                 ERR_DN_UNEXPECTED_CHAR.get(dnString, dnString.charAt(pos),
644                      pos));
645        }
646      }
647    }
648
649    // If we are expecting more information to be provided, then it means that
650    // the string ended with a comma or semicolon.
651    if (expectMore)
652    {
653      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
654                              ERR_DN_ENDS_WITH_COMMA.get(dnString));
655    }
656
657    // At this point, we should have all of the RDNs to use to create this DN.
658    rdns = new RDN[rdnList.size()];
659    rdnList.toArray(rdns);
660  }
661
662
663
664  /**
665   * Retrieves a trimmed version of the string representation of the RDN in the
666   * specified portion of the provided DN string.  Only non-escaped trailing
667   * spaces will be removed.
668   *
669   * @param  dnString  The string representation of the DN from which to extract
670   *                   the string representation of the RDN.
671   * @param  start     The position of the first character in the RDN.
672   * @param  end       The position marking the end of the RDN.
673   *
674   * @return  A properly-trimmed string representation of the RDN.
675   */
676  private static String getTrimmedRDN(final String dnString, final int start,
677                                      final int end)
678  {
679    final String rdnString = dnString.substring(start, end);
680    if (! rdnString.endsWith(" "))
681    {
682      return rdnString;
683    }
684
685    final StringBuilder buffer = new StringBuilder(rdnString);
686    while ((buffer.charAt(buffer.length() - 1) == ' ') &&
687           (buffer.charAt(buffer.length() - 2) != '\\'))
688    {
689      buffer.setLength(buffer.length() - 1);
690    }
691
692    return buffer.toString();
693  }
694
695
696
697  /**
698   * Indicates whether the provided string represents a valid DN.
699   *
700   * @param  s  The string for which to make the determination.  It must not be
701   *            {@code null}.
702   *
703   * @return  {@code true} if the provided string represents a valid DN, or
704   *          {@code false} if not.
705   */
706  public static boolean isValidDN(final String s)
707  {
708    try
709    {
710      new DN(s);
711      return true;
712    }
713    catch (final LDAPException le)
714    {
715      return false;
716    }
717  }
718
719
720
721
722  /**
723   * Retrieves the leftmost (i.e., furthest from the naming context) RDN
724   * component for this DN.
725   *
726   * @return  The leftmost RDN component for this DN, or {@code null} if this DN
727   *          does not have any RDNs (i.e., it is the null DN).
728   */
729  public RDN getRDN()
730  {
731    if (rdns.length == 0)
732    {
733      return null;
734    }
735    else
736    {
737      return rdns[0];
738    }
739  }
740
741
742
743  /**
744   * Retrieves the string representation of the leftmost (i.e., furthest from
745   * the naming context) RDN component for this DN.
746   *
747   * @return  The string representation of the leftmost RDN component for this
748   *          DN, or {@code null} if this DN does not have any RDNs (i.e., it is
749   *          the null DN).
750   */
751  public String getRDNString()
752  {
753    if (rdns.length == 0)
754    {
755      return null;
756    }
757    else
758    {
759      return rdns[0].toString();
760    }
761  }
762
763
764
765  /**
766   * Retrieves the string representation of the leftmost (i.e., furthest from
767   * the naming context) RDN component for the DN with the provided string
768   * representation.
769   *
770   * @param  s  The string representation of the DN to process.  It must not be
771   *            {@code null}.
772   *
773   * @return  The string representation of the leftmost RDN component for this
774   *          DN, or {@code null} if this DN does not have any RDNs (i.e., it is
775   *          the null DN).
776   *
777   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
778   */
779  public static String getRDNString(final String s)
780         throws LDAPException
781  {
782    return new DN(s).getRDNString();
783  }
784
785
786
787  /**
788   * Retrieves the set of RDNs that comprise this DN.
789   *
790   * @return  The set of RDNs that comprise this DN.
791   */
792  public RDN[] getRDNs()
793  {
794    return rdns;
795  }
796
797
798
799  /**
800   * Retrieves the set of RDNs that comprise the DN with the provided string
801   * representation.
802   *
803   * @param  s  The string representation of the DN for which to retrieve the
804   *            RDNs.  It must not be {@code null}.
805   *
806   * @return  The set of RDNs that comprise the DN with the provided string
807   *          representation.
808   *
809   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
810   */
811  public static RDN[] getRDNs(final String s)
812         throws LDAPException
813  {
814    return new DN(s).getRDNs();
815  }
816
817
818
819  /**
820   * Retrieves the set of string representations of the RDNs that comprise this
821   * DN.
822   *
823   * @return  The set of string representations of the RDNs that comprise this
824   *          DN.
825   */
826  public String[] getRDNStrings()
827  {
828    final String[] rdnStrings = new String[rdns.length];
829    for (int i=0; i < rdns.length; i++)
830    {
831      rdnStrings[i] = rdns[i].toString();
832    }
833    return rdnStrings;
834  }
835
836
837
838  /**
839   * Retrieves the set of string representations of the RDNs that comprise this
840   * DN.
841   *
842   * @param  s  The string representation of the DN for which to retrieve the
843   *            RDN strings.  It must not be {@code null}.
844   *
845   * @return  The set of string representations of the RDNs that comprise this
846   *          DN.
847   *
848   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
849   */
850  public static String[] getRDNStrings(final String s)
851         throws LDAPException
852  {
853    return new DN(s).getRDNStrings();
854  }
855
856
857
858  /**
859   * Indicates whether this DN represents the null DN, which does not have any
860   * RDN components.
861   *
862   * @return  {@code true} if this DN represents the null DN, or {@code false}
863   *          if not.
864   */
865  public boolean isNullDN()
866  {
867    return (rdns.length == 0);
868  }
869
870
871
872  /**
873   * Retrieves the DN that is the parent for this DN.  Note that neither the
874   * null DN nor DNs consisting of a single RDN component will be considered to
875   * have parent DNs.
876   *
877   * @return  The DN that is the parent for this DN, or {@code null} if there
878   *          is no parent.
879   */
880  public DN getParent()
881  {
882    switch (rdns.length)
883    {
884      case 0:
885      case 1:
886        return null;
887
888      case 2:
889        return new DN(rdns[1]);
890
891      case 3:
892        return new DN(rdns[1], rdns[2]);
893
894      case 4:
895        return new DN(rdns[1], rdns[2], rdns[3]);
896
897      case 5:
898        return new DN(rdns[1], rdns[2], rdns[3], rdns[4]);
899
900      default:
901        final RDN[] parentRDNs = new RDN[rdns.length - 1];
902        System.arraycopy(rdns, 1, parentRDNs, 0, parentRDNs.length);
903        return new DN(parentRDNs);
904    }
905  }
906
907
908
909  /**
910   * Retrieves the DN that is the parent for the DN with the provided string
911   * representation.  Note that neither the null DN nor DNs consisting of a
912   * single RDN component will be considered to have parent DNs.
913   *
914   * @param  s  The string representation of the DN for which to retrieve the
915   *            parent.  It must not be {@code null}.
916   *
917   * @return  The DN that is the parent for this DN, or {@code null} if there
918   *          is no parent.
919   *
920   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
921   */
922  public static DN getParent(final String s)
923         throws LDAPException
924  {
925    return new DN(s).getParent();
926  }
927
928
929
930  /**
931   * Retrieves the string representation of the DN that is the parent for this
932   * DN.  Note that neither the null DN nor DNs consisting of a single RDN
933   * component will be considered to have parent DNs.
934   *
935   * @return  The DN that is the parent for this DN, or {@code null} if there
936   *          is no parent.
937   */
938  public String getParentString()
939  {
940    final DN parentDN = getParent();
941    if (parentDN == null)
942    {
943      return null;
944    }
945    else
946    {
947      return parentDN.toString();
948    }
949  }
950
951
952
953  /**
954   * Retrieves the string representation of the DN that is the parent for the
955   * DN with the provided string representation.  Note that neither the null DN
956   * nor DNs consisting of a single RDN component will be considered to have
957   * parent DNs.
958   *
959   * @param  s  The string representation of the DN for which to retrieve the
960   *            parent.  It must not be {@code null}.
961   *
962   * @return  The DN that is the parent for this DN, or {@code null} if there
963   *          is no parent.
964   *
965   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
966   */
967  public static String getParentString(final String s)
968         throws LDAPException
969  {
970    return new DN(s).getParentString();
971  }
972
973
974
975  /**
976   * Indicates whether this DN is an ancestor of the provided DN.  It will be
977   * considered an ancestor of the provided DN if the array of RDN components
978   * for the provided DN ends with the elements that comprise the array of RDN
979   * components for this DN (i.e., if the provided DN is subordinate to, or
980   * optionally equal to, this DN).  The null DN will be considered an ancestor
981   * for all other DNs (with the exception of the null DN if {@code allowEquals}
982   * is {@code false}).
983   *
984   * @param  dn           The DN for which to make the determination.
985   * @param  allowEquals  Indicates whether a DN should be considered an
986   *                      ancestor of itself.
987   *
988   * @return  {@code true} if this DN may be considered an ancestor of the
989   *          provided DN, or {@code false} if not.
990   */
991  public boolean isAncestorOf(final DN dn, final boolean allowEquals)
992  {
993    int thisPos = rdns.length - 1;
994    int thatPos = dn.rdns.length - 1;
995
996    if (thisPos < 0)
997    {
998      // This DN must be the null DN, which is an ancestor for all other DNs
999      // (and equal to the null DN, which we may still classify as being an
1000      // ancestor).
1001      return (allowEquals || (thatPos >= 0));
1002    }
1003
1004    if ((thisPos > thatPos) || ((thisPos == thatPos) && (! allowEquals)))
1005    {
1006      // This DN has more RDN components than the provided DN, so it can't
1007      // possibly be an ancestor, or has the same number of components and equal
1008      // DNs shouldn't be considered ancestors.
1009      return false;
1010    }
1011
1012    while (thisPos >= 0)
1013    {
1014      if (! rdns[thisPos--].equals(dn.rdns[thatPos--]))
1015      {
1016        return false;
1017      }
1018    }
1019
1020    // If we've gotten here, then we can consider this DN to be an ancestor of
1021    // the provided DN.
1022    return true;
1023  }
1024
1025
1026
1027  /**
1028   * Indicates whether this DN is an ancestor of the DN with the provided string
1029   * representation.  It will be considered an ancestor of the provided DN if
1030   * the array of RDN components for the provided DN ends with the elements that
1031   * comprise the array of RDN components for this DN (i.e., if the provided DN
1032   * is subordinate to, or optionally equal to, this DN).  The null DN will be
1033   * considered an ancestor for all other DNs (with the exception of the null DN
1034   * if {@code allowEquals} is {@code false}).
1035   *
1036   * @param  s            The string representation of the DN for which to make
1037   *                      the determination.
1038   * @param  allowEquals  Indicates whether a DN should be considered an
1039   *                      ancestor of itself.
1040   *
1041   * @return  {@code true} if this DN may be considered an ancestor of the
1042   *          provided DN, or {@code false} if not.
1043   *
1044   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1045   */
1046  public boolean isAncestorOf(final String s, final boolean allowEquals)
1047         throws LDAPException
1048  {
1049    return isAncestorOf(new DN(s), allowEquals);
1050  }
1051
1052
1053
1054  /**
1055   * Indicates whether the DN represented by the first string is an ancestor of
1056   * the DN represented by the second string.  The first DN will be considered
1057   * an ancestor of the second DN if the array of RDN components for the first
1058   * DN ends with the elements that comprise the array of RDN components for the
1059   * second DN (i.e., if the first DN is subordinate to, or optionally equal to,
1060   * the second DN).  The null DN will be considered an ancestor for all other
1061   * DNs (with the exception of the null DN if {@code allowEquals} is
1062   * {@code false}).
1063   *
1064   * @param  s1           The string representation of the first DN for which to
1065   *                      make the determination.
1066   * @param  s2           The string representation of the second DN for which
1067   *                      to make the determination.
1068   * @param  allowEquals  Indicates whether a DN should be considered an
1069   *                      ancestor of itself.
1070   *
1071   * @return  {@code true} if the first DN may be considered an ancestor of the
1072   *          second DN, or {@code false} if not.
1073   *
1074   * @throws  LDAPException  If either of the provided strings cannot be parsed
1075   *                         as a DN.
1076   */
1077  public static boolean isAncestorOf(final String s1, final String s2,
1078                                     final boolean allowEquals)
1079         throws LDAPException
1080  {
1081    return new DN(s1).isAncestorOf(new DN(s2), allowEquals);
1082  }
1083
1084
1085
1086  /**
1087   * Indicates whether this DN is a descendant of the provided DN.  It will be
1088   * considered a descendant of the provided DN if the array of RDN components
1089   * for this DN ends with the elements that comprise the RDN components for the
1090   * provided DN (i.e., if this DN is subordinate to, or optionally equal to,
1091   * the provided DN).  The null DN will not be considered a descendant for any
1092   * other DNs (with the exception of the null DN if {@code allowEquals} is
1093   * {@code true}).
1094   *
1095   * @param  dn           The DN for which to make the determination.
1096   * @param  allowEquals  Indicates whether a DN should be considered a
1097   *                      descendant of itself.
1098   *
1099   * @return  {@code true} if this DN may be considered a descendant of the
1100   *          provided DN, or {@code false} if not.
1101   */
1102  public boolean isDescendantOf(final DN dn, final boolean allowEquals)
1103  {
1104    int thisPos = rdns.length - 1;
1105    int thatPos = dn.rdns.length - 1;
1106
1107    if (thatPos < 0)
1108    {
1109      // The provided DN must be the null DN, which will be considered an
1110      // ancestor for all other DNs (and equal to the null DN), making this DN
1111      // considered a descendant for that DN.
1112      return (allowEquals || (thisPos >= 0));
1113    }
1114
1115    if ((thisPos < thatPos) || ((thisPos == thatPos) && (! allowEquals)))
1116    {
1117      // This DN has fewer DN components than the provided DN, so it can't
1118      // possibly be a descendant, or it has the same number of components and
1119      // equal DNs shouldn't be considered descendants.
1120      return false;
1121    }
1122
1123    while (thatPos >= 0)
1124    {
1125      if (! rdns[thisPos--].equals(dn.rdns[thatPos--]))
1126      {
1127        return false;
1128      }
1129    }
1130
1131    // If we've gotten here, then we can consider this DN to be a descendant of
1132    // the provided DN.
1133    return true;
1134  }
1135
1136
1137
1138  /**
1139   * Indicates whether this DN is a descendant of the DN with the provided
1140   * string representation.  It will be considered a descendant of the provided
1141   * DN if the array of RDN components for this DN ends with the elements that
1142   * comprise the RDN components for the provided DN (i.e., if this DN is
1143   * subordinate to, or optionally equal to, the provided DN).  The null DN will
1144   * not be considered a descendant for any other DNs (with the exception of the
1145   * null DN if {@code allowEquals} is {@code true}).
1146   *
1147   * @param  s            The string representation of the DN for which to make
1148   *                      the determination.
1149   * @param  allowEquals  Indicates whether a DN should be considered a
1150   *                      descendant of itself.
1151   *
1152   * @return  {@code true} if this DN may be considered a descendant of the
1153   *          provided DN, or {@code false} if not.
1154   *
1155   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1156   */
1157  public boolean isDescendantOf(final String s, final boolean allowEquals)
1158         throws LDAPException
1159  {
1160    return isDescendantOf(new DN(s), allowEquals);
1161  }
1162
1163
1164
1165  /**
1166   * Indicates whether the DN represented by the first string is a descendant of
1167   * the DN represented by the second string.  The first DN will be considered a
1168   * descendant of the second DN if the array of RDN components for the first DN
1169   * ends with the elements that comprise the RDN components for the second DN
1170   * (i.e., if the first DN is subordinate to, or optionally equal to, the
1171   * second DN).  The null DN will not be considered a descendant for any other
1172   * DNs (with the exception of the null DN if {@code allowEquals} is
1173   * {@code true}).
1174   *
1175   * @param  s1           The string representation of the first DN for which to
1176   *                      make the determination.
1177   * @param  s2           The string representation of the second DN for which
1178   *                      to make the determination.
1179   * @param  allowEquals  Indicates whether a DN should be considered an
1180   *                      ancestor of itself.
1181   *
1182   * @return  {@code true} if this DN may be considered a descendant of the
1183   *          provided DN, or {@code false} if not.
1184   *
1185   * @throws  LDAPException  If either of the provided strings cannot be parsed
1186   *                         as a DN.
1187   */
1188  public static boolean isDescendantOf(final String s1, final String s2,
1189                                       final boolean allowEquals)
1190         throws LDAPException
1191  {
1192    return new DN(s1).isDescendantOf(new DN(s2), allowEquals);
1193  }
1194
1195
1196
1197  /**
1198   * Indicates whether this DN falls within the range of the provided search
1199   * base DN and scope.
1200   *
1201   * @param  baseDN  The base DN for which to make the determination.  It must
1202   *                 not be {@code null}.
1203   * @param  scope   The scope for which to make the determination.  It must not
1204   *                 be {@code null}.
1205   *
1206   * @return  {@code true} if this DN is within the range of the provided base
1207   *          and scope, or {@code false} if not.
1208   *
1209   * @throws  LDAPException  If a problem occurs while making the determination.
1210   */
1211  public boolean matchesBaseAndScope(final String baseDN,
1212                                     final SearchScope scope)
1213         throws LDAPException
1214  {
1215    return matchesBaseAndScope(new DN(baseDN), scope);
1216  }
1217
1218
1219
1220  /**
1221   * Indicates whether this DN falls within the range of the provided search
1222   * base DN and scope.
1223   *
1224   * @param  baseDN  The base DN for which to make the determination.  It must
1225   *                 not be {@code null}.
1226   * @param  scope   The scope for which to make the determination.  It must not
1227   *                 be {@code null}.
1228   *
1229   * @return  {@code true} if this DN is within the range of the provided base
1230   *          and scope, or {@code false} if not.
1231   *
1232   * @throws  LDAPException  If a problem occurs while making the determination.
1233   */
1234  public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope)
1235         throws LDAPException
1236  {
1237    ensureNotNull(baseDN, scope);
1238
1239    switch (scope.intValue())
1240    {
1241      case SearchScope.BASE_INT_VALUE:
1242        return equals(baseDN);
1243
1244      case SearchScope.ONE_INT_VALUE:
1245        return baseDN.equals(getParent());
1246
1247      case SearchScope.SUB_INT_VALUE:
1248        return isDescendantOf(baseDN, true);
1249
1250      case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
1251        return isDescendantOf(baseDN, false);
1252
1253      default:
1254        throw new LDAPException(ResultCode.PARAM_ERROR,
1255             ERR_DN_MATCHES_UNSUPPORTED_SCOPE.get(dnString,
1256                  String.valueOf(scope)));
1257    }
1258  }
1259
1260
1261
1262
1263  /**
1264   * Generates a hash code for this DN.
1265   *
1266   * @return  The generated hash code for this DN.
1267   */
1268  @Override() public int hashCode()
1269  {
1270    return toNormalizedString().hashCode();
1271  }
1272
1273
1274
1275  /**
1276   * Indicates whether the provided object is equal to this DN.  In order for
1277   * the provided object to be considered equal, it must be a non-null DN with
1278   * the same set of RDN components.
1279   *
1280   * @param  o  The object for which to make the determination.
1281   *
1282   * @return  {@code true} if the provided object is considered equal to this
1283   *          DN, or {@code false} if not.
1284   */
1285  @Override()
1286  public boolean equals(final Object o)
1287  {
1288    if (o == null)
1289    {
1290      return false;
1291    }
1292
1293    if (this == o)
1294    {
1295      return true;
1296    }
1297
1298    if (! (o instanceof DN))
1299    {
1300      return false;
1301    }
1302
1303    final DN dn = (DN) o;
1304    return (toNormalizedString().equals(dn.toNormalizedString()));
1305  }
1306
1307
1308
1309  /**
1310   * Indicates whether the DN with the provided string representation is equal
1311   * to this DN.
1312   *
1313   * @param  s  The string representation of the DN to compare with this DN.
1314   *
1315   * @return  {@code true} if the DN with the provided string representation is
1316   *          equal to this DN, or {@code false} if not.
1317   *
1318   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1319   */
1320  public boolean equals(final String s)
1321         throws LDAPException
1322  {
1323    if (s == null)
1324    {
1325      return false;
1326    }
1327
1328    return equals(new DN(s));
1329  }
1330
1331
1332
1333  /**
1334   * Indicates whether the two provided strings represent the same DN.
1335   *
1336   * @param  s1  The string representation of the first DN for which to make the
1337   *             determination.  It must not be {@code null}.
1338   * @param  s2  The string representation of the second DN for which to make
1339   *             the determination.  It must not be {@code null}.
1340   *
1341   * @return  {@code true} if the provided strings represent the same DN, or
1342   *          {@code false} if not.
1343   *
1344   * @throws  LDAPException  If either of the provided strings cannot be parsed
1345   *                         as a DN.
1346   */
1347  public static boolean equals(final String s1, final String s2)
1348         throws LDAPException
1349  {
1350    return new DN(s1).equals(new DN(s2));
1351  }
1352
1353
1354
1355  /**
1356   * Indicates whether the two provided strings represent the same DN.
1357   *
1358   * @param  s1      The string representation of the first DN for which to make
1359   *                 the determination.  It must not be {@code null}.
1360   * @param  s2      The string representation of the second DN for which to
1361   *                 make the determination.  It must not be {@code null}.
1362   * @param  schema  The schema to use while making the determination.  It may
1363   *                 be {@code null} if no schema is available.
1364   *
1365   * @return  {@code true} if the provided strings represent the same DN, or
1366   *          {@code false} if not.
1367   *
1368   * @throws  LDAPException  If either of the provided strings cannot be parsed
1369   *                         as a DN.
1370   */
1371  public static boolean equals(final String s1, final String s2,
1372                               final Schema schema)
1373         throws LDAPException
1374  {
1375    return new DN(s1, schema).equals(new DN(s2, schema));
1376  }
1377
1378
1379
1380  /**
1381   * Retrieves a string representation of this DN.
1382   *
1383   * @return  A string representation of this DN.
1384   */
1385  @Override()
1386  public String toString()
1387  {
1388    return dnString;
1389  }
1390
1391
1392
1393  /**
1394   * Retrieves a string representation of this DN with minimal encoding for
1395   * special characters.  Only those characters specified in RFC 4514 section
1396   * 2.4 will be escaped.  No escaping will be used for non-ASCII characters or
1397   * non-printable ASCII characters.
1398   *
1399   * @return  A string representation of this DN with minimal encoding for
1400   *          special characters.
1401   */
1402  public String toMinimallyEncodedString()
1403  {
1404    final StringBuilder buffer = new StringBuilder();
1405    toString(buffer, true);
1406    return buffer.toString();
1407  }
1408
1409
1410
1411  /**
1412   * Appends a string representation of this DN to the provided buffer.
1413   *
1414   * @param  buffer  The buffer to which to append the string representation of
1415   *                 this DN.
1416   */
1417  public void toString(final StringBuilder buffer)
1418  {
1419    toString(buffer, false);
1420  }
1421
1422
1423
1424  /**
1425   * Appends a string representation of this DN to the provided buffer.
1426   *
1427   * @param  buffer            The buffer to which the string representation is
1428   *                           to be appended.
1429   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1430   *                           special characters to the bare minimum required
1431   *                           by LDAP (as per RFC 4514 section 2.4).  If this
1432   *                           is {@code true}, then only leading and trailing
1433   *                           spaces, double quotes, plus signs, commas,
1434   *                           semicolons, greater-than, less-than, and
1435   *                           backslash characters will be encoded.
1436   */
1437  public void toString(final StringBuilder buffer,
1438                       final boolean minimizeEncoding)
1439  {
1440    for (int i=0; i < rdns.length; i++)
1441    {
1442      if (i > 0)
1443      {
1444        buffer.append(',');
1445      }
1446
1447      rdns[i].toString(buffer, minimizeEncoding);
1448    }
1449  }
1450
1451
1452
1453  /**
1454   * Retrieves a normalized string representation of this DN.
1455   *
1456   * @return  A normalized string representation of this DN.
1457   */
1458  public String toNormalizedString()
1459  {
1460    if (normalizedString == null)
1461    {
1462      final StringBuilder buffer = new StringBuilder();
1463      toNormalizedString(buffer);
1464      normalizedString = buffer.toString();
1465    }
1466
1467    return normalizedString;
1468  }
1469
1470
1471
1472  /**
1473   * Appends a normalized string representation of this DN to the provided
1474   * buffer.
1475   *
1476   * @param  buffer  The buffer to which to append the normalized string
1477   *                 representation of this DN.
1478   */
1479  public void toNormalizedString(final StringBuilder buffer)
1480  {
1481    for (int i=0; i < rdns.length; i++)
1482    {
1483      if (i > 0)
1484      {
1485        buffer.append(',');
1486      }
1487
1488      buffer.append(rdns[i].toNormalizedString());
1489    }
1490  }
1491
1492
1493
1494  /**
1495   * Retrieves a normalized representation of the DN with the provided string
1496   * representation.
1497   *
1498   * @param  s  The string representation of the DN to normalize.  It must not
1499   *            be {@code null}.
1500   *
1501   * @return  The normalized representation of the DN with the provided string
1502   *          representation.
1503   *
1504   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1505   */
1506  public static String normalize(final String s)
1507         throws LDAPException
1508  {
1509    return normalize(s, null);
1510  }
1511
1512
1513
1514  /**
1515   * Retrieves a normalized representation of the DN with the provided string
1516   * representation.
1517   *
1518   * @param  s       The string representation of the DN to normalize.  It must
1519   *                 not be {@code null}.
1520   * @param  schema  The schema to use to generate the normalized string
1521   *                 representation of the DN.  It may be {@code null} if no
1522   *                 schema is available.
1523   *
1524   * @return  The normalized representation of the DN with the provided string
1525   *          representation.
1526   *
1527   * @throws  LDAPException  If the provided string cannot be parsed as a DN.
1528   */
1529  public static String normalize(final String s, final Schema schema)
1530         throws LDAPException
1531  {
1532    return new DN(s, schema).toNormalizedString();
1533  }
1534
1535
1536
1537  /**
1538   * Compares the provided DN to this DN to determine their relative order in
1539   * a sorted list.
1540   *
1541   * @param  dn  The DN to compare against this DN.  It must not be
1542   *             {@code null}.
1543   *
1544   * @return  A negative integer if this DN should come before the provided DN
1545   *          in a sorted list, a positive integer if this DN should come after
1546   *          the provided DN in a sorted list, or zero if the provided DN can
1547   *          be considered equal to this DN.
1548   */
1549  public int compareTo(final DN dn)
1550  {
1551    return compare(this, dn);
1552  }
1553
1554
1555
1556  /**
1557   * Compares the provided DN values to determine their relative order in a
1558   * sorted list.
1559   *
1560   * @param  dn1  The first DN to be compared.  It must not be {@code null}.
1561   * @param  dn2  The second DN to be compared.  It must not be {@code null}.
1562   *
1563   * @return  A negative integer if the first DN should come before the second
1564   *          DN in a sorted list, a positive integer if the first DN should
1565   *          come after the second DN in a sorted list, or zero if the two DN
1566   *          values can be considered equal.
1567   */
1568  public int compare(final DN dn1, final DN dn2)
1569  {
1570    ensureNotNull(dn1, dn2);
1571
1572    // We want the comparison to be in reverse order, so that DNs will be sorted
1573    // hierarchically.
1574    int pos1 = dn1.rdns.length - 1;
1575    int pos2 = dn2.rdns.length - 1;
1576    if (pos1 < 0)
1577    {
1578      if (pos2 < 0)
1579      {
1580        // Both DNs are the null DN, so they are equal.
1581        return 0;
1582      }
1583      else
1584      {
1585        // The first DN is the null DN and the second isn't, so the first DN
1586        // comes first.
1587        return -1;
1588      }
1589    }
1590    else if (pos2 < 0)
1591    {
1592      // The second DN is the null DN, which always comes first.
1593      return 1;
1594    }
1595
1596
1597    while ((pos1 >= 0) && (pos2 >= 0))
1598    {
1599      final int compValue = dn1.rdns[pos1].compareTo(dn2.rdns[pos2]);
1600      if (compValue != 0)
1601      {
1602        return compValue;
1603      }
1604
1605      pos1--;
1606      pos2--;
1607    }
1608
1609
1610    // If we've gotten here, then one of the DNs is equal to or a descendant of
1611    // the other.
1612    if (pos1 < 0)
1613    {
1614      if (pos2 < 0)
1615      {
1616        // They're both the same length, so they should be considered equal.
1617        return 0;
1618      }
1619      else
1620      {
1621        // The first is shorter than the second, so it should come first.
1622        return -1;
1623      }
1624    }
1625    else
1626    {
1627      // The second RDN is shorter than the first, so it should come first.
1628      return 1;
1629    }
1630  }
1631
1632
1633
1634  /**
1635   * Compares the DNs with the provided string representations to determine
1636   * their relative order in a sorted list.
1637   *
1638   * @param  s1  The string representation for the first DN to be compared.  It
1639   *             must not be {@code null}.
1640   * @param  s2  The string representation for the second DN to be compared.  It
1641   *             must not be {@code null}.
1642   *
1643   * @return  A negative integer if the first DN should come before the second
1644   *          DN in a sorted list, a positive integer if the first DN should
1645   *          come after the second DN in a sorted list, or zero if the two DN
1646   *          values can be considered equal.
1647   *
1648   * @throws  LDAPException  If either of the provided strings cannot be parsed
1649   *                         as a DN.
1650   */
1651  public static int compare(final String s1, final String s2)
1652         throws LDAPException
1653  {
1654    return compare(s1, s2, null);
1655  }
1656
1657
1658
1659  /**
1660   * Compares the DNs with the provided string representations to determine
1661   * their relative order in a sorted list.
1662   *
1663   * @param  s1      The string representation for the first DN to be compared.
1664   *                 It must not be {@code null}.
1665   * @param  s2      The string representation for the second DN to be compared.
1666   *                 It must not be {@code null}.
1667   * @param  schema  The schema to use to generate the normalized string
1668   *                 representations of the DNs.  It may be {@code null} if no
1669   *                 schema is available.
1670   *
1671   * @return  A negative integer if the first DN should come before the second
1672   *          DN in a sorted list, a positive integer if the first DN should
1673   *          come after the second DN in a sorted list, or zero if the two DN
1674   *          values can be considered equal.
1675   *
1676   * @throws  LDAPException  If either of the provided strings cannot be parsed
1677   *                         as a DN.
1678   */
1679  public static int compare(final String s1, final String s2,
1680                            final Schema schema)
1681         throws LDAPException
1682  {
1683    return new DN(s1, schema).compareTo(new DN(s2, schema));
1684  }
1685}