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