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 an array of the attributes that comprise this RDN.
1156       *
1157       * @return  An array of the attributes that comprise this RDN.
1158       */
1159      public Attribute[] getAttributes()
1160      {
1161        final Attribute[] attrs = new Attribute[attributeNames.length];
1162        for (int i=0; i < attrs.length; i++)
1163        {
1164          attrs[i] = new Attribute(attributeNames[i], schema,
1165               new ASN1OctetString[] {  attributeValues[i] });
1166        }
1167    
1168        return attrs;
1169      }
1170    
1171    
1172    
1173      /**
1174       * Retrieves the set of attribute names for this RDN.
1175       *
1176       * @return  The set of attribute names for this RDN.
1177       */
1178      public String[] getAttributeNames()
1179      {
1180        return attributeNames;
1181      }
1182    
1183    
1184    
1185      /**
1186       * Retrieves the set of attribute values for this RDN.
1187       *
1188       * @return  The set of attribute values for this RDN.
1189       */
1190      public String[] getAttributeValues()
1191      {
1192        final String[] stringValues = new String[attributeValues.length];
1193        for (int i=0; i < stringValues.length; i++)
1194        {
1195          stringValues[i] = attributeValues[i].stringValue();
1196        }
1197    
1198        return stringValues;
1199      }
1200    
1201    
1202    
1203      /**
1204       * Retrieves the set of attribute values for this RDN.
1205       *
1206       * @return  The set of attribute values for this RDN.
1207       */
1208      public byte[][] getByteArrayAttributeValues()
1209      {
1210        final byte[][] byteValues = new byte[attributeValues.length][];
1211        for (int i=0; i < byteValues.length; i++)
1212        {
1213          byteValues[i] = attributeValues[i].getValue();
1214        }
1215    
1216        return byteValues;
1217      }
1218    
1219    
1220    
1221      /**
1222       * Retrieves the schema that will be used for this RDN, if any.
1223       *
1224       * @return  The schema that will be used for this RDN, or {@code null} if none
1225       *          has been provided.
1226       */
1227      Schema getSchema()
1228      {
1229        return schema;
1230      }
1231    
1232    
1233    
1234      /**
1235       * Indicates whether this RDN contains the specified attribute.
1236       *
1237       * @param  attributeName  The name of the attribute for which to make the
1238       *                        determination.
1239       *
1240       * @return  {@code true} if RDN contains the specified attribute, or
1241       *          {@code false} if not.
1242       */
1243      public boolean hasAttribute(final String attributeName)
1244      {
1245        for (final String name : attributeNames)
1246        {
1247          if (name.equalsIgnoreCase(attributeName))
1248          {
1249            return true;
1250          }
1251        }
1252    
1253        return false;
1254      }
1255    
1256    
1257    
1258      /**
1259       * Indicates whether this RDN contains the specified attribute value.
1260       *
1261       * @param  attributeName   The name of the attribute for which to make the
1262       *                         determination.
1263       * @param  attributeValue  The attribute value for which to make the
1264       *                         determination.
1265       *
1266       * @return  {@code true} if RDN contains the specified attribute, or
1267       *          {@code false} if not.
1268       */
1269      public boolean hasAttributeValue(final String attributeName,
1270                                       final String attributeValue)
1271      {
1272        for (int i=0; i < attributeNames.length; i++)
1273        {
1274          if (attributeNames[i].equalsIgnoreCase(attributeName))
1275          {
1276            final Attribute a =
1277                 new Attribute(attributeName, schema, attributeValue);
1278            final Attribute b = new Attribute(attributeName, schema,
1279                 attributeValues[i].stringValue());
1280    
1281            if (a.equals(b))
1282            {
1283              return true;
1284            }
1285          }
1286        }
1287    
1288        return false;
1289      }
1290    
1291    
1292    
1293      /**
1294       * Indicates whether this RDN contains the specified attribute value.
1295       *
1296       * @param  attributeName   The name of the attribute for which to make the
1297       *                         determination.
1298       * @param  attributeValue  The attribute value for which to make the
1299       *                         determination.
1300       *
1301       * @return  {@code true} if RDN contains the specified attribute, or
1302       *          {@code false} if not.
1303       */
1304      public boolean hasAttributeValue(final String attributeName,
1305                                       final byte[] attributeValue)
1306      {
1307        for (int i=0; i < attributeNames.length; i++)
1308        {
1309          if (attributeNames[i].equalsIgnoreCase(attributeName))
1310          {
1311            final Attribute a =
1312                 new Attribute(attributeName, schema, attributeValue);
1313            final Attribute b = new Attribute(attributeName, schema,
1314                 attributeValues[i].getValue());
1315    
1316            if (a.equals(b))
1317            {
1318              return true;
1319            }
1320          }
1321        }
1322    
1323        return false;
1324      }
1325    
1326    
1327    
1328      /**
1329       * Retrieves a string representation of this RDN.
1330       *
1331       * @return  A string representation of this RDN.
1332       */
1333      @Override()
1334      public String toString()
1335      {
1336        if (rdnString == null)
1337        {
1338          final StringBuilder buffer = new StringBuilder();
1339          toString(buffer, false);
1340          rdnString = buffer.toString();
1341        }
1342    
1343        return rdnString;
1344      }
1345    
1346    
1347    
1348      /**
1349       * Retrieves a string representation of this RDN with minimal encoding for
1350       * special characters.  Only those characters specified in RFC 4514 section
1351       * 2.4 will be escaped.  No escaping will be used for non-ASCII characters or
1352       * non-printable ASCII characters.
1353       *
1354       * @return  A string representation of this RDN with minimal encoding for
1355       *          special characters.
1356       */
1357      public String toMinimallyEncodedString()
1358      {
1359        final StringBuilder buffer = new StringBuilder();
1360        toString(buffer, true);
1361        return buffer.toString();
1362      }
1363    
1364    
1365    
1366      /**
1367       * Appends a string representation of this RDN to the provided buffer.
1368       *
1369       * @param  buffer  The buffer to which the string representation is to be
1370       *                 appended.
1371       */
1372      public void toString(final StringBuilder buffer)
1373      {
1374        toString(buffer, false);
1375      }
1376    
1377    
1378    
1379      /**
1380       * Appends a string representation of this RDN to the provided buffer.
1381       *
1382       * @param  buffer            The buffer to which the string representation is
1383       *                           to be appended.
1384       * @param  minimizeEncoding  Indicates whether to restrict the encoding of
1385       *                           special characters to the bare minimum required
1386       *                           by LDAP (as per RFC 4514 section 2.4).  If this
1387       *                           is {@code true}, then only leading and trailing
1388       *                           spaces, double quotes, plus signs, commas,
1389       *                           semicolons, greater-than, less-than, and
1390       *                           backslash characters will be encoded.
1391       */
1392      public void toString(final StringBuilder buffer,
1393                           final boolean minimizeEncoding)
1394      {
1395        if ((rdnString != null) && (! minimizeEncoding))
1396        {
1397          buffer.append(rdnString);
1398          return;
1399        }
1400    
1401        for (int i=0; i < attributeNames.length; i++)
1402        {
1403          if (i > 0)
1404          {
1405            buffer.append('+');
1406          }
1407    
1408          buffer.append(attributeNames[i]);
1409          buffer.append('=');
1410    
1411          // Iterate through the value character-by-character and do any escaping
1412          // that may be necessary.
1413          final String valueString = attributeValues[i].stringValue();
1414          final int length = valueString.length();
1415          for (int j=0; j < length; j++)
1416          {
1417            final char c = valueString.charAt(j);
1418            switch (c)
1419            {
1420              case '\\':
1421              case '#':
1422              case '=':
1423              case '"':
1424              case '+':
1425              case ',':
1426              case ';':
1427              case '<':
1428              case '>':
1429                buffer.append('\\');
1430                buffer.append(c);
1431                break;
1432    
1433              case ' ':
1434                // Escape this space only if it's the first character, the last
1435                // character, or if the next character is also a space.
1436                if ((j == 0) || ((j+1) == length) ||
1437                    (((j+1) < length) && (valueString.charAt(j+1) == ' ')))
1438                {
1439                  buffer.append("\\ ");
1440                }
1441                else
1442                {
1443                  buffer.append(' ');
1444                }
1445                break;
1446    
1447              case '\u0000':
1448                buffer.append("\\00");
1449                break;
1450    
1451              default:
1452                // If it's not a printable ASCII character, then hex-encode it
1453                // unless we're using minimized encoding.
1454                if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
1455                {
1456                  hexEncode(c, buffer);
1457                }
1458                else
1459                {
1460                  buffer.append(c);
1461                }
1462                break;
1463            }
1464          }
1465        }
1466      }
1467    
1468    
1469    
1470      /**
1471       * Retrieves a normalized string representation of this RDN.
1472       *
1473       * @return  A normalized string representation of this RDN.
1474       */
1475      public String toNormalizedString()
1476      {
1477        if (normalizedString == null)
1478        {
1479          final StringBuilder buffer = new StringBuilder();
1480          toNormalizedString(buffer);
1481          normalizedString = buffer.toString();
1482        }
1483    
1484        return normalizedString;
1485      }
1486    
1487    
1488    
1489      /**
1490       * Appends a normalized string representation of this RDN to the provided
1491       * buffer.
1492       *
1493       * @param  buffer  The buffer to which the normalized string representation is
1494       *                 to be appended.
1495       */
1496      public void toNormalizedString(final StringBuilder buffer)
1497      {
1498        if (attributeNames.length == 1)
1499        {
1500          // It's a single-valued RDN, so there is no need to sort anything.
1501          final String name = normalizeAttrName(attributeNames[0]);
1502          buffer.append(name);
1503          buffer.append('=');
1504          buffer.append(normalizeValue(name, attributeValues[0]));
1505        }
1506        else
1507        {
1508          // It's a multivalued RDN, so we need to sort the components.
1509          final TreeMap<String,ASN1OctetString> valueMap =
1510               new TreeMap<String,ASN1OctetString>();
1511          for (int i=0; i < attributeNames.length; i++)
1512          {
1513            final String name = normalizeAttrName(attributeNames[i]);
1514            valueMap.put(name, attributeValues[i]);
1515          }
1516    
1517          int i=0;
1518          for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet())
1519          {
1520            if (i++ > 0)
1521            {
1522              buffer.append('+');
1523            }
1524    
1525            buffer.append(entry.getKey());
1526            buffer.append('=');
1527            buffer.append(normalizeValue(entry.getKey(), entry.getValue()));
1528          }
1529        }
1530      }
1531    
1532    
1533    
1534      /**
1535       * Obtains a normalized representation of the provided attribute name.
1536       *
1537       * @param  name  The name of the attribute for which to create the normalized
1538       *               representation.
1539       *
1540       * @return  A normalized representation of the provided attribute name.
1541       */
1542      private String normalizeAttrName(final String name)
1543      {
1544        String n = name;
1545        if (schema != null)
1546        {
1547          final AttributeTypeDefinition at = schema.getAttributeType(name);
1548          if (at != null)
1549          {
1550            n = at.getNameOrOID();
1551          }
1552        }
1553        return toLowerCase(n);
1554      }
1555    
1556    
1557    
1558      /**
1559       * Retrieves a normalized string representation of the RDN with the provided
1560       * string representation.
1561       *
1562       * @param  s  The string representation of the RDN to normalize.  It must not
1563       *            be {@code null}.
1564       *
1565       * @return  The normalized string representation of the RDN with the provided
1566       *          string representation.
1567       *
1568       * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1569       */
1570      public static String normalize(final String s)
1571             throws LDAPException
1572      {
1573        return normalize(s, null);
1574      }
1575    
1576    
1577    
1578      /**
1579       * Retrieves a normalized string representation of the RDN with the provided
1580       * string representation.
1581       *
1582       * @param  s       The string representation of the RDN to normalize.  It must
1583       *                 not be {@code null}.
1584       * @param  schema  The schema to use to generate the normalized string
1585       *                 representation of the RDN.  It may be {@code null} if no
1586       *                 schema is available.
1587       *
1588       * @return  The normalized string representation of the RDN with the provided
1589       *          string representation.
1590       *
1591       * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1592       */
1593      public static String normalize(final String s, final Schema schema)
1594             throws LDAPException
1595      {
1596        return new RDN(s, schema).toNormalizedString();
1597      }
1598    
1599    
1600    
1601      /**
1602       * Normalizes the provided attribute value for use in an RDN.
1603       *
1604       * @param  attributeName  The name of the attribute with which the value is
1605       *                        associated.
1606       * @param  value           The value to be normalized.
1607       *
1608       * @return  A string builder containing a normalized representation of the
1609       *          value in a suitable form for inclusion in an RDN.
1610       */
1611      private StringBuilder normalizeValue(final String attributeName,
1612                                           final ASN1OctetString value)
1613      {
1614        final MatchingRule matchingRule =
1615             MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1616    
1617        ASN1OctetString rawNormValue;
1618        try
1619        {
1620          rawNormValue = matchingRule.normalize(value);
1621        }
1622        catch (final Exception e)
1623        {
1624          debugException(e);
1625          rawNormValue =
1626               new ASN1OctetString(toLowerCase(value.stringValue()));
1627        }
1628    
1629        final String valueString = rawNormValue.stringValue();
1630        final int length = valueString.length();
1631        final StringBuilder buffer = new StringBuilder(length);
1632    
1633        for (int i=0; i < length; i++)
1634        {
1635          final char c = valueString.charAt(i);
1636    
1637          switch (c)
1638          {
1639            case '\\':
1640            case '#':
1641            case '=':
1642            case '"':
1643            case '+':
1644            case ',':
1645            case ';':
1646            case '<':
1647            case '>':
1648              buffer.append('\\');
1649              buffer.append(c);
1650              break;
1651    
1652            case ' ':
1653              // Escape this space only if it's the first character, the last
1654              // character, or if the next character is also a space.
1655              if ((i == 0) || ((i+1) == length) ||
1656                  (((i+1) < length) && (valueString.charAt(i+1) == ' ')))
1657              {
1658                buffer.append("\\ ");
1659              }
1660              else
1661              {
1662                buffer.append(' ');
1663              }
1664              break;
1665    
1666            default:
1667              // If it's not a printable ASCII character, then hex-encode it.
1668              if ((c < ' ') || (c > '~'))
1669              {
1670                hexEncode(c, buffer);
1671              }
1672              else
1673              {
1674                buffer.append(c);
1675              }
1676              break;
1677          }
1678        }
1679    
1680        return buffer;
1681      }
1682    
1683    
1684    
1685      /**
1686       * Retrieves a hash code for this RDN.
1687       *
1688       * @return  The hash code for this RDN.
1689       */
1690      @Override()
1691      public int hashCode()
1692      {
1693        return toNormalizedString().hashCode();
1694      }
1695    
1696    
1697    
1698      /**
1699       * Indicates whether this RDN is equal to the provided object.  The given
1700       * object will only be considered equal to this RDN if it is also an RDN with
1701       * the same set of names and values.
1702       *
1703       * @param  o  The object for which to make the determination.
1704       *
1705       * @return  {@code true} if the provided object can be considered equal to
1706       *          this RDN, or {@code false} if not.
1707       */
1708      @Override()
1709      public boolean equals(final Object o)
1710      {
1711        if (o == null)
1712        {
1713          return false;
1714        }
1715    
1716        if (o == this)
1717        {
1718          return true;
1719        }
1720    
1721        if (! (o instanceof RDN))
1722        {
1723          return false;
1724        }
1725    
1726        final RDN rdn = (RDN) o;
1727        return (toNormalizedString().equals(rdn.toNormalizedString()));
1728      }
1729    
1730    
1731    
1732      /**
1733       * Indicates whether the RDN with the provided string representation is equal
1734       * to this RDN.
1735       *
1736       * @param  s  The string representation of the DN to compare with this RDN.
1737       *
1738       * @return  {@code true} if the DN with the provided string representation is
1739       *          equal to this RDN, or {@code false} if not.
1740       *
1741       * @throws  LDAPException  If the provided string cannot be parsed as an RDN.
1742       */
1743      public boolean equals(final String s)
1744             throws LDAPException
1745      {
1746        if (s == null)
1747        {
1748          return false;
1749        }
1750    
1751        return equals(new RDN(s, schema));
1752      }
1753    
1754    
1755    
1756      /**
1757       * Indicates whether the two provided strings represent the same RDN.
1758       *
1759       * @param  s1  The string representation of the first RDN for which to make
1760       *             the determination.  It must not be {@code null}.
1761       * @param  s2  The string representation of the second RDN for which to make
1762       *             the determination.  It must not be {@code null}.
1763       *
1764       * @return  {@code true} if the provided strings represent the same RDN, or
1765       *          {@code false} if not.
1766       *
1767       * @throws  LDAPException  If either of the provided strings cannot be parsed
1768       *                         as an RDN.
1769       */
1770      public static boolean equals(final String s1, final String s2)
1771             throws LDAPException
1772      {
1773        return new RDN(s1).equals(new RDN(s2));
1774      }
1775    
1776    
1777    
1778      /**
1779       * Compares the provided RDN to this RDN to determine their relative order in
1780       * a sorted list.
1781       *
1782       * @param  rdn  The RDN to compare against this RDN.  It must not be
1783       *              {@code null}.
1784       *
1785       * @return  A negative integer if this RDN should come before the provided RDN
1786       *          in a sorted list, a positive integer if this RDN should come after
1787       *          the provided RDN in a sorted list, or zero if the provided RDN
1788       *          can be considered equal to this RDN.
1789       */
1790      public int compareTo(final RDN rdn)
1791      {
1792        return compare(this, rdn);
1793      }
1794    
1795    
1796    
1797      /**
1798       * Compares the provided RDN values to determine their relative order in a
1799       * sorted list.
1800       *
1801       * @param  rdn1  The first RDN to be compared.  It must not be {@code null}.
1802       * @param  rdn2  The second RDN to be compared.  It must not be {@code null}.
1803       *
1804       * @return  A negative integer if the first RDN should come before the second
1805       *          RDN in a sorted list, a positive integer if the first RDN should
1806       *          come after the second RDN in a sorted list, or zero if the two RDN
1807       *          values can be considered equal.
1808       */
1809      public int compare(final RDN rdn1, final RDN rdn2)
1810      {
1811        ensureNotNull(rdn1, rdn2);
1812    
1813        return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString()));
1814      }
1815    
1816    
1817    
1818      /**
1819       * Compares the RDN values with the provided string representations to
1820       * determine their relative order in a sorted list.
1821       *
1822       * @param  s1  The string representation of the first RDN to be compared.  It
1823       *             must not be {@code null}.
1824       * @param  s2  The string representation of the second RDN to be compared.  It
1825       *             must not be {@code null}.
1826       *
1827       * @return  A negative integer if the first RDN should come before the second
1828       *          RDN in a sorted list, a positive integer if the first RDN should
1829       *          come after the second RDN in a sorted list, or zero if the two RDN
1830       *          values can be considered equal.
1831       *
1832       * @throws  LDAPException  If either of the provided strings cannot be parsed
1833       *                         as an RDN.
1834       */
1835      public static int compare(final String s1, final String s2)
1836             throws LDAPException
1837      {
1838        return compare(s1, s2, null);
1839      }
1840    
1841    
1842    
1843      /**
1844       * Compares the RDN values with the provided string representations to
1845       * determine their relative order in a sorted list.
1846       *
1847       * @param  s1      The string representation of the first RDN to be compared.
1848       *                 It must not be {@code null}.
1849       * @param  s2      The string representation of the second RDN to be compared.
1850       *                 It must not be {@code null}.
1851       * @param  schema  The schema to use to generate the normalized string
1852       *                 representations of the RDNs.  It may be {@code null} if no
1853       *                 schema is available.
1854       *
1855       * @return  A negative integer if the first RDN should come before the second
1856       *          RDN in a sorted list, a positive integer if the first RDN should
1857       *          come after the second RDN in a sorted list, or zero if the two RDN
1858       *          values can be considered equal.
1859       *
1860       * @throws  LDAPException  If either of the provided strings cannot be parsed
1861       *                         as an RDN.
1862       */
1863      public static int compare(final String s1, final String s2,
1864                                final Schema schema)
1865             throws LDAPException
1866      {
1867        return new RDN(s1, schema).compareTo(new RDN(s2, schema));
1868      }
1869    }