001/* 002 * Copyright 2018-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2018-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) 2018-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.util.Comparator; 042 043import com.unboundid.asn1.ASN1OctetString; 044import com.unboundid.ldap.matchingrules.MatchingRule; 045import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 046import com.unboundid.ldap.sdk.schema.Schema; 047import com.unboundid.util.ByteStringBuffer; 048import com.unboundid.util.Debug; 049import com.unboundid.util.NotMutable; 050import com.unboundid.util.NotNull; 051import com.unboundid.util.Nullable; 052import com.unboundid.util.StaticUtils; 053import com.unboundid.util.ThreadSafety; 054import com.unboundid.util.ThreadSafetyLevel; 055 056 057 058/** 059 * This class provides a data structure that represents a single name-value pair 060 * that may appear in a relative distinguished name. 061 */ 062@NotMutable() 063@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 064public final class RDNNameValuePair 065 implements Comparable<RDNNameValuePair>, Comparator<RDNNameValuePair>, 066 Serializable 067{ 068 /** 069 * The serial version UID for this serializable class. 070 */ 071 private static final long serialVersionUID = -8780852504883527870L; 072 073 074 075 // The attribute value for this name-value pair. 076 @NotNull private final ASN1OctetString attributeValue; 077 078 // The schema to use to generate the normalized string representation of this 079 // name-value pair, if any. 080 @Nullable private final Schema schema; 081 082 // The attribute name for this name-value pair. 083 @NotNull private final String attributeName; 084 085 // The all-lowercase representation of the attribute name for this name-value 086 // pair. 087 @Nullable private volatile String normalizedAttributeName; 088 089 // The normalized string representation for this RDN name-value pair. 090 @Nullable private volatile String normalizedString; 091 092 // The string representation for this RDN name-value pair. 093 @Nullable private volatile String stringRepresentation; 094 095 096 097 /** 098 * Creates a new RDN name-value pair with the provided information. 099 * 100 * @param attributeName The attribute name for this name-value pair. It 101 * must not be {@code null}. 102 * @param attributeValue The attribute value for this name-value pair. It 103 * must not be {@code null}. 104 * @param schema The schema to use to generate the normalized string 105 * representation of this name-value pair, if any. It 106 * may be {@code null} if no schema is available. 107 */ 108 RDNNameValuePair(@NotNull final String attributeName, 109 @NotNull final ASN1OctetString attributeValue, 110 @Nullable final Schema schema) 111 { 112 this.attributeName = attributeName; 113 this.attributeValue = attributeValue; 114 this.schema = schema; 115 116 normalizedAttributeName = null; 117 normalizedString = null; 118 stringRepresentation = null; 119 } 120 121 122 123 /** 124 * Retrieves the attribute name for this name-value pair. 125 * 126 * @return The attribute name for this name-value pair. 127 */ 128 @NotNull() 129 public String getAttributeName() 130 { 131 return attributeName; 132 } 133 134 135 136 /** 137 * Retrieves a normalized representation of the attribute name. 138 * 139 * @return A normalized representation of the attribute name. 140 */ 141 @NotNull() 142 public String getNormalizedAttributeName() 143 { 144 if (normalizedAttributeName == null) 145 { 146 if (schema != null) 147 { 148 final AttributeTypeDefinition attributeType = 149 schema.getAttributeType(attributeName); 150 if (attributeType != null) 151 { 152 normalizedAttributeName = 153 StaticUtils.toLowerCase(attributeType.getNameOrOID()); 154 } 155 } 156 157 if (normalizedAttributeName == null) 158 { 159 normalizedAttributeName = StaticUtils.toLowerCase(attributeName); 160 } 161 } 162 163 return normalizedAttributeName; 164 } 165 166 167 168 /** 169 * Indicates whether this RDN name-value pair has the provided attribute name 170 * (or a name that is logically equivalent to it). 171 * 172 * @param name The name for which to make the determination. 173 * 174 * @return {@code true} if this name-value pair has the provided attribute 175 * name (or a name that is logically equivalent to it), or 176 * {@code false} if not. 177 */ 178 public boolean hasAttributeName(@NotNull final String name) 179 { 180 if (attributeName.equalsIgnoreCase(name)) 181 { 182 return true; 183 } 184 185 if (schema != null) 186 { 187 final AttributeTypeDefinition attributeType = 188 schema.getAttributeType(attributeName); 189 return ((attributeType != null) && attributeType.hasNameOrOID(name)); 190 } 191 192 return false; 193 } 194 195 196 197 /** 198 * Retrieves the string representation of the attribute value for this 199 * name-value pair. 200 * 201 * @return The string representation of the attribute value for this 202 * name-value pair. 203 */ 204 @NotNull() 205 public String getAttributeValue() 206 { 207 return attributeValue.stringValue(); 208 } 209 210 211 212 /** 213 * Retrieves the bytes that comprise the attribute value for this name-value 214 * pair. 215 * 216 * @return The bytes that comprise the attribute value for this name-value 217 * pair. 218 */ 219 @NotNull() 220 public byte[] getAttributeValueBytes() 221 { 222 return attributeValue.getValue(); 223 } 224 225 226 227 /** 228 * Retrieves the raw attribute value for this name-value pair. 229 * 230 * @return The raw attribute value for this name-value pair. 231 */ 232 @NotNull() 233 public ASN1OctetString getRawAttributeValue() 234 { 235 return attributeValue; 236 } 237 238 239 240 /** 241 * Indicates whether this RDN name-value pair has the provided attribute value 242 * (or a value that is logically equivalent to it). 243 * 244 * @param value The value for which to make the determination. 245 * 246 * @return {@code true} if this RDN name-value pair has the provided 247 * attribute value (or a value that is logically equivalent to it), 248 * or {@code false} if not. 249 */ 250 public boolean hasAttributeValue(@NotNull final String value) 251 { 252 try 253 { 254 final MatchingRule matchingRule = 255 MatchingRule.selectEqualityMatchingRule(attributeName, schema); 256 return matchingRule.valuesMatch(new ASN1OctetString(value), 257 attributeValue); 258 } 259 catch (final Exception e) 260 { 261 Debug.debugException(e); 262 return false; 263 } 264 } 265 266 267 268 /** 269 * Indicates whether this RDN name-value pair has the provided attribute value 270 * (or a value that is logically equivalent to it). 271 * 272 * @param value The value for which to make the determination. 273 * 274 * @return {@code true} if this RDN name-value pair has the provided 275 * attribute value (or a value that is logically equivalent to it), 276 * or {@code false} if not. 277 */ 278 public boolean hasAttributeValue(@NotNull final byte[] value) 279 { 280 try 281 { 282 final MatchingRule matchingRule = 283 MatchingRule.selectEqualityMatchingRule(attributeName, schema); 284 return matchingRule.valuesMatch(new ASN1OctetString(value), 285 attributeValue); 286 } 287 catch (final Exception e) 288 { 289 Debug.debugException(e); 290 return false; 291 } 292 } 293 294 295 296 /** 297 * Retrieves an integer value that represents the order in which this RDN 298 * name-value pair should be placed in relation to the provided RDN name-value 299 * pair in a sorted list. 300 * 301 * @param p The RDN name-value pair to be ordered relative to this RDN 302 * name-value pair. It must not be {@code null}. 303 * 304 * @return A negative integer if this RDN name-value pair should be ordered 305 * before the provided RDN name-value pair, a positive integer if 306 * this RDN name-value pair should be ordered after the provided RDN 307 * name-value pair, or zero if this RDN name-value pair is logically 308 * equivalent to the provided RDN name-value pair. 309 */ 310 @Override() 311 public int compareTo(@NotNull final RDNNameValuePair p) 312 { 313 final String thisNormalizedName = getNormalizedAttributeName(); 314 final String thatNormalizedName = p.getNormalizedAttributeName(); 315 final int nameComparison = 316 thisNormalizedName.compareTo(thatNormalizedName); 317 if (nameComparison != 0) 318 { 319 return nameComparison; 320 } 321 322 try 323 { 324 final MatchingRule matchingRule = 325 MatchingRule.selectOrderingMatchingRule(attributeName, schema); 326 return matchingRule.compareValues(attributeValue, p.attributeValue); 327 } 328 catch (final Exception e) 329 { 330 Debug.debugException(e); 331 332 final String thisNormalizedString = toNormalizedString(); 333 final String thatNormalizedString = p.toNormalizedString(); 334 return thisNormalizedString.compareTo(thatNormalizedString); 335 } 336 } 337 338 339 340 /** 341 * Retrieves an integer value that represents the order in which the provided 342 * RDN name-value pairs should be placed in a sorted list. 343 * 344 * @param p1 The first RDN name-value pair to compare. It must not be 345 * {@code null}. 346 * @param p2 The second RDN name-value pair to compare. It must not be 347 * {@code null}. 348 * 349 * @return A negative integer if the first RDN name-value pair should be 350 * ordered before the second RDN name-value pair, a positive integer 351 * if the first RDN name-value pair should be ordered after the 352 * second RDN name-value pair, or zero if the provided RDN name-value 353 * pairs are logically equivalent. 354 */ 355 @Override() 356 public int compare(@NotNull final RDNNameValuePair p1, 357 @NotNull final RDNNameValuePair p2) 358 { 359 return p1.compareTo(p2); 360 } 361 362 363 364 /** 365 * Retrieves a hash code for this RDN name-value pair. 366 * 367 * @return A hash code for this RDN name-value pair. 368 */ 369 @Override() 370 public int hashCode() 371 { 372 return toNormalizedString().hashCode(); 373 } 374 375 376 377 /** 378 * Indicates whether the provided object is considered logically equivalent to 379 * this RDN name-value pair. 380 * 381 * @param o The object for which to make the determination. 382 * 383 * @return {@code true} if the provided object is an RDN name-value pair that 384 * is logically equivalent to this RDN name-value pair, or 385 * {@code false} if not. 386 */ 387 public boolean equals(@Nullable final Object o) 388 { 389 if (o == null) 390 { 391 return false; 392 } 393 394 if (o == this) 395 { 396 return true; 397 } 398 399 if (! (o instanceof RDNNameValuePair)) 400 { 401 return false; 402 } 403 404 final RDNNameValuePair p = (RDNNameValuePair) o; 405 return toNormalizedString().equals(p.toNormalizedString()); 406 } 407 408 409 410 /** 411 * Retrieves a string representation of this RDN name-value pair. 412 * 413 * @return A string representation of this RDN name-value pair. 414 */ 415 @Override() 416 @NotNull() 417 public String toString() 418 { 419 if (stringRepresentation == null) 420 { 421 final ByteStringBuffer buffer = new ByteStringBuffer(); 422 toString(buffer, DN.getDNEscapingStrategy()); 423 stringRepresentation = buffer.toString(); 424 } 425 426 return stringRepresentation; 427 } 428 429 430 431 /** 432 * Retrieves a string representation of this RDN name-value pair with minimal 433 * encoding for special characters. Only those characters specified in RFC 434 * 4514 section 2.4 will be escaped. No escaping will be used for non-ASCII 435 * characters or non-printable ASCII characters. 436 * 437 * @return A string representation of this RDN name-value pair with minimal 438 * encoding for special characters. 439 */ 440 @NotNull() 441 public String toMinimallyEncodedString() 442 { 443 final ByteStringBuffer buffer = new ByteStringBuffer(); 444 toString(buffer, DNEscapingStrategy.MINIMAL); 445 return buffer.toString(); 446 } 447 448 449 450 /** 451 * Appends a string representation of this RDN name-value pair to the provided 452 * buffer. 453 * 454 * @param buffer The buffer to which the string representation is 455 * to be appended. 456 * @param minimizeEncoding Indicates whether to restrict the encoding of 457 * special characters to the bare minimum required 458 * by LDAP (as per RFC 4514 section 2.4). If this 459 * is {@code true}, then only leading and trailing 460 * spaces, double quotes, plus signs, commas, 461 * semicolons, greater-than, less-than, and 462 * backslash characters will be encoded. 463 */ 464 public void toString(@NotNull final StringBuilder buffer, 465 final boolean minimizeEncoding) 466 { 467 final ByteStringBuffer byteStringBuffer = new ByteStringBuffer(); 468 final DNEscapingStrategy escapingStrategy = (minimizeEncoding 469 ? DNEscapingStrategy.MINIMAL 470 : DN.getDNEscapingStrategy()); 471 toString(byteStringBuffer, escapingStrategy); 472 buffer.append(byteStringBuffer.toString()); 473 } 474 475 476 477 /** 478 * Appends a string representation of this RDN name-value pair to the provided 479 * buffer. 480 * 481 * @param buffer The buffer to which the string representation is 482 * to be appended. It must not be {@code null}. 483 * @param escapingStrategy The strategy to use to determine which types of 484 * optional escaping should be used for values. It 485 * must not be {@code null}. 486 */ 487 public void toString(@NotNull final ByteStringBuffer buffer, 488 @NotNull final DNEscapingStrategy escapingStrategy) 489 { 490 buffer.append(attributeName); 491 buffer.append('='); 492 escapingStrategy.escape(attributeValue, buffer); 493 } 494 495 496 497 /** 498 * Retrieves a normalized string representation of this RDN name-value pair. 499 * 500 * @return A normalized string representation of this RDN name-value pair. 501 */ 502 @NotNull() 503 public String toNormalizedString() 504 { 505 if (normalizedString == null) 506 { 507 final StringBuilder buffer = new StringBuilder(); 508 toNormalizedString(buffer); 509 normalizedString = buffer.toString(); 510 } 511 512 return normalizedString; 513 } 514 515 516 517 /** 518 * Appends a normalized string representation of this RDN name-value pair to 519 * the provided buffer. 520 * 521 * @param buffer The buffer to which the normalized string representation 522 * should be appended. It must not be {@code null}. 523 */ 524 public void toNormalizedString(@NotNull final StringBuilder buffer) 525 { 526 buffer.append(getNormalizedAttributeName()); 527 buffer.append('='); 528 RDN.appendNormalizedValue(buffer, attributeName, attributeValue, schema); 529 } 530}