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