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