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