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