001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-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) 2007-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.nio.ByteBuffer;
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.Comparator;
045import java.util.Iterator;
046import java.util.SortedSet;
047import java.util.TreeSet;
048
049import com.unboundid.asn1.ASN1OctetString;
050import com.unboundid.ldap.matchingrules.MatchingRule;
051import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
052import com.unboundid.ldap.sdk.schema.Schema;
053import com.unboundid.util.ByteStringBuffer;
054import com.unboundid.util.Debug;
055import com.unboundid.util.NotMutable;
056import com.unboundid.util.NotNull;
057import com.unboundid.util.Nullable;
058import com.unboundid.util.StaticUtils;
059import com.unboundid.util.ThreadSafety;
060import com.unboundid.util.ThreadSafetyLevel;
061import com.unboundid.util.Validator;
062
063import static com.unboundid.ldap.sdk.LDAPMessages.*;
064
065
066
067/**
068 * This class provides a data structure for holding information about an LDAP
069 * relative distinguished name (RDN).  An RDN consists of one or more
070 * attribute name-value pairs.  See
071 * <A HREF="http://www.ietf.org/rfc/rfc4514.txt">RFC 4514</A> for more
072 * information about representing DNs and RDNs as strings.  See the
073 * documentation in the {@link DN} class for more information about DNs and
074 * RDNs.
075 */
076@NotMutable()
077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078public final class RDN
079       implements Comparable<RDN>, Comparator<RDN>, Serializable
080{
081  /**
082   * The serial version UID for this serializable class.
083   */
084  private static final long serialVersionUID = 2923419812807188487L;
085
086
087
088  // The set of attribute values for this RDN.
089  @NotNull private final ASN1OctetString[] attributeValues;
090
091  // The schema to use to generate the normalized string representation of this
092  // RDN, if any.
093  @Nullable private final Schema schema;
094
095  // The name-value pairs that comprise this RDN.
096  @Nullable private volatile SortedSet<RDNNameValuePair> nameValuePairs;
097
098  // The normalized string representation for this RDN.
099  @Nullable private volatile String normalizedString;
100
101  // The user-defined string representation for this RDN.
102  @Nullable private volatile String rdnString;
103
104  // The set of attribute names for this RDN.
105  @NotNull private final String[] attributeNames;
106
107
108
109  /**
110   * Creates a new single-valued RDN with the provided information.
111   *
112   * @param  attributeName   The attribute name for this RDN.  It must not be
113   *                         {@code null}.
114   * @param  attributeValue  The attribute value for this RDN.  It must not be
115   *                         {@code null}.
116   */
117  public RDN(@NotNull final String attributeName,
118             @NotNull final String attributeValue)
119  {
120    this(attributeName, attributeValue, null);
121  }
122
123
124
125  /**
126   * Creates a new single-valued RDN with the provided information.
127   *
128   * @param  attributeName   The attribute name for this RDN.  It must not be
129   *                         {@code null}.
130   * @param  attributeValue  The attribute value for this RDN.  It must not be
131   *                         {@code null}.
132   * @param  schema          The schema to use to generate the normalized string
133   *                         representation of this RDN.  It may be {@code null}
134   *                         if no schema is available.
135   */
136  public RDN(@NotNull final String attributeName,
137             @NotNull final String attributeValue,
138             @Nullable final Schema schema)
139  {
140    Validator.ensureNotNull(attributeName, attributeValue);
141
142    this.schema = schema;
143
144    attributeNames  = new String[] { attributeName };
145    attributeValues =
146         new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
147
148    nameValuePairs = null;
149    normalizedString = null;
150    rdnString = null;
151  }
152
153
154
155  /**
156   * Creates a new single-valued RDN with the provided information.
157   *
158   * @param  attributeName   The attribute name for this RDN.  It must not be
159   *                         {@code null}.
160   * @param  attributeValue  The attribute value for this RDN.  It must not be
161   *                         {@code null}.
162   */
163  public RDN(@NotNull final String attributeName,
164             @NotNull final byte[] attributeValue)
165  {
166    this(attributeName, attributeValue, null);
167  }
168
169
170
171  /**
172   * Creates a new single-valued RDN with the provided information.
173   *
174   * @param  attributeName   The attribute name for this RDN.  It must not be
175   *                         {@code null}.
176   * @param  attributeValue  The attribute value for this RDN.  It must not be
177   *                         {@code null}.
178   * @param  schema          The schema to use to generate the normalized string
179   *                         representation of this RDN.  It may be {@code null}
180   *                         if no schema is available.
181   */
182  public RDN(@NotNull final String attributeName,
183             @NotNull final byte[] attributeValue,
184             @Nullable final Schema schema)
185  {
186    Validator.ensureNotNull(attributeName, attributeValue);
187
188    this.schema = schema;
189
190    attributeNames  = new String[] { attributeName };
191    attributeValues =
192         new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
193
194    nameValuePairs = null;
195    normalizedString = null;
196    rdnString = null;
197  }
198
199
200
201  /**
202   * Creates a new (potentially multivalued) RDN.  The set of names must have
203   * the same number of elements as the set of values, and there must be at
204   * least one element in each array.
205   *
206   * @param  attributeNames   The set of attribute names for this RDN.  It must
207   *                          not be {@code null} or empty.
208   * @param  attributeValues  The set of attribute values for this RDN.  It must
209   *                          not be {@code null} or empty.
210   */
211  public RDN(@NotNull final String[] attributeNames,
212             @NotNull final String[] attributeValues)
213  {
214    this(attributeNames, attributeValues, null);
215  }
216
217
218
219  /**
220   * Creates a new (potentially multivalued) RDN.  The set of names must have
221   * the same number of elements as the set of values, and there must be at
222   * least one element in each array.
223   *
224   * @param  attributeNames   The set of attribute names for this RDN.  It must
225   *                          not be {@code null} or empty.
226   * @param  attributeValues  The set of attribute values for this RDN.  It must
227   *                          not be {@code null} or empty.
228   * @param  schema           The schema to use to generate the normalized
229   *                          string representation of this RDN.  It may be
230   *                          {@code null} if no schema is available.
231   */
232  public RDN(@NotNull final String[] attributeNames,
233             @NotNull final String[] attributeValues,
234             @Nullable final Schema schema)
235  {
236    Validator.ensureNotNull(attributeNames, attributeValues);
237    Validator.ensureTrue(attributeNames.length == attributeValues.length,
238         "RDN.attributeNames and attributeValues must be the same size.");
239    Validator.ensureTrue(attributeNames.length > 0,
240         "RDN.attributeNames must not be empty.");
241
242    this.attributeNames = attributeNames;
243    this.schema         = schema;
244
245    this.attributeValues = new ASN1OctetString[attributeValues.length];
246    for (int i=0; i < attributeValues.length; i++)
247    {
248      this.attributeValues[i] = new ASN1OctetString(attributeValues[i]);
249    }
250
251    nameValuePairs = null;
252    normalizedString = null;
253    rdnString = null;
254  }
255
256
257
258  /**
259   * Creates a new (potentially multivalued) RDN.  The set of names must have
260   * the same number of elements as the set of values, and there must be at
261   * least one element in each array.
262   *
263   * @param  attributeNames   The set of attribute names for this RDN.  It must
264   *                          not be {@code null} or empty.
265   * @param  attributeValues  The set of attribute values for this RDN.  It must
266   *                          not be {@code null} or empty.
267   */
268  public RDN(@NotNull final String[] attributeNames,
269             @NotNull final byte[][] attributeValues)
270  {
271    this(attributeNames, attributeValues, null);
272  }
273
274
275
276  /**
277   * Creates a new (potentially multivalued) RDN.  The set of names must have
278   * the same number of elements as the set of values, and there must be at
279   * least one element in each array.
280   *
281   * @param  attributeNames   The set of attribute names for this RDN.  It must
282   *                          not be {@code null} or empty.
283   * @param  attributeValues  The set of attribute values for this RDN.  It must
284   *                          not be {@code null} or empty.
285   * @param  schema           The schema to use to generate the normalized
286   *                          string representation of this RDN.  It may be
287   *                          {@code null} if no schema is available.
288   */
289  public RDN(@NotNull final String[] attributeNames,
290             @NotNull final byte[][] attributeValues,
291             @Nullable final Schema schema)
292  {
293    Validator.ensureNotNull(attributeNames, attributeValues);
294    Validator.ensureTrue(attributeNames.length == attributeValues.length,
295         "RDN.attributeNames and attributeValues must be the same size.");
296    Validator.ensureTrue(attributeNames.length > 0,
297         "RDN.attributeNames must not be empty.");
298
299    this.attributeNames = attributeNames;
300    this.schema         = schema;
301
302    this.attributeValues = new ASN1OctetString[attributeValues.length];
303    for (int i=0; i < attributeValues.length; i++)
304    {
305      this.attributeValues[i] = new ASN1OctetString(attributeValues[i]);
306    }
307
308    nameValuePairs = null;
309    normalizedString = null;
310    rdnString = null;
311  }
312
313
314
315  /**
316   * Creates a new single-valued RDN with the provided information.
317   *
318   * @param  attributeName   The name to use for this RDN.
319   * @param  attributeValue  The value to use for this RDN.
320   * @param  schema          The schema to use to generate the normalized string
321   *                         representation of this RDN.  It may be {@code null}
322   *                         if no schema is available.
323   * @param  rdnString       The string representation for this RDN.
324   */
325  RDN(@NotNull final String attributeName,
326      @NotNull final ASN1OctetString attributeValue,
327      @Nullable final Schema schema, @NotNull final String rdnString)
328  {
329    this.rdnString = rdnString;
330    this.schema    = schema;
331
332    attributeNames  = new String[] { attributeName };
333    attributeValues = new ASN1OctetString[] { attributeValue };
334
335    nameValuePairs = null;
336    normalizedString = null;
337  }
338
339
340
341  /**
342   * Creates a new potentially multivalued RDN with the provided information.
343   *
344   * @param  attributeNames   The set of names to use for this RDN.
345   * @param  attributeValues  The set of values to use for this RDN.
346   * @param  rdnString        The string representation for this RDN.
347   * @param  schema           The schema to use to generate the normalized
348   *                          string representation of this RDN.  It may be
349   *                          {@code null} if no schema is available.
350   */
351  RDN(@NotNull final String[] attributeNames,
352      @NotNull final ASN1OctetString[] attributeValues,
353      @Nullable final Schema schema, @NotNull final String rdnString)
354  {
355    this.rdnString = rdnString;
356    this.schema    = schema;
357
358    this.attributeNames  = attributeNames;
359    this.attributeValues = attributeValues;
360
361    nameValuePairs = null;
362    normalizedString = null;
363  }
364
365
366
367  /**
368   * Creates a new RDN from the provided string representation.
369   *
370   * @param  rdnString  The string representation to use for this RDN.  It must
371   *                    not be empty or {@code null}.
372   *
373   * @throws  LDAPException  If the provided string cannot be parsed as a valid
374   *                         RDN.
375   */
376  public RDN(@NotNull final String rdnString)
377         throws LDAPException
378  {
379    this(rdnString, (Schema) null, false);
380  }
381
382
383
384  /**
385   * Creates a new RDN from the provided string representation.
386   *
387   * @param  rdnString  The string representation to use for this RDN.  It must
388   *                    not be empty or {@code null}.
389   * @param  schema     The schema to use to generate the normalized string
390   *                    representation of this RDN.  It may be {@code null} if
391   *                    no schema is available.
392   *
393   * @throws  LDAPException  If the provided string cannot be parsed as a valid
394   *                         RDN.
395   */
396  public RDN(@NotNull final String rdnString, @Nullable final Schema schema)
397         throws LDAPException
398  {
399    this(rdnString, schema, false);
400  }
401
402
403
404  /**
405   * Creates a new RDN from the provided string representation.
406   *
407   * @param  rdnString           The string representation to use for this RDN.
408   *                             It must not be empty or {@code null}.
409   * @param  schema              The schema to use to generate the normalized
410   *                             string representation of this RDN.  It may be
411   *                             {@code null} if no schema is available.
412   * @param  strictNameChecking  Indicates whether to verify that all attribute
413   *                             type names are valid as per RFC 4514.  If this
414   *                             is {@code false}, then some technically invalid
415   *                             characters may be accepted in attribute type
416   *                             names.  If this is {@code true}, then names
417   *                             must be strictly compliant.
418   *
419   * @throws  LDAPException  If the provided string cannot be parsed as a valid
420   *                         RDN.
421   */
422  public RDN(@NotNull final String rdnString, @Nullable final Schema schema,
423             final boolean strictNameChecking)
424         throws LDAPException
425  {
426    Validator.ensureNotNull(rdnString);
427
428    this.rdnString = rdnString;
429    this.schema    = schema;
430
431    nameValuePairs = null;
432    normalizedString = null;
433
434    int pos = 0;
435    final int length = rdnString.length();
436
437    // First, skip over any leading spaces.
438    while ((pos < length) && (rdnString.charAt(pos) == ' '))
439    {
440      pos++;
441    }
442
443    // Read until we find a space or an equal sign.
444    int attrStartPos = pos;
445    while (pos < length)
446    {
447      final char c = rdnString.charAt(pos);
448      if ((c == ' ') || (c == '='))
449      {
450        break;
451      }
452
453      pos++;
454    }
455
456    // Extract the attribute name, and optionally verify that it is valid.
457    String attrName = rdnString.substring(attrStartPos, pos);
458    if (attrName.isEmpty())
459    {
460      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
461           ERR_RDN_NO_ATTR_NAME.get(rdnString));
462    }
463
464    if (strictNameChecking)
465    {
466      if (! (Attribute.nameIsValid(attrName) ||
467           StaticUtils.isNumericOID(attrName)))
468      {
469        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
470             ERR_RDN_INVALID_ATTR_NAME.get(rdnString, attrName));
471      }
472    }
473
474
475    // Skip over any spaces between the attribute name and the equal sign.
476    while ((pos < length) && (rdnString.charAt(pos) == ' '))
477    {
478      pos++;
479    }
480
481    if ((pos >= length) || (rdnString.charAt(pos) != '='))
482    {
483      // We didn't find an equal sign.
484      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
485           ERR_RDN_NO_EQUAL_SIGN.get(rdnString, attrName));
486    }
487
488
489    // The next character is the equal sign.  Skip it, and then skip over any
490    // spaces between it and the attribute value.
491    pos++;
492    while ((pos < length) && (rdnString.charAt(pos) == ' '))
493    {
494      pos++;
495    }
496
497
498    // Look at the next character.  If it is an octothorpe (#), then the value
499    // must be a hex-encoded BER element, which we'll need to parse and take the
500    // value of that element.  Otherwise, it's a regular string (although
501    // possibly containing escaped or quoted characters).
502    ASN1OctetString value;
503    if (pos >= length)
504    {
505      value = new ASN1OctetString();
506    }
507    else if (rdnString.charAt(pos) == '#')
508    {
509      // It is a hex-encoded value, so we'll read until we find the end of the
510      // string or the first non-hex character, which must be either a space or
511      // a plus sign.
512      final byte[] valueArray = readHexString(rdnString, ++pos);
513
514      try
515      {
516        value = ASN1OctetString.decodeAsOctetString(valueArray);
517      }
518      catch (final Exception e)
519      {
520        Debug.debugException(e);
521        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
522             ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(rdnString, attrName), e);
523      }
524
525      pos += (valueArray.length * 2);
526    }
527    else
528    {
529      // It is a string value, which potentially includes escaped characters.
530      final StringBuilder buffer = new StringBuilder();
531      pos = readValueString(rdnString, pos, buffer);
532      value = new ASN1OctetString(buffer.toString());
533    }
534
535
536    // Skip over any spaces until we find a plus sign or the end of the value.
537    while ((pos < length) && (rdnString.charAt(pos) == ' '))
538    {
539      pos++;
540    }
541
542    if (pos >= length)
543    {
544      // It's a single-valued RDN, so we have everything that we need.
545      attributeNames  = new String[] { attrName };
546      attributeValues = new ASN1OctetString[] { value };
547      return;
548    }
549
550    // It's a multivalued RDN, so create temporary lists to hold the names and
551    // values.
552    final ArrayList<String> nameList = new ArrayList<>(5);
553    final ArrayList<ASN1OctetString> valueList = new ArrayList<>(5);
554    nameList.add(attrName);
555    valueList.add(value);
556
557    if (rdnString.charAt(pos) == '+')
558    {
559      pos++;
560    }
561    else
562    {
563      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
564           ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString));
565    }
566
567    if (pos >= length)
568    {
569      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
570           ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString));
571    }
572
573    int numValues = 1;
574    while (pos < length)
575    {
576      // Skip over any spaces between the plus sign and the attribute name.
577      while ((pos < length) && (rdnString.charAt(pos) == ' '))
578      {
579        pos++;
580      }
581
582      attrStartPos = pos;
583      while (pos < length)
584      {
585        final char c = rdnString.charAt(pos);
586        if ((c == ' ') || (c == '='))
587        {
588          break;
589        }
590
591        pos++;
592      }
593
594      // Extract and validate the attribute type name.
595      attrName = rdnString.substring(attrStartPos, pos);
596      if (attrName.isEmpty())
597      {
598        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
599             ERR_RDN_NO_ATTR_NAME.get(rdnString));
600      }
601
602      if (strictNameChecking)
603      {
604        if (! (Attribute.nameIsValid(attrName) ||
605             StaticUtils.isNumericOID(attrName)))
606        {
607          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
608               ERR_RDN_INVALID_ATTR_NAME.get(rdnString, attrName));
609        }
610      }
611
612      // Skip over any spaces between the attribute name and the equal sign.
613      while ((pos < length) && (rdnString.charAt(pos) == ' '))
614      {
615        pos++;
616      }
617
618      if ((pos >= length) || (rdnString.charAt(pos) != '='))
619      {
620        // We didn't find an equal sign.
621        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
622             ERR_RDN_NO_EQUAL_SIGN.get(rdnString, attrName));
623      }
624
625      // The next character is the equal sign.  Skip it, and then skip over any
626      // spaces between it and the attribute value.
627      pos++;
628      while ((pos < length) && (rdnString.charAt(pos) == ' '))
629      {
630        pos++;
631      }
632
633      // Look at the next character.  If it is an octothorpe (#), then the value
634      // must be a hex-encoded BER element, which we'll need to parse and take
635      // the value of that element.  Otherwise, it's a regular string (although
636      // possibly containing escaped or quoted characters).
637      if (pos >= length)
638      {
639        value = new ASN1OctetString();
640      }
641      else if (rdnString.charAt(pos) == '#')
642      {
643        // It is a hex-encoded value, so we'll read until we find the end of the
644        // string or the first non-hex character, which must be either a space
645        // or a plus sign.
646        final byte[] valueArray = readHexString(rdnString, ++pos);
647
648        try
649        {
650          value = ASN1OctetString.decodeAsOctetString(valueArray);
651        }
652        catch (final Exception e)
653        {
654          Debug.debugException(e);
655          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
656               ERR_RDN_HEX_STRING_NOT_BER_ENCODED.get(rdnString, attrName), e);
657        }
658
659        pos += (valueArray.length * 2);
660      }
661      else
662      {
663        // It is a string value, which potentially includes escaped characters.
664        final StringBuilder buffer = new StringBuilder();
665        pos = readValueString(rdnString, pos, buffer);
666        value = new ASN1OctetString(buffer.toString());
667      }
668
669
670      // Skip over any spaces until we find a plus sign or the end of the value.
671      while ((pos < length) && (rdnString.charAt(pos) == ' '))
672      {
673        pos++;
674      }
675
676      nameList.add(attrName);
677      valueList.add(value);
678      numValues++;
679
680      if (pos >= length)
681      {
682        // We're at the end of the value, so break out of the loop.
683        break;
684      }
685      else
686      {
687        // Skip over the plus sign and loop again to read another name-value
688        // pair.
689        if (rdnString.charAt(pos) == '+')
690        {
691          pos++;
692        }
693        else
694        {
695          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
696               ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get(rdnString));
697        }
698      }
699
700      if (pos >= length)
701      {
702        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
703             ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get(rdnString));
704      }
705    }
706
707    attributeNames  = new String[numValues];
708    attributeValues = new ASN1OctetString[numValues];
709    for (int i=0; i < numValues; i++)
710    {
711      attributeNames[i]  = nameList.get(i);
712      attributeValues[i] = valueList.get(i);
713    }
714  }
715
716
717
718  /**
719   * Parses a hex-encoded RDN value from the provided string.  Reading will
720   * continue until the end of the string is reached or a non-escaped plus sign
721   * is encountered.  After returning, the caller should increment its position
722   * by two times the length of the value array.
723   *
724   * @param  rdnString  The string to be parsed.  It must not be {@code null}.
725   * @param  startPos   The position at which to start reading the value.  It
726   *                    should be the position immediately after the octothorpe
727   *                    at the start of the hex-encoded value.
728   *
729   * @return  A byte array containing the parsed value.
730   *
731   * @throws  LDAPException  If an error occurs while reading the value (e.g.,
732   *                         if it contains non-hex characters, or has an odd
733   *                         number of characters.
734   */
735  @NotNull()
736  static byte[] readHexString(@NotNull final String rdnString,
737                              final int startPos)
738         throws LDAPException
739  {
740    final int length = rdnString.length();
741    int pos = startPos;
742
743    final ByteBuffer buffer = ByteBuffer.allocate(length-pos);
744hexLoop:
745    while (pos < length)
746    {
747      final byte hexByte;
748      switch (rdnString.charAt(pos++))
749      {
750        case '0':
751          hexByte = 0x00;
752          break;
753        case '1':
754          hexByte = 0x10;
755          break;
756        case '2':
757          hexByte = 0x20;
758          break;
759        case '3':
760          hexByte = 0x30;
761          break;
762        case '4':
763          hexByte = 0x40;
764          break;
765        case '5':
766          hexByte = 0x50;
767          break;
768        case '6':
769          hexByte = 0x60;
770          break;
771        case '7':
772          hexByte = 0x70;
773          break;
774        case '8':
775          hexByte = (byte) 0x80;
776          break;
777        case '9':
778          hexByte = (byte) 0x90;
779          break;
780        case 'a':
781        case 'A':
782          hexByte = (byte) 0xA0;
783          break;
784        case 'b':
785        case 'B':
786          hexByte = (byte) 0xB0;
787          break;
788        case 'c':
789        case 'C':
790          hexByte = (byte) 0xC0;
791          break;
792        case 'd':
793        case 'D':
794          hexByte = (byte) 0xD0;
795          break;
796        case 'e':
797        case 'E':
798          hexByte = (byte) 0xE0;
799          break;
800        case 'f':
801        case 'F':
802          hexByte = (byte) 0xF0;
803          break;
804        case ' ':
805        case '+':
806        case ',':
807        case ';':
808          // This indicates that we've reached the end of the hex string.
809          break hexLoop;
810        default:
811          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
812               ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1),
813                    (pos-1)));
814      }
815
816      if (pos >= length)
817      {
818        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
819             ERR_RDN_MISSING_HEX_CHAR.get(rdnString));
820      }
821
822      switch (rdnString.charAt(pos++))
823      {
824        case '0':
825          buffer.put(hexByte);
826          break;
827        case '1':
828          buffer.put((byte) (hexByte | 0x01));
829          break;
830        case '2':
831          buffer.put((byte) (hexByte | 0x02));
832          break;
833        case '3':
834          buffer.put((byte) (hexByte | 0x03));
835          break;
836        case '4':
837          buffer.put((byte) (hexByte | 0x04));
838          break;
839        case '5':
840          buffer.put((byte) (hexByte | 0x05));
841          break;
842        case '6':
843          buffer.put((byte) (hexByte | 0x06));
844          break;
845        case '7':
846          buffer.put((byte) (hexByte | 0x07));
847          break;
848        case '8':
849          buffer.put((byte) (hexByte | 0x08));
850          break;
851        case '9':
852          buffer.put((byte) (hexByte | 0x09));
853          break;
854        case 'a':
855        case 'A':
856          buffer.put((byte) (hexByte | 0x0A));
857          break;
858        case 'b':
859        case 'B':
860          buffer.put((byte) (hexByte | 0x0B));
861          break;
862        case 'c':
863        case 'C':
864          buffer.put((byte) (hexByte | 0x0C));
865          break;
866        case 'd':
867        case 'D':
868          buffer.put((byte) (hexByte | 0x0D));
869          break;
870        case 'e':
871        case 'E':
872          buffer.put((byte) (hexByte | 0x0E));
873          break;
874        case 'f':
875        case 'F':
876          buffer.put((byte) (hexByte | 0x0F));
877          break;
878        default:
879          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
880               ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1),
881                    (pos-1)));
882      }
883    }
884
885    buffer.flip();
886    final byte[] valueArray = new byte[buffer.limit()];
887    buffer.get(valueArray);
888    return valueArray;
889  }
890
891
892
893  /**
894   * Reads a string value from the provided RDN string.  Reading will continue
895   * until the end of the string is reached or until a non-escaped plus sign is
896   * encountered.
897   *
898   * @param  rdnString  The string from which to read the value.
899   * @param  startPos   The position in the RDN string at which to start reading
900   *                    the value.
901   * @param  buffer     The buffer into which the parsed value should be
902   *                    placed.
903   *
904   * @return  The position at which the caller should continue reading when
905   *          parsing the RDN.
906   *
907   * @throws  LDAPException  If a problem occurs while reading the value.
908   */
909  static int readValueString(@NotNull final String rdnString,
910                             final int startPos,
911                             @NotNull final StringBuilder buffer)
912          throws LDAPException
913  {
914    final int length = rdnString.length();
915    int pos = startPos;
916
917    boolean inQuotes = false;
918valueLoop:
919    while (pos < length)
920    {
921      char c = rdnString.charAt(pos);
922      switch (c)
923      {
924        case '\\':
925          // It's an escaped value.  It can either be followed by a single
926          // character (e.g., backslash, space, octothorpe, equals, double
927          // quote, plus sign, comma, semicolon, less than, or greater-than), or
928          // two hex digits.  If it is followed by hex digits, then continue
929          // reading to see if there are more of them.
930          if ((pos+1) >= length)
931          {
932            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
933                 ERR_RDN_ENDS_WITH_BACKSLASH.get(rdnString));
934          }
935          else
936          {
937            pos++;
938            c = rdnString.charAt(pos);
939            if (StaticUtils.isHex(c))
940            {
941              // We need to subtract one from the resulting position because
942              // it will be incremented later.
943              pos = readEscapedHexString(rdnString, pos, buffer) - 1;
944            }
945            else
946            {
947              buffer.append(c);
948            }
949          }
950          break;
951
952        case '"':
953          if (inQuotes)
954          {
955            // This should be the end of the value.  If it's not, then fail.
956            pos++;
957            while (pos < length)
958            {
959              c = rdnString.charAt(pos);
960              if ((c == '+') || (c == ',') || (c == ';'))
961              {
962                break;
963              }
964              else if (c != ' ')
965              {
966                throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
967                     ERR_RDN_CHAR_OUTSIDE_QUOTES.get(rdnString, c, (pos-1)));
968              }
969
970              pos++;
971            }
972
973            inQuotes = false;
974            break valueLoop;
975          }
976          else
977          {
978            // This should be the first character of the value.
979            if (pos == startPos)
980            {
981              inQuotes = true;
982            }
983            else
984            {
985              throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
986                   ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(rdnString, pos));
987            }
988          }
989          break;
990
991        case ',':
992        case ';':
993        case '+':
994          // This denotes the end of the value, if it's not in quotes.
995          if (inQuotes)
996          {
997            buffer.append(c);
998          }
999          else
1000          {
1001            break valueLoop;
1002          }
1003          break;
1004
1005        default:
1006          // This is a normal character that should be added to the buffer.
1007          buffer.append(c);
1008          break;
1009      }
1010
1011      pos++;
1012    }
1013
1014
1015    // If the value started with a quotation mark, then make sure it was closed.
1016    if (inQuotes)
1017    {
1018      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1019           ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get(rdnString));
1020    }
1021
1022
1023    // If the value ends with any unescaped trailing spaces, then trim them off.
1024    int bufferPos = buffer.length() - 1;
1025    int rdnStrPos = pos - 2;
1026    while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' '))
1027    {
1028      if (rdnString.charAt(rdnStrPos) == '\\')
1029      {
1030        break;
1031      }
1032      else
1033      {
1034        buffer.deleteCharAt(bufferPos--);
1035        rdnStrPos--;
1036      }
1037    }
1038
1039    return pos;
1040  }
1041
1042
1043
1044  /**
1045   * Reads one or more hex-encoded bytes from the specified portion of the RDN
1046   * string.
1047   *
1048   * @param  rdnString  The string from which the data is to be read.
1049   * @param  startPos   The position at which to start reading.  This should be
1050   *                    the first hex character immediately after the initial
1051   *                    backslash.
1052   * @param  buffer     The buffer to which the decoded string portion should be
1053   *                    appended.
1054   *
1055   * @return  The position at which the caller may resume parsing.
1056   *
1057   * @throws  LDAPException  If a problem occurs while reading hex-encoded
1058   *                         bytes.
1059   */
1060  private static int readEscapedHexString(@NotNull final String rdnString,
1061                                          final int startPos,
1062                                          @NotNull final StringBuilder buffer)
1063          throws LDAPException
1064  {
1065    final int length = rdnString.length();
1066    int pos = startPos;
1067
1068    final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos);
1069    while (pos < length)
1070    {
1071      final byte b;
1072      switch (rdnString.charAt(pos++))
1073      {
1074        case '0':
1075          b = 0x00;
1076          break;
1077        case '1':
1078          b = 0x10;
1079          break;
1080        case '2':
1081          b = 0x20;
1082          break;
1083        case '3':
1084          b = 0x30;
1085          break;
1086        case '4':
1087          b = 0x40;
1088          break;
1089        case '5':
1090          b = 0x50;
1091          break;
1092        case '6':
1093          b = 0x60;
1094          break;
1095        case '7':
1096          b = 0x70;
1097          break;
1098        case '8':
1099          b = (byte) 0x80;
1100          break;
1101        case '9':
1102          b = (byte) 0x90;
1103          break;
1104        case 'a':
1105        case 'A':
1106          b = (byte) 0xA0;
1107          break;
1108        case 'b':
1109        case 'B':
1110          b = (byte) 0xB0;
1111          break;
1112        case 'c':
1113        case 'C':
1114          b = (byte) 0xC0;
1115          break;
1116        case 'd':
1117        case 'D':
1118          b = (byte) 0xD0;
1119          break;
1120        case 'e':
1121        case 'E':
1122          b = (byte) 0xE0;
1123          break;
1124        case 'f':
1125        case 'F':
1126          b = (byte) 0xF0;
1127          break;
1128        default:
1129          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1130               ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1),
1131                    (pos-1)));
1132      }
1133
1134      if (pos >= length)
1135      {
1136        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1137             ERR_RDN_MISSING_HEX_CHAR.get(rdnString));
1138      }
1139
1140      switch (rdnString.charAt(pos++))
1141      {
1142        case '0':
1143          byteBuffer.put(b);
1144          break;
1145        case '1':
1146          byteBuffer.put((byte) (b | 0x01));
1147          break;
1148        case '2':
1149          byteBuffer.put((byte) (b | 0x02));
1150          break;
1151        case '3':
1152          byteBuffer.put((byte) (b | 0x03));
1153          break;
1154        case '4':
1155          byteBuffer.put((byte) (b | 0x04));
1156          break;
1157        case '5':
1158          byteBuffer.put((byte) (b | 0x05));
1159          break;
1160        case '6':
1161          byteBuffer.put((byte) (b | 0x06));
1162          break;
1163        case '7':
1164          byteBuffer.put((byte) (b | 0x07));
1165          break;
1166        case '8':
1167          byteBuffer.put((byte) (b | 0x08));
1168          break;
1169        case '9':
1170          byteBuffer.put((byte) (b | 0x09));
1171          break;
1172        case 'a':
1173        case 'A':
1174          byteBuffer.put((byte) (b | 0x0A));
1175          break;
1176        case 'b':
1177        case 'B':
1178          byteBuffer.put((byte) (b | 0x0B));
1179          break;
1180        case 'c':
1181        case 'C':
1182          byteBuffer.put((byte) (b | 0x0C));
1183          break;
1184        case 'd':
1185        case 'D':
1186          byteBuffer.put((byte) (b | 0x0D));
1187          break;
1188        case 'e':
1189        case 'E':
1190          byteBuffer.put((byte) (b | 0x0E));
1191          break;
1192        case 'f':
1193        case 'F':
1194          byteBuffer.put((byte) (b | 0x0F));
1195          break;
1196        default:
1197          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1198               ERR_RDN_INVALID_HEX_CHAR.get(rdnString, rdnString.charAt(pos-1),
1199                    (pos-1)));
1200      }
1201
1202      if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') &&
1203          StaticUtils.isHex(rdnString.charAt(pos+1)))
1204      {
1205        // It appears that there are more hex-encoded bytes to follow, so keep
1206        // reading.
1207        pos++;
1208        continue;
1209      }
1210      else
1211      {
1212        break;
1213      }
1214    }
1215
1216    byteBuffer.flip();
1217    final byte[] byteArray = new byte[byteBuffer.limit()];
1218    byteBuffer.get(byteArray);
1219    buffer.append(StaticUtils.toUTF8String(byteArray));
1220    return pos;
1221  }
1222
1223
1224
1225  /**
1226   * Indicates whether the provided string represents a valid RDN.
1227   *
1228   * @param  s  The string for which to make the determination.  It must not be
1229   *            {@code null}.
1230   *
1231   * @return  {@code true} if the provided string represents a valid RDN, or
1232   *          {@code false} if not.
1233   */
1234  public static boolean isValidRDN(@NotNull final String s)
1235  {
1236    return isValidRDN(s, false);
1237  }
1238
1239
1240
1241  /**
1242   * Indicates whether the provided string represents a valid RDN.
1243   *
1244   * @param  s                   The string for which to make the determination.
1245   *                             It must not be {@code null}.
1246   * @param  strictNameChecking  Indicates whether to verify that all attribute
1247   *                             type names are valid as per RFC 4514.  If this
1248   *                             is {@code false}, then some technically invalid
1249   *                             characters may be accepted in attribute type
1250   *                             names.  If this is {@code true}, then names
1251   *                             must be strictly compliant.
1252   *
1253   * @return  {@code true} if the provided string represents a valid RDN, or
1254   *          {@code false} if not.
1255   */
1256  public static boolean isValidRDN(@NotNull final String s,
1257                                   final boolean strictNameChecking)
1258  {
1259    try
1260    {
1261      new RDN(s, null, strictNameChecking);
1262      return true;
1263    }
1264    catch (final LDAPException le)
1265    {
1266      Debug.debugException(le);
1267      return false;
1268    }
1269  }
1270
1271
1272
1273  /**
1274   * Indicates whether this RDN contains multiple values.
1275   *
1276   * @return  {@code true} if this RDN contains multiple values, or
1277   *          {@code false} if not.
1278   */
1279  public boolean isMultiValued()
1280  {
1281    return (attributeNames.length != 1);
1282  }
1283
1284
1285
1286  /**
1287   * Retrieves the number of values for this RDN.
1288   *
1289   * @return  The number of values for this RDN.
1290   */
1291  public int getValueCount()
1292  {
1293    return attributeNames.length;
1294  }
1295
1296
1297
1298  /**
1299   * Retrieves an array of the attributes that comprise this RDN.
1300   *
1301   * @return  An array of the attributes that comprise this RDN.
1302   */
1303  @NotNull()
1304  public Attribute[] getAttributes()
1305  {
1306    final Attribute[] attrs = new Attribute[attributeNames.length];
1307    for (int i=0; i < attrs.length; i++)
1308    {
1309      attrs[i] = new Attribute(attributeNames[i], schema,
1310           new ASN1OctetString[] {  attributeValues[i] });
1311    }
1312
1313    return attrs;
1314  }
1315
1316
1317
1318  /**
1319   * Retrieves the set of attribute names for this RDN.
1320   *
1321   * @return  The set of attribute names for this RDN.
1322   */
1323  @NotNull()
1324  public String[] getAttributeNames()
1325  {
1326    return attributeNames;
1327  }
1328
1329
1330
1331  /**
1332   * Retrieves the set of attribute values for this RDN.
1333   *
1334   * @return  The set of attribute values for this RDN.
1335   */
1336  @NotNull()
1337  public String[] getAttributeValues()
1338  {
1339    final String[] stringValues = new String[attributeValues.length];
1340    for (int i=0; i < stringValues.length; i++)
1341    {
1342      stringValues[i] = attributeValues[i].stringValue();
1343    }
1344
1345    return stringValues;
1346  }
1347
1348
1349
1350  /**
1351   * Retrieves the set of attribute values for this RDN.
1352   *
1353   * @return  The set of attribute values for this RDN.
1354   */
1355  @NotNull()
1356  public byte[][] getByteArrayAttributeValues()
1357  {
1358    final byte[][] byteValues = new byte[attributeValues.length][];
1359    for (int i=0; i < byteValues.length; i++)
1360    {
1361      byteValues[i] = attributeValues[i].getValue();
1362    }
1363
1364    return byteValues;
1365  }
1366
1367
1368
1369  /**
1370   * Retrieves a sorted set of the name-value pairs that comprise this RDN.
1371   *
1372   * @return  A sorted set of the name-value pairs that comprise this RDN.
1373   */
1374  @NotNull()
1375  public SortedSet<RDNNameValuePair> getNameValuePairs()
1376  {
1377    if (nameValuePairs == null)
1378    {
1379      final SortedSet<RDNNameValuePair> s = new TreeSet<>();
1380      for (int i=0; i < attributeNames.length; i++)
1381      {
1382        s.add(new RDNNameValuePair(attributeNames[i], attributeValues[i],
1383             schema));
1384      }
1385
1386      nameValuePairs = Collections.unmodifiableSortedSet(s);
1387    }
1388
1389    return nameValuePairs;
1390  }
1391
1392
1393
1394  /**
1395   * Retrieves the schema that will be used for this RDN, if any.
1396   *
1397   * @return  The schema that will be used for this RDN, or {@code null} if none
1398   *          has been provided.
1399   */
1400  @Nullable()
1401  Schema getSchema()
1402  {
1403    return schema;
1404  }
1405
1406
1407
1408  /**
1409   * Indicates whether this RDN contains the specified attribute.
1410   *
1411   * @param  attributeName  The name of the attribute for which to make the
1412   *                        determination.
1413   *
1414   * @return  {@code true} if RDN contains the specified attribute, or
1415   *          {@code false} if not.
1416   */
1417  public boolean hasAttribute(@NotNull final String attributeName)
1418  {
1419    for (final RDNNameValuePair nameValuePair : getNameValuePairs())
1420    {
1421      if (nameValuePair.hasAttributeName(attributeName))
1422      {
1423        return true;
1424      }
1425    }
1426
1427    return false;
1428  }
1429
1430
1431
1432  /**
1433   * Indicates whether this RDN contains the specified attribute value.
1434   *
1435   * @param  attributeName   The name of the attribute for which to make the
1436   *                         determination.
1437   * @param  attributeValue  The attribute value for which to make the
1438   *                         determination.
1439   *
1440   * @return  {@code true} if RDN contains the specified attribute, or
1441   *          {@code false} if not.
1442   */
1443  public boolean hasAttributeValue(@NotNull final String attributeName,
1444                                   @NotNull final String attributeValue)
1445  {
1446    for (final RDNNameValuePair nameValuePair : getNameValuePairs())
1447    {
1448      if (nameValuePair.hasAttributeName(attributeName) &&
1449           nameValuePair.hasAttributeValue(attributeValue))
1450      {
1451        return true;
1452      }
1453    }
1454
1455    return false;
1456  }
1457
1458
1459
1460  /**
1461   * Indicates whether this RDN contains the specified attribute value.
1462   *
1463   * @param  attributeName   The name of the attribute for which to make the
1464   *                         determination.
1465   * @param  attributeValue  The attribute value for which to make the
1466   *                         determination.
1467   *
1468   * @return  {@code true} if RDN contains the specified attribute, or
1469   *          {@code false} if not.
1470   */
1471  public boolean hasAttributeValue(@NotNull final String attributeName,
1472                                   @NotNull final byte[] attributeValue)
1473  {
1474    for (final RDNNameValuePair nameValuePair : getNameValuePairs())
1475    {
1476      if (nameValuePair.hasAttributeName(attributeName) &&
1477           nameValuePair.hasAttributeValue(attributeValue))
1478      {
1479        return true;
1480      }
1481    }
1482
1483    return false;
1484  }
1485
1486
1487
1488  /**
1489   * Retrieves a string representation of this RDN.
1490   *
1491   * @return  A string representation of this RDN.
1492   */
1493  @Override()
1494  @NotNull()
1495  public String toString()
1496  {
1497    if (rdnString == null)
1498    {
1499      final ByteStringBuffer buffer = new ByteStringBuffer();
1500      toString(buffer, DN.getDNEscapingStrategy());
1501      rdnString = buffer.toString();
1502    }
1503
1504    return rdnString;
1505  }
1506
1507
1508
1509  /**
1510   * Retrieves a string representation of this RDN with minimal encoding for
1511   * special characters.  Only those characters specified in RFC 4514 section
1512   * 2.4 will be escaped.  No escaping will be used for non-ASCII characters or
1513   * non-printable ASCII characters.
1514   *
1515   * @return  A string representation of this RDN with minimal encoding for
1516   *          special characters.
1517   */
1518  @NotNull()
1519  public String toMinimallyEncodedString()
1520  {
1521    final ByteStringBuffer buffer = new ByteStringBuffer();
1522    toString(buffer, DNEscapingStrategy.MINIMAL);
1523    return buffer.toString();
1524  }
1525
1526
1527
1528  /**
1529   * Appends a string representation of this RDN to the provided buffer.
1530   *
1531   * @param  buffer  The buffer to which the string representation is to be
1532   *                 appended.
1533   */
1534  public void toString(@NotNull final StringBuilder buffer)
1535  {
1536    toString(buffer, false);
1537  }
1538
1539
1540
1541  /**
1542   * Appends a string representation of this RDN to the provided buffer.
1543   *
1544   * @param  buffer            The buffer to which the string representation is
1545   *                           to be appended.
1546   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1547   *                           special characters to the bare minimum required
1548   *                           by LDAP (as per RFC 4514 section 2.4).  If this
1549   *                           is {@code true}, then only leading and trailing
1550   *                           spaces, double quotes, plus signs, commas,
1551   *                           semicolons, greater-than, less-than, and
1552   *                           backslash characters will be encoded.
1553   */
1554  public void toString(@NotNull final StringBuilder buffer,
1555                       final boolean minimizeEncoding)
1556  {
1557    final ByteStringBuffer byteStringBuffer = new ByteStringBuffer();
1558    final DNEscapingStrategy escapingStrategy = (minimizeEncoding
1559         ? DNEscapingStrategy.MINIMAL
1560         : DN.getDNEscapingStrategy());
1561    toString(byteStringBuffer, escapingStrategy);
1562    buffer.append(byteStringBuffer.toString());
1563  }
1564
1565
1566
1567  /**
1568   * Appends a string representation of this RDN to the provided buffer.
1569   *
1570   * @param  buffer            The buffer to which the string representation is
1571   *                           to be appended.  It must not be {@code null}.
1572   * @param  escapingStrategy  The strategy to use to determine which types of
1573   *                           optional escaping should be used for values.  It
1574   *                           must not be {@code null}.
1575   */
1576  public void toString(@NotNull final ByteStringBuffer buffer,
1577                       @NotNull final DNEscapingStrategy escapingStrategy)
1578  {
1579    for (int i=0; i < attributeNames.length; i++)
1580    {
1581      if (i > 0)
1582      {
1583        buffer.append('+');
1584      }
1585
1586      buffer.append(attributeNames[i]);
1587      buffer.append('=');
1588      escapingStrategy.escape(attributeValues[i], buffer);
1589    }
1590  }
1591
1592
1593
1594  /**
1595   * Retrieves a normalized string representation of this RDN.
1596   *
1597   * @return  A normalized string representation of this RDN.
1598   */
1599  @NotNull()
1600  public String toNormalizedString()
1601  {
1602    if (normalizedString == null)
1603    {
1604      final StringBuilder buffer = new StringBuilder();
1605      toNormalizedString(buffer);
1606      normalizedString = buffer.toString();
1607    }
1608
1609    return normalizedString;
1610  }
1611
1612
1613
1614  /**
1615   * Appends a normalized string representation of this RDN to the provided
1616   * buffer.
1617   *
1618   * @param  buffer  The buffer to which the normalized string representation is
1619   *                 to be appended.
1620   */
1621  public void toNormalizedString(@NotNull final StringBuilder buffer)
1622  {
1623    if (attributeNames.length == 1)
1624    {
1625      // It's a single-valued RDN, so there is no need to sort anything.
1626      final String name = normalizeAttrName(attributeNames[0]);
1627      buffer.append(name);
1628      buffer.append('=');
1629      appendNormalizedValue(buffer, name, attributeValues[0], schema);
1630    }
1631    else
1632    {
1633      // It's a multivalued RDN, so we need to sort the components.
1634      final Iterator<RDNNameValuePair> iterator =
1635           getNameValuePairs().iterator();
1636      while (iterator.hasNext())
1637      {
1638        buffer.append(iterator.next().toNormalizedString());
1639        if (iterator.hasNext())
1640        {
1641          buffer.append('+');
1642        }
1643      }
1644    }
1645  }
1646
1647
1648
1649  /**
1650   * Obtains a normalized representation of the provided attribute name.
1651   *
1652   * @param  name  The name of the attribute for which to create the normalized
1653   *               representation.
1654   *
1655   * @return  A normalized representation of the provided attribute name.
1656   */
1657  @NotNull()
1658  private String normalizeAttrName(@NotNull final String name)
1659  {
1660    String n = name;
1661    if (schema != null)
1662    {
1663      final AttributeTypeDefinition at = schema.getAttributeType(name);
1664      if (at != null)
1665      {
1666        n = at.getNameOrOID();
1667      }
1668    }
1669    return StaticUtils.toLowerCase(n);
1670  }
1671
1672
1673
1674  /**
1675   * Retrieves a normalized string representation of the RDN with the provided
1676   * string representation.
1677   *
1678   * @param  s  The string representation of the RDN to normalize.  It must not
1679   *            be {@code null}.
1680   *
1681   * @return  The normalized string representation of the RDN with the provided
1682   *          string representation.
1683   *
1684   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1685   */
1686  @NotNull()
1687  public static String normalize(@NotNull final String s)
1688         throws LDAPException
1689  {
1690    return normalize(s, null);
1691  }
1692
1693
1694
1695  /**
1696   * Retrieves a normalized string representation of the RDN with the provided
1697   * string representation.
1698   *
1699   * @param  s       The string representation of the RDN to normalize.  It must
1700   *                 not be {@code null}.
1701   * @param  schema  The schema to use to generate the normalized string
1702   *                 representation of the RDN.  It may be {@code null} if no
1703   *                 schema is available.
1704   *
1705   * @return  The normalized string representation of the RDN with the provided
1706   *          string representation.
1707   *
1708   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1709   */
1710  @NotNull()
1711  public static String normalize(@NotNull final String s,
1712                                 @Nullable final Schema schema)
1713         throws LDAPException
1714  {
1715    return new RDN(s, schema).toNormalizedString();
1716  }
1717
1718
1719
1720  /**
1721   * Appends a normalized string representation of the provided attribute value
1722   * to the given buffer.
1723   *
1724   * @param  buffer         The buffer to which the value should be appended.
1725   *                        It must not be {@code null}.
1726   * @param  attributeName  The name of the attribute whose value is to be
1727   *                        normalized.  It must not be {@code null}.
1728   * @param  value          The value to be normalized.  It must not be
1729   *                        {@code null}.
1730   * @param  schema         The schema to use to generate the normalized
1731   *                        representation of the value.  It may be {@code null}
1732   *                        if no schema is available.
1733   */
1734  static void appendNormalizedValue(@NotNull final StringBuilder buffer,
1735                                    @NotNull final String attributeName,
1736                                    @NotNull final ASN1OctetString value,
1737                                    @Nullable final Schema schema)
1738  {
1739    final MatchingRule matchingRule =
1740         MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1741
1742    ASN1OctetString rawNormValue;
1743    try
1744    {
1745      rawNormValue = matchingRule.normalize(value);
1746    }
1747    catch (final Exception e)
1748    {
1749      Debug.debugException(e);
1750      rawNormValue =
1751           new ASN1OctetString(StaticUtils.toLowerCase(value.stringValue()));
1752    }
1753
1754    final String valueString = rawNormValue.stringValue();
1755    final int length = valueString.length();
1756    for (int i=0; i < length; i++)
1757    {
1758      final char c = valueString.charAt(i);
1759
1760      switch (c)
1761      {
1762        case '\\':
1763        case '=':
1764        case '"':
1765        case '+':
1766        case ',':
1767        case ';':
1768        case '<':
1769        case '>':
1770          buffer.append('\\');
1771          buffer.append(c);
1772          break;
1773
1774        case '#':
1775          // Escape the octothorpe only if it's the first character.
1776          if (i == 0)
1777          {
1778            buffer.append("\\#");
1779          }
1780          else
1781          {
1782            buffer.append('#');
1783          }
1784          break;
1785
1786        case ' ':
1787          // Escape this space only if it's the first or last character.
1788          if ((i == 0) || ((i+1) == length))
1789          {
1790            buffer.append("\\ ");
1791          }
1792          else
1793          {
1794            buffer.append(' ');
1795          }
1796          break;
1797
1798        default:
1799          // If it's a printable ASCII character that isn't covered by one of
1800          // the above options, then just append it to the buffer.  Otherwise,
1801          // hex-encode all bytes that comprise its UTF-8 representation, which
1802          // might require special handling if it requires two Java characters
1803          // to encode the Unicode character.
1804          if ((c >= ' ') && (c <= '~'))
1805          {
1806            buffer.append(c);
1807          }
1808          else if (Character.isHighSurrogate(c))
1809          {
1810            if (((i+1) < length) &&
1811                 Character.isLowSurrogate(valueString.charAt(i+1)))
1812            {
1813              final char c2 = valueString.charAt(++i);
1814              final int codePoint = Character.toCodePoint(c, c2);
1815              StaticUtils.hexEncode(codePoint, buffer);
1816            }
1817            else
1818            {
1819              // This should never happen.
1820              StaticUtils.hexEncode(c, buffer);
1821            }
1822          }
1823          else
1824          {
1825            StaticUtils.hexEncode(c, buffer);
1826          }
1827          break;
1828      }
1829    }
1830  }
1831
1832
1833
1834  /**
1835   * Retrieves a hash code for this RDN.
1836   *
1837   * @return  The hash code for this RDN.
1838   */
1839  @Override()
1840  public int hashCode()
1841  {
1842    return toNormalizedString().hashCode();
1843  }
1844
1845
1846
1847  /**
1848   * Indicates whether this RDN is equal to the provided object.  The given
1849   * object will only be considered equal to this RDN if it is also an RDN with
1850   * the same set of names and values.
1851   *
1852   * @param  o  The object for which to make the determination.
1853   *
1854   * @return  {@code true} if the provided object can be considered equal to
1855   *          this RDN, or {@code false} if not.
1856   */
1857  @Override()
1858  public boolean equals(@Nullable final Object o)
1859  {
1860    if (o == null)
1861    {
1862      return false;
1863    }
1864
1865    if (o == this)
1866    {
1867      return true;
1868    }
1869
1870    if (! (o instanceof RDN))
1871    {
1872      return false;
1873    }
1874
1875    final RDN rdn = (RDN) o;
1876    return (toNormalizedString().equals(rdn.toNormalizedString()));
1877  }
1878
1879
1880
1881  /**
1882   * Indicates whether the RDN with the provided string representation is equal
1883   * to this RDN.
1884   *
1885   * @param  s  The string representation of the DN to compare with this RDN.
1886   *
1887   * @return  {@code true} if the DN with the provided string representation is
1888   *          equal to this RDN, or {@code false} if not.
1889   *
1890   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1891   */
1892  public boolean equals(@Nullable final String s)
1893         throws LDAPException
1894  {
1895    if (s == null)
1896    {
1897      return false;
1898    }
1899
1900    return equals(new RDN(s, schema));
1901  }
1902
1903
1904
1905  /**
1906   * Indicates whether the two provided strings represent the same RDN.
1907   *
1908   * @param  s1  The string representation of the first RDN for which to make
1909   *             the determination.  It must not be {@code null}.
1910   * @param  s2  The string representation of the second RDN for which to make
1911   *             the determination.  It must not be {@code null}.
1912   *
1913   * @return  {@code true} if the provided strings represent the same RDN, or
1914   *          {@code false} if not.
1915   *
1916   * @throws  LDAPException  If either of the provided strings cannot be parsed
1917   *                         as an RDN.
1918   */
1919  public static boolean equals(@NotNull final String s1,
1920                               @NotNull final String s2)
1921         throws LDAPException
1922  {
1923    return new RDN(s1).equals(new RDN(s2));
1924  }
1925
1926
1927
1928  /**
1929   * Compares the provided RDN to this RDN to determine their relative order in
1930   * a sorted list.
1931   *
1932   * @param  rdn  The RDN to compare against this RDN.  It must not be
1933   *              {@code null}.
1934   *
1935   * @return  A negative integer if this RDN should come before the provided RDN
1936   *          in a sorted list, a positive integer if this RDN should come after
1937   *          the provided RDN in a sorted list, or zero if the provided RDN
1938   *          can be considered equal to this RDN.
1939   */
1940  @Override()
1941  public int compareTo(@NotNull final RDN rdn)
1942  {
1943    return compare(this, rdn);
1944  }
1945
1946
1947
1948  /**
1949   * Compares the provided RDN values to determine their relative order in a
1950   * sorted list.
1951   *
1952   * @param  rdn1  The first RDN to be compared.  It must not be {@code null}.
1953   * @param  rdn2  The second RDN to be compared.  It must not be {@code null}.
1954   *
1955   * @return  A negative integer if the first RDN should come before the second
1956   *          RDN in a sorted list, a positive integer if the first RDN should
1957   *          come after the second RDN in a sorted list, or zero if the two RDN
1958   *          values can be considered equal.
1959   */
1960  @Override()
1961  public int compare(@NotNull final RDN rdn1, @NotNull final RDN rdn2)
1962  {
1963    Validator.ensureNotNull(rdn1, rdn2);
1964
1965    final Iterator<RDNNameValuePair> iterator1 =
1966         rdn1.getNameValuePairs().iterator();
1967    final Iterator<RDNNameValuePair> iterator2 =
1968         rdn2.getNameValuePairs().iterator();
1969
1970    while (iterator1.hasNext())
1971    {
1972      if (iterator2.hasNext())
1973      {
1974        final RDNNameValuePair p1 = iterator1.next();
1975        final RDNNameValuePair p2 = iterator2.next();
1976        final int compareValue = p1.compareTo(p2);
1977        if (compareValue != 0)
1978        {
1979          return compareValue;
1980        }
1981      }
1982      else
1983      {
1984        return 1;
1985      }
1986    }
1987
1988    if (iterator2.hasNext())
1989    {
1990      return -1;
1991    }
1992    else
1993    {
1994      return 0;
1995    }
1996  }
1997
1998
1999
2000  /**
2001   * Compares the RDN values with the provided string representations to
2002   * determine their relative order in a sorted list.
2003   *
2004   * @param  s1  The string representation of the first RDN to be compared.  It
2005   *             must not be {@code null}.
2006   * @param  s2  The string representation of the second RDN to be compared.  It
2007   *             must not be {@code null}.
2008   *
2009   * @return  A negative integer if the first RDN should come before the second
2010   *          RDN in a sorted list, a positive integer if the first RDN should
2011   *          come after the second RDN in a sorted list, or zero if the two RDN
2012   *          values can be considered equal.
2013   *
2014   * @throws  LDAPException  If either of the provided strings cannot be parsed
2015   *                         as an RDN.
2016   */
2017  public static int compare(@NotNull final String s1,
2018                            @NotNull final String s2)
2019         throws LDAPException
2020  {
2021    return compare(s1, s2, null);
2022  }
2023
2024
2025
2026  /**
2027   * Compares the RDN values with the provided string representations to
2028   * determine their relative order in a sorted list.
2029   *
2030   * @param  s1      The string representation of the first RDN to be compared.
2031   *                 It must not be {@code null}.
2032   * @param  s2      The string representation of the second RDN to be compared.
2033   *                 It must not be {@code null}.
2034   * @param  schema  The schema to use to generate the normalized string
2035   *                 representations of the RDNs.  It may be {@code null} if no
2036   *                 schema is available.
2037   *
2038   * @return  A negative integer if the first RDN should come before the second
2039   *          RDN in a sorted list, a positive integer if the first RDN should
2040   *          come after the second RDN in a sorted list, or zero if the two RDN
2041   *          values can be considered equal.
2042   *
2043   * @throws  LDAPException  If either of the provided strings cannot be parsed
2044   *                         as an RDN.
2045   */
2046  public static int compare(@NotNull final String s1, @NotNull final String s2,
2047                            @Nullable final Schema schema)
2048         throws LDAPException
2049  {
2050    return new RDN(s1, schema).compareTo(new RDN(s2, schema));
2051  }
2052}