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.Debug; 037import com.unboundid.util.NotMutable; 038import com.unboundid.util.StaticUtils; 039import com.unboundid.util.ThreadSafety; 040import com.unboundid.util.ThreadSafetyLevel; 041import com.unboundid.util.Validator; 042 043import static com.unboundid.ldap.sdk.LDAPMessages.*; 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 Validator.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 Validator.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 Validator.ensureNotNull(attributeNames, attributeValues); 200 Validator.ensureTrue(attributeNames.length == attributeValues.length, 201 "RDN.attributeNames and attributeValues must be the same size."); 202 Validator.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 Validator.ensureNotNull(attributeNames, attributeValues); 251 Validator.ensureTrue(attributeNames.length == attributeValues.length, 252 "RDN.attributeNames and attributeValues must be the same size."); 253 Validator.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 Validator.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.isEmpty()) 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 Debug.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<>(5); 461 final ArrayList<ASN1OctetString> valueList = new ArrayList<>(5); 462 nameList.add(attrName); 463 valueList.add(value); 464 465 if (rdnString.charAt(pos) == '+') 466 { 467 pos++; 468 } 469 else 470 { 471 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 472 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString)); 473 } 474 475 if (pos >= length) 476 { 477 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 478 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString)); 479 } 480 481 int numValues = 1; 482 while (pos < length) 483 { 484 // Skip over any spaces between the plus sign and the attribute name. 485 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 486 { 487 pos++; 488 } 489 490 attrStartPos = pos; 491 while (pos < length) 492 { 493 final char c = rdnString.charAt(pos); 494 if ((c == ' ') || (c == '=')) 495 { 496 break; 497 } 498 499 pos++; 500 } 501 502 // Skip over any spaces between the attribute name and the equal sign. 503 attrName = rdnString.substring(attrStartPos, pos); 504 if (attrName.isEmpty()) 505 { 506 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 507 ERR_RDN_NO_ATTR_NAME.get(rdnString)); 508 } 509 510 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 511 { 512 pos++; 513 } 514 515 if ((pos >= length) || (rdnString.charAt(pos) != '=')) 516 { 517 // We didn't find an equal sign. 518 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 519 ERR_RDN_NO_EQUAL_SIGN.get(rdnString, attrName)); 520 } 521 522 // The next character is the equal sign. Skip it, and then skip over any 523 // spaces between it and the attribute value. 524 pos++; 525 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 526 { 527 pos++; 528 } 529 530 // Look at the next character. If it is an octothorpe (#), then the value 531 // must be a hex-encoded BER element, which we'll need to parse and take 532 // the value of that element. Otherwise, it's a regular string (although 533 // possibly containing escaped or quoted characters). 534 if (pos >= length) 535 { 536 value = new ASN1OctetString(); 537 } 538 else if (rdnString.charAt(pos) == '#') 539 { 540 // It is a hex-encoded value, so we'll read until we find the end of the 541 // string or the first non-hex character, which must be either a space 542 // or a plus sign. 543 final byte[] valueArray = readHexString(rdnString, ++pos); 544 545 try 546 { 547 value = ASN1OctetString.decodeAsOctetString(valueArray); 548 } 549 catch (final Exception e) 550 { 551 Debug.debugException(e); 552 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 553 ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(rdnString, attrName), e); 554 } 555 556 pos += (valueArray.length * 2); 557 } 558 else 559 { 560 // It is a string value, which potentially includes escaped characters. 561 final StringBuilder buffer = new StringBuilder(); 562 pos = readValueString(rdnString, pos, buffer); 563 value = new ASN1OctetString(buffer.toString()); 564 } 565 566 567 // Skip over any spaces until we find a plus sign or the end of the value. 568 while ((pos < length) && (rdnString.charAt(pos) == ' ')) 569 { 570 pos++; 571 } 572 573 nameList.add(attrName); 574 valueList.add(value); 575 numValues++; 576 577 if (pos >= length) 578 { 579 // We're at the end of the value, so break out of the loop. 580 break; 581 } 582 else 583 { 584 // Skip over the plus sign and loop again to read another name-value 585 // pair. 586 if (rdnString.charAt(pos) == '+') 587 { 588 pos++; 589 } 590 else 591 { 592 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 593 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString)); 594 } 595 } 596 597 if (pos >= length) 598 { 599 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 600 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString)); 601 } 602 } 603 604 attributeNames = new String[numValues]; 605 attributeValues = new ASN1OctetString[numValues]; 606 for (int i=0; i < numValues; i++) 607 { 608 attributeNames[i] = nameList.get(i); 609 attributeValues[i] = valueList.get(i); 610 } 611 } 612 613 614 615 /** 616 * Parses a hex-encoded RDN value from the provided string. Reading will 617 * continue until the end of the string is reached or a non-escaped plus sign 618 * is encountered. After returning, the caller should increment its position 619 * by two times the length of the value array. 620 * 621 * @param rdnString The string to be parsed. It should be the position 622 * immediately after the octothorpe at the start of the 623 * hex-encoded value. 624 * @param startPos The position at which to start reading the value. 625 * 626 * @return A byte array containing the parsed value. 627 * 628 * @throws LDAPException If an error occurs while reading the value (e.g., 629 * if it contains non-hex characters, or has an odd 630 * number of characters. 631 */ 632 static byte[] readHexString(final String rdnString, final int startPos) 633 throws LDAPException 634 { 635 final int length = rdnString.length(); 636 int pos = startPos; 637 638 final ByteBuffer buffer = ByteBuffer.allocate(length-pos); 639hexLoop: 640 while (pos < length) 641 { 642 final byte hexByte; 643 switch (rdnString.charAt(pos++)) 644 { 645 case '0': 646 hexByte = 0x00; 647 break; 648 case '1': 649 hexByte = 0x10; 650 break; 651 case '2': 652 hexByte = 0x20; 653 break; 654 case '3': 655 hexByte = 0x30; 656 break; 657 case '4': 658 hexByte = 0x40; 659 break; 660 case '5': 661 hexByte = 0x50; 662 break; 663 case '6': 664 hexByte = 0x60; 665 break; 666 case '7': 667 hexByte = 0x70; 668 break; 669 case '8': 670 hexByte = (byte) 0x80; 671 break; 672 case '9': 673 hexByte = (byte) 0x90; 674 break; 675 case 'a': 676 case 'A': 677 hexByte = (byte) 0xA0; 678 break; 679 case 'b': 680 case 'B': 681 hexByte = (byte) 0xB0; 682 break; 683 case 'c': 684 case 'C': 685 hexByte = (byte) 0xC0; 686 break; 687 case 'd': 688 case 'D': 689 hexByte = (byte) 0xD0; 690 break; 691 case 'e': 692 case 'E': 693 hexByte = (byte) 0xE0; 694 break; 695 case 'f': 696 case 'F': 697 hexByte = (byte) 0xF0; 698 break; 699 case ' ': 700 case '+': 701 case ',': 702 case ';': 703 // This indicates that we've reached the end of the hex string. 704 break hexLoop; 705 default: 706 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 707 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 708 (pos-1))); 709 } 710 711 if (pos >= length) 712 { 713 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 714 ERR_RDN_MISSING_HEX_CHAR.get(rdnString)); 715 } 716 717 switch (rdnString.charAt(pos++)) 718 { 719 case '0': 720 buffer.put(hexByte); 721 break; 722 case '1': 723 buffer.put((byte) (hexByte | 0x01)); 724 break; 725 case '2': 726 buffer.put((byte) (hexByte | 0x02)); 727 break; 728 case '3': 729 buffer.put((byte) (hexByte | 0x03)); 730 break; 731 case '4': 732 buffer.put((byte) (hexByte | 0x04)); 733 break; 734 case '5': 735 buffer.put((byte) (hexByte | 0x05)); 736 break; 737 case '6': 738 buffer.put((byte) (hexByte | 0x06)); 739 break; 740 case '7': 741 buffer.put((byte) (hexByte | 0x07)); 742 break; 743 case '8': 744 buffer.put((byte) (hexByte | 0x08)); 745 break; 746 case '9': 747 buffer.put((byte) (hexByte | 0x09)); 748 break; 749 case 'a': 750 case 'A': 751 buffer.put((byte) (hexByte | 0x0A)); 752 break; 753 case 'b': 754 case 'B': 755 buffer.put((byte) (hexByte | 0x0B)); 756 break; 757 case 'c': 758 case 'C': 759 buffer.put((byte) (hexByte | 0x0C)); 760 break; 761 case 'd': 762 case 'D': 763 buffer.put((byte) (hexByte | 0x0D)); 764 break; 765 case 'e': 766 case 'E': 767 buffer.put((byte) (hexByte | 0x0E)); 768 break; 769 case 'f': 770 case 'F': 771 buffer.put((byte) (hexByte | 0x0F)); 772 break; 773 default: 774 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 775 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 776 (pos-1))); 777 } 778 } 779 780 buffer.flip(); 781 final byte[] valueArray = new byte[buffer.limit()]; 782 buffer.get(valueArray); 783 return valueArray; 784 } 785 786 787 788 /** 789 * Reads a string value from the provided RDN string. Reading will continue 790 * until the end of the string is reached or until a non-escaped plus sign is 791 * encountered. 792 * 793 * @param rdnString The string from which to read the value. 794 * @param startPos The position in the RDN string at which to start reading 795 * the value. 796 * @param buffer The buffer into which the parsed value should be 797 * placed. 798 * 799 * @return The position at which the caller should continue reading when 800 * parsing the RDN. 801 * 802 * @throws LDAPException If a problem occurs while reading the value. 803 */ 804 static int readValueString(final String rdnString, final int startPos, 805 final StringBuilder buffer) 806 throws LDAPException 807 { 808 final int length = rdnString.length(); 809 int pos = startPos; 810 811 boolean inQuotes = false; 812valueLoop: 813 while (pos < length) 814 { 815 char c = rdnString.charAt(pos); 816 switch (c) 817 { 818 case '\\': 819 // It's an escaped value. It can either be followed by a single 820 // character (e.g., backslash, space, octothorpe, equals, double 821 // quote, plus sign, comma, semicolon, less than, or greater-than), or 822 // two hex digits. If it is followed by hex digits, then continue 823 // reading to see if there are more of them. 824 if ((pos+1) >= length) 825 { 826 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 827 ERR_RDN_ENDS_WITH_BACKSLASH.get(rdnString)); 828 } 829 else 830 { 831 pos++; 832 c = rdnString.charAt(pos); 833 if (StaticUtils.isHex(c)) 834 { 835 // We need to subtract one from the resulting position because 836 // it will be incremented later. 837 pos = readEscapedHexString(rdnString, pos, buffer) - 1; 838 } 839 else 840 { 841 buffer.append(c); 842 } 843 } 844 break; 845 846 case '"': 847 if (inQuotes) 848 { 849 // This should be the end of the value. If it's not, then fail. 850 pos++; 851 while (pos < length) 852 { 853 c = rdnString.charAt(pos); 854 if ((c == '+') || (c == ',') || (c == ';')) 855 { 856 break; 857 } 858 else if (c != ' ') 859 { 860 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 861 ERR_RDN_CHAR_OUTSIDE_QUOTES.get(rdnString, c, (pos-1))); 862 } 863 864 pos++; 865 } 866 867 inQuotes = false; 868 break valueLoop; 869 } 870 else 871 { 872 // This should be the first character of the value. 873 if (pos == startPos) 874 { 875 inQuotes = true; 876 } 877 else 878 { 879 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 880 ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(rdnString, pos)); 881 } 882 } 883 break; 884 885 case ',': 886 case ';': 887 case '+': 888 // This denotes the end of the value, if it's not in quotes. 889 if (inQuotes) 890 { 891 buffer.append(c); 892 } 893 else 894 { 895 break valueLoop; 896 } 897 break; 898 899 default: 900 // This is a normal character that should be added to the buffer. 901 buffer.append(c); 902 break; 903 } 904 905 pos++; 906 } 907 908 909 // If the value started with a quotation mark, then make sure it was closed. 910 if (inQuotes) 911 { 912 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 913 ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get(rdnString)); 914 } 915 916 917 // If the value ends with any unescaped trailing spaces, then trim them off. 918 int bufferPos = buffer.length() - 1; 919 int rdnStrPos = pos - 2; 920 while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' ')) 921 { 922 if (rdnString.charAt(rdnStrPos) == '\\') 923 { 924 break; 925 } 926 else 927 { 928 buffer.deleteCharAt(bufferPos--); 929 rdnStrPos--; 930 } 931 } 932 933 return pos; 934 } 935 936 937 938 /** 939 * Reads one or more hex-encoded bytes from the specified portion of the RDN 940 * string. 941 * 942 * @param rdnString The string from which the data is to be read. 943 * @param startPos The position at which to start reading. This should be 944 * the first hex character immediately after the initial 945 * backslash. 946 * @param buffer The buffer to which the decoded string portion should be 947 * appended. 948 * 949 * @return The position at which the caller may resume parsing. 950 * 951 * @throws LDAPException If a problem occurs while reading hex-encoded 952 * bytes. 953 */ 954 private static int readEscapedHexString(final String rdnString, 955 final int startPos, 956 final StringBuilder buffer) 957 throws LDAPException 958 { 959 final int length = rdnString.length(); 960 int pos = startPos; 961 962 final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos); 963 while (pos < length) 964 { 965 final byte b; 966 switch (rdnString.charAt(pos++)) 967 { 968 case '0': 969 b = 0x00; 970 break; 971 case '1': 972 b = 0x10; 973 break; 974 case '2': 975 b = 0x20; 976 break; 977 case '3': 978 b = 0x30; 979 break; 980 case '4': 981 b = 0x40; 982 break; 983 case '5': 984 b = 0x50; 985 break; 986 case '6': 987 b = 0x60; 988 break; 989 case '7': 990 b = 0x70; 991 break; 992 case '8': 993 b = (byte) 0x80; 994 break; 995 case '9': 996 b = (byte) 0x90; 997 break; 998 case 'a': 999 case 'A': 1000 b = (byte) 0xA0; 1001 break; 1002 case 'b': 1003 case 'B': 1004 b = (byte) 0xB0; 1005 break; 1006 case 'c': 1007 case 'C': 1008 b = (byte) 0xC0; 1009 break; 1010 case 'd': 1011 case 'D': 1012 b = (byte) 0xD0; 1013 break; 1014 case 'e': 1015 case 'E': 1016 b = (byte) 0xE0; 1017 break; 1018 case 'f': 1019 case 'F': 1020 b = (byte) 0xF0; 1021 break; 1022 default: 1023 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1024 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 1025 (pos-1))); 1026 } 1027 1028 if (pos >= length) 1029 { 1030 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1031 ERR_RDN_MISSING_HEX_CHAR.get(rdnString)); 1032 } 1033 1034 switch (rdnString.charAt(pos++)) 1035 { 1036 case '0': 1037 byteBuffer.put(b); 1038 break; 1039 case '1': 1040 byteBuffer.put((byte) (b | 0x01)); 1041 break; 1042 case '2': 1043 byteBuffer.put((byte) (b | 0x02)); 1044 break; 1045 case '3': 1046 byteBuffer.put((byte) (b | 0x03)); 1047 break; 1048 case '4': 1049 byteBuffer.put((byte) (b | 0x04)); 1050 break; 1051 case '5': 1052 byteBuffer.put((byte) (b | 0x05)); 1053 break; 1054 case '6': 1055 byteBuffer.put((byte) (b | 0x06)); 1056 break; 1057 case '7': 1058 byteBuffer.put((byte) (b | 0x07)); 1059 break; 1060 case '8': 1061 byteBuffer.put((byte) (b | 0x08)); 1062 break; 1063 case '9': 1064 byteBuffer.put((byte) (b | 0x09)); 1065 break; 1066 case 'a': 1067 case 'A': 1068 byteBuffer.put((byte) (b | 0x0A)); 1069 break; 1070 case 'b': 1071 case 'B': 1072 byteBuffer.put((byte) (b | 0x0B)); 1073 break; 1074 case 'c': 1075 case 'C': 1076 byteBuffer.put((byte) (b | 0x0C)); 1077 break; 1078 case 'd': 1079 case 'D': 1080 byteBuffer.put((byte) (b | 0x0D)); 1081 break; 1082 case 'e': 1083 case 'E': 1084 byteBuffer.put((byte) (b | 0x0E)); 1085 break; 1086 case 'f': 1087 case 'F': 1088 byteBuffer.put((byte) (b | 0x0F)); 1089 break; 1090 default: 1091 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 1092 ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1), 1093 (pos-1))); 1094 } 1095 1096 if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') && 1097 StaticUtils.isHex(rdnString.charAt(pos+1))) 1098 { 1099 // It appears that there are more hex-encoded bytes to follow, so keep 1100 // reading. 1101 pos++; 1102 continue; 1103 } 1104 else 1105 { 1106 break; 1107 } 1108 } 1109 1110 byteBuffer.flip(); 1111 final byte[] byteArray = new byte[byteBuffer.limit()]; 1112 byteBuffer.get(byteArray); 1113 1114 try 1115 { 1116 buffer.append(StaticUtils.toUTF8String(byteArray)); 1117 } 1118 catch (final Exception e) 1119 { 1120 Debug.debugException(e); 1121 // This should never happen. 1122 buffer.append(new String(byteArray)); 1123 } 1124 1125 return pos; 1126 } 1127 1128 1129 1130 /** 1131 * Indicates whether the provided string represents a valid RDN. 1132 * 1133 * @param s The string for which to make the determination. It must not be 1134 * {@code null}. 1135 * 1136 * @return {@code true} if the provided string represents a valid RDN, or 1137 * {@code false} if not. 1138 */ 1139 public static boolean isValidRDN(final String s) 1140 { 1141 try 1142 { 1143 new RDN(s); 1144 return true; 1145 } 1146 catch (final LDAPException le) 1147 { 1148 return false; 1149 } 1150 } 1151 1152 1153 1154 /** 1155 * Indicates whether this RDN contains multiple components. 1156 * 1157 * @return {@code true} if this RDN contains multiple components, or 1158 * {@code false} if not. 1159 */ 1160 public boolean isMultiValued() 1161 { 1162 return (attributeNames.length != 1); 1163 } 1164 1165 1166 1167 /** 1168 * Retrieves an array of the attributes that comprise this RDN. 1169 * 1170 * @return An array of the attributes that comprise this RDN. 1171 */ 1172 public Attribute[] getAttributes() 1173 { 1174 final Attribute[] attrs = new Attribute[attributeNames.length]; 1175 for (int i=0; i < attrs.length; i++) 1176 { 1177 attrs[i] = new Attribute(attributeNames[i], schema, 1178 new ASN1OctetString[] { attributeValues[i] }); 1179 } 1180 1181 return attrs; 1182 } 1183 1184 1185 1186 /** 1187 * Retrieves the set of attribute names for this RDN. 1188 * 1189 * @return The set of attribute names for this RDN. 1190 */ 1191 public String[] getAttributeNames() 1192 { 1193 return attributeNames; 1194 } 1195 1196 1197 1198 /** 1199 * Retrieves the set of attribute values for this RDN. 1200 * 1201 * @return The set of attribute values for this RDN. 1202 */ 1203 public String[] getAttributeValues() 1204 { 1205 final String[] stringValues = new String[attributeValues.length]; 1206 for (int i=0; i < stringValues.length; i++) 1207 { 1208 stringValues[i] = attributeValues[i].stringValue(); 1209 } 1210 1211 return stringValues; 1212 } 1213 1214 1215 1216 /** 1217 * Retrieves the set of attribute values for this RDN. 1218 * 1219 * @return The set of attribute values for this RDN. 1220 */ 1221 public byte[][] getByteArrayAttributeValues() 1222 { 1223 final byte[][] byteValues = new byte[attributeValues.length][]; 1224 for (int i=0; i < byteValues.length; i++) 1225 { 1226 byteValues[i] = attributeValues[i].getValue(); 1227 } 1228 1229 return byteValues; 1230 } 1231 1232 1233 1234 /** 1235 * Retrieves the schema that will be used for this RDN, if any. 1236 * 1237 * @return The schema that will be used for this RDN, or {@code null} if none 1238 * has been provided. 1239 */ 1240 Schema getSchema() 1241 { 1242 return schema; 1243 } 1244 1245 1246 1247 /** 1248 * Indicates whether this RDN contains the specified attribute. 1249 * 1250 * @param attributeName The name of the attribute for which to make the 1251 * determination. 1252 * 1253 * @return {@code true} if RDN contains the specified attribute, or 1254 * {@code false} if not. 1255 */ 1256 public boolean hasAttribute(final String attributeName) 1257 { 1258 for (final String name : attributeNames) 1259 { 1260 if (name.equalsIgnoreCase(attributeName)) 1261 { 1262 return true; 1263 } 1264 } 1265 1266 return false; 1267 } 1268 1269 1270 1271 /** 1272 * Indicates whether this RDN contains the specified attribute value. 1273 * 1274 * @param attributeName The name of the attribute for which to make the 1275 * determination. 1276 * @param attributeValue The attribute value for which to make the 1277 * determination. 1278 * 1279 * @return {@code true} if RDN contains the specified attribute, or 1280 * {@code false} if not. 1281 */ 1282 public boolean hasAttributeValue(final String attributeName, 1283 final String attributeValue) 1284 { 1285 for (int i=0; i < attributeNames.length; i++) 1286 { 1287 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1288 { 1289 final Attribute a = 1290 new Attribute(attributeName, schema, attributeValue); 1291 final Attribute b = new Attribute(attributeName, schema, 1292 attributeValues[i].stringValue()); 1293 1294 if (a.equals(b)) 1295 { 1296 return true; 1297 } 1298 } 1299 } 1300 1301 return false; 1302 } 1303 1304 1305 1306 /** 1307 * Indicates whether this RDN contains the specified attribute value. 1308 * 1309 * @param attributeName The name of the attribute for which to make the 1310 * determination. 1311 * @param attributeValue The attribute value for which to make the 1312 * determination. 1313 * 1314 * @return {@code true} if RDN contains the specified attribute, or 1315 * {@code false} if not. 1316 */ 1317 public boolean hasAttributeValue(final String attributeName, 1318 final byte[] attributeValue) 1319 { 1320 for (int i=0; i < attributeNames.length; i++) 1321 { 1322 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1323 { 1324 final Attribute a = 1325 new Attribute(attributeName, schema, attributeValue); 1326 final Attribute b = new Attribute(attributeName, schema, 1327 attributeValues[i].getValue()); 1328 1329 if (a.equals(b)) 1330 { 1331 return true; 1332 } 1333 } 1334 } 1335 1336 return false; 1337 } 1338 1339 1340 1341 /** 1342 * Retrieves a string representation of this RDN. 1343 * 1344 * @return A string representation of this RDN. 1345 */ 1346 @Override() 1347 public String toString() 1348 { 1349 if (rdnString == null) 1350 { 1351 final StringBuilder buffer = new StringBuilder(); 1352 toString(buffer, false); 1353 rdnString = buffer.toString(); 1354 } 1355 1356 return rdnString; 1357 } 1358 1359 1360 1361 /** 1362 * Retrieves a string representation of this RDN with minimal encoding for 1363 * special characters. Only those characters specified in RFC 4514 section 1364 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or 1365 * non-printable ASCII characters. 1366 * 1367 * @return A string representation of this RDN with minimal encoding for 1368 * special characters. 1369 */ 1370 public String toMinimallyEncodedString() 1371 { 1372 final StringBuilder buffer = new StringBuilder(); 1373 toString(buffer, true); 1374 return buffer.toString(); 1375 } 1376 1377 1378 1379 /** 1380 * Appends a string representation of this RDN to the provided buffer. 1381 * 1382 * @param buffer The buffer to which the string representation is to be 1383 * appended. 1384 */ 1385 public void toString(final StringBuilder buffer) 1386 { 1387 toString(buffer, false); 1388 } 1389 1390 1391 1392 /** 1393 * Appends a string representation of this RDN to the provided buffer. 1394 * 1395 * @param buffer The buffer to which the string representation is 1396 * to be appended. 1397 * @param minimizeEncoding Indicates whether to restrict the encoding of 1398 * special characters to the bare minimum required 1399 * by LDAP (as per RFC 4514 section 2.4). If this 1400 * is {@code true}, then only leading and trailing 1401 * spaces, double quotes, plus signs, commas, 1402 * semicolons, greater-than, less-than, and 1403 * backslash characters will be encoded. 1404 */ 1405 public void toString(final StringBuilder buffer, 1406 final boolean minimizeEncoding) 1407 { 1408 if ((rdnString != null) && (! minimizeEncoding)) 1409 { 1410 buffer.append(rdnString); 1411 return; 1412 } 1413 1414 for (int i=0; i < attributeNames.length; i++) 1415 { 1416 if (i > 0) 1417 { 1418 buffer.append('+'); 1419 } 1420 1421 buffer.append(attributeNames[i]); 1422 buffer.append('='); 1423 1424 // Iterate through the value character-by-character and do any escaping 1425 // that may be necessary. 1426 final String valueString = attributeValues[i].stringValue(); 1427 final int length = valueString.length(); 1428 for (int j=0; j < length; j++) 1429 { 1430 final char c = valueString.charAt(j); 1431 switch (c) 1432 { 1433 case '\\': 1434 case '=': 1435 case '"': 1436 case '+': 1437 case ',': 1438 case ';': 1439 case '<': 1440 case '>': 1441 buffer.append('\\'); 1442 buffer.append(c); 1443 break; 1444 1445 case '#': 1446 // Escape the octothorpe only if it's the first character. 1447 if (j == 0) 1448 { 1449 buffer.append("\\#"); 1450 } 1451 else 1452 { 1453 buffer.append('#'); 1454 } 1455 break; 1456 1457 case ' ': 1458 // Escape this space only if it's the first or last character. 1459 if ((j == 0) || ((j+1) == length)) 1460 { 1461 buffer.append("\\ "); 1462 } 1463 else 1464 { 1465 buffer.append(' '); 1466 } 1467 break; 1468 1469 case '\u0000': 1470 buffer.append("\\00"); 1471 break; 1472 1473 default: 1474 // If it's not a printable ASCII character, then hex-encode it 1475 // unless we're using minimized encoding. 1476 if ((! minimizeEncoding) && ((c < ' ') || (c > '~'))) 1477 { 1478 StaticUtils.hexEncode(c, buffer); 1479 } 1480 else 1481 { 1482 buffer.append(c); 1483 } 1484 break; 1485 } 1486 } 1487 } 1488 } 1489 1490 1491 1492 /** 1493 * Retrieves a normalized string representation of this RDN. 1494 * 1495 * @return A normalized string representation of this RDN. 1496 */ 1497 public String toNormalizedString() 1498 { 1499 if (normalizedString == null) 1500 { 1501 final StringBuilder buffer = new StringBuilder(); 1502 toNormalizedString(buffer); 1503 normalizedString = buffer.toString(); 1504 } 1505 1506 return normalizedString; 1507 } 1508 1509 1510 1511 /** 1512 * Appends a normalized string representation of this RDN to the provided 1513 * buffer. 1514 * 1515 * @param buffer The buffer to which the normalized string representation is 1516 * to be appended. 1517 */ 1518 public void toNormalizedString(final StringBuilder buffer) 1519 { 1520 if (attributeNames.length == 1) 1521 { 1522 // It's a single-valued RDN, so there is no need to sort anything. 1523 final String name = normalizeAttrName(attributeNames[0]); 1524 buffer.append(name); 1525 buffer.append('='); 1526 buffer.append(normalizeValue(name, attributeValues[0])); 1527 } 1528 else 1529 { 1530 // It's a multivalued RDN, so we need to sort the components. 1531 final TreeMap<String,ASN1OctetString> valueMap = new TreeMap<>(); 1532 for (int i=0; i < attributeNames.length; i++) 1533 { 1534 final String name = normalizeAttrName(attributeNames[i]); 1535 valueMap.put(name, attributeValues[i]); 1536 } 1537 1538 int i=0; 1539 for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet()) 1540 { 1541 if (i++ > 0) 1542 { 1543 buffer.append('+'); 1544 } 1545 1546 buffer.append(entry.getKey()); 1547 buffer.append('='); 1548 buffer.append(normalizeValue(entry.getKey(), entry.getValue())); 1549 } 1550 } 1551 } 1552 1553 1554 1555 /** 1556 * Obtains a normalized representation of the provided attribute name. 1557 * 1558 * @param name The name of the attribute for which to create the normalized 1559 * representation. 1560 * 1561 * @return A normalized representation of the provided attribute name. 1562 */ 1563 private String normalizeAttrName(final String name) 1564 { 1565 String n = name; 1566 if (schema != null) 1567 { 1568 final AttributeTypeDefinition at = schema.getAttributeType(name); 1569 if (at != null) 1570 { 1571 n = at.getNameOrOID(); 1572 } 1573 } 1574 return StaticUtils.toLowerCase(n); 1575 } 1576 1577 1578 1579 /** 1580 * Retrieves a normalized string representation of the RDN with the provided 1581 * string representation. 1582 * 1583 * @param s The string representation of the RDN to normalize. It must not 1584 * be {@code null}. 1585 * 1586 * @return The normalized string representation of the RDN with the provided 1587 * string representation. 1588 * 1589 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1590 */ 1591 public static String normalize(final String s) 1592 throws LDAPException 1593 { 1594 return normalize(s, null); 1595 } 1596 1597 1598 1599 /** 1600 * Retrieves a normalized string representation of the RDN with the provided 1601 * string representation. 1602 * 1603 * @param s The string representation of the RDN to normalize. It must 1604 * not be {@code null}. 1605 * @param schema The schema to use to generate the normalized string 1606 * representation of the RDN. It may be {@code null} if no 1607 * schema is available. 1608 * 1609 * @return The normalized string representation of the RDN with the provided 1610 * string representation. 1611 * 1612 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1613 */ 1614 public static String normalize(final String s, final Schema schema) 1615 throws LDAPException 1616 { 1617 return new RDN(s, schema).toNormalizedString(); 1618 } 1619 1620 1621 1622 /** 1623 * Normalizes the provided attribute value for use in an RDN. 1624 * 1625 * @param attributeName The name of the attribute with which the value is 1626 * associated. 1627 * @param value The value to be normalized. 1628 * 1629 * @return A string builder containing a normalized representation of the 1630 * value in a suitable form for inclusion in an RDN. 1631 */ 1632 private StringBuilder normalizeValue(final String attributeName, 1633 final ASN1OctetString value) 1634 { 1635 final MatchingRule matchingRule = 1636 MatchingRule.selectEqualityMatchingRule(attributeName, schema); 1637 1638 ASN1OctetString rawNormValue; 1639 try 1640 { 1641 rawNormValue = matchingRule.normalize(value); 1642 } 1643 catch (final Exception e) 1644 { 1645 Debug.debugException(e); 1646 rawNormValue = 1647 new ASN1OctetString(StaticUtils.toLowerCase(value.stringValue())); 1648 } 1649 1650 final String valueString = rawNormValue.stringValue(); 1651 final int length = valueString.length(); 1652 final StringBuilder buffer = new StringBuilder(length); 1653 1654 for (int i=0; i < length; i++) 1655 { 1656 final char c = valueString.charAt(i); 1657 1658 switch (c) 1659 { 1660 case '\\': 1661 case '=': 1662 case '"': 1663 case '+': 1664 case ',': 1665 case ';': 1666 case '<': 1667 case '>': 1668 buffer.append('\\'); 1669 buffer.append(c); 1670 break; 1671 1672 case '#': 1673 // Escape the octothorpe only if it's the first character. 1674 if (i == 0) 1675 { 1676 buffer.append("\\#"); 1677 } 1678 else 1679 { 1680 buffer.append('#'); 1681 } 1682 break; 1683 1684 case ' ': 1685 // Escape this space only if it's the first or last character. 1686 if ((i == 0) || ((i+1) == length)) 1687 { 1688 buffer.append("\\ "); 1689 } 1690 else 1691 { 1692 buffer.append(' '); 1693 } 1694 break; 1695 1696 default: 1697 // If it's a printable ASCII character that isn't covered by one of 1698 // the above options, then just append it to the buffer. Otherwise, 1699 // hex-encode all bytes that comprise its UTF-8 representation, which 1700 // might require special handling if it requires two Java characters 1701 // to encode the Unicode character. 1702 if ((c >= ' ') && (c <= '~')) 1703 { 1704 buffer.append(c); 1705 } 1706 else if (Character.isHighSurrogate(c)) 1707 { 1708 if (((i+1) < length) && 1709 Character.isLowSurrogate(valueString.charAt(i+1))) 1710 { 1711 final char c2 = valueString.charAt(++i); 1712 final int codePoint = Character.toCodePoint(c, c2); 1713 StaticUtils.hexEncode(codePoint, buffer); 1714 } 1715 else 1716 { 1717 // This should never happen. 1718 StaticUtils.hexEncode(c, buffer); 1719 } 1720 } 1721 else 1722 { 1723 StaticUtils.hexEncode(c, buffer); 1724 } 1725 break; 1726 } 1727 } 1728 1729 return buffer; 1730 } 1731 1732 1733 1734 /** 1735 * Retrieves a hash code for this RDN. 1736 * 1737 * @return The hash code for this RDN. 1738 */ 1739 @Override() 1740 public int hashCode() 1741 { 1742 return toNormalizedString().hashCode(); 1743 } 1744 1745 1746 1747 /** 1748 * Indicates whether this RDN is equal to the provided object. The given 1749 * object will only be considered equal to this RDN if it is also an RDN with 1750 * the same set of names and values. 1751 * 1752 * @param o The object for which to make the determination. 1753 * 1754 * @return {@code true} if the provided object can be considered equal to 1755 * this RDN, or {@code false} if not. 1756 */ 1757 @Override() 1758 public boolean equals(final Object o) 1759 { 1760 if (o == null) 1761 { 1762 return false; 1763 } 1764 1765 if (o == this) 1766 { 1767 return true; 1768 } 1769 1770 if (! (o instanceof RDN)) 1771 { 1772 return false; 1773 } 1774 1775 final RDN rdn = (RDN) o; 1776 return (toNormalizedString().equals(rdn.toNormalizedString())); 1777 } 1778 1779 1780 1781 /** 1782 * Indicates whether the RDN with the provided string representation is equal 1783 * to this RDN. 1784 * 1785 * @param s The string representation of the DN to compare with this RDN. 1786 * 1787 * @return {@code true} if the DN with the provided string representation is 1788 * equal to this RDN, or {@code false} if not. 1789 * 1790 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1791 */ 1792 public boolean equals(final String s) 1793 throws LDAPException 1794 { 1795 if (s == null) 1796 { 1797 return false; 1798 } 1799 1800 return equals(new RDN(s, schema)); 1801 } 1802 1803 1804 1805 /** 1806 * Indicates whether the two provided strings represent the same RDN. 1807 * 1808 * @param s1 The string representation of the first RDN for which to make 1809 * the determination. It must not be {@code null}. 1810 * @param s2 The string representation of the second RDN for which to make 1811 * the determination. It must not be {@code null}. 1812 * 1813 * @return {@code true} if the provided strings represent the same RDN, or 1814 * {@code false} if not. 1815 * 1816 * @throws LDAPException If either of the provided strings cannot be parsed 1817 * as an RDN. 1818 */ 1819 public static boolean equals(final String s1, final String s2) 1820 throws LDAPException 1821 { 1822 return new RDN(s1).equals(new RDN(s2)); 1823 } 1824 1825 1826 1827 /** 1828 * Compares the provided RDN to this RDN to determine their relative order in 1829 * a sorted list. 1830 * 1831 * @param rdn The RDN to compare against this RDN. It must not be 1832 * {@code null}. 1833 * 1834 * @return A negative integer if this RDN should come before the provided RDN 1835 * in a sorted list, a positive integer if this RDN should come after 1836 * the provided RDN in a sorted list, or zero if the provided RDN 1837 * can be considered equal to this RDN. 1838 */ 1839 @Override() 1840 public int compareTo(final RDN rdn) 1841 { 1842 return compare(this, rdn); 1843 } 1844 1845 1846 1847 /** 1848 * Compares the provided RDN values to determine their relative order in a 1849 * sorted list. 1850 * 1851 * @param rdn1 The first RDN to be compared. It must not be {@code null}. 1852 * @param rdn2 The second RDN to be compared. It must not be {@code null}. 1853 * 1854 * @return A negative integer if the first RDN should come before the second 1855 * RDN in a sorted list, a positive integer if the first RDN should 1856 * come after the second RDN in a sorted list, or zero if the two RDN 1857 * values can be considered equal. 1858 */ 1859 @Override() 1860 public int compare(final RDN rdn1, final RDN rdn2) 1861 { 1862 Validator.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}