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