001 /* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005 /* 006 * Copyright (C) 2008-2014 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 the set of attribute names for this RDN. 1156 * 1157 * @return The set of attribute names for this RDN. 1158 */ 1159 public String[] getAttributeNames() 1160 { 1161 return attributeNames; 1162 } 1163 1164 1165 1166 /** 1167 * Retrieves the set of attribute values for this RDN. 1168 * 1169 * @return The set of attribute values for this RDN. 1170 */ 1171 public String[] getAttributeValues() 1172 { 1173 final String[] stringValues = new String[attributeValues.length]; 1174 for (int i=0; i < stringValues.length; i++) 1175 { 1176 stringValues[i] = attributeValues[i].stringValue(); 1177 } 1178 1179 return stringValues; 1180 } 1181 1182 1183 1184 /** 1185 * Retrieves the set of attribute values for this RDN. 1186 * 1187 * @return The set of attribute values for this RDN. 1188 */ 1189 public byte[][] getByteArrayAttributeValues() 1190 { 1191 final byte[][] byteValues = new byte[attributeValues.length][]; 1192 for (int i=0; i < byteValues.length; i++) 1193 { 1194 byteValues[i] = attributeValues[i].getValue(); 1195 } 1196 1197 return byteValues; 1198 } 1199 1200 1201 1202 /** 1203 * Retrieves the schema that will be used for this RDN, if any. 1204 * 1205 * @return The schema that will be used for this RDN, or {@code null} if none 1206 * has been provided. 1207 */ 1208 Schema getSchema() 1209 { 1210 return schema; 1211 } 1212 1213 1214 1215 /** 1216 * Indicates whether this RDN contains the specified attribute. 1217 * 1218 * @param attributeName The name of the attribute for which to make the 1219 * determination. 1220 * 1221 * @return {@code true} if RDN contains the specified attribute, or 1222 * {@code false} if not. 1223 */ 1224 public boolean hasAttribute(final String attributeName) 1225 { 1226 for (final String name : attributeNames) 1227 { 1228 if (name.equalsIgnoreCase(attributeName)) 1229 { 1230 return true; 1231 } 1232 } 1233 1234 return false; 1235 } 1236 1237 1238 1239 /** 1240 * Indicates whether this RDN contains the specified attribute value. 1241 * 1242 * @param attributeName The name of the attribute for which to make the 1243 * determination. 1244 * @param attributeValue The attribute value for which to make the 1245 * determination. 1246 * 1247 * @return {@code true} if RDN contains the specified attribute, or 1248 * {@code false} if not. 1249 */ 1250 public boolean hasAttributeValue(final String attributeName, 1251 final String attributeValue) 1252 { 1253 for (int i=0; i < attributeNames.length; i++) 1254 { 1255 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1256 { 1257 final Attribute a = 1258 new Attribute(attributeName, schema, attributeValue); 1259 final Attribute b = new Attribute(attributeName, schema, 1260 attributeValues[i].stringValue()); 1261 1262 if (a.equals(b)) 1263 { 1264 return true; 1265 } 1266 } 1267 } 1268 1269 return false; 1270 } 1271 1272 1273 1274 /** 1275 * Indicates whether this RDN contains the specified attribute value. 1276 * 1277 * @param attributeName The name of the attribute for which to make the 1278 * determination. 1279 * @param attributeValue The attribute value for which to make the 1280 * determination. 1281 * 1282 * @return {@code true} if RDN contains the specified attribute, or 1283 * {@code false} if not. 1284 */ 1285 public boolean hasAttributeValue(final String attributeName, 1286 final byte[] attributeValue) 1287 { 1288 for (int i=0; i < attributeNames.length; i++) 1289 { 1290 if (attributeNames[i].equalsIgnoreCase(attributeName)) 1291 { 1292 final Attribute a = 1293 new Attribute(attributeName, schema, attributeValue); 1294 final Attribute b = new Attribute(attributeName, schema, 1295 attributeValues[i].getValue()); 1296 1297 if (a.equals(b)) 1298 { 1299 return true; 1300 } 1301 } 1302 } 1303 1304 return false; 1305 } 1306 1307 1308 1309 /** 1310 * Retrieves a string representation of this RDN. 1311 * 1312 * @return A string representation of this RDN. 1313 */ 1314 @Override() 1315 public String toString() 1316 { 1317 if (rdnString == null) 1318 { 1319 final StringBuilder buffer = new StringBuilder(); 1320 toString(buffer, false); 1321 rdnString = buffer.toString(); 1322 } 1323 1324 return rdnString; 1325 } 1326 1327 1328 1329 /** 1330 * Retrieves a string representation of this RDN with minimal encoding for 1331 * special characters. Only those characters specified in RFC 4514 section 1332 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or 1333 * non-printable ASCII characters. 1334 * 1335 * @return A string representation of this RDN with minimal encoding for 1336 * special characters. 1337 */ 1338 public String toMinimallyEncodedString() 1339 { 1340 final StringBuilder buffer = new StringBuilder(); 1341 toString(buffer, true); 1342 return buffer.toString(); 1343 } 1344 1345 1346 1347 /** 1348 * Appends a string representation of this RDN to the provided buffer. 1349 * 1350 * @param buffer The buffer to which the string representation is to be 1351 * appended. 1352 */ 1353 public void toString(final StringBuilder buffer) 1354 { 1355 toString(buffer, false); 1356 } 1357 1358 1359 1360 /** 1361 * Appends a string representation of this RDN to the provided buffer. 1362 * 1363 * @param buffer The buffer to which the string representation is 1364 * to be appended. 1365 * @param minimizeEncoding Indicates whether to restrict the encoding of 1366 * special characters to the bare minimum required 1367 * by LDAP (as per RFC 4514 section 2.4). If this 1368 * is {@code true}, then only leading and trailing 1369 * spaces, double quotes, plus signs, commas, 1370 * semicolons, greater-than, less-than, and 1371 * backslash characters will be encoded. 1372 */ 1373 public void toString(final StringBuilder buffer, 1374 final boolean minimizeEncoding) 1375 { 1376 if ((rdnString != null) && (! minimizeEncoding)) 1377 { 1378 buffer.append(rdnString); 1379 return; 1380 } 1381 1382 for (int i=0; i < attributeNames.length; i++) 1383 { 1384 if (i > 0) 1385 { 1386 buffer.append('+'); 1387 } 1388 1389 buffer.append(attributeNames[i]); 1390 buffer.append('='); 1391 1392 // Iterate through the value character-by-character and do any escaping 1393 // that may be necessary. 1394 final String valueString = attributeValues[i].stringValue(); 1395 final int length = valueString.length(); 1396 for (int j=0; j < length; j++) 1397 { 1398 final char c = valueString.charAt(j); 1399 switch (c) 1400 { 1401 case '\\': 1402 case '#': 1403 case '=': 1404 case '"': 1405 case '+': 1406 case ',': 1407 case ';': 1408 case '<': 1409 case '>': 1410 buffer.append('\\'); 1411 buffer.append(c); 1412 break; 1413 1414 case ' ': 1415 // Escape this space only if it's the first character, the last 1416 // character, or if the next character is also a space. 1417 if ((j == 0) || ((j+1) == length) || 1418 (((j+1) < length) && (valueString.charAt(j+1) == ' '))) 1419 { 1420 buffer.append("\\ "); 1421 } 1422 else 1423 { 1424 buffer.append(' '); 1425 } 1426 break; 1427 1428 case '\u0000': 1429 buffer.append("\\00"); 1430 break; 1431 1432 default: 1433 // If it's not a printable ASCII character, then hex-encode it 1434 // unless we're using minimized encoding. 1435 if ((! minimizeEncoding) && ((c < ' ') || (c > '~'))) 1436 { 1437 hexEncode(c, buffer); 1438 } 1439 else 1440 { 1441 buffer.append(c); 1442 } 1443 break; 1444 } 1445 } 1446 } 1447 } 1448 1449 1450 1451 /** 1452 * Retrieves a normalized string representation of this RDN. 1453 * 1454 * @return A normalized string representation of this RDN. 1455 */ 1456 public String toNormalizedString() 1457 { 1458 if (normalizedString == null) 1459 { 1460 final StringBuilder buffer = new StringBuilder(); 1461 toNormalizedString(buffer); 1462 normalizedString = buffer.toString(); 1463 } 1464 1465 return normalizedString; 1466 } 1467 1468 1469 1470 /** 1471 * Appends a normalized string representation of this RDN to the provided 1472 * buffer. 1473 * 1474 * @param buffer The buffer to which the normalized string representation is 1475 * to be appended. 1476 */ 1477 public void toNormalizedString(final StringBuilder buffer) 1478 { 1479 if (attributeNames.length == 1) 1480 { 1481 // It's a single-valued RDN, so there is no need to sort anything. 1482 final String name = normalizeAttrName(attributeNames[0]); 1483 buffer.append(name); 1484 buffer.append('='); 1485 buffer.append(normalizeValue(name, attributeValues[0])); 1486 } 1487 else 1488 { 1489 // It's a multivalued RDN, so we need to sort the components. 1490 final TreeMap<String,ASN1OctetString> valueMap = 1491 new TreeMap<String,ASN1OctetString>(); 1492 for (int i=0; i < attributeNames.length; i++) 1493 { 1494 final String name = normalizeAttrName(attributeNames[i]); 1495 valueMap.put(name, attributeValues[i]); 1496 } 1497 1498 int i=0; 1499 for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet()) 1500 { 1501 if (i++ > 0) 1502 { 1503 buffer.append('+'); 1504 } 1505 1506 buffer.append(entry.getKey()); 1507 buffer.append('='); 1508 buffer.append(normalizeValue(entry.getKey(), entry.getValue())); 1509 } 1510 } 1511 } 1512 1513 1514 1515 /** 1516 * Obtains a normalized representation of the provided attribute name. 1517 * 1518 * @param name The name of the attribute for which to create the normalized 1519 * representation. 1520 * 1521 * @return A normalized representation of the provided attribute name. 1522 */ 1523 private String normalizeAttrName(final String name) 1524 { 1525 String n = name; 1526 if (schema != null) 1527 { 1528 final AttributeTypeDefinition at = schema.getAttributeType(name); 1529 if (at != null) 1530 { 1531 n = at.getNameOrOID(); 1532 } 1533 } 1534 return toLowerCase(n); 1535 } 1536 1537 1538 1539 /** 1540 * Retrieves a normalized string representation of the RDN with the provided 1541 * string representation. 1542 * 1543 * @param s The string representation of the RDN to normalize. It must not 1544 * be {@code null}. 1545 * 1546 * @return The normalized string representation of the RDN with the provided 1547 * string representation. 1548 * 1549 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1550 */ 1551 public static String normalize(final String s) 1552 throws LDAPException 1553 { 1554 return normalize(s, null); 1555 } 1556 1557 1558 1559 /** 1560 * Retrieves a normalized string representation of the RDN with the provided 1561 * string representation. 1562 * 1563 * @param s The string representation of the RDN to normalize. It must 1564 * not be {@code null}. 1565 * @param schema The schema to use to generate the normalized string 1566 * representation of the RDN. It may be {@code null} if no 1567 * schema is available. 1568 * 1569 * @return The normalized string representation of the RDN with the provided 1570 * string representation. 1571 * 1572 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1573 */ 1574 public static String normalize(final String s, final Schema schema) 1575 throws LDAPException 1576 { 1577 return new RDN(s, schema).toNormalizedString(); 1578 } 1579 1580 1581 1582 /** 1583 * Normalizes the provided attribute value for use in an RDN. 1584 * 1585 * @param attributeName The name of the attribute with which the value is 1586 * associated. 1587 * @param value The value to be normalized. 1588 * 1589 * @return A string builder containing a normalized representation of the 1590 * value in a suitable form for inclusion in an RDN. 1591 */ 1592 private StringBuilder normalizeValue(final String attributeName, 1593 final ASN1OctetString value) 1594 { 1595 final MatchingRule matchingRule = 1596 MatchingRule.selectEqualityMatchingRule(attributeName, schema); 1597 1598 ASN1OctetString rawNormValue; 1599 try 1600 { 1601 rawNormValue = matchingRule.normalize(value); 1602 } 1603 catch (final Exception e) 1604 { 1605 debugException(e); 1606 rawNormValue = 1607 new ASN1OctetString(toLowerCase(value.stringValue())); 1608 } 1609 1610 final String valueString = rawNormValue.stringValue(); 1611 final int length = valueString.length(); 1612 final StringBuilder buffer = new StringBuilder(length); 1613 1614 for (int i=0; i < length; i++) 1615 { 1616 final char c = valueString.charAt(i); 1617 1618 switch (c) 1619 { 1620 case '\\': 1621 case '#': 1622 case '=': 1623 case '"': 1624 case '+': 1625 case ',': 1626 case ';': 1627 case '<': 1628 case '>': 1629 buffer.append('\\'); 1630 buffer.append(c); 1631 break; 1632 1633 case ' ': 1634 // Escape this space only if it's the first character, the last 1635 // character, or if the next character is also a space. 1636 if ((i == 0) || ((i+1) == length) || 1637 (((i+1) < length) && (valueString.charAt(i+1) == ' '))) 1638 { 1639 buffer.append("\\ "); 1640 } 1641 else 1642 { 1643 buffer.append(' '); 1644 } 1645 break; 1646 1647 default: 1648 // If it's not a printable ASCII character, then hex-encode it. 1649 if ((c < ' ') || (c > '~')) 1650 { 1651 hexEncode(c, buffer); 1652 } 1653 else 1654 { 1655 buffer.append(c); 1656 } 1657 break; 1658 } 1659 } 1660 1661 return buffer; 1662 } 1663 1664 1665 1666 /** 1667 * Retrieves a hash code for this RDN. 1668 * 1669 * @return The hash code for this RDN. 1670 */ 1671 @Override() 1672 public int hashCode() 1673 { 1674 return toNormalizedString().hashCode(); 1675 } 1676 1677 1678 1679 /** 1680 * Indicates whether this RDN is equal to the provided object. The given 1681 * object will only be considered equal to this RDN if it is also an RDN with 1682 * the same set of names and values. 1683 * 1684 * @param o The object for which to make the determination. 1685 * 1686 * @return {@code true} if the provided object can be considered equal to 1687 * this RDN, or {@code false} if not. 1688 */ 1689 @Override() 1690 public boolean equals(final Object o) 1691 { 1692 if (o == null) 1693 { 1694 return false; 1695 } 1696 1697 if (o == this) 1698 { 1699 return true; 1700 } 1701 1702 if (! (o instanceof RDN)) 1703 { 1704 return false; 1705 } 1706 1707 final RDN rdn = (RDN) o; 1708 return (toNormalizedString().equals(rdn.toNormalizedString())); 1709 } 1710 1711 1712 1713 /** 1714 * Indicates whether the RDN with the provided string representation is equal 1715 * to this RDN. 1716 * 1717 * @param s The string representation of the DN to compare with this RDN. 1718 * 1719 * @return {@code true} if the DN with the provided string representation is 1720 * equal to this RDN, or {@code false} if not. 1721 * 1722 * @throws LDAPException If the provided string cannot be parsed as an RDN. 1723 */ 1724 public boolean equals(final String s) 1725 throws LDAPException 1726 { 1727 if (s == null) 1728 { 1729 return false; 1730 } 1731 1732 return equals(new RDN(s, schema)); 1733 } 1734 1735 1736 1737 /** 1738 * Indicates whether the two provided strings represent the same RDN. 1739 * 1740 * @param s1 The string representation of the first RDN for which to make 1741 * the determination. It must not be {@code null}. 1742 * @param s2 The string representation of the second RDN for which to make 1743 * the determination. It must not be {@code null}. 1744 * 1745 * @return {@code true} if the provided strings represent the same RDN, or 1746 * {@code false} if not. 1747 * 1748 * @throws LDAPException If either of the provided strings cannot be parsed 1749 * as an RDN. 1750 */ 1751 public static boolean equals(final String s1, final String s2) 1752 throws LDAPException 1753 { 1754 return new RDN(s1).equals(new RDN(s2)); 1755 } 1756 1757 1758 1759 /** 1760 * Compares the provided RDN to this RDN to determine their relative order in 1761 * a sorted list. 1762 * 1763 * @param rdn The RDN to compare against this RDN. It must not be 1764 * {@code null}. 1765 * 1766 * @return A negative integer if this RDN should come before the provided RDN 1767 * in a sorted list, a positive integer if this RDN should come after 1768 * the provided RDN in a sorted list, or zero if the provided RDN 1769 * can be considered equal to this RDN. 1770 */ 1771 public int compareTo(final RDN rdn) 1772 { 1773 return compare(this, rdn); 1774 } 1775 1776 1777 1778 /** 1779 * Compares the provided RDN values to determine their relative order in a 1780 * sorted list. 1781 * 1782 * @param rdn1 The first RDN to be compared. It must not be {@code null}. 1783 * @param rdn2 The second RDN to be compared. It must not be {@code null}. 1784 * 1785 * @return A negative integer if the first RDN should come before the second 1786 * RDN in a sorted list, a positive integer if the first RDN should 1787 * come after the second RDN in a sorted list, or zero if the two RDN 1788 * values can be considered equal. 1789 */ 1790 public int compare(final RDN rdn1, final RDN rdn2) 1791 { 1792 ensureNotNull(rdn1, rdn2); 1793 1794 return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString())); 1795 } 1796 1797 1798 1799 /** 1800 * Compares the RDN values with the provided string representations to 1801 * determine their relative order in a sorted list. 1802 * 1803 * @param s1 The string representation of the first RDN to be compared. It 1804 * must not be {@code null}. 1805 * @param s2 The string representation of the second RDN to be compared. It 1806 * must not be {@code null}. 1807 * 1808 * @return A negative integer if the first RDN should come before the second 1809 * RDN in a sorted list, a positive integer if the first RDN should 1810 * come after the second RDN in a sorted list, or zero if the two RDN 1811 * values can be considered equal. 1812 * 1813 * @throws LDAPException If either of the provided strings cannot be parsed 1814 * as an RDN. 1815 */ 1816 public static int compare(final String s1, final String s2) 1817 throws LDAPException 1818 { 1819 return compare(s1, s2, null); 1820 } 1821 1822 1823 1824 /** 1825 * Compares the RDN values with the provided string representations to 1826 * determine their relative order in a sorted list. 1827 * 1828 * @param s1 The string representation of the first RDN to be compared. 1829 * It must not be {@code null}. 1830 * @param s2 The string representation of the second RDN to be compared. 1831 * It must not be {@code null}. 1832 * @param schema The schema to use to generate the normalized string 1833 * representations of the RDNs. It may be {@code null} if no 1834 * schema is available. 1835 * 1836 * @return A negative integer if the first RDN should come before the second 1837 * RDN in a sorted list, a positive integer if the first RDN should 1838 * come after the second RDN in a sorted list, or zero if the two RDN 1839 * values can be considered equal. 1840 * 1841 * @throws LDAPException If either of the provided strings cannot be parsed 1842 * as an RDN. 1843 */ 1844 public static int compare(final String s1, final String s2, 1845 final Schema schema) 1846 throws LDAPException 1847 { 1848 return new RDN(s1, schema).compareTo(new RDN(s2, schema)); 1849 } 1850 }