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     */
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        // 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);
616    hexLoop:
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;
791    valueLoop:
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    }