001/*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 UnboundID Corp.
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());
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(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 hex-encoded.  Otherwise, it's a regular string (although possibly
408    // containing escaped or quoted characters).
409    ASN1OctetString value;
410    if (pos >= length)
411    {
412      value = new ASN1OctetString();
413    }
414    else if (rdnString.charAt(pos) == '#')
415    {
416      // It is a hex-encoded value, so we'll read until we find the end of the
417      // string or the first non-hex character, which must be either a space or
418      // a plus sign.
419      final byte[] valueArray = readHexString(rdnString, ++pos);
420      value = new ASN1OctetString(valueArray);
421      pos += (valueArray.length * 2);
422    }
423    else
424    {
425      // It is a string value, which potentially includes escaped characters.
426      final StringBuilder buffer = new StringBuilder();
427      pos = readValueString(rdnString, pos, buffer);
428      value = new ASN1OctetString(buffer.toString());
429    }
430
431
432    // Skip over any spaces until we find a plus sign or the end of the value.
433    while ((pos < length) && (rdnString.charAt(pos) == ' '))
434    {
435      pos++;
436    }
437
438    if (pos >= length)
439    {
440      // It's a single-valued RDN, so we have everything that we need.
441      attributeNames  = new String[] { attrName };
442      attributeValues = new ASN1OctetString[] { value };
443      return;
444    }
445
446    // It's a multivalued RDN, so create temporary lists to hold the names and
447    // values.
448    final ArrayList<String> nameList = new ArrayList<String>(5);
449    final ArrayList<ASN1OctetString> valueList =
450         new ArrayList<ASN1OctetString>(5);
451    nameList.add(attrName);
452    valueList.add(value);
453
454    if (rdnString.charAt(pos) == '+')
455    {
456      pos++;
457    }
458    else
459    {
460      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
461                              ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get());
462    }
463
464    if (pos >= length)
465    {
466      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
467                              ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get());
468    }
469
470    int numValues = 1;
471    while (pos < length)
472    {
473      // Skip over any spaces between the plus sign and the attribute name.
474      while ((pos < length) && (rdnString.charAt(pos) == ' '))
475      {
476        pos++;
477      }
478
479      attrStartPos = pos;
480      while (pos < length)
481      {
482        final char c = rdnString.charAt(pos);
483        if ((c == ' ') || (c == '='))
484        {
485          break;
486        }
487
488        pos++;
489      }
490
491      // Skip over any spaces between the attribute name and the equal sign.
492      attrName = rdnString.substring(attrStartPos, pos);
493      if (attrName.length() == 0)
494      {
495        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
496                                ERR_RDN_NO_ATTR_NAME.get());
497      }
498
499      while ((pos < length) && (rdnString.charAt(pos) == ' '))
500      {
501        pos++;
502      }
503
504      if ((pos >= length) || (rdnString.charAt(pos) != '='))
505      {
506        // We didn't find an equal sign.
507        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
508                                ERR_RDN_NO_EQUAL_SIGN.get(attrName));
509      }
510
511      // The next character is the equal sign.  Skip it, and then skip over any
512      // spaces between it and the attribute value.
513      pos++;
514      while ((pos < length) && (rdnString.charAt(pos) == ' '))
515      {
516        pos++;
517      }
518
519      // Look at the next character.  If it is an octothorpe (#), then the value
520      // must be hex-encoded.  Otherwise, it's a regular string (although
521      // possibly containing escaped or quoted characters).
522      if (pos >= length)
523      {
524        value = new ASN1OctetString();
525      }
526      else if (rdnString.charAt(pos) == '#')
527      {
528        // It is a hex-encoded value, so we'll read until we find the end of the
529        // string or the first non-hex character, which must be either a space
530        // or a plus sign.
531        final byte[] valueArray = readHexString(rdnString, ++pos);
532        value = new ASN1OctetString(valueArray);
533        pos += (valueArray.length * 2);
534      }
535      else
536      {
537        // It is a string value, which potentially includes escaped characters.
538        final StringBuilder buffer = new StringBuilder();
539        pos = readValueString(rdnString, pos, buffer);
540        value = new ASN1OctetString(buffer.toString());
541      }
542
543
544      // Skip over any spaces until we find a plus sign or the end of the value.
545      while ((pos < length) && (rdnString.charAt(pos) == ' '))
546      {
547        pos++;
548      }
549
550      nameList.add(attrName);
551      valueList.add(value);
552      numValues++;
553
554      if (pos >= length)
555      {
556        // We're at the end of the value, so break out of the loop.
557        break;
558      }
559      else
560      {
561        // Skip over the plus sign and loop again to read another name-value
562        // pair.
563        if (rdnString.charAt(pos) == '+')
564        {
565          pos++;
566        }
567        else
568        {
569          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
570                                  ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get());
571        }
572      }
573
574      if (pos >= length)
575      {
576        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
577                                ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get());
578      }
579    }
580
581    attributeNames  = new String[numValues];
582    attributeValues = new ASN1OctetString[numValues];
583    for (int i=0; i < numValues; i++)
584    {
585      attributeNames[i]  = nameList.get(i);
586      attributeValues[i] = valueList.get(i);
587    }
588  }
589
590
591
592  /**
593   * Parses a hex-encoded RDN value from the provided string.  Reading will
594   * continue until the end of the string is reached or a non-escaped plus sign
595   * is encountered.  After returning, the caller should increment its position
596   * by two times the length of the value array.
597   *
598   * @param  rdnString  The string to be parsed.  It should be the position
599   *                    immediately after the octothorpe at the start of the
600   *                    hex-encoded value.
601   * @param  startPos   The position at which to start reading the value.
602   *
603   * @return  A byte array containing the parsed value.
604   *
605   * @throws  LDAPException  If an error occurs while reading the value (e.g.,
606   *                         if it contains non-hex characters, or has an odd
607   *                         number of characters.
608   */
609  static byte[] readHexString(final String rdnString, final int startPos)
610         throws LDAPException
611  {
612    final int length = rdnString.length();
613    int pos = startPos;
614
615    final ByteBuffer buffer = ByteBuffer.allocate(length-pos);
616hexLoop:
617    while (pos < length)
618    {
619      byte hexByte;
620      switch (rdnString.charAt(pos++))
621      {
622        case '0':
623          hexByte = 0x00;
624          break;
625        case '1':
626          hexByte = 0x10;
627          break;
628        case '2':
629          hexByte = 0x20;
630          break;
631        case '3':
632          hexByte = 0x30;
633          break;
634        case '4':
635          hexByte = 0x40;
636          break;
637        case '5':
638          hexByte = 0x50;
639          break;
640        case '6':
641          hexByte = 0x60;
642          break;
643        case '7':
644          hexByte = 0x70;
645          break;
646        case '8':
647          hexByte = (byte) 0x80;
648          break;
649        case '9':
650          hexByte = (byte) 0x90;
651          break;
652        case 'a':
653        case 'A':
654          hexByte = (byte) 0xA0;
655          break;
656        case 'b':
657        case 'B':
658          hexByte = (byte) 0xB0;
659          break;
660        case 'c':
661        case 'C':
662          hexByte = (byte) 0xC0;
663          break;
664        case 'd':
665        case 'D':
666          hexByte = (byte) 0xD0;
667          break;
668        case 'e':
669        case 'E':
670          hexByte = (byte) 0xE0;
671          break;
672        case 'f':
673        case 'F':
674          hexByte = (byte) 0xF0;
675          break;
676        case ' ':
677        case '+':
678        case ',':
679        case ';':
680          // This indicates that we've reached the end of the hex string.
681          break hexLoop;
682        default:
683          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
684                                  ERR_RDN_INVALID_HEX_CHAR.get(
685                                       rdnString.charAt(pos-1), (pos-1)));
686      }
687
688      if (pos >= length)
689      {
690        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
691                                ERR_RDN_MISSING_HEX_CHAR.get());
692      }
693
694      switch (rdnString.charAt(pos++))
695      {
696        case '0':
697          // No action is required.
698          break;
699        case '1':
700          hexByte |= 0x01;
701          break;
702        case '2':
703          hexByte |= 0x02;
704          break;
705        case '3':
706          hexByte |= 0x03;
707          break;
708        case '4':
709          hexByte |= 0x04;
710          break;
711        case '5':
712          hexByte |= 0x05;
713          break;
714        case '6':
715          hexByte |= 0x06;
716          break;
717        case '7':
718          hexByte |= 0x07;
719          break;
720        case '8':
721          hexByte |= 0x08;
722          break;
723        case '9':
724          hexByte |= 0x09;
725          break;
726        case 'a':
727        case 'A':
728          hexByte |= 0x0A;
729          break;
730        case 'b':
731        case 'B':
732          hexByte |= 0x0B;
733          break;
734        case 'c':
735        case 'C':
736          hexByte |= 0x0C;
737          break;
738        case 'd':
739        case 'D':
740          hexByte |= 0x0D;
741          break;
742        case 'e':
743        case 'E':
744          hexByte |= 0x0E;
745          break;
746        case 'f':
747        case 'F':
748          hexByte |= 0x0F;
749          break;
750        default:
751          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
752                                  ERR_RDN_INVALID_HEX_CHAR.get(
753                                       rdnString.charAt(pos-1), (pos-1)));
754      }
755
756      buffer.put(hexByte);
757    }
758
759    buffer.flip();
760    final byte[] valueArray = new byte[buffer.limit()];
761    buffer.get(valueArray);
762    return valueArray;
763  }
764
765
766
767  /**
768   * Reads a string value from the provided RDN string.  Reading will continue
769   * until the end of the string is reached or until a non-escaped plus sign is
770   * encountered.
771   *
772   * @param  rdnString  The string from which to read the value.
773   * @param  startPos   The position in the RDN string at which to start reading
774   *                    the value.
775   * @param  buffer     The buffer into which the parsed value should be
776   *                    placed.
777   *
778   * @return  The position at which the caller should continue reading when
779   *          parsing the RDN.
780   *
781   * @throws  LDAPException  If a problem occurs while reading the value.
782   */
783  static int readValueString(final String rdnString, final int startPos,
784                             final StringBuilder buffer)
785          throws LDAPException
786  {
787    final int bufferLength = buffer.length();
788    final int length       = rdnString.length();
789    int pos = startPos;
790
791    boolean inQuotes = false;
792valueLoop:
793    while (pos < length)
794    {
795      char c = rdnString.charAt(pos);
796      switch (c)
797      {
798        case '\\':
799          // It's an escaped value.  It can either be followed by a single
800          // character (e.g., backslash, space, octothorpe, equals, double
801          // quote, plus sign, comma, semicolon, less than, or greater-than), or
802          // two hex digits.  If it is followed by hex digits, then continue
803          // reading to see if there are more of them.
804          if ((pos+1) >= length)
805          {
806            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
807                                    ERR_RDN_ENDS_WITH_BACKSLASH.get());
808          }
809          else
810          {
811            pos++;
812            c = rdnString.charAt(pos);
813            if (isHex(c))
814            {
815              // We need to subtract one from the resulting position because
816              // it will be incremented later.
817              pos = readEscapedHexString(rdnString, pos, buffer) - 1;
818            }
819            else
820            {
821              buffer.append(c);
822            }
823          }
824          break;
825
826        case '"':
827          if (inQuotes)
828          {
829            // This should be the end of the value.  If it's not, then fail.
830            pos++;
831            while (pos < length)
832            {
833              c = rdnString.charAt(pos);
834              if ((c == '+') || (c == ',') || (c == ';'))
835              {
836                break;
837              }
838              else if (c != ' ')
839              {
840                throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
841                                        ERR_RDN_CHAR_OUTSIDE_QUOTES.get(c,
842                                             (pos-1)));
843              }
844
845              pos++;
846            }
847
848            inQuotes = false;
849            break valueLoop;
850          }
851          else
852          {
853            // This should be the first character of the value.
854            if (pos == startPos)
855            {
856              inQuotes = true;
857            }
858            else
859            {
860              throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
861                                      ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(pos));
862            }
863          }
864          break;
865
866        case ' ':
867          // We'll add this character if we're in quotes, or if the next
868          // character is not also a space.
869          if (inQuotes ||
870              (((pos+1) < length) && (rdnString.charAt(pos+1) != ' ')))
871          {
872            buffer.append(' ');
873          }
874          break;
875
876        case ',':
877        case ';':
878        case '+':
879          // This denotes the end of the value, if it's not in quotes.
880          if (inQuotes)
881          {
882            buffer.append(c);
883          }
884          else
885          {
886            break valueLoop;
887          }
888          break;
889
890        default:
891          // This is a normal character that should be added to the buffer.
892          buffer.append(c);
893          break;
894      }
895
896      pos++;
897    }
898
899
900    // If the value started with a quotation mark, then make sure it was closed.
901    if (inQuotes)
902    {
903      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
904                              ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get());
905    }
906
907
908    // If the value ends with any unescaped trailing spaces, then trim them off.
909    int bufferPos = buffer.length() - 1;
910    int rdnStrPos = pos - 2;
911    while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' '))
912    {
913      if (rdnString.charAt(rdnStrPos) == '\\')
914      {
915        break;
916      }
917      else
918      {
919        buffer.deleteCharAt(bufferPos--);
920        rdnStrPos--;
921      }
922    }
923
924    return pos;
925  }
926
927
928
929  /**
930   * Reads one or more hex-encoded bytes from the specified portion of the RDN
931   * string.
932   *
933   * @param  rdnString  The string from which the data is to be read.
934   * @param  startPos   The position at which to start reading.  This should be
935   *                    the first hex character immediately after the initial
936   *                    backslash.
937   * @param  buffer     The buffer to which the decoded string portion should be
938   *                    appended.
939   *
940   * @return  The position at which the caller may resume parsing.
941   *
942   * @throws  LDAPException  If a problem occurs while reading hex-encoded
943   *                         bytes.
944   */
945  private static int readEscapedHexString(final String rdnString,
946                                          final int startPos,
947                                          final StringBuilder buffer)
948          throws LDAPException
949  {
950    final int length = rdnString.length();
951    int pos = startPos;
952
953    final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos);
954    while (pos < length)
955    {
956      byte b;
957      switch (rdnString.charAt(pos++))
958      {
959        case '0':
960          b = 0x00;
961          break;
962        case '1':
963          b = 0x10;
964          break;
965        case '2':
966          b = 0x20;
967          break;
968        case '3':
969          b = 0x30;
970          break;
971        case '4':
972          b = 0x40;
973          break;
974        case '5':
975          b = 0x50;
976          break;
977        case '6':
978          b = 0x60;
979          break;
980        case '7':
981          b = 0x70;
982          break;
983        case '8':
984          b = (byte) 0x80;
985          break;
986        case '9':
987          b = (byte) 0x90;
988          break;
989        case 'a':
990        case 'A':
991          b = (byte) 0xA0;
992          break;
993        case 'b':
994        case 'B':
995          b = (byte) 0xB0;
996          break;
997        case 'c':
998        case 'C':
999          b = (byte) 0xC0;
1000          break;
1001        case 'd':
1002        case 'D':
1003          b = (byte) 0xD0;
1004          break;
1005        case 'e':
1006        case 'E':
1007          b = (byte) 0xE0;
1008          break;
1009        case 'f':
1010        case 'F':
1011          b = (byte) 0xF0;
1012          break;
1013        default:
1014          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1015                                  ERR_RDN_INVALID_HEX_CHAR.get(
1016                                       rdnString.charAt(pos-1), (pos-1)));
1017      }
1018
1019      if (pos >= length)
1020      {
1021        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1022                                ERR_RDN_MISSING_HEX_CHAR.get());
1023      }
1024
1025      switch (rdnString.charAt(pos++))
1026      {
1027        case '0':
1028          // No action is required.
1029          break;
1030        case '1':
1031          b |= 0x01;
1032          break;
1033        case '2':
1034          b |= 0x02;
1035          break;
1036        case '3':
1037          b |= 0x03;
1038          break;
1039        case '4':
1040          b |= 0x04;
1041          break;
1042        case '5':
1043          b |= 0x05;
1044          break;
1045        case '6':
1046          b |= 0x06;
1047          break;
1048        case '7':
1049          b |= 0x07;
1050          break;
1051        case '8':
1052          b |= 0x08;
1053          break;
1054        case '9':
1055          b |= 0x09;
1056          break;
1057        case 'a':
1058        case 'A':
1059          b |= 0x0A;
1060          break;
1061        case 'b':
1062        case 'B':
1063          b |= 0x0B;
1064          break;
1065        case 'c':
1066        case 'C':
1067          b |= 0x0C;
1068          break;
1069        case 'd':
1070        case 'D':
1071          b |= 0x0D;
1072          break;
1073        case 'e':
1074        case 'E':
1075          b |= 0x0E;
1076          break;
1077        case 'f':
1078        case 'F':
1079          b |= 0x0F;
1080          break;
1081        default:
1082          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1083                                  ERR_RDN_INVALID_HEX_CHAR.get(
1084                                       rdnString.charAt(pos-1), (pos-1)));
1085      }
1086
1087      byteBuffer.put(b);
1088      if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') &&
1089          isHex(rdnString.charAt(pos+1)))
1090      {
1091        // It appears that there are more hex-encoded bytes to follow, so keep
1092        // reading.
1093        pos++;
1094        continue;
1095      }
1096      else
1097      {
1098        break;
1099      }
1100    }
1101
1102    byteBuffer.flip();
1103    final byte[] byteArray = new byte[byteBuffer.limit()];
1104    byteBuffer.get(byteArray);
1105
1106    try
1107    {
1108      buffer.append(toUTF8String(byteArray));
1109    }
1110    catch (final Exception e)
1111    {
1112      debugException(e);
1113      // This should never happen.
1114      buffer.append(new String(byteArray));
1115    }
1116
1117    return pos;
1118  }
1119
1120
1121
1122  /**
1123   * Indicates whether the provided string represents a valid RDN.
1124   *
1125   * @param  s  The string for which to make the determination.  It must not be
1126   *            {@code null}.
1127   *
1128   * @return  {@code true} if the provided string represents a valid RDN, or
1129   *          {@code false} if not.
1130   */
1131  public static boolean isValidRDN(final String s)
1132  {
1133    try
1134    {
1135      new RDN(s);
1136      return true;
1137    }
1138    catch (LDAPException le)
1139    {
1140      return false;
1141    }
1142  }
1143
1144
1145
1146  /**
1147   * Indicates whether this RDN contains multiple components.
1148   *
1149   * @return  {@code true} if this RDN contains multiple components, or
1150   *          {@code false} if not.
1151   */
1152  public boolean isMultiValued()
1153  {
1154    return (attributeNames.length != 1);
1155  }
1156
1157
1158
1159  /**
1160   * Retrieves the set of attribute names for this RDN.
1161   *
1162   * @return  The set of attribute names for this RDN.
1163   */
1164  public String[] getAttributeNames()
1165  {
1166    return attributeNames;
1167  }
1168
1169
1170
1171  /**
1172   * Retrieves the set of attribute values for this RDN.
1173   *
1174   * @return  The set of attribute values for this RDN.
1175   */
1176  public String[] getAttributeValues()
1177  {
1178    final String[] stringValues = new String[attributeValues.length];
1179    for (int i=0; i < stringValues.length; i++)
1180    {
1181      stringValues[i] = attributeValues[i].stringValue();
1182    }
1183
1184    return stringValues;
1185  }
1186
1187
1188
1189  /**
1190   * Retrieves the set of attribute values for this RDN.
1191   *
1192   * @return  The set of attribute values for this RDN.
1193   */
1194  public byte[][] getByteArrayAttributeValues()
1195  {
1196    final byte[][] byteValues = new byte[attributeValues.length][];
1197    for (int i=0; i < byteValues.length; i++)
1198    {
1199      byteValues[i] = attributeValues[i].getValue();
1200    }
1201
1202    return byteValues;
1203  }
1204
1205
1206
1207  /**
1208   * Retrieves the schema that will be used for this RDN, if any.
1209   *
1210   * @return  The schema that will be used for this RDN, or {@code null} if none
1211   *          has been provided.
1212   */
1213  Schema getSchema()
1214  {
1215    return schema;
1216  }
1217
1218
1219
1220  /**
1221   * Indicates whether this RDN contains the specified attribute.
1222   *
1223   * @param  attributeName  The name of the attribute for which to make the
1224   *                        determination.
1225   *
1226   * @return  {@code true} if RDN contains the specified attribute, or
1227   *          {@code false} if not.
1228   */
1229  public boolean hasAttribute(final String attributeName)
1230  {
1231    for (final String name : attributeNames)
1232    {
1233      if (name.equalsIgnoreCase(attributeName))
1234      {
1235        return true;
1236      }
1237    }
1238
1239    return false;
1240  }
1241
1242
1243
1244  /**
1245   * Indicates whether this RDN contains the specified attribute value.
1246   *
1247   * @param  attributeName   The name of the attribute for which to make the
1248   *                         determination.
1249   * @param  attributeValue  The attribute value for which to make the
1250   *                         determination.
1251   *
1252   * @return  {@code true} if RDN contains the specified attribute, or
1253   *          {@code false} if not.
1254   */
1255  public boolean hasAttributeValue(final String attributeName,
1256                                   final String attributeValue)
1257  {
1258    for (int i=0; i < attributeNames.length; i++)
1259    {
1260      if (attributeNames[i].equalsIgnoreCase(attributeName))
1261      {
1262        final Attribute a =
1263             new Attribute(attributeName, schema, attributeValue);
1264        final Attribute b = new Attribute(attributeName, schema,
1265             attributeValues[i].stringValue());
1266
1267        if (a.equals(b))
1268        {
1269          return true;
1270        }
1271      }
1272    }
1273
1274    return false;
1275  }
1276
1277
1278
1279  /**
1280   * Indicates whether this RDN contains the specified attribute value.
1281   *
1282   * @param  attributeName   The name of the attribute for which to make the
1283   *                         determination.
1284   * @param  attributeValue  The attribute value for which to make the
1285   *                         determination.
1286   *
1287   * @return  {@code true} if RDN contains the specified attribute, or
1288   *          {@code false} if not.
1289   */
1290  public boolean hasAttributeValue(final String attributeName,
1291                                   final byte[] attributeValue)
1292  {
1293    for (int i=0; i < attributeNames.length; i++)
1294    {
1295      if (attributeNames[i].equalsIgnoreCase(attributeName))
1296      {
1297        final Attribute a =
1298             new Attribute(attributeName, schema, attributeValue);
1299        final Attribute b = new Attribute(attributeName, schema,
1300             attributeValues[i].getValue());
1301
1302        if (a.equals(b))
1303        {
1304          return true;
1305        }
1306      }
1307    }
1308
1309    return false;
1310  }
1311
1312
1313
1314  /**
1315   * Retrieves a string representation of this RDN.
1316   *
1317   * @return  A string representation of this RDN.
1318   */
1319  @Override()
1320  public String toString()
1321  {
1322    if (rdnString == null)
1323    {
1324      final StringBuilder buffer = new StringBuilder();
1325      toString(buffer, false);
1326      rdnString = buffer.toString();
1327    }
1328
1329    return rdnString;
1330  }
1331
1332
1333
1334  /**
1335   * Retrieves a string representation of this RDN with minimal encoding for
1336   * special characters.  Only those characters specified in RFC 4514 section
1337   * 2.4 will be escaped.  No escaping will be used for non-ASCII characters or
1338   * non-printable ASCII characters.
1339   *
1340   * @return  A string representation of this RDN with minimal encoding for
1341   *          special characters.
1342   */
1343  public String toMinimallyEncodedString()
1344  {
1345    final StringBuilder buffer = new StringBuilder();
1346    toString(buffer, true);
1347    return buffer.toString();
1348  }
1349
1350
1351
1352  /**
1353   * Appends a string representation of this RDN to the provided buffer.
1354   *
1355   * @param  buffer  The buffer to which the string representation is to be
1356   *                 appended.
1357   */
1358  public void toString(final StringBuilder buffer)
1359  {
1360    toString(buffer, false);
1361  }
1362
1363
1364
1365  /**
1366   * Appends a string representation of this RDN to the provided buffer.
1367   *
1368   * @param  buffer            The buffer to which the string representation is
1369   *                           to be appended.
1370   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1371   *                           special characters to the bare minimum required
1372   *                           by LDAP (as per RFC 4514 section 2.4).  If this
1373   *                           is {@code true}, then only leading and trailing
1374   *                           spaces, double quotes, plus signs, commas,
1375   *                           semicolons, greater-than, less-than, and
1376   *                           backslash characters will be encoded.
1377   */
1378  public void toString(final StringBuilder buffer,
1379                       final boolean minimizeEncoding)
1380  {
1381    if ((rdnString != null) && (! minimizeEncoding))
1382    {
1383      buffer.append(rdnString);
1384      return;
1385    }
1386
1387    for (int i=0; i < attributeNames.length; i++)
1388    {
1389      if (i > 0)
1390      {
1391        buffer.append('+');
1392      }
1393
1394      buffer.append(attributeNames[i]);
1395      buffer.append('=');
1396
1397      // Iterate through the value character-by-character and do any escaping
1398      // that may be necessary.
1399      final String valueString = attributeValues[i].stringValue();
1400      final int length = valueString.length();
1401      for (int j=0; j < length; j++)
1402      {
1403        final char c = valueString.charAt(j);
1404        switch (c)
1405        {
1406          case '\\':
1407          case '#':
1408          case '=':
1409          case '"':
1410          case '+':
1411          case ',':
1412          case ';':
1413          case '<':
1414          case '>':
1415            buffer.append('\\');
1416            buffer.append(c);
1417            break;
1418
1419          case ' ':
1420            // Escape this space only if it's the first character, the last
1421            // character, or if the next character is also a space.
1422            if ((j == 0) || ((j+1) == length) ||
1423                (((j+1) < length) && (valueString.charAt(j+1) == ' ')))
1424            {
1425              buffer.append("\\ ");
1426            }
1427            else
1428            {
1429              buffer.append(' ');
1430            }
1431            break;
1432
1433          case '\u0000':
1434            buffer.append("\\00");
1435            break;
1436
1437          default:
1438            // If it's not a printable ASCII character, then hex-encode it
1439            // unless we're using minimized encoding.
1440            if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
1441            {
1442              hexEncode(c, buffer);
1443            }
1444            else
1445            {
1446              buffer.append(c);
1447            }
1448            break;
1449        }
1450      }
1451    }
1452  }
1453
1454
1455
1456  /**
1457   * Retrieves a normalized string representation of this RDN.
1458   *
1459   * @return  A normalized string representation of this RDN.
1460   */
1461  public String toNormalizedString()
1462  {
1463    if (normalizedString == null)
1464    {
1465      final StringBuilder buffer = new StringBuilder();
1466      toNormalizedString(buffer);
1467      normalizedString = buffer.toString();
1468    }
1469
1470    return normalizedString;
1471  }
1472
1473
1474
1475  /**
1476   * Appends a normalized string representation of this RDN to the provided
1477   * buffer.
1478   *
1479   * @param  buffer  The buffer to which the normalized string representation is
1480   *                 to be appended.
1481   */
1482  public void toNormalizedString(final StringBuilder buffer)
1483  {
1484    if (attributeNames.length == 1)
1485    {
1486      // It's a single-valued RDN, so there is no need to sort anything.
1487      final String name = normalizeAttrName(attributeNames[0]);
1488      buffer.append(name);
1489      buffer.append('=');
1490      buffer.append(normalizeValue(name, attributeValues[0]));
1491    }
1492    else
1493    {
1494      // It's a multivalued RDN, so we need to sort the components.
1495      final TreeMap<String,ASN1OctetString> valueMap =
1496           new TreeMap<String,ASN1OctetString>();
1497      for (int i=0; i < attributeNames.length; i++)
1498      {
1499        final String name = normalizeAttrName(attributeNames[i]);
1500        valueMap.put(name, attributeValues[i]);
1501      }
1502
1503      int i=0;
1504      for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet())
1505      {
1506        if (i++ > 0)
1507        {
1508          buffer.append('+');
1509        }
1510
1511        buffer.append(entry.getKey());
1512        buffer.append('=');
1513        buffer.append(normalizeValue(entry.getKey(), entry.getValue()));
1514      }
1515    }
1516  }
1517
1518
1519
1520  /**
1521   * Obtains a normalized representation of the provided attribute name.
1522   *
1523   * @param  name  The name of the attribute for which to create the normalized
1524   *               representation.
1525   *
1526   * @return  A normalized representation of the provided attribute name.
1527   */
1528  private String normalizeAttrName(final String name)
1529  {
1530    String n = name;
1531    if (schema != null)
1532    {
1533      final AttributeTypeDefinition at = schema.getAttributeType(name);
1534      if (at != null)
1535      {
1536        n = at.getNameOrOID();
1537      }
1538    }
1539    return toLowerCase(n);
1540  }
1541
1542
1543
1544  /**
1545   * Retrieves a normalized string representation of the RDN with the provided
1546   * string representation.
1547   *
1548   * @param  s  The string representation of the RDN to normalize.  It must not
1549   *            be {@code null}.
1550   *
1551   * @return  The normalized string representation of the RDN with the provided
1552   *          string representation.
1553   *
1554   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1555   */
1556  public static String normalize(final String s)
1557         throws LDAPException
1558  {
1559    return normalize(s, null);
1560  }
1561
1562
1563
1564  /**
1565   * Retrieves a normalized string representation of the RDN with the provided
1566   * string representation.
1567   *
1568   * @param  s       The string representation of the RDN to normalize.  It must
1569   *                 not be {@code null}.
1570   * @param  schema  The schema to use to generate the normalized string
1571   *                 representation of the RDN.  It may be {@code null} if no
1572   *                 schema is available.
1573   *
1574   * @return  The normalized string representation of the RDN with the provided
1575   *          string representation.
1576   *
1577   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1578   */
1579  public static String normalize(final String s, final Schema schema)
1580         throws LDAPException
1581  {
1582    return new RDN(s, schema).toNormalizedString();
1583  }
1584
1585
1586
1587  /**
1588   * Normalizes the provided attribute value for use in an RDN.
1589   *
1590   * @param  attributeName  The name of the attribute with which the value is
1591   *                        associated.
1592   * @param  value           The value to be normalized.
1593   *
1594   * @return  A string builder containing a normalized representation of the
1595   *          value in a suitable form for inclusion in an RDN.
1596   */
1597  private StringBuilder normalizeValue(final String attributeName,
1598                                       final ASN1OctetString value)
1599  {
1600    final MatchingRule matchingRule =
1601         MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1602
1603    ASN1OctetString rawNormValue;
1604    try
1605    {
1606      rawNormValue = matchingRule.normalize(value);
1607    }
1608    catch (final Exception e)
1609    {
1610      debugException(e);
1611      rawNormValue =
1612           new ASN1OctetString(toLowerCase(value.stringValue()));
1613    }
1614
1615    final String valueString = rawNormValue.stringValue();
1616    final int length = valueString.length();
1617    final StringBuilder buffer = new StringBuilder(length);
1618
1619    for (int i=0; i < length; i++)
1620    {
1621      final char c = valueString.charAt(i);
1622
1623      switch (c)
1624      {
1625        case '\\':
1626        case '#':
1627        case '=':
1628        case '"':
1629        case '+':
1630        case ',':
1631        case ';':
1632        case '<':
1633        case '>':
1634          buffer.append('\\');
1635          buffer.append(c);
1636          break;
1637
1638        case ' ':
1639          // Escape this space only if it's the first character, the last
1640          // character, or if the next character is also a space.
1641          if ((i == 0) || ((i+1) == length) ||
1642              (((i+1) < length) && (valueString.charAt(i+1) == ' ')))
1643          {
1644            buffer.append("\\ ");
1645          }
1646          else
1647          {
1648            buffer.append(' ');
1649          }
1650          break;
1651
1652        default:
1653          // If it's not a printable ASCII character, then hex-encode it.
1654          if ((c < ' ') || (c > '~'))
1655          {
1656            hexEncode(c, buffer);
1657          }
1658          else
1659          {
1660            buffer.append(c);
1661          }
1662          break;
1663      }
1664    }
1665
1666    return buffer;
1667  }
1668
1669
1670
1671  /**
1672   * Retrieves a hash code for this RDN.
1673   *
1674   * @return  The hash code for this RDN.
1675   */
1676  @Override()
1677  public int hashCode()
1678  {
1679    return toNormalizedString().hashCode();
1680  }
1681
1682
1683
1684  /**
1685   * Indicates whether this RDN is equal to the provided object.  The given
1686   * object will only be considered equal to this RDN if it is also an RDN with
1687   * the same set of names and values.
1688   *
1689   * @param  o  The object for which to make the determination.
1690   *
1691   * @return  {@code true} if the provided object can be considered equal to
1692   *          this RDN, or {@code false} if not.
1693   */
1694  @Override()
1695  public boolean equals(final Object o)
1696  {
1697    if (o == null)
1698    {
1699      return false;
1700    }
1701
1702    if (o == this)
1703    {
1704      return true;
1705    }
1706
1707    if (! (o instanceof RDN))
1708    {
1709      return false;
1710    }
1711
1712    final RDN rdn = (RDN) o;
1713    return (toNormalizedString().equals(rdn.toNormalizedString()));
1714  }
1715
1716
1717
1718  /**
1719   * Indicates whether the RDN with the provided string representation is equal
1720   * to this RDN.
1721   *
1722   * @param  s  The string representation of the DN to compare with this RDN.
1723   *
1724   * @return  {@code true} if the DN with the provided string representation is
1725   *          equal to this RDN, or {@code false} if not.
1726   *
1727   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1728   */
1729  public boolean equals(final String s)
1730         throws LDAPException
1731  {
1732    if (s == null)
1733    {
1734      return false;
1735    }
1736
1737    return equals(new RDN(s, schema));
1738  }
1739
1740
1741
1742  /**
1743   * Indicates whether the two provided strings represent the same RDN.
1744   *
1745   * @param  s1  The string representation of the first RDN for which to make
1746   *             the determination.  It must not be {@code null}.
1747   * @param  s2  The string representation of the second RDN for which to make
1748   *             the determination.  It must not be {@code null}.
1749   *
1750   * @return  {@code true} if the provided strings represent the same RDN, or
1751   *          {@code false} if not.
1752   *
1753   * @throws  LDAPException  If either of the provided strings cannot be parsed
1754   *                         as an RDN.
1755   */
1756  public static boolean equals(final String s1, final String s2)
1757         throws LDAPException
1758  {
1759    return new RDN(s1).equals(new RDN(s2));
1760  }
1761
1762
1763
1764  /**
1765   * Compares the provided RDN to this RDN to determine their relative order in
1766   * a sorted list.
1767   *
1768   * @param  rdn  The RDN to compare against this RDN.  It must not be
1769   *              {@code null}.
1770   *
1771   * @return  A negative integer if this RDN should come before the provided RDN
1772   *          in a sorted list, a positive integer if this RDN should come after
1773   *          the provided RDN in a sorted list, or zero if the provided RDN
1774   *          can be considered equal to this RDN.
1775   */
1776  public int compareTo(final RDN rdn)
1777  {
1778    return compare(this, rdn);
1779  }
1780
1781
1782
1783  /**
1784   * Compares the provided RDN values to determine their relative order in a
1785   * sorted list.
1786   *
1787   * @param  rdn1  The first RDN to be compared.  It must not be {@code null}.
1788   * @param  rdn2  The second RDN to be compared.  It must not be {@code null}.
1789   *
1790   * @return  A negative integer if the first RDN should come before the second
1791   *          RDN in a sorted list, a positive integer if the first RDN should
1792   *          come after the second RDN in a sorted list, or zero if the two RDN
1793   *          values can be considered equal.
1794   */
1795  public int compare(final RDN rdn1, final RDN rdn2)
1796  {
1797    ensureNotNull(rdn1, rdn2);
1798
1799    return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString()));
1800  }
1801
1802
1803
1804  /**
1805   * Compares the RDN values with the provided string representations to
1806   * determine their relative order in a sorted list.
1807   *
1808   * @param  s1  The string representation of the first RDN to be compared.  It
1809   *             must not be {@code null}.
1810   * @param  s2  The string representation of the second RDN to be compared.  It
1811   *             must not be {@code null}.
1812   *
1813   * @return  A negative integer if the first RDN should come before the second
1814   *          RDN in a sorted list, a positive integer if the first RDN should
1815   *          come after the second RDN in a sorted list, or zero if the two RDN
1816   *          values can be considered equal.
1817   *
1818   * @throws  LDAPException  If either of the provided strings cannot be parsed
1819   *                         as an RDN.
1820   */
1821  public static int compare(final String s1, final String s2)
1822         throws LDAPException
1823  {
1824    return compare(s1, s2, null);
1825  }
1826
1827
1828
1829  /**
1830   * Compares the RDN values with the provided string representations to
1831   * determine their relative order in a sorted list.
1832   *
1833   * @param  s1      The string representation of the first RDN to be compared.
1834   *                 It must not be {@code null}.
1835   * @param  s2      The string representation of the second RDN to be compared.
1836   *                 It must not be {@code null}.
1837   * @param  schema  The schema to use to generate the normalized string
1838   *                 representations of the RDNs.  It may be {@code null} if no
1839   *                 schema is available.
1840   *
1841   * @return  A negative integer if the first RDN should come before the second
1842   *          RDN in a sorted list, a positive integer if the first RDN should
1843   *          come after the second RDN in a sorted list, or zero if the two RDN
1844   *          values can be considered equal.
1845   *
1846   * @throws  LDAPException  If either of the provided strings cannot be parsed
1847   *                         as an RDN.
1848   */
1849  public static int compare(final String s1, final String s2,
1850                            final Schema schema)
1851         throws LDAPException
1852  {
1853    return new RDN(s1, schema).compareTo(new RDN(s2, schema));
1854  }
1855}