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