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