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