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