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