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