001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.io.Serializable; 026import java.nio.ByteBuffer; 027import java.util.ArrayList; 028import java.util.Comparator; 029import java.util.Map; 030import java.util.TreeMap; 031 032import com.unboundid.asn1.ASN1OctetString; 033import com.unboundid.ldap.matchingrules.MatchingRule; 034import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 035import com.unboundid.ldap.sdk.schema.Schema; 036import com.unboundid.util.NotMutable; 037import com.unboundid.util.ThreadSafety; 038import com.unboundid.util.ThreadSafetyLevel; 039 040import static com.unboundid.ldap.sdk.LDAPMessages.*; 041import static com.unboundid.util.Debug.*; 042import static com.unboundid.util.StaticUtils.*; 043import static com.unboundid.util.Validator.*; 044 045 046 047/** 048 * This class provides a data structure for holding information about an LDAP 049 * relative distinguished name (RDN). An RDN consists of one or more 050 * attribute name-value pairs. See 051 * <A HREF="http://www.ietf.org/rfc/rfc4514.txt">RFC 4514</A> for more 052 * information about representing DNs and RDNs as strings. See the 053 * documentation in the {@link DN} class for more information about DNs and 054 * RDNs. 055 */ 056@NotMutable() 057@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 058public final class RDN 059 implements Comparable<RDN>, Comparator<RDN>, Serializable 060{ 061 /** 062 * The serial version UID for this serializable class. 063 */ 064 private static final long serialVersionUID = 2923419812807188487L; 065 066 067 068 // The set of attribute values for this RDN. 069 private final ASN1OctetString[] attributeValues; 070 071 // The schema to use to generate the normalized string representation of this 072 // RDN, if any. 073 private final Schema schema; 074 075 // The normalized string representation for this RDN. 076 private volatile String normalizedString; 077 078 // The user-defined string representation for this RDN. 079 private volatile String rdnString; 080 081 // The set of attribute names for this RDN. 082 private final String[] attributeNames; 083 084 085 086 /** 087 * Creates a new single-valued RDN with the provided information. 088 * 089 * @param attributeName The attribute name for this RDN. It must not be 090 * {@code null}. 091 * @param attributeValue The attribute value for this RDN. It must not be 092 * {@code null}. 093 */ 094 public RDN(final String attributeName, final String attributeValue) 095 { 096 this(attributeName, attributeValue, null); 097 } 098 099 100 101 /** 102 * Creates a new single-valued RDN with the provided information. 103 * 104 * @param attributeName The attribute name for this RDN. It must not be 105 * {@code null}. 106 * @param attributeValue The attribute value for this RDN. It must not be 107 * {@code null}. 108 * @param schema The schema to use to generate the normalized string 109 * representation of this RDN. It may be {@code null} 110 * if no schema is available. 111 */ 112 public RDN(final String attributeName, final String attributeValue, 113 final Schema schema) 114 { 115 ensureNotNull(attributeName, attributeValue); 116 117 this.schema = schema; 118 119 attributeNames = new String[] { attributeName }; 120 attributeValues = 121 new ASN1OctetString[] { new ASN1OctetString(attributeValue) }; 122 } 123 124 125 126 /** 127 * Creates a new single-valued RDN with the provided information. 128 * 129 * @param attributeName The attribute name for this RDN. It must not be 130 * {@code null}. 131 * @param attributeValue The attribute value for this RDN. It must not be 132 * {@code null}. 133 */ 134 public RDN(final String attributeName, final byte[] attributeValue) 135 { 136 this(attributeName, attributeValue, null); 137 } 138 139 140 141 /** 142 * Creates a new single-valued RDN with the provided information. 143 * 144 * @param attributeName The attribute name for this RDN. It must not be 145 * {@code null}. 146 * @param attributeValue The attribute value for this RDN. It must not be 147 * {@code null}. 148 * @param schema The schema to use to generate the normalized string 149 * representation of this RDN. It may be {@code null} 150 * if no schema is available. 151 */ 152 public RDN(final String attributeName, final byte[] attributeValue, 153 final Schema schema) 154 { 155 ensureNotNull(attributeName, attributeValue); 156 157 this.schema = schema; 158 159 attributeNames = new String[] { attributeName }; 160 attributeValues = 161 new ASN1OctetString[] { new ASN1OctetString(attributeValue) }; 162 } 163 164 165 166 /** 167 * Creates a new (potentially multivalued) RDN. The set of names must have 168 * the same number of elements as the set of values, and there must be at 169 * least one element in each array. 170 * 171 * @param attributeNames The set of attribute names for this RDN. It must 172 * not be {@code null} or empty. 173 * @param attributeValues The set of attribute values for this RDN. It must 174 * not be {@code null} or empty. 175 */ 176 public RDN(final String[] attributeNames, final String[] attributeValues) 177 { 178 this(attributeNames, attributeValues, null); 179 } 180 181 182 183 /** 184 * Creates a new (potentially multivalued) RDN. The set of names must have 185 * the same number of elements as the set of values, and there must be at 186 * least one element in each array. 187 * 188 * @param attributeNames The set of attribute names for this RDN. It must 189 * not be {@code null} or empty. 190 * @param attributeValues The set of attribute values for this RDN. It must 191 * not be {@code null} or empty. 192 * @param schema The schema to use to generate the normalized 193 * string representation of this RDN. It may be 194 * {@code null} if no schema is available. 195 */ 196 public RDN(final String[] attributeNames, final String[] attributeValues, 197 final Schema schema) 198 { 199 ensureNotNull(attributeNames, attributeValues); 200 ensureTrue(attributeNames.length == attributeValues.length, 201 "RDN.attributeNames and attributeValues must be the same size."); 202 ensureTrue(attributeNames.length > 0, 203 "RDN.attributeNames must not be empty."); 204 205 this.attributeNames = attributeNames; 206 this.schema = schema; 207 208 this.attributeValues = new ASN1OctetString[attributeValues.length]; 209 for (int i=0; i < attributeValues.length; i++) 210 { 211 this.attributeValues[i] = new ASN1OctetString(attributeValues[i]); 212 } 213 } 214 215 216 217 /** 218 * Creates a new (potentially multivalued) RDN. The set of names must have 219 * the same number of elements as the set of values, and there must be at 220 * least one element in each array. 221 * 222 * @param attributeNames The set of attribute names for this RDN. It must 223 * not be {@code null} or empty. 224 * @param attributeValues The set of attribute values for this RDN. It must 225 * not be {@code null} or empty. 226 */ 227 public RDN(final String[] attributeNames, final byte[][] attributeValues) 228 { 229 this(attributeNames, attributeValues, null); 230 } 231 232 233 234 /** 235 * Creates a new (potentially multivalued) RDN. The set of names must have 236 * the same number of elements as the set of values, and there must be at 237 * least one element in each array. 238 * 239 * @param attributeNames The set of attribute names for this RDN. It must 240 * not be {@code null} or empty. 241 * @param attributeValues The set of attribute values for this RDN. It must 242 * not be {@code null} or empty. 243 * @param schema The schema to use to generate the normalized 244 * string representation of this RDN. It may be 245 * {@code null} if no schema is available. 246 */ 247 public RDN(final String[] attributeNames, final byte[][] attributeValues, 248 final Schema schema) 249 { 250 ensureNotNull(attributeNames, attributeValues); 251 ensureTrue(attributeNames.length == attributeValues.length, 252 "RDN.attributeNames and attributeValues must be the same size."); 253 ensureTrue(attributeNames.length > 0, 254 "RDN.attributeNames must not be empty."); 255 256 this.attributeNames = attributeNames; 257 this.schema = schema; 258 259 this.attributeValues = new ASN1OctetString[attributeValues.length]; 260 for (int i=0; i < attributeValues.length; i++) 261 { 262 this.attributeValues[i] = new ASN1OctetString(attributeValues[i]); 263 } 264 } 265 266 267 268 /** 269 * Creates a new single-valued RDN with the provided information. 270 * 271 * @param attributeName The name to use for this RDN. 272 * @param attributeValue The value to use for this RDN. 273 * @param schema The schema to use to generate the normalized string 274 * representation of this RDN. It may be {@code null} 275 * if no schema is available. 276 * @param rdnString The string representation for this RDN. 277 */ 278 RDN(final String attributeName, final ASN1OctetString attributeValue, 279 final Schema schema, final String rdnString) 280 { 281 this.rdnString = rdnString; 282 this.schema = schema; 283 284 attributeNames = new String[] { attributeName }; 285 attributeValues = new ASN1OctetString[] { attributeValue }; 286 } 287 288 289 290 /** 291 * Creates a new potentially multivalued RDN with the provided information. 292 * 293 * @param attributeNames The set of names to use for this RDN. 294 * @param attributeValues The set of values to use for this RDN. 295 * @param rdnString The string representation for this RDN. 296 * @param schema The schema to use to generate the normalized 297 * string representation of this RDN. It may be 298 * {@code null} if no schema is available. 299 */ 300 RDN(final String[] attributeNames, final ASN1OctetString[] attributeValues, 301 final Schema schema, final String rdnString) 302 { 303 this.rdnString = rdnString; 304 this.schema = schema; 305 306 this.attributeNames = attributeNames; 307 this.attributeValues = attributeValues; 308 } 309 310 311 312 /** 313 * Creates a new RDN from the provided string representation. 314 * 315 * @param rdnString The string representation to use for this RDN. It must 316 * not be empty or {@code null}. 317 * 318 * @throws LDAPException If the provided string cannot be parsed as a valid 319 * RDN. 320 */ 321 public RDN(final String rdnString) 322 throws LDAPException 323 { 324 this(rdnString, (Schema) null); 325 } 326 327 328 329 /** 330 * Creates a new RDN from the provided string representation. 331 * 332 * @param rdnString The string representation to use for this RDN. It must 333 * not be empty or {@code null}. 334 * @param schema The schema to use to generate the normalized string 335 * representation of this RDN. It may be {@code null} if 336 * no schema is available. 337 * 338 * @throws LDAPException If the provided string cannot be parsed as a valid 339 * RDN. 340 */ 341 public RDN(final String rdnString, final Schema schema) 342 throws LDAPException 343 { 344 ensureNotNull(rdnString); 345 346 this.rdnString = rdnString; 347 this.schema = schema; 348 349 int pos = 0; 350 final int length = rdnString.length(); 351 352 // First, skip over any leading spaces. 353 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 354 { 355 pos++; 356 } 357 358 // Read until we find a space or an equal sign. Technically, we should 359 // ensure that all characters before that point are ASCII letters, numeric 360 // digits, or dashes, or that it is a valid numeric OID, but since some 361 // directories allow technically invalid characters in attribute names, 362 // we'll just blindly take whatever is provided. 363 int attrStartPos = pos; 364 while (pos < length) 365 { 366 final char c = rdnString.charAt(pos); 367 if ((c == ' ') || (c == '=')) 368 { 369 break; 370 } 371 372 pos++; 373 } 374 375 // Extract the attribute name, then skip over any spaces between the 376 // attribute name and the equal sign. 377 String attrName = rdnString.substring(attrStartPos, pos); 378 if (attrName.length() == 0) 379 { 380 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 381 ERR_RDN_NO_ATTR_NAME.get(rdnString)); 382 } 383 384 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 385 { 386 pos++; 387 } 388 389 if ((pos >= length) || (rdnString.charAt(pos) != '=')) 390 { 391 // We didn't find an equal sign. 392 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 393 ERR_RDN_NO_EQUAL_SIGN.get(rdnString, attrName)); 394 } 395 396 397 // The next character is the equal sign. Skip it, and then skip over any 398 // spaces between it and the attribute value. 399 pos++; 400 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 401 { 402 pos++; 403 } 404 405 406 // Look at the next character. If it is an octothorpe (#), then the value 407 // must be a hex-encoded BER element, which we'll need to parse and take the 408 // value of that element. Otherwise, it's a regular string (although 409 // possibly containing escaped or quoted characters). 410 ASN1OctetString value; 411 if (pos >= length) 412 { 413 value = new ASN1OctetString(); 414 } 415 else if (rdnString.charAt(pos) == '#') 416 { 417 // It is a hex-encoded value, so we'll read until we find the end of the 418 // string or the first non-hex character, which must be either a space or 419 // a plus sign. 420 final byte[] valueArray = readHexString(rdnString, ++pos); 421 422 try 423 { 424 value = ASN1OctetString.decodeAsOctetString(valueArray); 425 } 426 catch (final Exception e) 427 { 428 debugException(e); 429 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 430 ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(rdnString, attrName), e); 431 } 432 433 pos += (valueArray.length * 2); 434 } 435 else 436 { 437 // It is a string value, which potentially includes escaped characters. 438 final StringBuilder buffer = new StringBuilder(); 439 pos = readValueString(rdnString, pos, buffer); 440 value = new ASN1OctetString(buffer.toString()); 441 } 442 443 444 // Skip over any spaces until we find a plus sign or the end of the value. 445 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 446 { 447 pos++; 448 } 449 450 if (pos >= length) 451 { 452 // It's a single-valued RDN, so we have everything that we need. 453 attributeNames = new String[] { attrName }; 454 attributeValues = new ASN1OctetString[] { value }; 455 return; 456 } 457 458 // It's a multivalued RDN, so create temporary lists to hold the names and 459 // values. 460 final ArrayList<String> nameList = new ArrayList<String>(5); 461 final ArrayList<ASN1OctetString> valueList = 462 new ArrayList<ASN1OctetString>(5); 463 nameList.add(attrName); 464 valueList.add(value); 465 466 if (rdnString.charAt(pos) == '+') 467 { 468 pos++; 469 } 470 else 471 { 472 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 473 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString)); 474 } 475 476 if (pos >= length) 477 { 478 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 479 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString)); 480 } 481 482 int numValues = 1; 483 while (pos < length) 484 { 485 // Skip over any spaces between the plus sign and the attribute name. 486 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 487 { 488 pos++; 489 } 490 491 attrStartPos = pos; 492 while (pos < length) 493 { 494 final char c = rdnString.charAt(pos); 495 if ((c == ' ') || (c == '=')) 496 { 497 break; 498 } 499 500 pos++; 501 } 502 503 // Skip over any spaces between the attribute name and the equal sign. 504 attrName = rdnString.substring(attrStartPos, pos); 505 if (attrName.length() == 0) 506 { 507 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 508 ERR_RDN_NO_ATTR_NAME.get(rdnString)); 509 } 510 511 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 512 { 513 pos++; 514 } 515 516 if ((pos >= length) || (rdnString.charAt(pos) != '=')) 517 { 518 // We didn't find an equal sign. 519 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 520 ERR_RDN_NO_EQUAL_SIGN.get(rdnString, attrName)); 521 } 522 523 // The next character is the equal sign. Skip it, and then skip over any 524 // spaces between it and the attribute value. 525 pos++; 526 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 527 { 528 pos++; 529 } 530 531 // Look at the next character. If it is an octothorpe (#), then the value 532 // must be a hex-encoded BER element, which we'll need to parse and take 533 // the value of that element. Otherwise, it's a regular string (although 534 // possibly containing escaped or quoted characters). 535 if (pos >= length) 536 { 537 value = new ASN1OctetString(); 538 } 539 else if (rdnString.charAt(pos) == '#') 540 { 541 // It is a hex-encoded value, so we'll read until we find the end of the 542 // string or the first non-hex character, which must be either a space 543 // or a plus sign. 544 final byte[] valueArray = readHexString(rdnString, ++pos); 545 546 try 547 { 548 value = ASN1OctetString.decodeAsOctetString(valueArray); 549 } 550 catch (final Exception e) 551 { 552 debugException(e); 553 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 554 ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(rdnString, attrName), e); 555 } 556 557 pos += (valueArray.length * 2); 558 } 559 else 560 { 561 // It is a string value, which potentially includes escaped characters. 562 final StringBuilder buffer = new StringBuilder(); 563 pos = readValueString(rdnString, pos, buffer); 564 value = new ASN1OctetString(buffer.toString()); 565 } 566 567 568 // Skip over any spaces until we find a plus sign or the end of the value. 569 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 570 { 571 pos++; 572 } 573 574 nameList.add(attrName); 575 valueList.add(value); 576 numValues++; 577 578 if (pos >= length) 579 { 580 // We're at the end of the value, so break out of the loop. 581 break; 582 } 583 else 584 { 585 // Skip over the plus sign and loop again to read another name-value 586 // pair. 587 if (rdnString.charAt(pos) == '+') 588 { 589 pos++; 590 } 591 else 592 { 593 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 594 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString)); 595 } 596 } 597 598 if (pos >= length) 599 { 600 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 601 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString)); 602 } 603 } 604 605 attributeNames = new String[numValues]; 606 attributeValues = new ASN1OctetString[numValues]; 607 for (int i=0; i < numValues; i++) 608 { 609 attributeNames[i] = nameList.get(i); 610 attributeValues[i] = valueList.get(i); 611 } 612 } 613 614 615 616 /** 617 * Parses a hex-encoded RDN value from the provided string. Reading will 618 * continue until the end of the string is reached or a non-escaped plus sign 619 * is encountered. After returning, the caller should increment its position 620 * by two times the length of the value array. 621 * 622 * @param rdnString The string to be parsed. It should be the position 623 * immediately after the octothorpe at the start of the 624 * hex-encoded value. 625 * @param startPos The position at which to start reading the value. 626 * 627 * @return A byte array containing the parsed value. 628 * 629 * @throws LDAPException If an error occurs while reading the value (e.g., 630 * if it contains non-hex characters, or has an odd 631 * number of characters. 632 */ 633 static byte[] readHexString(final String rdnString, final int startPos) 634 throws LDAPException 635 { 636 final int length = rdnString.length(); 637 int pos = startPos; 638 639 final ByteBuffer buffer = ByteBuffer.allocate(length-pos); 640hexLoop: 641 while (pos < length) 642 { 643 final byte hexByte; 644 switch (rdnString.charAt(pos++)) 645 { 646 case '0': 647 hexByte = 0x00; 648 break; 649 case '1': 650 hexByte = 0x10; 651 break; 652 case '2': 653 hexByte = 0x20; 654 break; 655 case '3': 656 hexByte = 0x30; 657 break; 658 case '4': 659 hexByte = 0x40; 660 break; 661 case '5': 662 hexByte = 0x50; 663 break; 664 case '6': 665 hexByte = 0x60; 666 break; 667 case '7': 668 hexByte = 0x70; 669 break; 670 case '8': 671 hexByte = (byte) 0x80; 672 break; 673 case '9': 674 hexByte = (byte) 0x90; 675 break; 676 case 'a': 677 case 'A': 678 hexByte = (byte) 0xA0; 679 break; 680 case 'b': 681 case 'B': 682 hexByte = (byte) 0xB0; 683 break; 684 case 'c': 685 case 'C': 686 hexByte = (byte) 0xC0; 687 break; 688 case 'd': 689 case 'D': 690 hexByte = (byte) 0xD0; 691 break; 692 case 'e': 693 case 'E': 694 hexByte = (byte) 0xE0; 695 break; 696 case 'f': 697 case 'F': 698 hexByte = (byte) 0xF0; 699 break; 700 case ' ': 701 case '+': 702 case ',': 703 case ';': 704 // This indicates that we've reached the end of the hex string. 705 break hexLoop; 706 default: 707 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 708 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 709 (pos-1))); 710 } 711 712 if (pos >= length) 713 { 714 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 715 ERR_RDN_MISSING_HEX_CHAR.get(rdnString)); 716 } 717 718 switch (rdnString.charAt(pos++)) 719 { 720 case '0': 721 buffer.put(hexByte); 722 break; 723 case '1': 724 buffer.put((byte) (hexByte | 0x01)); 725 break; 726 case '2': 727 buffer.put((byte) (hexByte | 0x02)); 728 break; 729 case '3': 730 buffer.put((byte) (hexByte | 0x03)); 731 break; 732 case '4': 733 buffer.put((byte) (hexByte | 0x04)); 734 break; 735 case '5': 736 buffer.put((byte) (hexByte | 0x05)); 737 break; 738 case '6': 739 buffer.put((byte) (hexByte | 0x06)); 740 break; 741 case '7': 742 buffer.put((byte) (hexByte | 0x07)); 743 break; 744 case '8': 745 buffer.put((byte) (hexByte | 0x08)); 746 break; 747 case '9': 748 buffer.put((byte) (hexByte | 0x09)); 749 break; 750 case 'a': 751 case 'A': 752 buffer.put((byte) (hexByte | 0x0A)); 753 break; 754 case 'b': 755 case 'B': 756 buffer.put((byte) (hexByte | 0x0B)); 757 break; 758 case 'c': 759 case 'C': 760 buffer.put((byte) (hexByte | 0x0C)); 761 break; 762 case 'd': 763 case 'D': 764 buffer.put((byte) (hexByte | 0x0D)); 765 break; 766 case 'e': 767 case 'E': 768 buffer.put((byte) (hexByte | 0x0E)); 769 break; 770 case 'f': 771 case 'F': 772 buffer.put((byte) (hexByte | 0x0F)); 773 break; 774 default: 775 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 776 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 777 (pos-1))); 778 } 779 } 780 781 buffer.flip(); 782 final byte[] valueArray = new byte[buffer.limit()]; 783 buffer.get(valueArray); 784 return valueArray; 785 } 786 787 788 789 /** 790 * Reads a string value from the provided RDN string. Reading will continue 791 * until the end of the string is reached or until a non-escaped plus sign is 792 * encountered. 793 * 794 * @param rdnString The string from which to read the value. 795 * @param startPos The position in the RDN string at which to start reading 796 * the value. 797 * @param buffer The buffer into which the parsed value should be 798 * placed. 799 * 800 * @return The position at which the caller should continue reading when 801 * parsing the RDN. 802 * 803 * @throws LDAPException If a problem occurs while reading the value. 804 */ 805 static int readValueString(final String rdnString, final int startPos, 806 final StringBuilder buffer) 807 throws LDAPException 808 { 809 final int length = rdnString.length(); 810 int pos = startPos; 811 812 boolean inQuotes = false; 813valueLoop: 814 while (pos < length) 815 { 816 char c = rdnString.charAt(pos); 817 switch (c) 818 { 819 case '\\': 820 // It's an escaped value. It can either be followed by a single 821 // character (e.g., backslash, space, octothorpe, equals, double 822 // quote, plus sign, comma, semicolon, less than, or greater-than), or 823 // two hex digits. If it is followed by hex digits, then continue 824 // reading to see if there are more of them. 825 if ((pos+1) >= length) 826 { 827 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 828 ERR_RDN_ENDS_WITH_BACKSLASH.get(rdnString)); 829 } 830 else 831 { 832 pos++; 833 c = rdnString.charAt(pos); 834 if (isHex(c)) 835 { 836 // We need to subtract one from the resulting position because 837 // it will be incremented later. 838 pos = readEscapedHexString(rdnString, pos, buffer) - 1; 839 } 840 else 841 { 842 buffer.append(c); 843 } 844 } 845 break; 846 847 case '"': 848 if (inQuotes) 849 { 850 // This should be the end of the value. If it's not, then fail. 851 pos++; 852 while (pos < length) 853 { 854 c = rdnString.charAt(pos); 855 if ((c == '+') || (c == ',') || (c == ';')) 856 { 857 break; 858 } 859 else if (c != ' ') 860 { 861 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 862 ERR_RDN_CHAR_OUTSIDE_QUOTES.get(rdnString, c, (pos-1))); 863 } 864 865 pos++; 866 } 867 868 inQuotes = false; 869 break valueLoop; 870 } 871 else 872 { 873 // This should be the first character of the value. 874 if (pos == startPos) 875 { 876 inQuotes = true; 877 } 878 else 879 { 880 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 881 ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(rdnString, pos)); 882 } 883 } 884 break; 885 886 case ',': 887 case ';': 888 case '+': 889 // This denotes the end of the value, if it's not in quotes. 890 if (inQuotes) 891 { 892 buffer.append(c); 893 } 894 else 895 { 896 break valueLoop; 897 } 898 break; 899 900 default: 901 // This is a normal character that should be added to the buffer. 902 buffer.append(c); 903 break; 904 } 905 906 pos++; 907 } 908 909 910 // If the value started with a quotation mark, then make sure it was closed. 911 if (inQuotes) 912 { 913 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 914 ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get(rdnString)); 915 } 916 917 918 // If the value ends with any unescaped trailing spaces, then trim them off. 919 int bufferPos = buffer.length() - 1; 920 int rdnStrPos = pos - 2; 921 while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' ')) 922 { 923 if (rdnString.charAt(rdnStrPos) == '\\') 924 { 925 break; 926 } 927 else 928 { 929 buffer.deleteCharAt(bufferPos--); 930 rdnStrPos--; 931 } 932 } 933 934 return pos; 935 } 936 937 938 939 /** 940 * Reads one or more hex-encoded bytes from the specified portion of the RDN 941 * string. 942 * 943 * @param rdnString The string from which the data is to be read. 944 * @param startPos The position at which to start reading. This should be 945 * the first hex character immediately after the initial 946 * backslash. 947 * @param buffer The buffer to which the decoded string portion should be 948 * appended. 949 * 950 * @return The position at which the caller may resume parsing. 951 * 952 * @throws LDAPException If a problem occurs while reading hex-encoded 953 * bytes. 954 */ 955 private static int readEscapedHexString(final String rdnString, 956 final int startPos, 957 final StringBuilder buffer) 958 throws LDAPException 959 { 960 final int length = rdnString.length(); 961 int pos = startPos; 962 963 final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos); 964 while (pos < length) 965 { 966 final byte b; 967 switch (rdnString.charAt(pos++)) 968 { 969 case '0': 970 b = 0x00; 971 break; 972 case '1': 973 b = 0x10; 974 break; 975 case '2': 976 b = 0x20; 977 break; 978 case '3': 979 b = 0x30; 980 break; 981 case '4': 982 b = 0x40; 983 break; 984 case '5': 985 b = 0x50; 986 break; 987 case '6': 988 b = 0x60; 989 break; 990 case '7': 991 b = 0x70; 992 break; 993 case '8': 994 b = (byte) 0x80; 995 break; 996 case '9': 997 b = (byte) 0x90; 998 break; 999 case 'a': 1000 case 'A': 1001 b = (byte) 0xA0; 1002 break; 1003 case 'b': 1004 case 'B': 1005 b = (byte) 0xB0; 1006 break; 1007 case 'c': 1008 case 'C': 1009 b = (byte) 0xC0; 1010 break; 1011 case 'd': 1012 case 'D': 1013 b = (byte) 0xD0; 1014 break; 1015 case 'e': 1016 case 'E': 1017 b = (byte) 0xE0; 1018 break; 1019 case 'f': 1020 case 'F': 1021 b = (byte) 0xF0; 1022 break; 1023 default: 1024 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1025 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 1026 (pos-1))); 1027 } 1028 1029 if (pos >= length) 1030 { 1031 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1032 ERR_RDN_MISSING_HEX_CHAR.get(rdnString)); 1033 } 1034 1035 switch (rdnString.charAt(pos++)) 1036 { 1037 case '0': 1038 byteBuffer.put(b); 1039 break; 1040 case '1': 1041 byteBuffer.put((byte) (b | 0x01)); 1042 break; 1043 case '2': 1044 byteBuffer.put((byte) (b | 0x02)); 1045 break; 1046 case '3': 1047 byteBuffer.put((byte) (b | 0x03)); 1048 break; 1049 case '4': 1050 byteBuffer.put((byte) (b | 0x04)); 1051 break; 1052 case '5': 1053 byteBuffer.put((byte) (b | 0x05)); 1054 break; 1055 case '6': 1056 byteBuffer.put((byte) (b | 0x06)); 1057 break; 1058 case '7': 1059 byteBuffer.put((byte) (b | 0x07)); 1060 break; 1061 case '8': 1062 byteBuffer.put((byte) (b | 0x08)); 1063 break; 1064 case '9': 1065 byteBuffer.put((byte) (b | 0x09)); 1066 break; 1067 case 'a': 1068 case 'A': 1069 byteBuffer.put((byte) (b | 0x0A)); 1070 break; 1071 case 'b': 1072 case 'B': 1073 byteBuffer.put((byte) (b | 0x0B)); 1074 break; 1075 case 'c': 1076 case 'C': 1077 byteBuffer.put((byte) (b | 0x0C)); 1078 break; 1079 case 'd': 1080 case 'D': 1081 byteBuffer.put((byte) (b | 0x0D)); 1082 break; 1083 case 'e': 1084 case 'E': 1085 byteBuffer.put((byte) (b | 0x0E)); 1086 break; 1087 case 'f': 1088 case 'F': 1089 byteBuffer.put((byte) (b | 0x0F)); 1090 break; 1091 default: 1092 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1093 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 1094 (pos-1))); 1095 } 1096 1097 if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') && 1098 isHex(rdnString.charAt(pos+1))) 1099 { 1100 // It appears that there are more hex-encoded bytes to follow, so keep 1101 // reading. 1102 pos++; 1103 continue; 1104 } 1105 else 1106 { 1107 break; 1108 } 1109 } 1110 1111 byteBuffer.flip(); 1112 final byte[] byteArray = new byte[byteBuffer.limit()]; 1113 byteBuffer.get(byteArray); 1114 1115 try 1116 { 1117 buffer.append(toUTF8String(byteArray)); 1118 } 1119 catch (final Exception e) 1120 { 1121 debugException(e); 1122 // This should never happen. 1123 buffer.append(new String(byteArray)); 1124 } 1125 1126 return pos; 1127 } 1128 1129 1130 1131 /** 1132 * Indicates whether the provided string represents a valid RDN. 1133 * 1134 * @param s The string for which to make the determination. It must not be 1135 * {@code null}. 1136 * 1137 * @return {@code true} if the provided string represents a valid RDN, or 1138 * {@code false} if not. 1139 */ 1140 public static boolean isValidRDN(final String s) 1141 { 1142 try 1143 { 1144 new RDN(s); 1145 return true; 1146 } 1147 catch (final LDAPException le) 1148 { 1149 return false; 1150 } 1151 } 1152 1153 1154 1155 /** 1156 * Indicates whether this RDN contains multiple components. 1157 * 1158 * @return {@code true} if this RDN contains multiple components, or 1159 * {@code false} if not. 1160 */ 1161 public boolean isMultiValued() 1162 { 1163 return (attributeNames.length != 1); 1164 } 1165 1166 1167 1168 /** 1169 * Retrieves an array of the attributes that comprise this RDN. 1170 * 1171 * @return An array of the attributes that comprise this RDN. 1172 */ 1173 public Attribute[] getAttributes() 1174 { 1175 final Attribute[] attrs = new Attribute[attributeNames.length]; 1176 for (int i=0; i < attrs.length; i++) 1177 { 1178 attrs[i] = new Attribute(attributeNames[i], schema, 1179 new ASN1OctetString[] { attributeValues[i] }); 1180 } 1181 1182 return attrs; 1183 } 1184 1185 1186 1187 /** 1188 * Retrieves the set of attribute names for this RDN. 1189 * 1190 * @return The set of attribute names for this RDN. 1191 */ 1192 public String[] getAttributeNames() 1193 { 1194 return attributeNames; 1195 } 1196 1197 1198 1199 /** 1200 * Retrieves the set of attribute values for this RDN. 1201 * 1202 * @return The set of attribute values for this RDN. 1203 */ 1204 public String[] getAttributeValues() 1205 { 1206 final String[] stringValues = new String[attributeValues.length]; 1207 for (int i=0; i < stringValues.length; i++) 1208 { 1209 stringValues[i] = attributeValues[i].stringValue(); 1210 } 1211 1212 return stringValues; 1213 } 1214 1215 1216 1217 /** 1218 * Retrieves the set of attribute values for this RDN. 1219 * 1220 * @return The set of attribute values for this RDN. 1221 */ 1222 public byte[][] getByteArrayAttributeValues() 1223 { 1224 final byte[][] byteValues = new byte[attributeValues.length][]; 1225 for (int i=0; i < byteValues.length; i++) 1226 { 1227 byteValues[i] = attributeValues[i].getValue(); 1228 } 1229 1230 return byteValues; 1231 } 1232 1233 1234 1235 /** 1236 * Retrieves the schema that will be used for this RDN, if any. 1237 * 1238 * @return The schema that will be used for this RDN, or {@code null} if none 1239 * has been provided. 1240 */ 1241 Schema getSchema() 1242 { 1243 return schema; 1244 } 1245 1246 1247 1248 /** 1249 * Indicates whether this RDN contains the specified attribute. 1250 * 1251 * @param attributeName The name of the attribute for which to make the 1252 * determination. 1253 * 1254 * @return {@code true} if RDN contains the specified attribute, or 1255 * {@code false} if not. 1256 */ 1257 public boolean hasAttribute(final String attributeName) 1258 { 1259 for (final String name : attributeNames) 1260 { 1261 if (name.equalsIgnoreCase(attributeName)) 1262 { 1263 return true; 1264 } 1265 } 1266 1267 return false; 1268 } 1269 1270 1271 1272 /** 1273 * Indicates whether this RDN contains the specified attribute value. 1274 * 1275 * @param attributeName The name of the attribute for which to make the 1276 * determination. 1277 * @param attributeValue The attribute value for which to make the 1278 * determination. 1279 * 1280 * @return {@code true} if RDN contains the specified attribute, or 1281 * {@code false} if not. 1282 */ 1283 public boolean hasAttributeValue(final String attributeName, 1284 final String attributeValue) 1285 { 1286 for (int i=0; i < attributeNames.length; i++) 1287 { 1288 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1289 { 1290 final Attribute a = 1291 new Attribute(attributeName, schema, attributeValue); 1292 final Attribute b = new Attribute(attributeName, schema, 1293 attributeValues[i].stringValue()); 1294 1295 if (a.equals(b)) 1296 { 1297 return true; 1298 } 1299 } 1300 } 1301 1302 return false; 1303 } 1304 1305 1306 1307 /** 1308 * Indicates whether this RDN contains the specified attribute value. 1309 * 1310 * @param attributeName The name of the attribute for which to make the 1311 * determination. 1312 * @param attributeValue The attribute value for which to make the 1313 * determination. 1314 * 1315 * @return {@code true} if RDN contains the specified attribute, or 1316 * {@code false} if not. 1317 */ 1318 public boolean hasAttributeValue(final String attributeName, 1319 final byte[] attributeValue) 1320 { 1321 for (int i=0; i < attributeNames.length; i++) 1322 { 1323 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1324 { 1325 final Attribute a = 1326 new Attribute(attributeName, schema, attributeValue); 1327 final Attribute b = new Attribute(attributeName, schema, 1328 attributeValues[i].getValue()); 1329 1330 if (a.equals(b)) 1331 { 1332 return true; 1333 } 1334 } 1335 } 1336 1337 return false; 1338 } 1339 1340 1341 1342 /** 1343 * Retrieves a string representation of this RDN. 1344 * 1345 * @return A string representation of this RDN. 1346 */ 1347 @Override() 1348 public String toString() 1349 { 1350 if (rdnString == null) 1351 { 1352 final StringBuilder buffer = new StringBuilder(); 1353 toString(buffer, false); 1354 rdnString = buffer.toString(); 1355 } 1356 1357 return rdnString; 1358 } 1359 1360 1361 1362 /** 1363 * Retrieves a string representation of this RDN with minimal encoding for 1364 * special characters. Only those characters specified in RFC 4514 section 1365 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or 1366 * non-printable ASCII characters. 1367 * 1368 * @return A string representation of this RDN with minimal encoding for 1369 * special characters. 1370 */ 1371 public String toMinimallyEncodedString() 1372 { 1373 final StringBuilder buffer = new StringBuilder(); 1374 toString(buffer, true); 1375 return buffer.toString(); 1376 } 1377 1378 1379 1380 /** 1381 * Appends a string representation of this RDN to the provided buffer. 1382 * 1383 * @param buffer The buffer to which the string representation is to be 1384 * appended. 1385 */ 1386 public void toString(final StringBuilder buffer) 1387 { 1388 toString(buffer, false); 1389 } 1390 1391 1392 1393 /** 1394 * Appends a string representation of this RDN to the provided buffer. 1395 * 1396 * @param buffer The buffer to which the string representation is 1397 * to be appended. 1398 * @param minimizeEncoding Indicates whether to restrict the encoding of 1399 * special characters to the bare minimum required 1400 * by LDAP (as per RFC 4514 section 2.4). If this 1401 * is {@code true}, then only leading and trailing 1402 * spaces, double quotes, plus signs, commas, 1403 * semicolons, greater-than, less-than, and 1404 * backslash characters will be encoded. 1405 */ 1406 public void toString(final StringBuilder buffer, 1407 final boolean minimizeEncoding) 1408 { 1409 if ((rdnString != null) && (! minimizeEncoding)) 1410 { 1411 buffer.append(rdnString); 1412 return; 1413 } 1414 1415 for (int i=0; i < attributeNames.length; i++) 1416 { 1417 if (i > 0) 1418 { 1419 buffer.append('+'); 1420 } 1421 1422 buffer.append(attributeNames[i]); 1423 buffer.append('='); 1424 1425 // Iterate through the value character-by-character and do any escaping 1426 // that may be necessary. 1427 final String valueString = attributeValues[i].stringValue(); 1428 final int length = valueString.length(); 1429 for (int j=0; j < length; j++) 1430 { 1431 final char c = valueString.charAt(j); 1432 switch (c) 1433 { 1434 case '\\': 1435 case '=': 1436 case '"': 1437 case '+': 1438 case ',': 1439 case ';': 1440 case '<': 1441 case '>': 1442 buffer.append('\\'); 1443 buffer.append(c); 1444 break; 1445 1446 case '#': 1447 // Escape the octothorpe only if it's the first character. 1448 if (j == 0) 1449 { 1450 buffer.append("\\#"); 1451 } 1452 else 1453 { 1454 buffer.append('#'); 1455 } 1456 break; 1457 1458 case ' ': 1459 // Escape this space only if it's the first or last character. 1460 if ((j == 0) || ((j+1) == length)) 1461 { 1462 buffer.append("\\ "); 1463 } 1464 else 1465 { 1466 buffer.append(' '); 1467 } 1468 break; 1469 1470 case '\u0000': 1471 buffer.append("\\00"); 1472 break; 1473 1474 default: 1475 // If it's not a printable ASCII character, then hex-encode it 1476 // unless we're using minimized encoding. 1477 if ((! minimizeEncoding) && ((c < ' ') || (c > '~'))) 1478 { 1479 hexEncode(c, buffer); 1480 } 1481 else 1482 { 1483 buffer.append(c); 1484 } 1485 break; 1486 } 1487 } 1488 } 1489 } 1490 1491 1492 1493 /** 1494 * Retrieves a normalized string representation of this RDN. 1495 * 1496 * @return A normalized string representation of this RDN. 1497 */ 1498 public String toNormalizedString() 1499 { 1500 if (normalizedString == null) 1501 { 1502 final StringBuilder buffer = new StringBuilder(); 1503 toNormalizedString(buffer); 1504 normalizedString = buffer.toString(); 1505 } 1506 1507 return normalizedString; 1508 } 1509 1510 1511 1512 /** 1513 * Appends a normalized string representation of this RDN to the provided 1514 * buffer. 1515 * 1516 * @param buffer The buffer to which the normalized string representation is 1517 * to be appended. 1518 */ 1519 public void toNormalizedString(final StringBuilder buffer) 1520 { 1521 if (attributeNames.length == 1) 1522 { 1523 // It's a single-valued RDN, so there is no need to sort anything. 1524 final String name = normalizeAttrName(attributeNames[0]); 1525 buffer.append(name); 1526 buffer.append('='); 1527 buffer.append(normalizeValue(name, attributeValues[0])); 1528 } 1529 else 1530 { 1531 // It's a multivalued RDN, so we need to sort the components. 1532 final TreeMap<String,ASN1OctetString> valueMap = 1533 new TreeMap<String,ASN1OctetString>(); 1534 for (int i=0; i < attributeNames.length; i++) 1535 { 1536 final String name = normalizeAttrName(attributeNames[i]); 1537 valueMap.put(name, attributeValues[i]); 1538 } 1539 1540 int i=0; 1541 for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet()) 1542 { 1543 if (i++ > 0) 1544 { 1545 buffer.append('+'); 1546 } 1547 1548 buffer.append(entry.getKey()); 1549 buffer.append('='); 1550 buffer.append(normalizeValue(entry.getKey(), entry.getValue())); 1551 } 1552 } 1553 } 1554 1555 1556 1557 /** 1558 * Obtains a normalized representation of the provided attribute name. 1559 * 1560 * @param name The name of the attribute for which to create the normalized 1561 * representation. 1562 * 1563 * @return A normalized representation of the provided attribute name. 1564 */ 1565 private String normalizeAttrName(final String name) 1566 { 1567 String n = name; 1568 if (schema != null) 1569 { 1570 final AttributeTypeDefinition at = schema.getAttributeType(name); 1571 if (at != null) 1572 { 1573 n = at.getNameOrOID(); 1574 } 1575 } 1576 return toLowerCase(n); 1577 } 1578 1579 1580 1581 /** 1582 * Retrieves a normalized string representation of the RDN with the provided 1583 * string representation. 1584 * 1585 * @param s The string representation of the RDN to normalize. It must not 1586 * be {@code null}. 1587 * 1588 * @return The normalized string representation of the RDN with the provided 1589 * string representation. 1590 * 1591 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1592 */ 1593 public static String normalize(final String s) 1594 throws LDAPException 1595 { 1596 return normalize(s, null); 1597 } 1598 1599 1600 1601 /** 1602 * Retrieves a normalized string representation of the RDN with the provided 1603 * string representation. 1604 * 1605 * @param s The string representation of the RDN to normalize. It must 1606 * not be {@code null}. 1607 * @param schema The schema to use to generate the normalized string 1608 * representation of the RDN. It may be {@code null} if no 1609 * schema is available. 1610 * 1611 * @return The normalized string representation of the RDN with the provided 1612 * string representation. 1613 * 1614 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1615 */ 1616 public static String normalize(final String s, final Schema schema) 1617 throws LDAPException 1618 { 1619 return new RDN(s, schema).toNormalizedString(); 1620 } 1621 1622 1623 1624 /** 1625 * Normalizes the provided attribute value for use in an RDN. 1626 * 1627 * @param attributeName The name of the attribute with which the value is 1628 * associated. 1629 * @param value The value to be normalized. 1630 * 1631 * @return A string builder containing a normalized representation of the 1632 * value in a suitable form for inclusion in an RDN. 1633 */ 1634 private StringBuilder normalizeValue(final String attributeName, 1635 final ASN1OctetString value) 1636 { 1637 final MatchingRule matchingRule = 1638 MatchingRule.selectEqualityMatchingRule(attributeName, schema); 1639 1640 ASN1OctetString rawNormValue; 1641 try 1642 { 1643 rawNormValue = matchingRule.normalize(value); 1644 } 1645 catch (final Exception e) 1646 { 1647 debugException(e); 1648 rawNormValue = 1649 new ASN1OctetString(toLowerCase(value.stringValue())); 1650 } 1651 1652 final String valueString = rawNormValue.stringValue(); 1653 final int length = valueString.length(); 1654 final StringBuilder buffer = new StringBuilder(length); 1655 1656 for (int i=0; i < length; i++) 1657 { 1658 final char c = valueString.charAt(i); 1659 1660 switch (c) 1661 { 1662 case '\\': 1663 case '=': 1664 case '"': 1665 case '+': 1666 case ',': 1667 case ';': 1668 case '<': 1669 case '>': 1670 buffer.append('\\'); 1671 buffer.append(c); 1672 break; 1673 1674 case '#': 1675 // Escape the octothorpe only if it's the first character. 1676 if (i == 0) 1677 { 1678 buffer.append("\\#"); 1679 } 1680 else 1681 { 1682 buffer.append('#'); 1683 } 1684 break; 1685 1686 case ' ': 1687 // Escape this space only if it's the first or last character. 1688 if ((i == 0) || ((i+1) == length)) 1689 { 1690 buffer.append("\\ "); 1691 } 1692 else 1693 { 1694 buffer.append(' '); 1695 } 1696 break; 1697 1698 default: 1699 // If it's a printable ASCII character that isn't covered by one of 1700 // the above options, then just append it to the buffer. Otherwise, 1701 // hex-encode all bytes that comprise its UTF-8 representation, which 1702 // might require special handling if it requires two Java characters 1703 // to encode the Unicode character. 1704 if ((c >= ' ') && (c <= '~')) 1705 { 1706 buffer.append(c); 1707 } 1708 else if (Character.isHighSurrogate(c)) 1709 { 1710 if (((i+1) < length) && 1711 Character.isLowSurrogate(valueString.charAt(i+1))) 1712 { 1713 final char c2 = valueString.charAt(++i); 1714 final int codePoint = Character.toCodePoint(c, c2); 1715 hexEncode(codePoint, buffer); 1716 } 1717 else 1718 { 1719 // This should never happen. 1720 hexEncode(c, buffer); 1721 } 1722 } 1723 else 1724 { 1725 hexEncode(c, buffer); 1726 } 1727 break; 1728 } 1729 } 1730 1731 return buffer; 1732 } 1733 1734 1735 1736 /** 1737 * Retrieves a hash code for this RDN. 1738 * 1739 * @return The hash code for this RDN. 1740 */ 1741 @Override() 1742 public int hashCode() 1743 { 1744 return toNormalizedString().hashCode(); 1745 } 1746 1747 1748 1749 /** 1750 * Indicates whether this RDN is equal to the provided object. The given 1751 * object will only be considered equal to this RDN if it is also an RDN with 1752 * the same set of names and values. 1753 * 1754 * @param o The object for which to make the determination. 1755 * 1756 * @return {@code true} if the provided object can be considered equal to 1757 * this RDN, or {@code false} if not. 1758 */ 1759 @Override() 1760 public boolean equals(final Object o) 1761 { 1762 if (o == null) 1763 { 1764 return false; 1765 } 1766 1767 if (o == this) 1768 { 1769 return true; 1770 } 1771 1772 if (! (o instanceof RDN)) 1773 { 1774 return false; 1775 } 1776 1777 final RDN rdn = (RDN) o; 1778 return (toNormalizedString().equals(rdn.toNormalizedString())); 1779 } 1780 1781 1782 1783 /** 1784 * Indicates whether the RDN with the provided string representation is equal 1785 * to this RDN. 1786 * 1787 * @param s The string representation of the DN to compare with this RDN. 1788 * 1789 * @return {@code true} if the DN with the provided string representation is 1790 * equal to this RDN, or {@code false} if not. 1791 * 1792 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1793 */ 1794 public boolean equals(final String s) 1795 throws LDAPException 1796 { 1797 if (s == null) 1798 { 1799 return false; 1800 } 1801 1802 return equals(new RDN(s, schema)); 1803 } 1804 1805 1806 1807 /** 1808 * Indicates whether the two provided strings represent the same RDN. 1809 * 1810 * @param s1 The string representation of the first RDN for which to make 1811 * the determination. It must not be {@code null}. 1812 * @param s2 The string representation of the second RDN for which to make 1813 * the determination. It must not be {@code null}. 1814 * 1815 * @return {@code true} if the provided strings represent the same RDN, or 1816 * {@code false} if not. 1817 * 1818 * @throws LDAPException If either of the provided strings cannot be parsed 1819 * as an RDN. 1820 */ 1821 public static boolean equals(final String s1, final String s2) 1822 throws LDAPException 1823 { 1824 return new RDN(s1).equals(new RDN(s2)); 1825 } 1826 1827 1828 1829 /** 1830 * Compares the provided RDN to this RDN to determine their relative order in 1831 * a sorted list. 1832 * 1833 * @param rdn The RDN to compare against this RDN. It must not be 1834 * {@code null}. 1835 * 1836 * @return A negative integer if this RDN should come before the provided RDN 1837 * in a sorted list, a positive integer if this RDN should come after 1838 * the provided RDN in a sorted list, or zero if the provided RDN 1839 * can be considered equal to this RDN. 1840 */ 1841 public int compareTo(final RDN rdn) 1842 { 1843 return compare(this, rdn); 1844 } 1845 1846 1847 1848 /** 1849 * Compares the provided RDN values to determine their relative order in a 1850 * sorted list. 1851 * 1852 * @param rdn1 The first RDN to be compared. It must not be {@code null}. 1853 * @param rdn2 The second RDN to be compared. It must not be {@code null}. 1854 * 1855 * @return A negative integer if the first RDN should come before the second 1856 * RDN in a sorted list, a positive integer if the first RDN should 1857 * come after the second RDN in a sorted list, or zero if the two RDN 1858 * values can be considered equal. 1859 */ 1860 public int compare(final RDN rdn1, final RDN rdn2) 1861 { 1862 ensureNotNull(rdn1, rdn2); 1863 1864 return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString())); 1865 } 1866 1867 1868 1869 /** 1870 * Compares the RDN values with the provided string representations to 1871 * determine their relative order in a sorted list. 1872 * 1873 * @param s1 The string representation of the first RDN to be compared. It 1874 * must not be {@code null}. 1875 * @param s2 The string representation of the second RDN to be compared. It 1876 * must not be {@code null}. 1877 * 1878 * @return A negative integer if the first RDN should come before the second 1879 * RDN in a sorted list, a positive integer if the first RDN should 1880 * come after the second RDN in a sorted list, or zero if the two RDN 1881 * values can be considered equal. 1882 * 1883 * @throws LDAPException If either of the provided strings cannot be parsed 1884 * as an RDN. 1885 */ 1886 public static int compare(final String s1, final String s2) 1887 throws LDAPException 1888 { 1889 return compare(s1, s2, null); 1890 } 1891 1892 1893 1894 /** 1895 * Compares the RDN values with the provided string representations to 1896 * determine their relative order in a sorted list. 1897 * 1898 * @param s1 The string representation of the first RDN to be compared. 1899 * It must not be {@code null}. 1900 * @param s2 The string representation of the second RDN to be compared. 1901 * It must not be {@code null}. 1902 * @param schema The schema to use to generate the normalized string 1903 * representations of the RDNs. It may be {@code null} if no 1904 * schema is available. 1905 * 1906 * @return A negative integer if the first RDN should come before the second 1907 * RDN in a sorted list, a positive integer if the first RDN should 1908 * come after the second RDN in a sorted list, or zero if the two RDN 1909 * values can be considered equal. 1910 * 1911 * @throws LDAPException If either of the provided strings cannot be parsed 1912 * as an RDN. 1913 */ 1914 public static int compare(final String s1, final String s2, 1915 final Schema schema) 1916 throws LDAPException 1917 { 1918 return new RDN(s1, schema).compareTo(new RDN(s2, schema)); 1919 } 1920}