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}