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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.io.Serializable;
026import java.nio.ByteBuffer;
027import java.util.ArrayList;
028import java.util.Comparator;
029import java.util.Map;
030import java.util.TreeMap;
031
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.ldap.matchingrules.MatchingRule;
034import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
035import com.unboundid.ldap.sdk.schema.Schema;
036import com.unboundid.util.NotMutable;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039
040import static com.unboundid.ldap.sdk.LDAPMessages.*;
041import static com.unboundid.util.Debug.*;
042import static com.unboundid.util.StaticUtils.*;
043import 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)
058public 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);
616hexLoop:
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;
792valueLoop:
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}