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