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