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