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