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