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