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