001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.types; 028 import org.opends.messages.Message; 029 030 031 032 import java.util.Iterator; 033 import java.util.List; 034 import java.util.TreeMap; 035 import java.util.TreeSet; 036 037 import org.opends.server.api.OrderingMatchingRule; 038 import org.opends.server.core.DirectoryServer; 039 import org.opends.server.protocols.asn1.ASN1OctetString; 040 041 import static org.opends.server.loggers.debug.DebugLogger.*; 042 import org.opends.server.loggers.debug.DebugTracer; 043 import static org.opends.messages.CoreMessages.*; 044 import static org.opends.server.util.StaticUtils.*; 045 046 047 048 /** 049 * This class defines a data structure for storing and interacting 050 * with the relative distinguished names associated with entries in 051 * the Directory Server. 052 */ 053 @org.opends.server.types.PublicAPI( 054 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 055 mayInstantiate=true, 056 mayExtend=false, 057 mayInvoke=true) 058 public final class RDN 059 implements Comparable<RDN> 060 { 061 /** 062 * The tracer object for the debug logger. 063 */ 064 private static final DebugTracer TRACER = getTracer(); 065 066 067 068 069 // The set of attribute types for the elements in this RDN. 070 private AttributeType[] attributeTypes; 071 072 // The set of values for the elements in this RDN. 073 private AttributeValue[] attributeValues; 074 075 // The number of values for this RDN. 076 private int numValues; 077 078 // The string representation of the normalized form of this RDN. 079 private String normalizedRDN; 080 081 // The string representation of this RDN. 082 private String rdnString; 083 084 // The set of user-provided names for the attributes in this RDN. 085 private String[] attributeNames; 086 087 088 089 /** 090 * Creates a new RDN with the provided information. 091 * 092 * @param attributeType The attribute type for this RDN. It must 093 * not be {@code null}. 094 * @param attributeValue The value for this RDN. It must not be 095 * {@code null}. 096 */ 097 public RDN(AttributeType attributeType, 098 AttributeValue attributeValue) 099 { 100 attributeTypes = new AttributeType[] { attributeType }; 101 attributeNames = new String[] { attributeType.getPrimaryName() }; 102 attributeValues = new AttributeValue[] { attributeValue }; 103 104 numValues = 1; 105 rdnString = null; 106 normalizedRDN = null; 107 } 108 109 110 111 /** 112 * Creates a new RDN with the provided information. 113 * 114 * @param attributeType The attribute type for this RDN. It must 115 * not be {@code null}. 116 * @param attributeName The user-provided name for this RDN. It 117 * must not be {@code null}. 118 * @param attributeValue The value for this RDN. It must not be 119 * {@code null}. 120 */ 121 public RDN(AttributeType attributeType, String attributeName, 122 AttributeValue attributeValue) 123 { 124 attributeTypes = new AttributeType[] { attributeType }; 125 attributeNames = new String[] { attributeName }; 126 attributeValues = new AttributeValue[] { attributeValue }; 127 128 numValues = 1; 129 rdnString = null; 130 normalizedRDN = null; 131 } 132 133 134 135 /** 136 * Creates a new RDN with the provided information. The number of 137 * type, name, and value elements must be nonzero and equal. 138 * 139 * @param attributeTypes The set of attribute types for this RDN. 140 * It must not be empty or {@code null}. 141 * @param attributeNames The set of user-provided names for this 142 * RDN. It must have the same number of 143 * elements as the {@code attributeTypes} 144 * argument. 145 * @param attributeValues The set of values for this RDN. It must 146 * have the same number of elements as the 147 * {@code attributeTypes} argument. 148 */ 149 public RDN(List<AttributeType> attributeTypes, 150 List<String> attributeNames, 151 List<AttributeValue> attributeValues) 152 { 153 this.attributeTypes = new AttributeType[attributeTypes.size()]; 154 this.attributeNames = new String[attributeNames.size()]; 155 this.attributeValues = new AttributeValue[attributeValues.size()]; 156 157 attributeTypes.toArray(this.attributeTypes); 158 attributeNames.toArray(this.attributeNames); 159 attributeValues.toArray(this.attributeValues); 160 161 numValues = attributeTypes.size(); 162 rdnString = null; 163 normalizedRDN = null; 164 } 165 166 167 168 /** 169 * Creates a new RDN with the provided information. The number of 170 * type, name, and value elements must be nonzero and equal. 171 * 172 * @param attributeTypes The set of attribute types for this RDN. 173 * It must not be empty or {@code null}. 174 * @param attributeNames The set of user-provided names for this 175 * RDN. It must have the same number of 176 * elements as the {@code attributeTypes} 177 * argument. 178 * @param attributeValues The set of values for this RDN. It must 179 * have the same number of elements as the 180 * {@code attributeTypes} argument. 181 */ 182 public RDN(AttributeType[] attributeTypes, String[] attributeNames, 183 AttributeValue[] attributeValues) 184 { 185 this.numValues = attributeTypes.length; 186 this.attributeTypes = attributeTypes; 187 this.attributeNames = attributeNames; 188 this.attributeValues = attributeValues; 189 190 rdnString = null; 191 normalizedRDN = null; 192 } 193 194 195 196 /** 197 * Creates a new RDN with the provided information. 198 * 199 * @param attributeType The attribute type for this RDN. It must 200 * not be {@code null}. 201 * @param attributeValue The value for this RDN. It must not be 202 * {@code null}. 203 * 204 * @return The RDN created with the provided information. 205 */ 206 public static RDN create(AttributeType attributeType, 207 AttributeValue attributeValue) 208 { 209 return new RDN(attributeType, attributeValue); 210 } 211 212 213 214 /** 215 * Retrieves the number of attribute-value pairs contained in this 216 * RDN. 217 * 218 * @return The number of attribute-value pairs contained in this 219 * RDN. 220 */ 221 public int getNumValues() 222 { 223 return numValues; 224 } 225 226 227 228 /** 229 * Indicates whether this RDN includes the specified attribute type. 230 * 231 * @param attributeType The attribute type for which to make the 232 * determination. 233 * 234 * @return <CODE>true</CODE> if the RDN includes the specified 235 * attribute type, or <CODE>false</CODE> if not. 236 */ 237 public boolean hasAttributeType(AttributeType attributeType) 238 { 239 for (AttributeType t : attributeTypes) 240 { 241 if (t.equals(attributeType)) 242 { 243 return true; 244 } 245 } 246 247 return false; 248 } 249 250 251 252 /** 253 * Indicates whether this RDN includes the specified attribute type. 254 * 255 * @param lowerName The name or OID for the attribute type for 256 * which to make the determination, formatted in 257 * all lowercase characters. 258 * 259 * @return <CODE>true</CODE> if the RDN includes the specified 260 * attribute type, or <CODE>false</CODE> if not. 261 */ 262 public boolean hasAttributeType(String lowerName) 263 { 264 for (AttributeType t : attributeTypes) 265 { 266 if (t.hasNameOrOID(lowerName)) 267 { 268 return true; 269 } 270 } 271 272 for (String s : attributeNames) 273 { 274 if (s.equalsIgnoreCase(lowerName)) 275 { 276 return true; 277 } 278 } 279 280 return false; 281 } 282 283 284 285 /** 286 * Retrieves the attribute type at the specified position in the set 287 * of attribute types for this RDN. 288 * 289 * @param pos The position of the attribute type to retrieve. 290 * 291 * @return The attribute type at the specified position in the set 292 * of attribute types for this RDN. 293 */ 294 public AttributeType getAttributeType(int pos) 295 { 296 return attributeTypes[pos]; 297 } 298 299 300 301 /** 302 * Retrieves the name for the attribute type at the specified 303 * position in the set of attribute types for this RDN. 304 * 305 * @param pos The position of the attribute type for which to 306 * retrieve the name. 307 * 308 * @return The name for the attribute type at the specified 309 * position in the set of attribute types for this RDN. 310 */ 311 public String getAttributeName(int pos) 312 { 313 return attributeNames[pos]; 314 } 315 316 317 318 /** 319 * Retrieves the attribute value that is associated with the 320 * specified attribute type. 321 * 322 * @param attributeType The attribute type for which to retrieve 323 * the corresponding value. 324 * 325 * @return The value for the requested attribute type, or 326 * <CODE>null</CODE> if the specified attribute type is not 327 * present in the RDN. 328 */ 329 public AttributeValue getAttributeValue(AttributeType attributeType) 330 { 331 for (int i=0; i < numValues; i++) 332 { 333 if (attributeTypes[i].equals(attributeType)) 334 { 335 return attributeValues[i]; 336 } 337 } 338 339 return null; 340 } 341 342 343 344 /** 345 * Retrieves the value for the attribute type at the specified 346 * position in the set of attribute types for this RDN. 347 * 348 * @param pos The position of the attribute type for which to 349 * retrieve the value. 350 * 351 * @return The value for the attribute type at the specified 352 * position in the set of attribute types for this RDN. 353 */ 354 public AttributeValue getAttributeValue(int pos) 355 { 356 return attributeValues[pos]; 357 } 358 359 360 361 /** 362 * Indicates whether this RDN is multivalued. 363 * 364 * @return <CODE>true</CODE> if this RDN is multivalued, or 365 * <CODE>false</CODE> if not. 366 */ 367 public boolean isMultiValued() 368 { 369 return (numValues > 1); 370 } 371 372 373 374 /** 375 * Indicates whether this RDN contains the specified type-value 376 * pair. 377 * 378 * @param type The attribute type for which to make the 379 * determination. 380 * @param value The value for which to make the determination. 381 * 382 * @return <CODE>true</CODE> if this RDN contains the specified 383 * attribute value, or <CODE>false</CODE> if not. 384 */ 385 public boolean hasValue(AttributeType type, AttributeValue value) 386 { 387 for (int i=0; i < numValues; i++) 388 { 389 if (attributeTypes[i].equals(type) && 390 attributeValues[i].equals(value)) 391 { 392 return true; 393 } 394 } 395 396 return false; 397 } 398 399 400 401 /** 402 * Adds the provided type-value pair from this RDN. Note that this 403 * is intended only for internal use when constructing DN values. 404 * 405 * @param type The attribute type of the pair to add. 406 * @param name The user-provided name of the pair to add. 407 * @param value The attribute value of the pair to add. 408 * 409 * @return <CODE>true</CODE> if the type-value pair was added to 410 * this RDN, or <CODE>false</CODE> if it was not (e.g., it 411 * was already present). 412 */ 413 boolean addValue(AttributeType type, String name, 414 AttributeValue value) 415 { 416 for (int i=0; i < numValues; i++) 417 { 418 if (attributeTypes[i].equals(type) && 419 attributeValues[i].equals(value)) 420 { 421 return false; 422 } 423 } 424 425 numValues++; 426 427 AttributeType[] newTypes = new AttributeType[numValues]; 428 System.arraycopy(attributeTypes, 0, newTypes, 0, 429 attributeTypes.length); 430 newTypes[attributeTypes.length] = type; 431 attributeTypes = newTypes; 432 433 String[] newNames = new String[numValues]; 434 System.arraycopy(attributeNames, 0, newNames, 0, 435 attributeNames.length); 436 newNames[attributeNames.length] = name; 437 attributeNames = newNames; 438 439 AttributeValue[] newValues = new AttributeValue[numValues]; 440 System.arraycopy(attributeValues, 0, newValues, 0, 441 attributeValues.length); 442 newValues[attributeValues.length] = value; 443 attributeValues = newValues; 444 445 rdnString = null; 446 normalizedRDN = null; 447 448 return true; 449 } 450 451 452 453 /** 454 * Retrieves a version of the provided value in a form that is 455 * properly escaped for use in a DN or RDN. 456 * 457 * @param value The value to be represented in a DN-safe form. 458 * 459 * @return A version of the provided value in a form that is 460 * properly escaped for use in a DN or RDN. 461 */ 462 private static String getDNValue(String value) { 463 if ((value == null) || (value.length() == 0)) { 464 return ""; 465 } 466 467 // Only copy the string value if required. 468 boolean needsEscaping = false; 469 int length = value.length(); 470 471 needsEscaping: { 472 char c = value.charAt(0); 473 if ((c == ' ') || (c == '#')) { 474 needsEscaping = true; 475 break needsEscaping; 476 } 477 478 if (value.charAt(length - 1) == ' ') { 479 needsEscaping = true; 480 break needsEscaping; 481 } 482 483 for (int i = 0; i < length; i++) { 484 c = value.charAt(i); 485 if (c < ' ') { 486 needsEscaping = true; 487 break needsEscaping; 488 } else { 489 switch (c) { 490 case ',': 491 case '+': 492 case '"': 493 case '\\': 494 case '<': 495 case '>': 496 case ';': 497 needsEscaping = true; 498 break needsEscaping; 499 } 500 } 501 } 502 } 503 504 if (!needsEscaping) { 505 return value; 506 } 507 508 // We need to copy and escape the string (allow for at least one 509 // escaped character). 510 StringBuilder buffer = new StringBuilder(length + 3); 511 512 // If the lead character is a space or a # it must be escaped. 513 int start = 0; 514 char c = value.charAt(0); 515 if ((c == ' ') || (c == '#')) { 516 buffer.append('\\'); 517 buffer.append(c); 518 start = 1; 519 } 520 521 // Escape remaining characters as necessary. 522 for (int i = start; i < length; i++) { 523 c = value.charAt(i); 524 if (c < ' ') { 525 for (byte b : getBytes(String.valueOf(c))) { 526 buffer.append('\\'); 527 buffer.append(byteToLowerHex(b)); 528 } 529 } else { 530 switch (value.charAt(i)) { 531 case ',': 532 case '+': 533 case '"': 534 case '\\': 535 case '<': 536 case '>': 537 case ';': 538 buffer.append('\\'); 539 buffer.append(c); 540 break; 541 default: 542 buffer.append(c); 543 break; 544 } 545 } 546 } 547 548 // If the last character is a space it must be escaped. 549 if (value.charAt(length - 1) == ' ') { 550 length = buffer.length(); 551 buffer.insert(length - 1, '\\'); 552 } 553 554 return buffer.toString(); 555 } 556 557 558 559 /** 560 * Decodes the provided string as an RDN. 561 * 562 * @param rdnString 563 * The string to decode as an RDN. 564 * @return The decoded RDN. 565 * @throws DirectoryException 566 * If a problem occurs while trying to decode the provided 567 * string as a RDN. 568 */ 569 public static RDN decode(String rdnString) 570 throws DirectoryException 571 { 572 // A null or empty RDN is not acceptable. 573 if (rdnString == null) 574 { 575 Message message = ERR_RDN_DECODE_NULL.get(); 576 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 577 message); 578 } 579 580 int length = rdnString.length(); 581 if (length == 0) 582 { 583 Message message = ERR_RDN_DECODE_NULL.get(); 584 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 585 message); 586 } 587 588 589 // Iterate through the RDN string. The first thing to do is to 590 // get rid of any leading spaces. 591 int pos = 0; 592 char c = rdnString.charAt(pos); 593 while (c == ' ') 594 { 595 pos++; 596 if (pos == length) 597 { 598 // This means that the RDN was completely comprised of spaces, 599 // which is not valid. 600 Message message = ERR_RDN_DECODE_NULL.get(); 601 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 602 message); 603 } 604 else 605 { 606 c = rdnString.charAt(pos); 607 } 608 } 609 610 611 // We know that it's not an empty RDN, so we can do the real 612 // processing. First, parse the attribute name. We can borrow 613 // the DN code for this. 614 boolean allowExceptions = 615 DirectoryServer.allowAttributeNameExceptions(); 616 StringBuilder attributeName = new StringBuilder(); 617 pos = DN.parseAttributeName(rdnString, pos, attributeName, 618 allowExceptions); 619 620 621 // Make sure that we're not at the end of the RDN string because 622 // that would be invalid. 623 if (pos >= length) 624 { 625 Message message = ERR_RDN_END_WITH_ATTR_NAME.get( 626 rdnString, attributeName.toString()); 627 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 628 message); 629 } 630 631 632 // Skip over any spaces between the attribute name and its value. 633 c = rdnString.charAt(pos); 634 while (c == ' ') 635 { 636 pos++; 637 if (pos >= length) 638 { 639 // This means that we hit the end of the string before 640 // finding a '='. This is illegal because there is no 641 // attribute-value separator. 642 Message message = ERR_RDN_END_WITH_ATTR_NAME.get( 643 rdnString, attributeName.toString()); 644 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 645 message); 646 } 647 else 648 { 649 c = rdnString.charAt(pos); 650 } 651 } 652 653 654 // The next character must be an equal sign. If it is not, then 655 // that's an error. 656 if (c == '=') 657 { 658 pos++; 659 } 660 else 661 { 662 Message message = ERR_RDN_NO_EQUAL.get( 663 rdnString, attributeName.toString(), c); 664 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 665 message); 666 } 667 668 669 // Skip over any spaces between the equal sign and the value. 670 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) 671 { 672 pos++; 673 } 674 675 676 // If we are at the end of the RDN string, then that must mean 677 // that the attribute value was empty. This will probably never 678 // happen in a real-world environment, but technically isn't 679 // illegal. If it does happen, then go ahead and return the RDN. 680 if (pos >= length) 681 { 682 String name = attributeName.toString(); 683 String lowerName = toLowerCase(name); 684 AttributeType attrType = 685 DirectoryServer.getAttributeType(lowerName); 686 687 if (attrType == null) 688 { 689 // This must be an attribute type that we don't know about. 690 // In that case, we'll create a new attribute using the 691 // default syntax. If this is a problem, it will be caught 692 // later either by not finding the target entry or by not 693 // allowing the entry to be added. 694 attrType = DirectoryServer.getDefaultAttributeType(name); 695 } 696 697 AttributeValue value = new AttributeValue(new ASN1OctetString(), 698 new ASN1OctetString()); 699 return new RDN(attrType, name, value); 700 } 701 702 703 // Parse the value for this RDN component. This can be done using 704 // the DN code. 705 ByteString parsedValue = new ASN1OctetString(); 706 pos = DN.parseAttributeValue(rdnString, pos, parsedValue); 707 708 709 // Create the new RDN with the provided information. However, 710 // don't return it yet because this could be a multi-valued RDN. 711 String name = attributeName.toString(); 712 String lowerName = toLowerCase(name); 713 AttributeType attrType = 714 DirectoryServer.getAttributeType(lowerName); 715 if (attrType == null) 716 { 717 // This must be an attribute type that we don't know about. 718 // In that case, we'll create a new attribute using the default 719 // syntax. If this is a problem, it will be caught later either 720 // by not finding the target entry or by not allowing the entry 721 // to be added. 722 attrType = DirectoryServer.getDefaultAttributeType(name); 723 } 724 725 AttributeValue value = new AttributeValue(attrType, parsedValue); 726 RDN rdn = new RDN(attrType, name, value); 727 728 729 // Skip over any spaces that might be after the attribute value. 730 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) 731 { 732 pos++; 733 } 734 735 736 // Most likely, this is the end of the RDN. If so, then return 737 // it. 738 if (pos >= length) 739 { 740 return rdn; 741 } 742 743 744 // If the next character is a comma or semicolon, then that is not 745 // allowed. It would be legal for a DN but not an RDN. 746 if ((c == ',') || (c == ';')) 747 { 748 Message message = ERR_RDN_UNEXPECTED_COMMA.get(rdnString, pos); 749 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 750 message); 751 } 752 753 754 // If the next character is anything but a plus sign, then it is 755 // illegal. 756 if (c != '+') 757 { 758 Message message = 759 ERR_RDN_ILLEGAL_CHARACTER.get(rdnString, c, pos); 760 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 761 message); 762 } 763 764 765 // If we have gotten here, then it is a multi-valued RDN. Parse 766 // the remaining attribute/value pairs and add them to the RDN 767 // that we've already created. 768 while (true) 769 { 770 // Skip over the plus sign and any spaces that may follow it 771 // before the next attribute name. 772 pos++; 773 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) 774 { 775 pos++; 776 } 777 778 779 // Parse the attribute name. 780 attributeName = new StringBuilder(); 781 pos = DN.parseAttributeName(rdnString, pos, attributeName, 782 allowExceptions); 783 784 785 // Make sure we're not at the end of the RDN. 786 if (pos >= length) 787 { 788 Message message = ERR_RDN_END_WITH_ATTR_NAME.get( 789 rdnString, attributeName.toString()); 790 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 791 message); 792 } 793 794 795 // Skip over any spaces between the attribute name and the equal 796 // sign. 797 c = rdnString.charAt(pos); 798 while (c == ' ') 799 { 800 pos++; 801 if (pos >= length) 802 { 803 // This means that we hit the end of the string before 804 // finding a '='. This is illegal because there is no 805 // attribute-value separator. 806 Message message = ERR_RDN_END_WITH_ATTR_NAME.get( 807 rdnString, attributeName.toString()); 808 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 809 message); 810 } 811 else 812 { 813 c = rdnString.charAt(pos); 814 } 815 } 816 817 818 // The next character must be an equal sign. 819 if (c == '=') 820 { 821 pos++; 822 } 823 else 824 { 825 Message message = ERR_RDN_NO_EQUAL.get( 826 rdnString, attributeName.toString(), c); 827 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 828 message); 829 } 830 831 832 // Skip over any spaces after the equal sign. 833 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) 834 { 835 pos++; 836 } 837 838 839 // If we are at the end of the RDN string, then that must mean 840 // that the attribute value was empty. This will probably never 841 // happen in a real-world environment, but technically isn't 842 // illegal. If it does happen, then go ahead and return the 843 // RDN. 844 if (pos >= length) 845 { 846 name = attributeName.toString(); 847 lowerName = toLowerCase(name); 848 attrType = DirectoryServer.getAttributeType(lowerName); 849 850 if (attrType == null) 851 { 852 // This must be an attribute type that we don't know about. 853 // In that case, we'll create a new attribute using the 854 // default syntax. If this is a problem, it will be caught 855 // later either by not finding the target entry or by not 856 // allowing the entry to be added. 857 attrType = DirectoryServer.getDefaultAttributeType(name); 858 } 859 860 value = new AttributeValue(new ASN1OctetString(), 861 new ASN1OctetString()); 862 rdn.addValue(attrType, name, value); 863 return rdn; 864 } 865 866 867 // Parse the value for this RDN component. 868 parsedValue = new ASN1OctetString(); 869 pos = DN.parseAttributeValue(rdnString, pos, parsedValue); 870 871 872 // Update the RDN to include the new attribute/value. 873 name = attributeName.toString(); 874 lowerName = toLowerCase(name); 875 attrType = DirectoryServer.getAttributeType(lowerName); 876 if (attrType == null) 877 { 878 // This must be an attribute type that we don't know about. 879 // In that case, we'll create a new attribute using the 880 // default syntax. If this is a problem, it will be caught 881 // later either by not finding the target entry or by not 882 // allowing the entry to be added. 883 attrType = DirectoryServer.getDefaultAttributeType(name); 884 } 885 886 value = new AttributeValue(attrType, parsedValue); 887 rdn.addValue(attrType, name, value); 888 889 890 // Skip over any spaces that might be after the attribute value. 891 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) 892 { 893 pos++; 894 } 895 896 897 // If we're at the end of the string, then return the RDN. 898 if (pos >= length) 899 { 900 return rdn; 901 } 902 903 904 // If the next character is a comma or semicolon, then that is 905 // not allowed. It would be legal for a DN but not an RDN. 906 if ((c == ',') || (c == ';')) 907 { 908 Message message = 909 ERR_RDN_UNEXPECTED_COMMA.get(rdnString, pos); 910 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 911 message); 912 } 913 914 915 // If the next character is anything but a plus sign, then it is 916 // illegal. 917 if (c != '+') 918 { 919 Message message = 920 ERR_RDN_ILLEGAL_CHARACTER.get(rdnString, c, pos); 921 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 922 message); 923 } 924 } 925 } 926 927 928 929 /** 930 * Creates a duplicate of this RDN that can be modified without 931 * impacting this RDN. 932 * 933 * @return A duplicate of this RDN that can be modified without 934 * impacting this RDN. 935 */ 936 public RDN duplicate() 937 { 938 AttributeType[] newTypes = new AttributeType[numValues]; 939 System.arraycopy(attributeTypes, 0, newTypes, 0, numValues); 940 941 String[] newNames = new String[numValues]; 942 System.arraycopy(attributeNames, 0, newNames, 0, numValues); 943 944 AttributeValue[] newValues = new AttributeValue[numValues]; 945 System.arraycopy(attributeValues, 0, newValues, 0, numValues); 946 947 return new RDN(newTypes, newNames, newValues); 948 } 949 950 951 952 /** 953 * Indicates whether the provided object is equal to this RDN. It 954 * will only be considered equal if it is an RDN object that 955 * contains the same number of elements in the same order with the 956 * same types and normalized values. 957 * 958 * @param o The object for which to make the determination. 959 * 960 * @return <CODE>true</CODE> if it is determined that the provided 961 * object is equal to this RDN, or <CODE>false</CODE> if 962 * not. 963 */ 964 public boolean equals(Object o) 965 { 966 if (this == o) 967 { 968 return true; 969 } 970 971 if ((o == null) || (! (o instanceof RDN))) 972 { 973 return false; 974 } 975 976 RDN rdn = (RDN) o; 977 return toNormalizedString().equals(rdn.toNormalizedString()); 978 } 979 980 981 982 /** 983 * Retrieves the hash code for this RDN. It will be calculated as 984 * the sum of the hash codes of the types and values. 985 * 986 * @return The hash code for this RDN. 987 */ 988 public int hashCode() 989 { 990 return toNormalizedString().hashCode(); 991 } 992 993 994 995 /** 996 * Retrieves a string representation of this RDN. 997 * 998 * @return A string representation of this RDN. 999 */ 1000 public String toString() 1001 { 1002 if (rdnString == null) 1003 { 1004 StringBuilder buffer = new StringBuilder(); 1005 1006 buffer.append(attributeNames[0]); 1007 buffer.append("="); 1008 1009 String s = attributeValues[0].getStringValue(); 1010 buffer.append(getDNValue(s)); 1011 1012 for (int i=1; i < numValues; i++) 1013 { 1014 buffer.append("+"); 1015 buffer.append(attributeNames[i]); 1016 buffer.append("="); 1017 1018 s = attributeValues[i].getStringValue(); 1019 buffer.append(getDNValue(s)); 1020 } 1021 1022 rdnString = buffer.toString(); 1023 } 1024 1025 return rdnString; 1026 } 1027 1028 1029 1030 /** 1031 * Appends a string representation of this RDN to the provided 1032 * buffer. 1033 * 1034 * @param buffer The buffer to which the string representation 1035 * should be appended. 1036 */ 1037 public void toString(StringBuilder buffer) 1038 { 1039 buffer.append(toString()); 1040 } 1041 1042 1043 1044 /** 1045 * Retrieves a normalized string representation of this RDN. 1046 * 1047 * @return A normalized string representation of this RDN. 1048 */ 1049 public String toNormalizedString() 1050 { 1051 if (normalizedRDN == null) 1052 { 1053 StringBuilder buffer = new StringBuilder(); 1054 toNormalizedString(buffer); 1055 } 1056 1057 return normalizedRDN; 1058 } 1059 1060 1061 1062 /** 1063 * Appends a normalized string representation of this RDN to the 1064 * provided buffer. 1065 * 1066 * @param buffer The buffer to which to append the information. 1067 */ 1068 public void toNormalizedString(StringBuilder buffer) 1069 { 1070 if (normalizedRDN != null) 1071 { 1072 buffer.append(normalizedRDN); 1073 return; 1074 } 1075 1076 boolean bufferEmpty = (buffer.length() == 0); 1077 1078 if (attributeNames.length == 1) 1079 { 1080 toLowerCase(attributeTypes[0].getNameOrOID(), buffer); 1081 buffer.append('='); 1082 1083 try 1084 { 1085 String s = attributeValues[0].getNormalizedStringValue(); 1086 buffer.append(getDNValue(s)); 1087 } 1088 catch (Exception e) 1089 { 1090 if (debugEnabled()) 1091 { 1092 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1093 } 1094 1095 String s = attributeValues[0].getStringValue(); 1096 buffer.append(getDNValue(s)); 1097 } 1098 } 1099 else 1100 { 1101 TreeSet<String> rdnElementStrings = new TreeSet<String>(); 1102 1103 for (int i=0; i < attributeNames.length; i++) 1104 { 1105 StringBuilder b2 = new StringBuilder(); 1106 toLowerCase(attributeTypes[i].getNameOrOID(), b2); 1107 b2.append('='); 1108 1109 try 1110 { 1111 String s = attributeValues[i].getNormalizedStringValue(); 1112 b2.append(getDNValue(s)); 1113 } 1114 catch (Exception e) 1115 { 1116 if (debugEnabled()) 1117 { 1118 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1119 } 1120 1121 String s = attributeValues[i].getStringValue(); 1122 b2.append(getDNValue(s)); 1123 } 1124 1125 rdnElementStrings.add(b2.toString()); 1126 } 1127 1128 Iterator<String> iterator = rdnElementStrings.iterator(); 1129 buffer.append(iterator.next()); 1130 1131 while (iterator.hasNext()) 1132 { 1133 buffer.append('+'); 1134 buffer.append(iterator.next()); 1135 } 1136 } 1137 1138 if (bufferEmpty) 1139 { 1140 normalizedRDN = buffer.toString(); 1141 } 1142 } 1143 1144 1145 1146 /** 1147 * Compares this RDN with the provided RDN based on an alphabetic 1148 * comparison of the attribute names and values. 1149 * 1150 * @param rdn The RDN against which to compare this RDN. 1151 * 1152 * @return A negative integer if this RDN should come before the 1153 * provided RDN, a positive integer if this RDN should come 1154 * after the provided RDN, or zero if there is no 1155 * difference with regard to ordering. 1156 */ 1157 public int compareTo(RDN rdn) 1158 { 1159 if ((attributeTypes.length == 1) && 1160 (rdn.attributeTypes.length == 1)) 1161 { 1162 if (attributeTypes[0].equals(rdn.attributeTypes[0])) 1163 { 1164 OrderingMatchingRule omr = 1165 attributeTypes[0].getOrderingMatchingRule(); 1166 if (omr == null) 1167 { 1168 try 1169 { 1170 return attributeValues[0].getNormalizedStringValue(). 1171 compareTo(rdn.attributeValues[0]. 1172 getNormalizedStringValue()); 1173 } 1174 catch (Exception e) 1175 { 1176 if (debugEnabled()) 1177 { 1178 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1179 } 1180 1181 return attributeValues[0].getStringValue(). 1182 compareTo(rdn.attributeValues[0]. 1183 getStringValue()); 1184 } 1185 } 1186 else 1187 { 1188 try 1189 { 1190 return omr.compareValues( 1191 attributeValues[0].getNormalizedValue(), 1192 rdn.attributeValues[0].getNormalizedValue()); 1193 } 1194 catch (Exception e) 1195 { 1196 if (debugEnabled()) 1197 { 1198 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1199 } 1200 1201 return omr.compareValues( 1202 attributeValues[0].getValue(), 1203 rdn.attributeValues[0].getValue()); 1204 } 1205 } 1206 } 1207 else 1208 { 1209 String name1 = toLowerCase(attributeTypes[0].getNameOrOID()); 1210 String name2 = 1211 toLowerCase(rdn.attributeTypes[0].getNameOrOID()); 1212 return name1.compareTo(name2); 1213 } 1214 } 1215 1216 if (equals(rdn)) 1217 { 1218 return 0; 1219 } 1220 1221 TreeMap<String,AttributeType> typeMap1 = 1222 new TreeMap<String,AttributeType>(); 1223 TreeMap<String,AttributeValue> valueMap1 = 1224 new TreeMap<String,AttributeValue>(); 1225 for (int i=0; i < attributeTypes.length; i++) 1226 { 1227 String lowerName = 1228 toLowerCase(attributeTypes[i].getNameOrOID()); 1229 typeMap1.put(lowerName, attributeTypes[i]); 1230 valueMap1.put(lowerName, attributeValues[i]); 1231 } 1232 1233 TreeMap<String,AttributeType> typeMap2 = 1234 new TreeMap<String,AttributeType>(); 1235 TreeMap<String,AttributeValue> valueMap2 = 1236 new TreeMap<String,AttributeValue>(); 1237 for (int i=0; i < rdn.attributeTypes.length; i++) 1238 { 1239 String lowerName = 1240 toLowerCase(rdn.attributeTypes[i].getNameOrOID()); 1241 typeMap2.put(lowerName, rdn.attributeTypes[i]); 1242 valueMap2.put(lowerName, rdn.attributeValues[i]); 1243 } 1244 1245 Iterator<String> iterator1 = valueMap1.keySet().iterator(); 1246 Iterator<String> iterator2 = valueMap2.keySet().iterator(); 1247 String name1 = iterator1.next(); 1248 String name2 = iterator2.next(); 1249 AttributeType type1 = typeMap1.get(name1); 1250 AttributeType type2 = typeMap2.get(name2); 1251 AttributeValue value1 = valueMap1.get(name1); 1252 AttributeValue value2 = valueMap2.get(name2); 1253 1254 while (true) 1255 { 1256 if (type1.equals(type2)) 1257 { 1258 int valueComparison; 1259 OrderingMatchingRule omr = type1.getOrderingMatchingRule(); 1260 if (omr == null) 1261 { 1262 try 1263 { 1264 valueComparison = 1265 value1.getNormalizedStringValue().compareTo( 1266 value2.getNormalizedStringValue()); 1267 } 1268 catch (Exception e) 1269 { 1270 if (debugEnabled()) 1271 { 1272 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1273 } 1274 1275 valueComparison = 1276 value1.getStringValue().compareTo( 1277 value2.getStringValue()); 1278 } 1279 } 1280 else 1281 { 1282 try 1283 { 1284 valueComparison = 1285 omr.compareValues(value1.getNormalizedValue(), 1286 value2.getNormalizedValue()); 1287 } 1288 catch (Exception e) 1289 { 1290 if (debugEnabled()) 1291 { 1292 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1293 } 1294 1295 valueComparison = 1296 omr.compareValues(value1.getValue(), 1297 value2.getValue()); 1298 } 1299 } 1300 1301 if (valueComparison == 0) 1302 { 1303 if (! iterator1.hasNext()) 1304 { 1305 if (iterator2.hasNext()) 1306 { 1307 return -1; 1308 } 1309 else 1310 { 1311 return 0; 1312 } 1313 } 1314 1315 if (! iterator2.hasNext()) 1316 { 1317 return 1; 1318 } 1319 1320 name1 = iterator1.next(); 1321 name2 = iterator2.next(); 1322 type1 = typeMap1.get(name1); 1323 type2 = typeMap2.get(name2); 1324 value1 = valueMap1.get(name1); 1325 value2 = valueMap2.get(name2); 1326 } 1327 else 1328 { 1329 return valueComparison; 1330 } 1331 } 1332 else 1333 { 1334 return name1.compareTo(name2); 1335 } 1336 } 1337 } 1338 } 1339