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