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