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}