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