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.sdk.schema.Schema;
030import com.unboundid.util.NotMutable;
031import com.unboundid.util.StaticUtils;
032import com.unboundid.util.ThreadSafety;
033import com.unboundid.util.ThreadSafetyLevel;
034
035
036
037/**
038 * This class provides a data structure that represents a single name-value pair
039 * that may appear in a relative distinguished name.
040 */
041@NotMutable()
042@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
043public final class RDNNameValuePair
044       implements Comparable<RDNNameValuePair>, Comparator<RDNNameValuePair>,
045                  Serializable
046{
047  /**
048   * The serial version UID for this serializable class.
049   */
050  private static final long serialVersionUID = -8780852504883527870L;
051
052
053
054  // The attribute value for this name-value pair.
055  private final ASN1OctetString attributeValue;
056
057  // The schema to use to generate the normalized string representation of this
058  // name-value pair, if any.
059  private final Schema schema;
060
061  // The attribute name for this name-value pair.
062  private final String attributeName;
063
064  // The all-lowercase representation of the attribute name for this name-value
065  // pair.
066  private volatile String lowerAttributeName;
067
068  // The normalized string representation for this RDN name-value pair.
069  private volatile String normalizedString;
070
071  // The string representation for this RDN name-value pair.
072  private volatile String stringRepresentation;
073
074
075
076  /**
077   * Creates a new RDN name-value pair with the provided information.
078   *
079   * @param attributeName  The attribute name for this name-value pair.  It must
080   *                       not be {@code null}.
081   * @param attributeValue The attribute value for this name-value pair.  It
082   *                       must not be {@code null}.
083   * @param schema         The schema to use to generate the normalized string
084   *                       representation of this name-value pair, if any.  It
085   *                       may be {@code null} if no schema is available.
086   */
087  RDNNameValuePair(final String attributeName,
088                   final ASN1OctetString attributeValue, final Schema schema)
089  {
090    this.attributeName = attributeName;
091    this.attributeValue = attributeValue;
092    this.schema = schema;
093
094    lowerAttributeName = null;
095    normalizedString = null;
096    stringRepresentation = null;
097  }
098
099
100
101  /**
102   * Retrieves the attribute name for this name-value pair.
103   *
104   * @return The attribute name for this name-value pair.
105   */
106  public String getAttributeName()
107  {
108    return attributeName;
109  }
110
111
112
113  /**
114   * Retrieves an all-lowercase representation of the attribute name.
115   *
116   * @return An all-lowercase representation of the attribute name.
117   */
118  String getLowercaseAttributeName()
119  {
120    if (lowerAttributeName == null)
121    {
122      lowerAttributeName = StaticUtils.toLowerCase(attributeName);
123    }
124
125    return lowerAttributeName;
126  }
127
128
129
130  /**
131   * Retrieves the string representation of the attribute value for this
132   * name-value pair.
133   *
134   * @return The string representation of the attribute value for this
135   * name-value pair.
136   */
137  public String getAttributeValue()
138  {
139    return attributeValue.stringValue();
140  }
141
142
143
144  /**
145   * Retrieves the bytes that comprise the attribute value for this name-value
146   * pair.
147   *
148   * @return The bytes that comprise the attribute value for this name-value
149   * pair.
150   */
151  public byte[] getAttributeValueBytes()
152  {
153    return attributeValue.getValue();
154  }
155
156
157
158  /**
159   * Retrieves the raw attribute value for this name-value pair.
160   *
161   * @return The raw attribute value for this name-value pair.
162   */
163  public ASN1OctetString getRawAttributeValue()
164  {
165    return attributeValue;
166  }
167
168
169
170  /**
171   * Retrieves an integer value that represents the order in which this RDN
172   * name-value pair should be placed in relation to the provided RDN name-value
173   * pair in a sorted list.
174   *
175   * @param p The RDN name-value pair to be ordered relative to this RDN
176   *          name-value pair.  It must not be {@code null}.
177   *
178   * @return A negative integer if this RDN name-value pair should be ordered
179   * before the provided RDN name-value pair, a positive integer if this RDN
180   * name-value pair should be ordered after the provided RDN name-value pair,
181   * or zero if this RDN name-value pair is logically equivalent to the provided
182   * RDN name-value pair.
183   */
184  @Override()
185  public int compareTo(final RDNNameValuePair p)
186  {
187    final String thisLowerName = getLowercaseAttributeName();
188    final String thatLowerName = p.getLowercaseAttributeName();
189    final int nameComparison = thisLowerName.compareTo(thatLowerName);
190    if (nameComparison != 0)
191    {
192      return nameComparison;
193    }
194
195    final String thisNormalizedString = toNormalizedString();
196    final String thatNormalizedString = p.toNormalizedString();
197    return thisNormalizedString.compareTo(thatNormalizedString);
198  }
199
200
201
202  /**
203   * Retrieves an integer value that represents the order in which the provided
204   * RDN name-value pairs should be placed in a sorted list.
205   *
206   * @param p1 The first RDN name-value pair to compare.  It must not be {@code
207   *           null}.
208   * @param p2 The second RDN name-value pair to compare.  It must not be {@code
209   *           null}.
210   *
211   * @return A negative integer if the first RDN name-value pair should be
212   * ordered before the second RDN name-value pair, a positive integer if the
213   * first RDN name-value pair should be ordered after the second RDN name-value
214   * pair, or zero if the provided RDN name-value pairs are logically
215   * equivalent.
216   */
217  @Override()
218  public int compare(final RDNNameValuePair p1, final RDNNameValuePair p2)
219  {
220    return p1.compareTo(p2);
221  }
222
223
224
225  /**
226   * Retrieves a hash code for this RDN name-value pair.
227   *
228   * @return A hash code for this RDN name-value pair.
229   */
230  @Override()
231  public int hashCode()
232  {
233    return toNormalizedString().hashCode();
234  }
235
236
237
238  /**
239   * Indicates whether the provided object is considered logically equivalent to
240   * this RDN name-value pair.
241   *
242   * @param o The object for which to make the determination.
243   *
244   * @return {@code true} if the provided object is an RDN name-value pair that
245   * is logically equivalent to this RDN name-value pair, or {@code false} if
246   * not.
247   */
248  public boolean equals(final Object o)
249  {
250    if (o == null)
251    {
252      return false;
253    }
254
255    if (o == this)
256    {
257      return true;
258    }
259
260    if (! (o instanceof RDNNameValuePair))
261    {
262      return false;
263    }
264
265    final RDNNameValuePair p = (RDNNameValuePair) o;
266    return toNormalizedString().equals(p.toNormalizedString());
267  }
268
269
270
271  /**
272   * Retrieves a string representation of this RDN name-value pair.
273   *
274   * @return  A string representation of this RDN name-value pair.
275   */
276  @Override()
277  public String toString()
278  {
279    if (stringRepresentation == null)
280    {
281      final StringBuilder buffer = new StringBuilder();
282      toString(buffer, false);
283      stringRepresentation = buffer.toString();
284    }
285
286    return stringRepresentation;
287  }
288
289
290
291  /**
292   * Retrieves a string representation of this RDN name-value pair with minimal
293   * encoding for special characters.  Only those characters specified in RFC
294   * 4514 section 2.4 will be escaped.  No escaping will be used for non-ASCII
295   * characters or non-printable ASCII characters.
296   *
297   * @return  A string representation of this RDN name-value pair with minimal
298   *          encoding for special characters.
299   */
300  public String toMinimallyEncodedString()
301  {
302    final StringBuilder buffer = new StringBuilder();
303    toString(buffer, true);
304    return buffer.toString();
305  }
306
307
308
309  /**
310   * Appends a string representation of this RDN name-value pair to the provided
311   * buffer.
312   *
313   * @param  buffer            The buffer to which the string representation is
314   *                           to be appended.
315   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
316   *                           special characters to the bare minimum required
317   *                           by LDAP (as per RFC 4514 section 2.4).  If this
318   *                           is {@code true}, then only leading and trailing
319   *                           spaces, double quotes, plus signs, commas,
320   *                           semicolons, greater-than, less-than, and
321   *                           backslash characters will be encoded.
322   */
323  public void toString(final StringBuilder buffer,
324                       final boolean minimizeEncoding)
325  {
326    if ((stringRepresentation != null) && (! minimizeEncoding))
327    {
328      buffer.append(stringRepresentation);
329      return;
330    }
331
332    final boolean bufferWasEmpty = (buffer.length() == 0);
333
334    buffer.append(attributeName);
335    buffer.append('=');
336    RDN.appendValue(buffer, attributeValue, minimizeEncoding);
337
338    if (bufferWasEmpty && (! minimizeEncoding))
339    {
340      stringRepresentation = buffer.toString();
341    }
342  }
343
344
345
346  /**
347   * Retrieves a normalized string representation of this RDN name-value pair.
348   *
349   * @return  A normalized string representation of this RDN name-value pair.
350   */
351  public String toNormalizedString()
352  {
353    if (normalizedString == null)
354    {
355      final StringBuilder buffer = new StringBuilder();
356      toNormalizedString(buffer);
357      normalizedString = buffer.toString();
358    }
359
360    return normalizedString;
361  }
362
363
364
365  /**
366   * Appends a normalized string representation of this RDN name-value pair to
367   * the provided buffer.
368   *
369   * @param  buffer  The buffer to which the normalized string representation
370   *                 should be appended.  It must not be {@code null}.
371   */
372  public void toNormalizedString(final StringBuilder buffer)
373  {
374    buffer.append(getLowercaseAttributeName());
375    buffer.append('=');
376    RDN.appendNormalizedValue(buffer, attributeName, attributeValue, schema);
377  }
378}