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