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