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