001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 length = rdnString.length();
788    int pos = startPos;
789
790    boolean inQuotes = false;
791valueLoop:
792    while (pos < length)
793    {
794      char c = rdnString.charAt(pos);
795      switch (c)
796      {
797        case '\\':
798          // It's an escaped value.  It can either be followed by a single
799          // character (e.g., backslash, space, octothorpe, equals, double
800          // quote, plus sign, comma, semicolon, less than, or greater-than), or
801          // two hex digits.  If it is followed by hex digits, then continue
802          // reading to see if there are more of them.
803          if ((pos+1) >= length)
804          {
805            throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
806                                    ERR_RDN_ENDS_WITH_BACKSLASH.get());
807          }
808          else
809          {
810            pos++;
811            c = rdnString.charAt(pos);
812            if (isHex(c))
813            {
814              // We need to subtract one from the resulting position because
815              // it will be incremented later.
816              pos = readEscapedHexString(rdnString, pos, buffer) - 1;
817            }
818            else
819            {
820              buffer.append(c);
821            }
822          }
823          break;
824
825        case '"':
826          if (inQuotes)
827          {
828            // This should be the end of the value.  If it's not, then fail.
829            pos++;
830            while (pos < length)
831            {
832              c = rdnString.charAt(pos);
833              if ((c == '+') || (c == ',') || (c == ';'))
834              {
835                break;
836              }
837              else if (c != ' ')
838              {
839                throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
840                                        ERR_RDN_CHAR_OUTSIDE_QUOTES.get(c,
841                                             (pos-1)));
842              }
843
844              pos++;
845            }
846
847            inQuotes = false;
848            break valueLoop;
849          }
850          else
851          {
852            // This should be the first character of the value.
853            if (pos == startPos)
854            {
855              inQuotes = true;
856            }
857            else
858            {
859              throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
860                                      ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(pos));
861            }
862          }
863          break;
864
865        case ',':
866        case ';':
867        case '+':
868          // This denotes the end of the value, if it's not in quotes.
869          if (inQuotes)
870          {
871            buffer.append(c);
872          }
873          else
874          {
875            break valueLoop;
876          }
877          break;
878
879        default:
880          // This is a normal character that should be added to the buffer.
881          buffer.append(c);
882          break;
883      }
884
885      pos++;
886    }
887
888
889    // If the value started with a quotation mark, then make sure it was closed.
890    if (inQuotes)
891    {
892      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
893                              ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get());
894    }
895
896
897    // If the value ends with any unescaped trailing spaces, then trim them off.
898    int bufferPos = buffer.length() - 1;
899    int rdnStrPos = pos - 2;
900    while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' '))
901    {
902      if (rdnString.charAt(rdnStrPos) == '\\')
903      {
904        break;
905      }
906      else
907      {
908        buffer.deleteCharAt(bufferPos--);
909        rdnStrPos--;
910      }
911    }
912
913    return pos;
914  }
915
916
917
918  /**
919   * Reads one or more hex-encoded bytes from the specified portion of the RDN
920   * string.
921   *
922   * @param  rdnString  The string from which the data is to be read.
923   * @param  startPos   The position at which to start reading.  This should be
924   *                    the first hex character immediately after the initial
925   *                    backslash.
926   * @param  buffer     The buffer to which the decoded string portion should be
927   *                    appended.
928   *
929   * @return  The position at which the caller may resume parsing.
930   *
931   * @throws  LDAPException  If a problem occurs while reading hex-encoded
932   *                         bytes.
933   */
934  private static int readEscapedHexString(final String rdnString,
935                                          final int startPos,
936                                          final StringBuilder buffer)
937          throws LDAPException
938  {
939    final int length = rdnString.length();
940    int pos = startPos;
941
942    final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos);
943    while (pos < length)
944    {
945      byte b;
946      switch (rdnString.charAt(pos++))
947      {
948        case '0':
949          b = 0x00;
950          break;
951        case '1':
952          b = 0x10;
953          break;
954        case '2':
955          b = 0x20;
956          break;
957        case '3':
958          b = 0x30;
959          break;
960        case '4':
961          b = 0x40;
962          break;
963        case '5':
964          b = 0x50;
965          break;
966        case '6':
967          b = 0x60;
968          break;
969        case '7':
970          b = 0x70;
971          break;
972        case '8':
973          b = (byte) 0x80;
974          break;
975        case '9':
976          b = (byte) 0x90;
977          break;
978        case 'a':
979        case 'A':
980          b = (byte) 0xA0;
981          break;
982        case 'b':
983        case 'B':
984          b = (byte) 0xB0;
985          break;
986        case 'c':
987        case 'C':
988          b = (byte) 0xC0;
989          break;
990        case 'd':
991        case 'D':
992          b = (byte) 0xD0;
993          break;
994        case 'e':
995        case 'E':
996          b = (byte) 0xE0;
997          break;
998        case 'f':
999        case 'F':
1000          b = (byte) 0xF0;
1001          break;
1002        default:
1003          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1004                                  ERR_RDN_INVALID_HEX_CHAR.get(
1005                                       rdnString.charAt(pos-1), (pos-1)));
1006      }
1007
1008      if (pos >= length)
1009      {
1010        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1011                                ERR_RDN_MISSING_HEX_CHAR.get());
1012      }
1013
1014      switch (rdnString.charAt(pos++))
1015      {
1016        case '0':
1017          // No action is required.
1018          break;
1019        case '1':
1020          b |= 0x01;
1021          break;
1022        case '2':
1023          b |= 0x02;
1024          break;
1025        case '3':
1026          b |= 0x03;
1027          break;
1028        case '4':
1029          b |= 0x04;
1030          break;
1031        case '5':
1032          b |= 0x05;
1033          break;
1034        case '6':
1035          b |= 0x06;
1036          break;
1037        case '7':
1038          b |= 0x07;
1039          break;
1040        case '8':
1041          b |= 0x08;
1042          break;
1043        case '9':
1044          b |= 0x09;
1045          break;
1046        case 'a':
1047        case 'A':
1048          b |= 0x0A;
1049          break;
1050        case 'b':
1051        case 'B':
1052          b |= 0x0B;
1053          break;
1054        case 'c':
1055        case 'C':
1056          b |= 0x0C;
1057          break;
1058        case 'd':
1059        case 'D':
1060          b |= 0x0D;
1061          break;
1062        case 'e':
1063        case 'E':
1064          b |= 0x0E;
1065          break;
1066        case 'f':
1067        case 'F':
1068          b |= 0x0F;
1069          break;
1070        default:
1071          throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1072                                  ERR_RDN_INVALID_HEX_CHAR.get(
1073                                       rdnString.charAt(pos-1), (pos-1)));
1074      }
1075
1076      byteBuffer.put(b);
1077      if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') &&
1078          isHex(rdnString.charAt(pos+1)))
1079      {
1080        // It appears that there are more hex-encoded bytes to follow, so keep
1081        // reading.
1082        pos++;
1083        continue;
1084      }
1085      else
1086      {
1087        break;
1088      }
1089    }
1090
1091    byteBuffer.flip();
1092    final byte[] byteArray = new byte[byteBuffer.limit()];
1093    byteBuffer.get(byteArray);
1094
1095    try
1096    {
1097      buffer.append(toUTF8String(byteArray));
1098    }
1099    catch (final Exception e)
1100    {
1101      debugException(e);
1102      // This should never happen.
1103      buffer.append(new String(byteArray));
1104    }
1105
1106    return pos;
1107  }
1108
1109
1110
1111  /**
1112   * Indicates whether the provided string represents a valid RDN.
1113   *
1114   * @param  s  The string for which to make the determination.  It must not be
1115   *            {@code null}.
1116   *
1117   * @return  {@code true} if the provided string represents a valid RDN, or
1118   *          {@code false} if not.
1119   */
1120  public static boolean isValidRDN(final String s)
1121  {
1122    try
1123    {
1124      new RDN(s);
1125      return true;
1126    }
1127    catch (LDAPException le)
1128    {
1129      return false;
1130    }
1131  }
1132
1133
1134
1135  /**
1136   * Indicates whether this RDN contains multiple components.
1137   *
1138   * @return  {@code true} if this RDN contains multiple components, or
1139   *          {@code false} if not.
1140   */
1141  public boolean isMultiValued()
1142  {
1143    return (attributeNames.length != 1);
1144  }
1145
1146
1147
1148  /**
1149   * Retrieves an array of the attributes that comprise this RDN.
1150   *
1151   * @return  An array of the attributes that comprise this RDN.
1152   */
1153  public Attribute[] getAttributes()
1154  {
1155    final Attribute[] attrs = new Attribute[attributeNames.length];
1156    for (int i=0; i < attrs.length; i++)
1157    {
1158      attrs[i] = new Attribute(attributeNames[i], schema,
1159           new ASN1OctetString[] {  attributeValues[i] });
1160    }
1161
1162    return attrs;
1163  }
1164
1165
1166
1167  /**
1168   * Retrieves the set of attribute names for this RDN.
1169   *
1170   * @return  The set of attribute names for this RDN.
1171   */
1172  public String[] getAttributeNames()
1173  {
1174    return attributeNames;
1175  }
1176
1177
1178
1179  /**
1180   * Retrieves the set of attribute values for this RDN.
1181   *
1182   * @return  The set of attribute values for this RDN.
1183   */
1184  public String[] getAttributeValues()
1185  {
1186    final String[] stringValues = new String[attributeValues.length];
1187    for (int i=0; i < stringValues.length; i++)
1188    {
1189      stringValues[i] = attributeValues[i].stringValue();
1190    }
1191
1192    return stringValues;
1193  }
1194
1195
1196
1197  /**
1198   * Retrieves the set of attribute values for this RDN.
1199   *
1200   * @return  The set of attribute values for this RDN.
1201   */
1202  public byte[][] getByteArrayAttributeValues()
1203  {
1204    final byte[][] byteValues = new byte[attributeValues.length][];
1205    for (int i=0; i < byteValues.length; i++)
1206    {
1207      byteValues[i] = attributeValues[i].getValue();
1208    }
1209
1210    return byteValues;
1211  }
1212
1213
1214
1215  /**
1216   * Retrieves the schema that will be used for this RDN, if any.
1217   *
1218   * @return  The schema that will be used for this RDN, or {@code null} if none
1219   *          has been provided.
1220   */
1221  Schema getSchema()
1222  {
1223    return schema;
1224  }
1225
1226
1227
1228  /**
1229   * Indicates whether this RDN contains the specified attribute.
1230   *
1231   * @param  attributeName  The name of the attribute for which to make the
1232   *                        determination.
1233   *
1234   * @return  {@code true} if RDN contains the specified attribute, or
1235   *          {@code false} if not.
1236   */
1237  public boolean hasAttribute(final String attributeName)
1238  {
1239    for (final String name : attributeNames)
1240    {
1241      if (name.equalsIgnoreCase(attributeName))
1242      {
1243        return true;
1244      }
1245    }
1246
1247    return false;
1248  }
1249
1250
1251
1252  /**
1253   * Indicates whether this RDN contains the specified attribute value.
1254   *
1255   * @param  attributeName   The name of the attribute for which to make the
1256   *                         determination.
1257   * @param  attributeValue  The attribute value for which to make the
1258   *                         determination.
1259   *
1260   * @return  {@code true} if RDN contains the specified attribute, or
1261   *          {@code false} if not.
1262   */
1263  public boolean hasAttributeValue(final String attributeName,
1264                                   final String attributeValue)
1265  {
1266    for (int i=0; i < attributeNames.length; i++)
1267    {
1268      if (attributeNames[i].equalsIgnoreCase(attributeName))
1269      {
1270        final Attribute a =
1271             new Attribute(attributeName, schema, attributeValue);
1272        final Attribute b = new Attribute(attributeName, schema,
1273             attributeValues[i].stringValue());
1274
1275        if (a.equals(b))
1276        {
1277          return true;
1278        }
1279      }
1280    }
1281
1282    return false;
1283  }
1284
1285
1286
1287  /**
1288   * Indicates whether this RDN contains the specified attribute value.
1289   *
1290   * @param  attributeName   The name of the attribute for which to make the
1291   *                         determination.
1292   * @param  attributeValue  The attribute value for which to make the
1293   *                         determination.
1294   *
1295   * @return  {@code true} if RDN contains the specified attribute, or
1296   *          {@code false} if not.
1297   */
1298  public boolean hasAttributeValue(final String attributeName,
1299                                   final byte[] attributeValue)
1300  {
1301    for (int i=0; i < attributeNames.length; i++)
1302    {
1303      if (attributeNames[i].equalsIgnoreCase(attributeName))
1304      {
1305        final Attribute a =
1306             new Attribute(attributeName, schema, attributeValue);
1307        final Attribute b = new Attribute(attributeName, schema,
1308             attributeValues[i].getValue());
1309
1310        if (a.equals(b))
1311        {
1312          return true;
1313        }
1314      }
1315    }
1316
1317    return false;
1318  }
1319
1320
1321
1322  /**
1323   * Retrieves a string representation of this RDN.
1324   *
1325   * @return  A string representation of this RDN.
1326   */
1327  @Override()
1328  public String toString()
1329  {
1330    if (rdnString == null)
1331    {
1332      final StringBuilder buffer = new StringBuilder();
1333      toString(buffer, false);
1334      rdnString = buffer.toString();
1335    }
1336
1337    return rdnString;
1338  }
1339
1340
1341
1342  /**
1343   * Retrieves a string representation of this RDN with minimal encoding for
1344   * special characters.  Only those characters specified in RFC 4514 section
1345   * 2.4 will be escaped.  No escaping will be used for non-ASCII characters or
1346   * non-printable ASCII characters.
1347   *
1348   * @return  A string representation of this RDN with minimal encoding for
1349   *          special characters.
1350   */
1351  public String toMinimallyEncodedString()
1352  {
1353    final StringBuilder buffer = new StringBuilder();
1354    toString(buffer, true);
1355    return buffer.toString();
1356  }
1357
1358
1359
1360  /**
1361   * Appends a string representation of this RDN to the provided buffer.
1362   *
1363   * @param  buffer  The buffer to which the string representation is to be
1364   *                 appended.
1365   */
1366  public void toString(final StringBuilder buffer)
1367  {
1368    toString(buffer, false);
1369  }
1370
1371
1372
1373  /**
1374   * Appends a string representation of this RDN to the provided buffer.
1375   *
1376   * @param  buffer            The buffer to which the string representation is
1377   *                           to be appended.
1378   * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1379   *                           special characters to the bare minimum required
1380   *                           by LDAP (as per RFC 4514 section 2.4).  If this
1381   *                           is {@code true}, then only leading and trailing
1382   *                           spaces, double quotes, plus signs, commas,
1383   *                           semicolons, greater-than, less-than, and
1384   *                           backslash characters will be encoded.
1385   */
1386  public void toString(final StringBuilder buffer,
1387                       final boolean minimizeEncoding)
1388  {
1389    if ((rdnString != null) && (! minimizeEncoding))
1390    {
1391      buffer.append(rdnString);
1392      return;
1393    }
1394
1395    for (int i=0; i < attributeNames.length; i++)
1396    {
1397      if (i > 0)
1398      {
1399        buffer.append('+');
1400      }
1401
1402      buffer.append(attributeNames[i]);
1403      buffer.append('=');
1404
1405      // Iterate through the value character-by-character and do any escaping
1406      // that may be necessary.
1407      final String valueString = attributeValues[i].stringValue();
1408      final int length = valueString.length();
1409      for (int j=0; j < length; j++)
1410      {
1411        final char c = valueString.charAt(j);
1412        switch (c)
1413        {
1414          case '\\':
1415          case '#':
1416          case '=':
1417          case '"':
1418          case '+':
1419          case ',':
1420          case ';':
1421          case '<':
1422          case '>':
1423            buffer.append('\\');
1424            buffer.append(c);
1425            break;
1426
1427          case ' ':
1428            // Escape this space only if it's the first character, the last
1429            // character, or if the next character is also a space.
1430            if ((j == 0) || ((j+1) == length) ||
1431                (((j+1) < length) && (valueString.charAt(j+1) == ' ')))
1432            {
1433              buffer.append("\\ ");
1434            }
1435            else
1436            {
1437              buffer.append(' ');
1438            }
1439            break;
1440
1441          case '\u0000':
1442            buffer.append("\\00");
1443            break;
1444
1445          default:
1446            // If it's not a printable ASCII character, then hex-encode it
1447            // unless we're using minimized encoding.
1448            if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
1449            {
1450              hexEncode(c, buffer);
1451            }
1452            else
1453            {
1454              buffer.append(c);
1455            }
1456            break;
1457        }
1458      }
1459    }
1460  }
1461
1462
1463
1464  /**
1465   * Retrieves a normalized string representation of this RDN.
1466   *
1467   * @return  A normalized string representation of this RDN.
1468   */
1469  public String toNormalizedString()
1470  {
1471    if (normalizedString == null)
1472    {
1473      final StringBuilder buffer = new StringBuilder();
1474      toNormalizedString(buffer);
1475      normalizedString = buffer.toString();
1476    }
1477
1478    return normalizedString;
1479  }
1480
1481
1482
1483  /**
1484   * Appends a normalized string representation of this RDN to the provided
1485   * buffer.
1486   *
1487   * @param  buffer  The buffer to which the normalized string representation is
1488   *                 to be appended.
1489   */
1490  public void toNormalizedString(final StringBuilder buffer)
1491  {
1492    if (attributeNames.length == 1)
1493    {
1494      // It's a single-valued RDN, so there is no need to sort anything.
1495      final String name = normalizeAttrName(attributeNames[0]);
1496      buffer.append(name);
1497      buffer.append('=');
1498      buffer.append(normalizeValue(name, attributeValues[0]));
1499    }
1500    else
1501    {
1502      // It's a multivalued RDN, so we need to sort the components.
1503      final TreeMap<String,ASN1OctetString> valueMap =
1504           new TreeMap<String,ASN1OctetString>();
1505      for (int i=0; i < attributeNames.length; i++)
1506      {
1507        final String name = normalizeAttrName(attributeNames[i]);
1508        valueMap.put(name, attributeValues[i]);
1509      }
1510
1511      int i=0;
1512      for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet())
1513      {
1514        if (i++ > 0)
1515        {
1516          buffer.append('+');
1517        }
1518
1519        buffer.append(entry.getKey());
1520        buffer.append('=');
1521        buffer.append(normalizeValue(entry.getKey(), entry.getValue()));
1522      }
1523    }
1524  }
1525
1526
1527
1528  /**
1529   * Obtains a normalized representation of the provided attribute name.
1530   *
1531   * @param  name  The name of the attribute for which to create the normalized
1532   *               representation.
1533   *
1534   * @return  A normalized representation of the provided attribute name.
1535   */
1536  private String normalizeAttrName(final String name)
1537  {
1538    String n = name;
1539    if (schema != null)
1540    {
1541      final AttributeTypeDefinition at = schema.getAttributeType(name);
1542      if (at != null)
1543      {
1544        n = at.getNameOrOID();
1545      }
1546    }
1547    return toLowerCase(n);
1548  }
1549
1550
1551
1552  /**
1553   * Retrieves a normalized string representation of the RDN with the provided
1554   * string representation.
1555   *
1556   * @param  s  The string representation of the RDN to normalize.  It must not
1557   *            be {@code null}.
1558   *
1559   * @return  The normalized string representation of the RDN with the provided
1560   *          string representation.
1561   *
1562   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1563   */
1564  public static String normalize(final String s)
1565         throws LDAPException
1566  {
1567    return normalize(s, null);
1568  }
1569
1570
1571
1572  /**
1573   * Retrieves a normalized string representation of the RDN with the provided
1574   * string representation.
1575   *
1576   * @param  s       The string representation of the RDN to normalize.  It must
1577   *                 not be {@code null}.
1578   * @param  schema  The schema to use to generate the normalized string
1579   *                 representation of the RDN.  It may be {@code null} if no
1580   *                 schema is available.
1581   *
1582   * @return  The normalized string representation of the RDN with the provided
1583   *          string representation.
1584   *
1585   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1586   */
1587  public static String normalize(final String s, final Schema schema)
1588         throws LDAPException
1589  {
1590    return new RDN(s, schema).toNormalizedString();
1591  }
1592
1593
1594
1595  /**
1596   * Normalizes the provided attribute value for use in an RDN.
1597   *
1598   * @param  attributeName  The name of the attribute with which the value is
1599   *                        associated.
1600   * @param  value           The value to be normalized.
1601   *
1602   * @return  A string builder containing a normalized representation of the
1603   *          value in a suitable form for inclusion in an RDN.
1604   */
1605  private StringBuilder normalizeValue(final String attributeName,
1606                                       final ASN1OctetString value)
1607  {
1608    final MatchingRule matchingRule =
1609         MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1610
1611    ASN1OctetString rawNormValue;
1612    try
1613    {
1614      rawNormValue = matchingRule.normalize(value);
1615    }
1616    catch (final Exception e)
1617    {
1618      debugException(e);
1619      rawNormValue =
1620           new ASN1OctetString(toLowerCase(value.stringValue()));
1621    }
1622
1623    final String valueString = rawNormValue.stringValue();
1624    final int length = valueString.length();
1625    final StringBuilder buffer = new StringBuilder(length);
1626
1627    for (int i=0; i < length; i++)
1628    {
1629      final char c = valueString.charAt(i);
1630
1631      switch (c)
1632      {
1633        case '\\':
1634        case '#':
1635        case '=':
1636        case '"':
1637        case '+':
1638        case ',':
1639        case ';':
1640        case '<':
1641        case '>':
1642          buffer.append('\\');
1643          buffer.append(c);
1644          break;
1645
1646        case ' ':
1647          // Escape this space only if it's the first character, the last
1648          // character, or if the next character is also a space.
1649          if ((i == 0) || ((i+1) == length) ||
1650              (((i+1) < length) && (valueString.charAt(i+1) == ' ')))
1651          {
1652            buffer.append("\\ ");
1653          }
1654          else
1655          {
1656            buffer.append(' ');
1657          }
1658          break;
1659
1660        default:
1661          // If it's not a printable ASCII character, then hex-encode it.
1662          if ((c < ' ') || (c > '~'))
1663          {
1664            hexEncode(c, buffer);
1665          }
1666          else
1667          {
1668            buffer.append(c);
1669          }
1670          break;
1671      }
1672    }
1673
1674    return buffer;
1675  }
1676
1677
1678
1679  /**
1680   * Retrieves a hash code for this RDN.
1681   *
1682   * @return  The hash code for this RDN.
1683   */
1684  @Override()
1685  public int hashCode()
1686  {
1687    return toNormalizedString().hashCode();
1688  }
1689
1690
1691
1692  /**
1693   * Indicates whether this RDN is equal to the provided object.  The given
1694   * object will only be considered equal to this RDN if it is also an RDN with
1695   * the same set of names and values.
1696   *
1697   * @param  o  The object for which to make the determination.
1698   *
1699   * @return  {@code true} if the provided object can be considered equal to
1700   *          this RDN, or {@code false} if not.
1701   */
1702  @Override()
1703  public boolean equals(final Object o)
1704  {
1705    if (o == null)
1706    {
1707      return false;
1708    }
1709
1710    if (o == this)
1711    {
1712      return true;
1713    }
1714
1715    if (! (o instanceof RDN))
1716    {
1717      return false;
1718    }
1719
1720    final RDN rdn = (RDN) o;
1721    return (toNormalizedString().equals(rdn.toNormalizedString()));
1722  }
1723
1724
1725
1726  /**
1727   * Indicates whether the RDN with the provided string representation is equal
1728   * to this RDN.
1729   *
1730   * @param  s  The string representation of the DN to compare with this RDN.
1731   *
1732   * @return  {@code true} if the DN with the provided string representation is
1733   *          equal to this RDN, or {@code false} if not.
1734   *
1735   * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1736   */
1737  public boolean equals(final String s)
1738         throws LDAPException
1739  {
1740    if (s == null)
1741    {
1742      return false;
1743    }
1744
1745    return equals(new RDN(s, schema));
1746  }
1747
1748
1749
1750  /**
1751   * Indicates whether the two provided strings represent the same RDN.
1752   *
1753   * @param  s1  The string representation of the first RDN for which to make
1754   *             the determination.  It must not be {@code null}.
1755   * @param  s2  The string representation of the second RDN for which to make
1756   *             the determination.  It must not be {@code null}.
1757   *
1758   * @return  {@code true} if the provided strings represent the same RDN, or
1759   *          {@code false} if not.
1760   *
1761   * @throws  LDAPException  If either of the provided strings cannot be parsed
1762   *                         as an RDN.
1763   */
1764  public static boolean equals(final String s1, final String s2)
1765         throws LDAPException
1766  {
1767    return new RDN(s1).equals(new RDN(s2));
1768  }
1769
1770
1771
1772  /**
1773   * Compares the provided RDN to this RDN to determine their relative order in
1774   * a sorted list.
1775   *
1776   * @param  rdn  The RDN to compare against this RDN.  It must not be
1777   *              {@code null}.
1778   *
1779   * @return  A negative integer if this RDN should come before the provided RDN
1780   *          in a sorted list, a positive integer if this RDN should come after
1781   *          the provided RDN in a sorted list, or zero if the provided RDN
1782   *          can be considered equal to this RDN.
1783   */
1784  public int compareTo(final RDN rdn)
1785  {
1786    return compare(this, rdn);
1787  }
1788
1789
1790
1791  /**
1792   * Compares the provided RDN values to determine their relative order in a
1793   * sorted list.
1794   *
1795   * @param  rdn1  The first RDN to be compared.  It must not be {@code null}.
1796   * @param  rdn2  The second RDN to be compared.  It must not be {@code null}.
1797   *
1798   * @return  A negative integer if the first RDN should come before the second
1799   *          RDN in a sorted list, a positive integer if the first RDN should
1800   *          come after the second RDN in a sorted list, or zero if the two RDN
1801   *          values can be considered equal.
1802   */
1803  public int compare(final RDN rdn1, final RDN rdn2)
1804  {
1805    ensureNotNull(rdn1, rdn2);
1806
1807    return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString()));
1808  }
1809
1810
1811
1812  /**
1813   * Compares the RDN values with the provided string representations to
1814   * determine their relative order in a sorted list.
1815   *
1816   * @param  s1  The string representation of the first RDN to be compared.  It
1817   *             must not be {@code null}.
1818   * @param  s2  The string representation of the second RDN to be compared.  It
1819   *             must not be {@code null}.
1820   *
1821   * @return  A negative integer if the first RDN should come before the second
1822   *          RDN in a sorted list, a positive integer if the first RDN should
1823   *          come after the second RDN in a sorted list, or zero if the two RDN
1824   *          values can be considered equal.
1825   *
1826   * @throws  LDAPException  If either of the provided strings cannot be parsed
1827   *                         as an RDN.
1828   */
1829  public static int compare(final String s1, final String s2)
1830         throws LDAPException
1831  {
1832    return compare(s1, s2, null);
1833  }
1834
1835
1836
1837  /**
1838   * Compares the RDN values with the provided string representations to
1839   * determine their relative order in a sorted list.
1840   *
1841   * @param  s1      The string representation of the first RDN to be compared.
1842   *                 It must not be {@code null}.
1843   * @param  s2      The string representation of the second RDN to be compared.
1844   *                 It must not be {@code null}.
1845   * @param  schema  The schema to use to generate the normalized string
1846   *                 representations of the RDNs.  It may be {@code null} if no
1847   *                 schema is available.
1848   *
1849   * @return  A negative integer if the first RDN should come before the second
1850   *          RDN in a sorted list, a positive integer if the first RDN should
1851   *          come after the second RDN in a sorted list, or zero if the two RDN
1852   *          values can be considered equal.
1853   *
1854   * @throws  LDAPException  If either of the provided strings cannot be parsed
1855   *                         as an RDN.
1856   */
1857  public static int compare(final String s1, final String s2,
1858                            final Schema schema)
1859         throws LDAPException
1860  {
1861    return new RDN(s1, schema).compareTo(new RDN(s2, schema));
1862  }
1863}