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