001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Comparator;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1OctetString;
031import com.unboundid.ldap.sdk.schema.Schema;
032import com.unboundid.util.NotMutable;
033import com.unboundid.util.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035
036import static com.unboundid.ldap.sdk.LDAPMessages.*;
037import 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)
111public 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;
320rdnLoop:
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}