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