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