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}