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