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    buffer.append(StaticUtils.toUTF8String(byteArray));
1189    return pos;
1190  }
1191
1192
1193
1194  /**
1195   * Indicates whether the provided string represents a valid RDN.
1196   *
1197   * @param  s  The string for which to make the determination.  It must not be
1198   *            {@code null}.
1199   *
1200   * @return  {@code true} if the provided string represents a valid RDN, or
1201   *          {@code false} if not.
1202   */
1203  public static boolean isValidRDN(final String s)
1204  {
1205    return isValidRDN(s, false);
1206  }
1207
1208
1209
1210  /**
1211   * Indicates whether the provided string represents a valid RDN.
1212   *
1213   * @param  s                   The string for which to make the determination.
1214   *                             It must not be {@code null}.
1215   * @param  strictNameChecking  Indicates whether to verify that all attribute
1216   *                             type names are valid as per RFC 4514.  If this
1217   *                             is {@code false}, then some technically invalid
1218   *                             characters may be accepted in attribute type
1219   *                             names.  If this is {@code true}, then names
1220   *                             must be strictly compliant.
1221   *
1222   * @return  {@code true} if the provided string represents a valid RDN, or
1223   *          {@code false} if not.
1224   */
1225  public static boolean isValidRDN(final String s,
1226                                   final boolean strictNameChecking)
1227  {
1228    try
1229    {
1230      new RDN(s, null, strictNameChecking);
1231      return true;
1232    }
1233    catch (final LDAPException le)
1234    {
1235      Debug.debugException(le);
1236      return false;
1237    }
1238  }
1239
1240
1241
1242  /**
1243   * Indicates whether this RDN contains multiple values.
1244   *
1245   * @return  {@code true} if this RDN contains multiple values, or
1246   *          {@code false} if not.
1247   */
1248  public boolean isMultiValued()
1249  {
1250    return (attributeNames.length != 1);
1251  }
1252
1253
1254
1255  /**
1256   * Retrieves the number of values for this RDN.
1257   *
1258   * @return  The number of values for this RDN.
1259   */
1260  public int getValueCount()
1261  {
1262    return attributeNames.length;
1263  }
1264
1265
1266
1267  /**
1268   * Retrieves an array of the attributes that comprise this RDN.
1269   *
1270   * @return  An array of the attributes that comprise this RDN.
1271   */
1272  public Attribute[] getAttributes()
1273  {
1274    final Attribute[] attrs = new Attribute[attributeNames.length];
1275    for (int i=0; i < attrs.length; i++)
1276    {
1277      attrs[i] = new Attribute(attributeNames[i], schema,
1278           new ASN1OctetString[] {  attributeValues[i] });
1279    }
1280
1281    return attrs;
1282  }
1283
1284
1285
1286  /**
1287   * Retrieves the set of attribute names for this RDN.
1288   *
1289   * @return  The set of attribute names for this RDN.
1290   */
1291  public String[] getAttributeNames()
1292  {
1293    return attributeNames;
1294  }
1295
1296
1297
1298  /**
1299   * Retrieves the set of attribute values for this RDN.
1300   *
1301   * @return  The set of attribute values for this RDN.
1302   */
1303  public String[] getAttributeValues()
1304  {
1305    final String[] stringValues = new String[attributeValues.length];
1306    for (int i=0; i < stringValues.length; i++)
1307    {
1308      stringValues[i] = attributeValues[i].stringValue();
1309    }
1310
1311    return stringValues;
1312  }
1313
1314
1315
1316  /**
1317   * Retrieves the set of attribute values for this RDN.
1318   *
1319   * @return  The set of attribute values for this RDN.
1320   */
1321  public byte[][] getByteArrayAttributeValues()
1322  {
1323    final byte[][] byteValues = new byte[attributeValues.length][];
1324    for (int i=0; i < byteValues.length; i++)
1325    {
1326      byteValues[i] = attributeValues[i].getValue();
1327    }
1328
1329    return byteValues;
1330  }
1331
1332
1333
1334  /**
1335   * Retrieves a sorted set of the name-value pairs that comprise this RDN.
1336   *
1337   * @return  A sorted set of the name-value pairs that comprise this RDN.
1338   */
1339  public SortedSet<RDNNameValuePair> getNameValuePairs()
1340  {
1341    if (nameValuePairs == null)
1342    {
1343      final SortedSet<RDNNameValuePair> s = new TreeSet<>();
1344      for (int i=0; i < attributeNames.length; i++)
1345      {
1346        s.add(new RDNNameValuePair(attributeNames[i], attributeValues[i],
1347             schema));
1348      }
1349
1350      nameValuePairs = Collections.unmodifiableSortedSet(s);
1351    }
1352
1353    return nameValuePairs;
1354  }
1355
1356
1357
1358  /**
1359   * Retrieves the schema that will be used for this RDN, if any.
1360   *
1361   * @return  The schema that will be used for this RDN, or {@code null} if none
1362   *          has been provided.
1363   */
1364  Schema getSchema()
1365  {
1366    return schema;
1367  }
1368
1369
1370
1371  /**
1372   * Indicates whether this RDN contains the specified attribute.
1373   *
1374   * @param  attributeName  The name of the attribute for which to make the
1375   *                        determination.
1376   *
1377   * @return  {@code true} if RDN contains the specified attribute, or
1378   *          {@code false} if not.
1379   */
1380  public boolean hasAttribute(final String attributeName)
1381  {
1382    for (final RDNNameValuePair nameValuePair : getNameValuePairs())
1383    {
1384      if (nameValuePair.hasAttributeName(attributeName))
1385      {
1386        return true;
1387      }
1388    }
1389
1390    return false;
1391  }
1392
1393
1394
1395  /**
1396   * Indicates whether this RDN contains the specified attribute value.
1397   *
1398   * @param  attributeName   The name of the attribute for which to make the
1399   *                         determination.
1400   * @param  attributeValue  The attribute value for which to make the
1401   *                         determination.
1402   *
1403   * @return  {@code true} if RDN contains the specified attribute, or
1404   *          {@code false} if not.
1405   */
1406  public boolean hasAttributeValue(final String attributeName,
1407                                   final String attributeValue)
1408  {
1409    for (final RDNNameValuePair nameValuePair : getNameValuePairs())
1410    {
1411      if (nameValuePair.hasAttributeName(attributeName) &&
1412           nameValuePair.hasAttributeValue(attributeValue))
1413      {
1414        return true;
1415      }
1416    }
1417
1418    return false;
1419  }
1420
1421
1422
1423  /**
1424   * Indicates whether this RDN contains the specified attribute value.
1425   *
1426   * @param  attributeName   The name of the attribute for which to make the
1427   *                         determination.
1428   * @param  attributeValue  The attribute value for which to make the
1429   *                         determination.
1430   *
1431   * @return  {@code true} if RDN contains the specified attribute, or
1432   *          {@code false} if not.
1433   */
1434  public boolean hasAttributeValue(final String attributeName,
1435                                   final byte[] attributeValue)
1436  {
1437    for (final RDNNameValuePair nameValuePair : getNameValuePairs())
1438    {
1439      if (nameValuePair.hasAttributeName(attributeName) &&
1440           nameValuePair.hasAttributeValue(attributeValue))
1441      {
1442        return true;
1443      }
1444    }
1445
1446    return false;
1447  }
1448
1449
1450
1451  /**
1452   * Retrieves a string representation of this RDN.
1453   *
1454   * @return  A string representation of this RDN.
1455   */
1456  @Override()
1457  public String toString()
1458  {
1459    if (rdnString == null)
1460    {
1461      final StringBuilder buffer = new StringBuilder();
1462      toString(buffer, false);
1463      rdnString = buffer.toString();
1464    }
1465
1466    return rdnString;
1467  }
1468
1469
1470
1471  /**
1472   * Retrieves a string representation of this RDN with minimal encoding for
1473   * special characters.  Only those characters specified in RFC 4514 section
1474   * 2.4 will be escaped.  No escaping will be used for non-ASCII characters or
1475   * non-printable ASCII characters.
1476   *
1477   * @return  A string representation of this RDN with minimal encoding for
1478   *          special characters.
1479   */
1480  public String toMinimallyEncodedString()
1481  {
1482    final StringBuilder buffer = new StringBuilder();
1483    toString(buffer, true);
1484    return buffer.toString();
1485  }
1486
1487
1488
1489  /**
1490   * Appends a string representation of this RDN to the provided buffer.
1491   *
1492   * @param  buffer  The buffer to which the string representation is to be
1493   *                 appended.
1494   */
1495  public void toString(final StringBuilder buffer)
1496  {
1497    toString(buffer, false);
1498  }
1499
1500
1501
1502  /**
1503   * Appends a string representation of this RDN to the provided buffer.
1504   *
1505   * @param  buffer            The buffer to which the string representation is
1506   *                           to be appended.
1507   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1508   *                           special characters to the bare minimum required
1509   *                           by LDAP (as per RFC 4514 section 2.4).  If this
1510   *                           is {@code true}, then only leading and trailing
1511   *                           spaces, double quotes, plus signs, commas,
1512   *                           semicolons, greater-than, less-than, and
1513   *                           backslash characters will be encoded.
1514   */
1515  public void toString(final StringBuilder buffer,
1516                       final boolean minimizeEncoding)
1517  {
1518    if ((rdnString != null) && (! minimizeEncoding))
1519    {
1520      buffer.append(rdnString);
1521      return;
1522    }
1523
1524    for (int i=0; i < attributeNames.length; i++)
1525    {
1526      if (i > 0)
1527      {
1528        buffer.append('+');
1529      }
1530
1531      buffer.append(attributeNames[i]);
1532      buffer.append('=');
1533      appendValue(buffer, attributeValues[i], minimizeEncoding);
1534    }
1535  }
1536
1537
1538
1539  /**
1540   * Appends an appropriately escaped version of the provided value to the given
1541   * buffer.
1542   *
1543   * @param  buffer            The buffer to which the value should be appended.
1544   *                           It must not be {@code null}.
1545   * @param  value             The value to be appended in an appropriately
1546   *                           escaped form.  It must not be {@code null}.
1547   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1548   *                           special characters to the bare minimum required
1549   *                           by LDAP (as per RFC 4514 section 2.4).  If this
1550   *                           is {@code true}, then only leading and trailing
1551   *                           spaces, double quotes, plus signs, commas,
1552   *                           semicolons, greater-than, less-than, and
1553   *                           backslash characters will be encoded.
1554   */
1555  static void appendValue(final StringBuilder buffer,
1556                          final ASN1OctetString value,
1557                          final boolean minimizeEncoding)
1558  {
1559    final String valueString = value.stringValue();
1560    final int length = valueString.length();
1561    for (int j=0; j < length; j++)
1562    {
1563      final char c = valueString.charAt(j);
1564      switch (c)
1565      {
1566        case '\\':
1567        case '=':
1568        case '"':
1569        case '+':
1570        case ',':
1571        case ';':
1572        case '<':
1573        case '>':
1574          // These characters will always be escaped.
1575          buffer.append('\\');
1576          buffer.append(c);
1577          break;
1578
1579        case '#':
1580          // Escape the octothorpe only if it's the first character.
1581          if (j == 0)
1582          {
1583            buffer.append("\\#");
1584          }
1585          else
1586          {
1587            buffer.append('#');
1588          }
1589          break;
1590
1591        case ' ':
1592          // Escape this space only if it's the first or last character.
1593          if ((j == 0) || ((j+1) == length))
1594          {
1595            buffer.append("\\ ");
1596          }
1597          else
1598          {
1599            buffer.append(' ');
1600          }
1601          break;
1602
1603        case '\u0000':
1604          buffer.append("\\00");
1605          break;
1606
1607        default:
1608          // If it's not a printable ASCII character, then hex-encode it
1609          // unless we're using minimized encoding.
1610          if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
1611          {
1612            StaticUtils.hexEncode(c, buffer);
1613          }
1614          else
1615          {
1616            buffer.append(c);
1617          }
1618          break;
1619      }
1620    }
1621  }
1622
1623
1624
1625  /**
1626   * Retrieves a normalized string representation of this RDN.
1627   *
1628   * @return  A normalized string representation of this RDN.
1629   */
1630  public String toNormalizedString()
1631  {
1632    if (normalizedString == null)
1633    {
1634      final StringBuilder buffer = new StringBuilder();
1635      toNormalizedString(buffer);
1636      normalizedString = buffer.toString();
1637    }
1638
1639    return normalizedString;
1640  }
1641
1642
1643
1644  /**
1645   * Appends a normalized string representation of this RDN to the provided
1646   * buffer.
1647   *
1648   * @param  buffer  The buffer to which the normalized string representation is
1649   *                 to be appended.
1650   */
1651  public void toNormalizedString(final StringBuilder buffer)
1652  {
1653    if (attributeNames.length == 1)
1654    {
1655      // It's a single-valued RDN, so there is no need to sort anything.
1656      final String name = normalizeAttrName(attributeNames[0]);
1657      buffer.append(name);
1658      buffer.append('=');
1659      appendNormalizedValue(buffer, name, attributeValues[0], schema);
1660    }
1661    else
1662    {
1663      // It's a multivalued RDN, so we need to sort the components.
1664      final Iterator<RDNNameValuePair> iterator =
1665           getNameValuePairs().iterator();
1666      while (iterator.hasNext())
1667      {
1668        buffer.append(iterator.next().toNormalizedString());
1669        if (iterator.hasNext())
1670        {
1671          buffer.append('+');
1672        }
1673      }
1674    }
1675  }
1676
1677
1678
1679  /**
1680   * Obtains a normalized representation of the provided attribute name.
1681   *
1682   * @param  name  The name of the attribute for which to create the normalized
1683   *               representation.
1684   *
1685   * @return  A normalized representation of the provided attribute name.
1686   */
1687  private String normalizeAttrName(final String name)
1688  {
1689    String n = name;
1690    if (schema != null)
1691    {
1692      final AttributeTypeDefinition at = schema.getAttributeType(name);
1693      if (at != null)
1694      {
1695        n = at.getNameOrOID();
1696      }
1697    }
1698    return StaticUtils.toLowerCase(n);
1699  }
1700
1701
1702
1703  /**
1704   * Retrieves a normalized string representation of the RDN with the provided
1705   * string representation.
1706   *
1707   * @param  s  The string representation of the RDN to normalize.  It must not
1708   *            be {@code null}.
1709   *
1710   * @return  The normalized string representation of the RDN with the provided
1711   *          string representation.
1712   *
1713   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1714   */
1715  public static String normalize(final String s)
1716         throws LDAPException
1717  {
1718    return normalize(s, null);
1719  }
1720
1721
1722
1723  /**
1724   * Retrieves a normalized string representation of the RDN with the provided
1725   * string representation.
1726   *
1727   * @param  s       The string representation of the RDN to normalize.  It must
1728   *                 not be {@code null}.
1729   * @param  schema  The schema to use to generate the normalized string
1730   *                 representation of the RDN.  It may be {@code null} if no
1731   *                 schema is available.
1732   *
1733   * @return  The normalized string representation of the RDN with the provided
1734   *          string representation.
1735   *
1736   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1737   */
1738  public static String normalize(final String s, final Schema schema)
1739         throws LDAPException
1740  {
1741    return new RDN(s, schema).toNormalizedString();
1742  }
1743
1744
1745
1746  /**
1747   * Appends a normalized string representation of the provided attribute value
1748   * to the given buffer.
1749   *
1750   * @param  buffer         The buffer to which the value should be appended.
1751   *                        It must not be {@code null}.
1752   * @param  attributeName  The name of the attribute whose value is to be
1753   *                        normalized.  It must not be {@code null}.
1754   * @param  value          The value to be normalized.  It must not be
1755   *                        {@code null}.
1756   * @param  schema         The schema to use to generate the normalized
1757   *                        representation of the value.  It may be {@code null}
1758   *                        if no schema is available.
1759   */
1760  static void appendNormalizedValue(final StringBuilder buffer,
1761                                    final String attributeName,
1762                                    final ASN1OctetString value,
1763                                    final Schema schema)
1764  {
1765    final MatchingRule matchingRule =
1766         MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1767
1768    ASN1OctetString rawNormValue;
1769    try
1770    {
1771      rawNormValue = matchingRule.normalize(value);
1772    }
1773    catch (final Exception e)
1774    {
1775      Debug.debugException(e);
1776      rawNormValue =
1777           new ASN1OctetString(StaticUtils.toLowerCase(value.stringValue()));
1778    }
1779
1780    final String valueString = rawNormValue.stringValue();
1781    final int length = valueString.length();
1782    for (int i=0; i < length; i++)
1783    {
1784      final char c = valueString.charAt(i);
1785
1786      switch (c)
1787      {
1788        case '\\':
1789        case '=':
1790        case '"':
1791        case '+':
1792        case ',':
1793        case ';':
1794        case '<':
1795        case '>':
1796          buffer.append('\\');
1797          buffer.append(c);
1798          break;
1799
1800        case '#':
1801          // Escape the octothorpe only if it's the first character.
1802          if (i == 0)
1803          {
1804            buffer.append("\\#");
1805          }
1806          else
1807          {
1808            buffer.append('#');
1809          }
1810          break;
1811
1812        case ' ':
1813          // Escape this space only if it's the first or last character.
1814          if ((i == 0) || ((i+1) == length))
1815          {
1816            buffer.append("\\ ");
1817          }
1818          else
1819          {
1820            buffer.append(' ');
1821          }
1822          break;
1823
1824        default:
1825          // If it's a printable ASCII character that isn't covered by one of
1826          // the above options, then just append it to the buffer.  Otherwise,
1827          // hex-encode all bytes that comprise its UTF-8 representation, which
1828          // might require special handling if it requires two Java characters
1829          // to encode the Unicode character.
1830          if ((c >= ' ') && (c <= '~'))
1831          {
1832            buffer.append(c);
1833          }
1834          else if (Character.isHighSurrogate(c))
1835          {
1836            if (((i+1) < length) &&
1837                 Character.isLowSurrogate(valueString.charAt(i+1)))
1838            {
1839              final char c2 = valueString.charAt(++i);
1840              final int codePoint = Character.toCodePoint(c, c2);
1841              StaticUtils.hexEncode(codePoint, buffer);
1842            }
1843            else
1844            {
1845              // This should never happen.
1846              StaticUtils.hexEncode(c, buffer);
1847            }
1848          }
1849          else
1850          {
1851            StaticUtils.hexEncode(c, buffer);
1852          }
1853          break;
1854      }
1855    }
1856  }
1857
1858
1859
1860  /**
1861   * Retrieves a hash code for this RDN.
1862   *
1863   * @return  The hash code for this RDN.
1864   */
1865  @Override()
1866  public int hashCode()
1867  {
1868    return toNormalizedString().hashCode();
1869  }
1870
1871
1872
1873  /**
1874   * Indicates whether this RDN is equal to the provided object.  The given
1875   * object will only be considered equal to this RDN if it is also an RDN with
1876   * the same set of names and values.
1877   *
1878   * @param  o  The object for which to make the determination.
1879   *
1880   * @return  {@code true} if the provided object can be considered equal to
1881   *          this RDN, or {@code false} if not.
1882   */
1883  @Override()
1884  public boolean equals(final Object o)
1885  {
1886    if (o == null)
1887    {
1888      return false;
1889    }
1890
1891    if (o == this)
1892    {
1893      return true;
1894    }
1895
1896    if (! (o instanceof RDN))
1897    {
1898      return false;
1899    }
1900
1901    final RDN rdn = (RDN) o;
1902    return (toNormalizedString().equals(rdn.toNormalizedString()));
1903  }
1904
1905
1906
1907  /**
1908   * Indicates whether the RDN with the provided string representation is equal
1909   * to this RDN.
1910   *
1911   * @param  s  The string representation of the DN to compare with this RDN.
1912   *
1913   * @return  {@code true} if the DN with the provided string representation is
1914   *          equal to this RDN, or {@code false} if not.
1915   *
1916   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1917   */
1918  public boolean equals(final String s)
1919         throws LDAPException
1920  {
1921    if (s == null)
1922    {
1923      return false;
1924    }
1925
1926    return equals(new RDN(s, schema));
1927  }
1928
1929
1930
1931  /**
1932   * Indicates whether the two provided strings represent the same RDN.
1933   *
1934   * @param  s1  The string representation of the first RDN for which to make
1935   *             the determination.  It must not be {@code null}.
1936   * @param  s2  The string representation of the second RDN for which to make
1937   *             the determination.  It must not be {@code null}.
1938   *
1939   * @return  {@code true} if the provided strings represent the same RDN, or
1940   *          {@code false} if not.
1941   *
1942   * @throws  LDAPException  If either of the provided strings cannot be parsed
1943   *                         as an RDN.
1944   */
1945  public static boolean equals(final String s1, final String s2)
1946         throws LDAPException
1947  {
1948    return new RDN(s1).equals(new RDN(s2));
1949  }
1950
1951
1952
1953  /**
1954   * Compares the provided RDN to this RDN to determine their relative order in
1955   * a sorted list.
1956   *
1957   * @param  rdn  The RDN to compare against this RDN.  It must not be
1958   *              {@code null}.
1959   *
1960   * @return  A negative integer if this RDN should come before the provided RDN
1961   *          in a sorted list, a positive integer if this RDN should come after
1962   *          the provided RDN in a sorted list, or zero if the provided RDN
1963   *          can be considered equal to this RDN.
1964   */
1965  @Override()
1966  public int compareTo(final RDN rdn)
1967  {
1968    return compare(this, rdn);
1969  }
1970
1971
1972
1973  /**
1974   * Compares the provided RDN values to determine their relative order in a
1975   * sorted list.
1976   *
1977   * @param  rdn1  The first RDN to be compared.  It must not be {@code null}.
1978   * @param  rdn2  The second RDN to be compared.  It must not be {@code null}.
1979   *
1980   * @return  A negative integer if the first RDN should come before the second
1981   *          RDN in a sorted list, a positive integer if the first RDN should
1982   *          come after the second RDN in a sorted list, or zero if the two RDN
1983   *          values can be considered equal.
1984   */
1985  @Override()
1986  public int compare(final RDN rdn1, final RDN rdn2)
1987  {
1988    Validator.ensureNotNull(rdn1, rdn2);
1989
1990    final Iterator<RDNNameValuePair> iterator1 =
1991         rdn1.getNameValuePairs().iterator();
1992    final Iterator<RDNNameValuePair> iterator2 =
1993         rdn2.getNameValuePairs().iterator();
1994
1995    while (iterator1.hasNext())
1996    {
1997      if (iterator2.hasNext())
1998      {
1999        final RDNNameValuePair p1 = iterator1.next();
2000        final RDNNameValuePair p2 = iterator2.next();
2001        final int compareValue = p1.compareTo(p2);
2002        if (compareValue != 0)
2003        {
2004          return compareValue;
2005        }
2006      }
2007      else
2008      {
2009        return 1;
2010      }
2011    }
2012
2013    if (iterator2.hasNext())
2014    {
2015      return -1;
2016    }
2017    else
2018    {
2019      return 0;
2020    }
2021  }
2022
2023
2024
2025  /**
2026   * Compares the RDN values with the provided string representations to
2027   * determine their relative order in a sorted list.
2028   *
2029   * @param  s1  The string representation of the first RDN to be compared.  It
2030   *             must not be {@code null}.
2031   * @param  s2  The string representation of the second RDN to be compared.  It
2032   *             must not be {@code null}.
2033   *
2034   * @return  A negative integer if the first RDN should come before the second
2035   *          RDN in a sorted list, a positive integer if the first RDN should
2036   *          come after the second RDN in a sorted list, or zero if the two RDN
2037   *          values can be considered equal.
2038   *
2039   * @throws  LDAPException  If either of the provided strings cannot be parsed
2040   *                         as an RDN.
2041   */
2042  public static int compare(final String s1, final String s2)
2043         throws LDAPException
2044  {
2045    return compare(s1, s2, null);
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.
2055   *                 It must not be {@code null}.
2056   * @param  s2      The string representation of the second RDN to be compared.
2057   *                 It must not be {@code null}.
2058   * @param  schema  The schema to use to generate the normalized string
2059   *                 representations of the RDNs.  It may be {@code null} if no
2060   *                 schema is available.
2061   *
2062   * @return  A negative integer if the first RDN should come before the second
2063   *          RDN in a sorted list, a positive integer if the first RDN should
2064   *          come after the second RDN in a sorted list, or zero if the two RDN
2065   *          values can be considered equal.
2066   *
2067   * @throws  LDAPException  If either of the provided strings cannot be parsed
2068   *                         as an RDN.
2069   */
2070  public static int compare(final String s1, final String s2,
2071                            final Schema schema)
2072         throws LDAPException
2073  {
2074    return new RDN(s1, schema).compareTo(new RDN(s2, schema));
2075  }
2076}