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