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