001 /*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 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.util.ArrayList;
027 import java.util.Comparator;
028 import java.util.List;
029
030 import com.unboundid.asn1.ASN1OctetString;
031 import com.unboundid.ldap.sdk.schema.Schema;
032 import com.unboundid.util.NotMutable;
033 import com.unboundid.util.ThreadSafety;
034 import com.unboundid.util.ThreadSafetyLevel;
035
036 import static com.unboundid.ldap.sdk.LDAPMessages.*;
037 import static com.unboundid.util.Validator.*;
038
039
040
041 /**
042 * This class provides a data structure for holding information about an LDAP
043 * distinguished name (DN). A DN consists of a comma-delimited list of zero or
044 * more RDN components. See
045 * <A HREF="http://www.ietf.org/rfc/rfc4514.txt">RFC 4514</A> for more
046 * information about representing DNs and RDNs as strings.
047 * <BR><BR>
048 * Examples of valid DNs (excluding the quotation marks, which are provided for
049 * clarity) include:
050 * <UL>
051 * <LI>"" -- This is the zero-length DN (also called the null DN), which may
052 * be used to refer to the directory server root DSE.</LI>
053 * <LI>"{@code o=example.com}". This is a DN with a single, single-valued
054 * RDN. The RDN attribute is "{@code o}" and the RDN value is
055 * "{@code example.com}".</LI>
056 * <LI>"{@code givenName=John+sn=Doe,ou=People,dc=example,dc=com}". This is a
057 * DN with four different RDNs ("{@code givenName=John+sn=Doe"},
058 * "{@code ou=People}", "{@code dc=example}", and "{@code dc=com}". The
059 * first RDN is multivalued with attribute-value pairs of
060 * "{@code givenName=John}" and "{@code sn=Doe}".</LI>
061 * </UL>
062 * Note that there is some inherent ambiguity in the string representations of
063 * distinguished names. In particular, there may be differences in spacing
064 * (particularly around commas and equal signs, as well as plus signs in
065 * multivalued RDNs), and also differences in capitalization in attribute names
066 * and/or values. For example, the strings
067 * "{@code uid=john.doe,ou=people,dc=example,dc=com}" and
068 * "{@code UID = JOHN.DOE , OU = PEOPLE , DC = EXAMPLE , DC = COM}" actually
069 * refer to the same distinguished name. To deal with these differences, the
070 * normalized representation may be used. The normalized representation is a
071 * standardized way of representing a DN, and it is obtained by eliminating any
072 * unnecessary spaces and converting all non-case-sensitive characters to
073 * lowercase. The normalized representation of a DN may be obtained using the
074 * {@link DN#toNormalizedString} method, and two DNs may be compared to
075 * determine if they are equal using the standard {@link DN#equals} method.
076 * <BR><BR>
077 * Distinguished names are hierarchical. The rightmost RDN refers to the root
078 * of the directory information tree (DIT), and each successive RDN to the left
079 * indicates the addition of another level of hierarchy. For example, in the
080 * DN "{@code uid=john.doe,ou=People,o=example.com}", the entry
081 * "{@code o=example.com}" is at the root of the DIT, the entry
082 * "{@code ou=People,o=example.com}" is an immediate descendant of the
083 * "{@code o=example.com}" entry, and the
084 * "{@code uid=john.doe,ou=People,o=example.com}" entry is an immediate
085 * descendant of the "{@code ou=People,o=example.com}" entry. Similarly, the
086 * entry "{@code uid=jane.doe,ou=People,o=example.com}" would be considered a
087 * peer of the "{@code uid=john.doe,ou=People,o=example.com}" entry because they
088 * have the same parent.
089 * <BR><BR>
090 * Note that in some cases, the root of the DIT may actually contain a DN with
091 * multiple RDNs. For example, in the DN
092 * "{@code uid=john.doe,ou=People,dc=example,dc=com}", the directory server may
093 * or may not actually have a "{@code dc=com}" entry. In many such cases, the
094 * base entry may actually be just "{@code dc=example,dc=com}". The DNs of the
095 * entries that are at the base of the directory information tree are called
096 * "naming contexts" or "suffixes" and they are generally available in the
097 * {@code namingContexts} attribute of the root DSE. See the {@link RootDSE}
098 * class for more information about interacting with the server root DSE.
099 * <BR><BR>
100 * This class provides methods for making determinations based on the
101 * hierarchical relationships of DNs. For example, the
102 * {@link DN#isAncestorOf} and {@link DN#isDescendantOf} methods may be used to
103 * determine whether two DNs have a hierarchical relationship. In addition,
104 * this class implements the {@link Comparable} and {@link Comparator}
105 * interfaces so that it may be used to easily sort DNs (ancestors will always
106 * be sorted before descendants, and peers will always be sorted
107 * lexicographically based on their normalized representations).
108 */
109 @NotMutable()
110 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
111 public final class DN
112 implements Comparable<DN>, Comparator<DN>, Serializable
113 {
114 /**
115 * The RDN array that will be used for the null DN.
116 */
117 private static final RDN[] NO_RDNS = new RDN[0];
118
119
120
121 /**
122 * A pre-allocated DN object equivalent to the null DN.
123 */
124 public static final DN NULL_DN = new DN();
125
126
127
128 /**
129 * The serial version UID for this serializable class.
130 */
131 private static final long serialVersionUID = -5272968942085729346L;
132
133
134
135 // The set of RDN components that make up this DN.
136 private final RDN[] rdns;
137
138 // The schema to use to generate the normalized string representation of this
139 // DN, if any.
140 private final Schema schema;
141
142 // The string representation of this DN.
143 private final String dnString;
144
145 // The normalized string representation of this DN.
146 private volatile String normalizedString;
147
148
149
150 /**
151 * Creates a new DN with the provided set of RDNs.
152 *
153 * @param rdns The RDN components for this DN. It must not be {@code null}.
154 */
155 public DN(final RDN... rdns)
156 {
157 ensureNotNull(rdns);
158
159 this.rdns = rdns;
160 if (rdns.length == 0)
161 {
162 dnString = "";
163 normalizedString = "";
164 schema = null;
165 }
166 else
167 {
168 Schema s = null;
169 final StringBuilder buffer = new StringBuilder();
170 for (final RDN rdn : rdns)
171 {
172 if (buffer.length() > 0)
173 {
174 buffer.append(',');
175 }
176 rdn.toString(buffer, false);
177
178 if (s == null)
179 {
180 s = rdn.getSchema();
181 }
182 }
183
184 dnString = buffer.toString();
185 schema = s;
186 }
187 }
188
189
190
191 /**
192 * Creates a new DN with the provided set of RDNs.
193 *
194 * @param rdns The RDN components for this DN. It must not be {@code null}.
195 */
196 public DN(final List<RDN> rdns)
197 {
198 ensureNotNull(rdns);
199
200 if (rdns.isEmpty())
201 {
202 this.rdns = NO_RDNS;
203 dnString = "";
204 normalizedString = "";
205 schema = null;
206 }
207 else
208 {
209 this.rdns = rdns.toArray(new RDN[rdns.size()]);
210
211 Schema s = null;
212 final StringBuilder buffer = new StringBuilder();
213 for (final RDN rdn : this.rdns)
214 {
215 if (buffer.length() > 0)
216 {
217 buffer.append(',');
218 }
219 rdn.toString(buffer, false);
220
221 if (s == null)
222 {
223 s = rdn.getSchema();
224 }
225 }
226
227 dnString = buffer.toString();
228 schema = s;
229 }
230 }
231
232
233
234 /**
235 * Creates a new DN below the provided parent DN with the given RDN.
236 *
237 * @param rdn The RDN for the new DN. It must not be {@code null}.
238 * @param parentDN The parent DN for the new DN to create. It must not be
239 * {@code null}.
240 */
241 public DN(final RDN rdn, final DN parentDN)
242 {
243 ensureNotNull(rdn, parentDN);
244
245 rdns = new RDN[parentDN.rdns.length + 1];
246 rdns[0] = rdn;
247 System.arraycopy(parentDN.rdns, 0, rdns, 1, parentDN.rdns.length);
248
249 Schema s = null;
250 final StringBuilder buffer = new StringBuilder();
251 for (final RDN r : rdns)
252 {
253 if (buffer.length() > 0)
254 {
255 buffer.append(',');
256 }
257 r.toString(buffer, false);
258
259 if (s == null)
260 {
261 s = r.getSchema();
262 }
263 }
264
265 dnString = buffer.toString();
266 schema = s;
267 }
268
269
270
271 /**
272 * Creates a new DN from the provided string representation.
273 *
274 * @param dnString The string representation to use to create this DN. It
275 * must not be {@code null}.
276 *
277 * @throws LDAPException If the provided string cannot be parsed as a valid
278 * DN.
279 */
280 public DN(final String dnString)
281 throws LDAPException
282 {
283 this(dnString, null);
284 }
285
286
287
288 /**
289 * Creates a new DN from the provided string representation.
290 *
291 * @param dnString The string representation to use to create this DN. It
292 * must not be {@code null}.
293 * @param schema The schema to use to generate the normalized string
294 * representation of this DN. It may be {@code null} if no
295 * schema is available.
296 *
297 * @throws LDAPException If the provided string cannot be parsed as a valid
298 * DN.
299 */
300 public DN(final String dnString, final Schema schema)
301 throws LDAPException
302 {
303 ensureNotNull(dnString);
304
305 this.dnString = dnString;
306 this.schema = schema;
307
308 final ArrayList<RDN> rdnList = new ArrayList<RDN>(5);
309
310 final int length = dnString.length();
311 if (length == 0)
312 {
313 rdns = NO_RDNS;
314 normalizedString = "";
315 return;
316 }
317
318 int pos = 0;
319 boolean expectMore = false;
320 rdnLoop:
321 while (pos < length)
322 {
323 // Skip over any spaces before the attribute name.
324 while ((pos < length) && (dnString.charAt(pos) == ' '))
325 {
326 pos++;
327 }
328
329 if (pos >= length)
330 {
331 // This is only acceptable if we haven't read anything yet.
332 if (rdnList.isEmpty())
333 {
334 break;
335 }
336 else
337 {
338 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
339 ERR_DN_ENDS_WITH_COMMA.get());
340 }
341 }
342
343 // Read the attribute name, until we find a space or equal sign.
344 int rdnEndPos;
345 int rdnStartPos = pos;
346 int attrStartPos = pos;
347 while (pos < length)
348 {
349 final char c = dnString.charAt(pos);
350 if ((c == ' ') || (c == '='))
351 {
352 break;
353 }
354 else if ((c == ',') || (c == ';'))
355 {
356 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
357 ERR_DN_UNEXPECTED_COMMA.get(pos));
358 }
359
360 pos++;
361 }
362
363 String attrName = dnString.substring(attrStartPos, pos);
364 if (attrName.length() == 0)
365 {
366 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
367 ERR_DN_NO_ATTR_IN_RDN.get());
368 }
369
370
371 // Skip over any spaces before the equal sign.
372 while ((pos < length) && (dnString.charAt(pos) == ' '))
373 {
374 pos++;
375 }
376
377 if ((pos >= length) || (dnString.charAt(pos) != '='))
378 {
379 // We didn't find an equal sign.
380 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
381 ERR_DN_NO_EQUAL_SIGN.get(attrName));
382 }
383
384 // Skip over the equal sign, and then any spaces leading up to the
385 // attribute value.
386 pos++;
387 while ((pos < length) && (dnString.charAt(pos) == ' '))
388 {
389 pos++;
390 }
391
392
393 // If we're at the end of the string, then it's not a valid DN.
394 if (pos >= length)
395 {
396 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
397 ERR_DN_NO_VALUE_FOR_ATTR.get(attrName));
398 }
399
400
401 // Read the value for this RDN component.
402 ASN1OctetString value;
403 if (dnString.charAt(pos) == '#')
404 {
405 // It is a hex-encoded value, so we'll read until we find the end of the
406 // string or the first non-hex character, which must be a space, a
407 // comma, or a plus sign.
408 final byte[] valueArray = RDN.readHexString(dnString, ++pos);
409 value = new ASN1OctetString(valueArray);
410 pos += (valueArray.length * 2);
411 rdnEndPos = pos;
412 }
413 else
414 {
415 // It is a string value, which potentially includes escaped characters.
416 final StringBuilder buffer = new StringBuilder();
417 pos = RDN.readValueString(dnString, pos, buffer);
418 value = new ASN1OctetString(buffer.toString());
419 rdnEndPos = pos;
420 }
421
422
423 // Skip over any spaces until we find a comma, a plus sign, or the end of
424 // the value.
425 while ((pos < length) && (dnString.charAt(pos) == ' '))
426 {
427 pos++;
428 }
429
430 if (pos >= length)
431 {
432 // It's a single-valued RDN, and we're at the end of the DN.
433 rdnList.add(new RDN(attrName, value, schema,
434 getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
435 expectMore = false;
436 break;
437 }
438
439 switch (dnString.charAt(pos))
440 {
441 case '+':
442 // It is a multivalued RDN, so we're not done reading either the DN
443 // or the RDN.
444 pos++;
445 break;
446
447 case ',':
448 case ';':
449 // We hit the end of the single-valued RDN, but there's still more of
450 // the DN to be read.
451 rdnList.add(new RDN(attrName, value, schema,
452 getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
453 pos++;
454 expectMore = true;
455 continue rdnLoop;
456
457 default:
458 // It's an illegal character. This should never happen.
459 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
460 ERR_DN_UNEXPECTED_CHAR.get(
461 dnString.charAt(pos), pos));
462 }
463
464 if (pos >= length)
465 {
466 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
467 ERR_DN_ENDS_WITH_PLUS.get());
468 }
469
470
471 // If we've gotten here, then we're dealing with a multivalued RDN.
472 // Create lists to hold the names and values, and then loop until we hit
473 // the end of the RDN.
474 final ArrayList<String> nameList = new ArrayList<String>(5);
475 final ArrayList<ASN1OctetString> valueList =
476 new ArrayList<ASN1OctetString>(5);
477 nameList.add(attrName);
478 valueList.add(value);
479
480 while (pos < length)
481 {
482 // Skip over any spaces before the attribute name.
483 while ((pos < length) && (dnString.charAt(pos) == ' '))
484 {
485 pos++;
486 }
487
488 if (pos >= length)
489 {
490 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
491 ERR_DN_ENDS_WITH_PLUS.get());
492 }
493
494 // Read the attribute name, until we find a space or equal sign.
495 attrStartPos = pos;
496 while (pos < length)
497 {
498 final char c = dnString.charAt(pos);
499 if ((c == ' ') || (c == '='))
500 {
501 break;
502 }
503 else if ((c == ',') || (c == ';'))
504 {
505 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
506 ERR_DN_UNEXPECTED_COMMA.get(pos));
507 }
508
509 pos++;
510 }
511
512 attrName = dnString.substring(attrStartPos, pos);
513 if (attrName.length() == 0)
514 {
515 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
516 ERR_DN_NO_ATTR_IN_RDN.get());
517 }
518
519
520 // Skip over any spaces before the equal sign.
521 while ((pos < length) && (dnString.charAt(pos) == ' '))
522 {
523 pos++;
524 }
525
526 if ((pos >= length) || (dnString.charAt(pos) != '='))
527 {
528 // We didn't find an equal sign.
529 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
530 ERR_DN_NO_EQUAL_SIGN.get(attrName));
531 }
532
533 // Skip over the equal sign, and then any spaces leading up to the
534 // attribute value.
535 pos++;
536 while ((pos < length) && (dnString.charAt(pos) == ' '))
537 {
538 pos++;
539 }
540
541
542 // If we're at the end of the string, then it's not a valid DN.
543 if (pos >= length)
544 {
545 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
546 ERR_DN_NO_VALUE_FOR_ATTR.get(attrName));
547 }
548
549
550 // Read the value for this RDN component.
551 if (dnString.charAt(pos) == '#')
552 {
553 // It is a hex-encoded value, so we'll read until we find the end of
554 // the string or the first non-hex character, which must be a space, a
555 // comma, or a plus sign.
556 final byte[] valueArray = RDN.readHexString(dnString, ++pos);
557 value = new ASN1OctetString(valueArray);
558 pos += (valueArray.length * 2);
559 rdnEndPos = pos;
560 }
561 else
562 {
563 // It is a string value, which potentially includes escaped
564 // characters.
565 final StringBuilder buffer = new StringBuilder();
566 pos = RDN.readValueString(dnString, pos, buffer);
567 value = new ASN1OctetString(buffer.toString());
568 rdnEndPos = pos;
569 }
570
571
572 // Skip over any spaces until we find a comma, a plus sign, or the end
573 // of the value.
574 while ((pos < length) && (dnString.charAt(pos) == ' '))
575 {
576 pos++;
577 }
578
579 nameList.add(attrName);
580 valueList.add(value);
581
582 if (pos >= length)
583 {
584 // We've hit the end of the RDN and the end of the DN.
585 final String[] names = nameList.toArray(new String[nameList.size()]);
586 final ASN1OctetString[] values =
587 valueList.toArray(new ASN1OctetString[valueList.size()]);
588 rdnList.add(new RDN(names, values, schema,
589 getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
590 expectMore = false;
591 break rdnLoop;
592 }
593
594 switch (dnString.charAt(pos))
595 {
596 case '+':
597 // There are still more RDN components to be read, so we're not done
598 // yet.
599 pos++;
600
601 if (pos >= length)
602 {
603 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
604 ERR_DN_ENDS_WITH_PLUS.get());
605 }
606 break;
607
608 case ',':
609 case ';':
610 // We've hit the end of the RDN, but there is still more of the DN
611 // to be read.
612 final String[] names =
613 nameList.toArray(new String[nameList.size()]);
614 final ASN1OctetString[] values =
615 valueList.toArray(new ASN1OctetString[valueList.size()]);
616 rdnList.add(new RDN(names, values, schema,
617 getTrimmedRDN(dnString, rdnStartPos,rdnEndPos)));
618 pos++;
619 expectMore = true;
620 continue rdnLoop;
621
622 default:
623 // It's an illegal character. This should never happen.
624 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
625 ERR_DN_UNEXPECTED_CHAR.get(
626 dnString.charAt(pos), pos));
627 }
628 }
629 }
630
631 // If we are expecting more information to be provided, then it means that
632 // the string ended with a comma or semicolon.
633 if (expectMore)
634 {
635 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
636 ERR_DN_ENDS_WITH_COMMA.get());
637 }
638
639 // At this point, we should have all of the RDNs to use to create this DN.
640 rdns = new RDN[rdnList.size()];
641 rdnList.toArray(rdns);
642 }
643
644
645
646 /**
647 * Retrieves a trimmed version of the string representation of the RDN in the
648 * specified portion of the provided DN string. Only non-escaped trailing
649 * spaces will be removed.
650 *
651 * @param dnString The string representation of the DN from which to extract
652 * the string representation of the RDN.
653 * @param start The position of the first character in the RDN.
654 * @param end The position marking the end of the RDN.
655 *
656 * @return A properly-trimmed string representation of the RDN.
657 */
658 private static String getTrimmedRDN(final String dnString, final int start,
659 final int end)
660 {
661 final String rdnString = dnString.substring(start, end);
662 if (! rdnString.endsWith(" "))
663 {
664 return rdnString;
665 }
666
667 final StringBuilder buffer = new StringBuilder(rdnString);
668 while ((buffer.charAt(buffer.length() - 1) == ' ') &&
669 (buffer.charAt(buffer.length() - 2) != '\\'))
670 {
671 buffer.setLength(buffer.length() - 1);
672 }
673
674 return buffer.toString();
675 }
676
677
678
679 /**
680 * Indicates whether the provided string represents a valid DN.
681 *
682 * @param s The string for which to make the determination. It must not be
683 * {@code null}.
684 *
685 * @return {@code true} if the provided string represents a valid DN, or
686 * {@code false} if not.
687 */
688 public static boolean isValidDN(final String s)
689 {
690 try
691 {
692 new DN(s);
693 return true;
694 }
695 catch (LDAPException le)
696 {
697 return false;
698 }
699 }
700
701
702
703
704 /**
705 * Retrieves the leftmost (i.e., furthest from the naming context) RDN
706 * component for this DN.
707 *
708 * @return The leftmost RDN component for this DN, or {@code null} if this DN
709 * does not have any RDNs (i.e., it is the null DN).
710 */
711 public RDN getRDN()
712 {
713 if (rdns.length == 0)
714 {
715 return null;
716 }
717 else
718 {
719 return rdns[0];
720 }
721 }
722
723
724
725 /**
726 * Retrieves the string representation of the leftmost (i.e., furthest from
727 * the naming context) RDN component for this DN.
728 *
729 * @return The string representation of the leftmost RDN component for this
730 * DN, or {@code null} if this DN does not have any RDNs (i.e., it is
731 * the null DN).
732 */
733 public String getRDNString()
734 {
735 if (rdns.length == 0)
736 {
737 return null;
738 }
739 else
740 {
741 return rdns[0].toString();
742 }
743 }
744
745
746
747 /**
748 * Retrieves the string representation of the leftmost (i.e., furthest from
749 * the naming context) RDN component for the DN with the provided string
750 * representation.
751 *
752 * @param s The string representation of the DN to process. It must not be
753 * {@code null}.
754 *
755 * @return The string representation of the leftmost RDN component for this
756 * DN, or {@code null} if this DN does not have any RDNs (i.e., it is
757 * the null DN).
758 *
759 * @throws LDAPException If the provided string cannot be parsed as a DN.
760 */
761 public static String getRDNString(final String s)
762 throws LDAPException
763 {
764 return new DN(s).getRDNString();
765 }
766
767
768
769 /**
770 * Retrieves the set of RDNs that comprise this DN.
771 *
772 * @return The set of RDNs that comprise this DN.
773 */
774 public RDN[] getRDNs()
775 {
776 return rdns;
777 }
778
779
780
781 /**
782 * Retrieves the set of RDNs that comprise the DN with the provided string
783 * representation.
784 *
785 * @param s The string representation of the DN for which to retrieve the
786 * RDNs. It must not be {@code null}.
787 *
788 * @return The set of RDNs that comprise the DN with the provided string
789 * representation.
790 *
791 * @throws LDAPException If the provided string cannot be parsed as a DN.
792 */
793 public static RDN[] getRDNs(final String s)
794 throws LDAPException
795 {
796 return new DN(s).getRDNs();
797 }
798
799
800
801 /**
802 * Retrieves the set of string representations of the RDNs that comprise this
803 * DN.
804 *
805 * @return The set of string representations of the RDNs that comprise this
806 * DN.
807 */
808 public String[] getRDNStrings()
809 {
810 final String[] rdnStrings = new String[rdns.length];
811 for (int i=0; i < rdns.length; i++)
812 {
813 rdnStrings[i] = rdns[i].toString();
814 }
815 return rdnStrings;
816 }
817
818
819
820 /**
821 * Retrieves the set of string representations of the RDNs that comprise this
822 * DN.
823 *
824 * @param s The string representation of the DN for which to retrieve the
825 * RDN strings. It must not be {@code null}.
826 *
827 * @return The set of string representations of the RDNs that comprise this
828 * DN.
829 *
830 * @throws LDAPException If the provided string cannot be parsed as a DN.
831 */
832 public static String[] getRDNStrings(final String s)
833 throws LDAPException
834 {
835 return new DN(s).getRDNStrings();
836 }
837
838
839
840 /**
841 * Indicates whether this DN represents the null DN, which does not have any
842 * RDN components.
843 *
844 * @return {@code true} if this DN represents the null DN, or {@code false}
845 * if not.
846 */
847 public boolean isNullDN()
848 {
849 return (rdns.length == 0);
850 }
851
852
853
854 /**
855 * Retrieves the DN that is the parent for this DN. Note that neither the
856 * null DN nor DNs consisting of a single RDN component will be considered to
857 * have parent DNs.
858 *
859 * @return The DN that is the parent for this DN, or {@code null} if there
860 * is no parent.
861 */
862 public DN getParent()
863 {
864 switch (rdns.length)
865 {
866 case 0:
867 case 1:
868 return null;
869
870 case 2:
871 return new DN(rdns[1]);
872
873 case 3:
874 return new DN(rdns[1], rdns[2]);
875
876 case 4:
877 return new DN(rdns[1], rdns[2], rdns[3]);
878
879 case 5:
880 return new DN(rdns[1], rdns[2], rdns[3], rdns[4]);
881
882 default:
883 final RDN[] parentRDNs = new RDN[rdns.length - 1];
884 System.arraycopy(rdns, 1, parentRDNs, 0, parentRDNs.length);
885 return new DN(parentRDNs);
886 }
887 }
888
889
890
891 /**
892 * Retrieves the DN that is the parent for the DN with the provided string
893 * representation. Note that neither the null DN nor DNs consisting of a
894 * single RDN component will be considered to have parent DNs.
895 *
896 * @param s The string representation of the DN for which to retrieve the
897 * parent. It must not be {@code null}.
898 *
899 * @return The DN that is the parent for this DN, or {@code null} if there
900 * is no parent.
901 *
902 * @throws LDAPException If the provided string cannot be parsed as a DN.
903 */
904 public static DN getParent(final String s)
905 throws LDAPException
906 {
907 return new DN(s).getParent();
908 }
909
910
911
912 /**
913 * Retrieves the string representation of the DN that is the parent for this
914 * DN. Note that neither the null DN nor DNs consisting of a single RDN
915 * component will be considered to have parent DNs.
916 *
917 * @return The DN that is the parent for this DN, or {@code null} if there
918 * is no parent.
919 */
920 public String getParentString()
921 {
922 final DN parentDN = getParent();
923 if (parentDN == null)
924 {
925 return null;
926 }
927 else
928 {
929 return parentDN.toString();
930 }
931 }
932
933
934
935 /**
936 * Retrieves the string representation of the DN that is the parent for the
937 * DN with the provided string representation. Note that neither the null DN
938 * nor DNs consisting of a single RDN component will be considered to have
939 * parent DNs.
940 *
941 * @param s The string representation of the DN for which to retrieve the
942 * parent. It must not be {@code null}.
943 *
944 * @return The DN that is the parent for this DN, or {@code null} if there
945 * is no parent.
946 *
947 * @throws LDAPException If the provided string cannot be parsed as a DN.
948 */
949 public static String getParentString(final String s)
950 throws LDAPException
951 {
952 return new DN(s).getParentString();
953 }
954
955
956
957 /**
958 * Indicates whether this DN is an ancestor of the provided DN. It will be
959 * considered an ancestor of the provided DN if the array of RDN components
960 * for the provided DN ends with the elements that comprise the array of RDN
961 * components for this DN (i.e., if the provided DN is subordinate to, or
962 * optionally equal to, this DN). The null DN will be considered an ancestor
963 * for all other DNs (with the exception of the null DN if {@code allowEquals}
964 * is {@code false}).
965 *
966 * @param dn The DN for which to make the determination.
967 * @param allowEquals Indicates whether a DN should be considered an
968 * ancestor of itself.
969 *
970 * @return {@code true} if this DN may be considered an ancestor of the
971 * provided DN, or {@code false} if not.
972 */
973 public boolean isAncestorOf(final DN dn, final boolean allowEquals)
974 {
975 int thisPos = rdns.length - 1;
976 int thatPos = dn.rdns.length - 1;
977
978 if (thisPos < 0)
979 {
980 // This DN must be the null DN, which is an ancestor for all other DNs
981 // (and equal to the null DN, which we may still classify as being an
982 // ancestor).
983 return (allowEquals || (thatPos >= 0));
984 }
985
986 if ((thisPos > thatPos) || ((thisPos == thatPos) && (! allowEquals)))
987 {
988 // This DN has more RDN components than the provided DN, so it can't
989 // possibly be an ancestor, or has the same number of components and equal
990 // DNs shouldn't be considered ancestors.
991 return false;
992 }
993
994 while (thisPos >= 0)
995 {
996 if (! rdns[thisPos--].equals(dn.rdns[thatPos--]))
997 {
998 return false;
999 }
1000 }
1001
1002 // If we've gotten here, then we can consider this DN to be an ancestor of
1003 // the provided DN.
1004 return true;
1005 }
1006
1007
1008
1009 /**
1010 * Indicates whether this DN is an ancestor of the DN with the provided string
1011 * representation. It will be considered an ancestor of the provided DN if
1012 * the array of RDN components for the provided DN ends with the elements that
1013 * comprise the array of RDN components for this DN (i.e., if the provided DN
1014 * is subordinate to, or optionally equal to, this DN). The null DN will be
1015 * considered an ancestor for all other DNs (with the exception of the null DN
1016 * if {@code allowEquals} is {@code false}).
1017 *
1018 * @param s The string representation of the DN for which to make
1019 * the determination.
1020 * @param allowEquals Indicates whether a DN should be considered an
1021 * ancestor of itself.
1022 *
1023 * @return {@code true} if this DN may be considered an ancestor of the
1024 * provided DN, or {@code false} if not.
1025 *
1026 * @throws LDAPException If the provided string cannot be parsed as a DN.
1027 */
1028 public boolean isAncestorOf(final String s, final boolean allowEquals)
1029 throws LDAPException
1030 {
1031 return isAncestorOf(new DN(s), allowEquals);
1032 }
1033
1034
1035
1036 /**
1037 * Indicates whether the DN represented by the first string is an ancestor of
1038 * the DN represented by the second string. The first DN will be considered
1039 * an ancestor of the second DN if the array of RDN components for the first
1040 * DN ends with the elements that comprise the array of RDN components for the
1041 * second DN (i.e., if the first DN is subordinate to, or optionally equal to,
1042 * the second DN). The null DN will be considered an ancestor for all other
1043 * DNs (with the exception of the null DN if {@code allowEquals} is
1044 * {@code false}).
1045 *
1046 * @param s1 The string representation of the first DN for which to
1047 * make the determination.
1048 * @param s2 The string representation of the second DN for which
1049 * to make the determination.
1050 * @param allowEquals Indicates whether a DN should be considered an
1051 * ancestor of itself.
1052 *
1053 * @return {@code true} if the first DN may be considered an ancestor of the
1054 * second DN, or {@code false} if not.
1055 *
1056 * @throws LDAPException If either of the provided strings cannot be parsed
1057 * as a DN.
1058 */
1059 public static boolean isAncestorOf(final String s1, final String s2,
1060 final boolean allowEquals)
1061 throws LDAPException
1062 {
1063 return new DN(s1).isAncestorOf(new DN(s2), allowEquals);
1064 }
1065
1066
1067
1068 /**
1069 * Indicates whether this DN is a descendant of the provided DN. It will be
1070 * considered a descendant of the provided DN if the array of RDN components
1071 * for this DN ends with the elements that comprise the RDN components for the
1072 * provided DN (i.e., if this DN is subordinate to, or optionally equal to,
1073 * the provided DN). The null DN will not be considered a descendant for any
1074 * other DNs (with the exception of the null DN if {@code allowEquals} is
1075 * {@code true}).
1076 *
1077 * @param dn The DN for which to make the determination.
1078 * @param allowEquals Indicates whether a DN should be considered a
1079 * descendant of itself.
1080 *
1081 * @return {@code true} if this DN may be considered a descendant of the
1082 * provided DN, or {@code false} if not.
1083 */
1084 public boolean isDescendantOf(final DN dn, final boolean allowEquals)
1085 {
1086 int thisPos = rdns.length - 1;
1087 int thatPos = dn.rdns.length - 1;
1088
1089 if (thatPos < 0)
1090 {
1091 // The provided DN must be the null DN, which will be considered an
1092 // ancestor for all other DNs (and equal to the null DN), making this DN
1093 // considered a descendant for that DN.
1094 return (allowEquals || (thisPos >= 0));
1095 }
1096
1097 if ((thisPos < thatPos) || ((thisPos == thatPos) && (! allowEquals)))
1098 {
1099 // This DN has fewer DN components than the provided DN, so it can't
1100 // possibly be a descendant, or it has the same number of components and
1101 // equal DNs shouldn't be considered descendants.
1102 return false;
1103 }
1104
1105 while (thatPos >= 0)
1106 {
1107 if (! rdns[thisPos--].equals(dn.rdns[thatPos--]))
1108 {
1109 return false;
1110 }
1111 }
1112
1113 // If we've gotten here, then we can consider this DN to be a descendant of
1114 // the provided DN.
1115 return true;
1116 }
1117
1118
1119
1120 /**
1121 * Indicates whether this DN is a descendant of the DN with the provided
1122 * string representation. It will be considered a descendant of the provided
1123 * DN if the array of RDN components for this DN ends with the elements that
1124 * comprise the RDN components for the provided DN (i.e., if this DN is
1125 * subordinate to, or optionally equal to, the provided DN). The null DN will
1126 * not be considered a descendant for any other DNs (with the exception of the
1127 * null DN if {@code allowEquals} is {@code true}).
1128 *
1129 * @param s The string representation of the DN for which to make
1130 * the determination.
1131 * @param allowEquals Indicates whether a DN should be considered a
1132 * descendant of itself.
1133 *
1134 * @return {@code true} if this DN may be considered a descendant of the
1135 * provided DN, or {@code false} if not.
1136 *
1137 * @throws LDAPException If the provided string cannot be parsed as a DN.
1138 */
1139 public boolean isDescendantOf(final String s, final boolean allowEquals)
1140 throws LDAPException
1141 {
1142 return isDescendantOf(new DN(s), allowEquals);
1143 }
1144
1145
1146
1147 /**
1148 * Indicates whether the DN represented by the first string is a descendant of
1149 * the DN represented by the second string. The first DN will be considered a
1150 * descendant of the second DN if the array of RDN components for the first DN
1151 * ends with the elements that comprise the RDN components for the second DN
1152 * (i.e., if the first DN is subordinate to, or optionally equal to, the
1153 * second DN). The null DN will not be considered a descendant for any other
1154 * DNs (with the exception of the null DN if {@code allowEquals} is
1155 * {@code true}).
1156 *
1157 * @param s1 The string representation of the first DN for which to
1158 * make the determination.
1159 * @param s2 The string representation of the second DN for which
1160 * to make the determination.
1161 * @param allowEquals Indicates whether a DN should be considered an
1162 * ancestor of itself.
1163 *
1164 * @return {@code true} if this DN may be considered a descendant of the
1165 * provided DN, or {@code false} if not.
1166 *
1167 * @throws LDAPException If either of the provided strings cannot be parsed
1168 * as a DN.
1169 */
1170 public static boolean isDescendantOf(final String s1, final String s2,
1171 final boolean allowEquals)
1172 throws LDAPException
1173 {
1174 return new DN(s1).isDescendantOf(new DN(s2), allowEquals);
1175 }
1176
1177
1178
1179 /**
1180 * Indicates whether this DN falls within the range of the provided search
1181 * base DN and scope.
1182 *
1183 * @param baseDN The base DN for which to make the determination. It must
1184 * not be {@code null}.
1185 * @param scope The scope for which to make the determination. It must not
1186 * be {@code null}.
1187 *
1188 * @return {@code true} if this DN is within the range of the provided base
1189 * and scope, or {@code false} if not.
1190 *
1191 * @throws LDAPException If a problem occurs while making the determination.
1192 */
1193 public boolean matchesBaseAndScope(final String baseDN,
1194 final SearchScope scope)
1195 throws LDAPException
1196 {
1197 return matchesBaseAndScope(new DN(baseDN), scope);
1198 }
1199
1200
1201
1202 /**
1203 * Indicates whether this DN falls within the range of the provided search
1204 * base DN and scope.
1205 *
1206 * @param baseDN The base DN for which to make the determination. It must
1207 * not be {@code null}.
1208 * @param scope The scope for which to make the determination. It must not
1209 * be {@code null}.
1210 *
1211 * @return {@code true} if this DN is within the range of the provided base
1212 * and scope, or {@code false} if not.
1213 *
1214 * @throws LDAPException If a problem occurs while making the determination.
1215 */
1216 public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope)
1217 throws LDAPException
1218 {
1219 ensureNotNull(baseDN, scope);
1220
1221 switch (scope.intValue())
1222 {
1223 case SearchScope.BASE_INT_VALUE:
1224 return equals(baseDN);
1225
1226 case SearchScope.ONE_INT_VALUE:
1227 return baseDN.equals(getParent());
1228
1229 case SearchScope.SUB_INT_VALUE:
1230 return isDescendantOf(baseDN, true);
1231
1232 case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
1233 return isDescendantOf(baseDN, false);
1234
1235 default:
1236 throw new LDAPException(ResultCode.PARAM_ERROR,
1237 ERR_DN_MATCHES_UNSUPPORTED_SCOPE.get(dnString,
1238 String.valueOf(scope)));
1239 }
1240 }
1241
1242
1243
1244
1245 /**
1246 * Generates a hash code for this DN.
1247 *
1248 * @return The generated hash code for this DN.
1249 */
1250 @Override() public int hashCode()
1251 {
1252 return toNormalizedString().hashCode();
1253 }
1254
1255
1256
1257 /**
1258 * Indicates whether the provided object is equal to this DN. In order for
1259 * the provided object to be considered equal, it must be a non-null DN with
1260 * the same set of RDN components.
1261 *
1262 * @param o The object for which to make the determination.
1263 *
1264 * @return {@code true} if the provided object is considered equal to this
1265 * DN, or {@code false} if not.
1266 */
1267 @Override()
1268 public boolean equals(final Object o)
1269 {
1270 if (o == null)
1271 {
1272 return false;
1273 }
1274
1275 if (this == o)
1276 {
1277 return true;
1278 }
1279
1280 if (! (o instanceof DN))
1281 {
1282 return false;
1283 }
1284
1285 final DN dn = (DN) o;
1286 return (toNormalizedString().equals(dn.toNormalizedString()));
1287 }
1288
1289
1290
1291 /**
1292 * Indicates whether the DN with the provided string representation is equal
1293 * to this DN.
1294 *
1295 * @param s The string representation of the DN to compare with this DN.
1296 *
1297 * @return {@code true} if the DN with the provided string representation is
1298 * equal to this DN, or {@code false} if not.
1299 *
1300 * @throws LDAPException If the provided string cannot be parsed as a DN.
1301 */
1302 public boolean equals(final String s)
1303 throws LDAPException
1304 {
1305 if (s == null)
1306 {
1307 return false;
1308 }
1309
1310 return equals(new DN(s));
1311 }
1312
1313
1314
1315 /**
1316 * Indicates whether the two provided strings represent the same DN.
1317 *
1318 * @param s1 The string representation of the first DN for which to make the
1319 * determination. It must not be {@code null}.
1320 * @param s2 The string representation of the second DN for which to make
1321 * the determination. It must not be {@code null}.
1322 *
1323 * @return {@code true} if the provided strings represent the same DN, or
1324 * {@code false} if not.
1325 *
1326 * @throws LDAPException If either of the provided strings cannot be parsed
1327 * as a DN.
1328 */
1329 public static boolean equals(final String s1, final String s2)
1330 throws LDAPException
1331 {
1332 return new DN(s1).equals(new DN(s2));
1333 }
1334
1335
1336
1337 /**
1338 * Retrieves a string representation of this DN.
1339 *
1340 * @return A string representation of this DN.
1341 */
1342 @Override()
1343 public String toString()
1344 {
1345 return dnString;
1346 }
1347
1348
1349
1350 /**
1351 * Retrieves a string representation of this DN with minimal encoding for
1352 * special characters. Only those characters specified in RFC 4514 section
1353 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or
1354 * non-printable ASCII characters.
1355 *
1356 * @return A string representation of this DN with minimal encoding for
1357 * special characters.
1358 */
1359 public String toMinimallyEncodedString()
1360 {
1361 final StringBuilder buffer = new StringBuilder();
1362 toString(buffer, true);
1363 return buffer.toString();
1364 }
1365
1366
1367
1368 /**
1369 * Appends a string representation of this DN to the provided buffer.
1370 *
1371 * @param buffer The buffer to which to append the string representation of
1372 * this DN.
1373 */
1374 public void toString(final StringBuilder buffer)
1375 {
1376 toString(buffer, false);
1377 }
1378
1379
1380
1381 /**
1382 * Appends a string representation of this DN to the provided buffer.
1383 *
1384 * @param buffer The buffer to which the string representation is
1385 * to be appended.
1386 * @param minimizeEncoding Indicates whether to restrict the encoding of
1387 * special characters to the bare minimum required
1388 * by LDAP (as per RFC 4514 section 2.4). If this
1389 * is {@code true}, then only leading and trailing
1390 * spaces, double quotes, plus signs, commas,
1391 * semicolons, greater-than, less-than, and
1392 * backslash characters will be encoded.
1393 */
1394 public void toString(final StringBuilder buffer,
1395 final boolean minimizeEncoding)
1396 {
1397 for (int i=0; i < rdns.length; i++)
1398 {
1399 if (i > 0)
1400 {
1401 buffer.append(',');
1402 }
1403
1404 rdns[i].toString(buffer, minimizeEncoding);
1405 }
1406 }
1407
1408
1409
1410 /**
1411 * Retrieves a normalized string representation of this DN.
1412 *
1413 * @return A normalized string representation of this DN.
1414 */
1415 public String toNormalizedString()
1416 {
1417 if (normalizedString == null)
1418 {
1419 final StringBuilder buffer = new StringBuilder();
1420 toNormalizedString(buffer);
1421 normalizedString = buffer.toString();
1422 }
1423
1424 return normalizedString;
1425 }
1426
1427
1428
1429 /**
1430 * Appends a normalized string representation of this DN to the provided
1431 * buffer.
1432 *
1433 * @param buffer The buffer to which to append the normalized string
1434 * representation of this DN.
1435 */
1436 public void toNormalizedString(final StringBuilder buffer)
1437 {
1438 for (int i=0; i < rdns.length; i++)
1439 {
1440 if (i > 0)
1441 {
1442 buffer.append(',');
1443 }
1444
1445 buffer.append(rdns[i].toNormalizedString());
1446 }
1447 }
1448
1449
1450
1451 /**
1452 * Retrieves a normalized representation of the DN with the provided string
1453 * representation.
1454 *
1455 * @param s The string representation of the DN to normalize. It must not
1456 * be {@code null}.
1457 *
1458 * @return The normalized representation of the DN with the provided string
1459 * representation.
1460 *
1461 * @throws LDAPException If the provided string cannot be parsed as a DN.
1462 */
1463 public static String normalize(final String s)
1464 throws LDAPException
1465 {
1466 return normalize(s, null);
1467 }
1468
1469
1470
1471 /**
1472 * Retrieves a normalized representation of the DN with the provided string
1473 * representation.
1474 *
1475 * @param s The string representation of the DN to normalize. It must
1476 * not be {@code null}.
1477 * @param schema The schema to use to generate the normalized string
1478 * representation of the DN. It may be {@code null} if no
1479 * schema is available.
1480 *
1481 * @return The normalized representation of the DN with the provided string
1482 * representation.
1483 *
1484 * @throws LDAPException If the provided string cannot be parsed as a DN.
1485 */
1486 public static String normalize(final String s, final Schema schema)
1487 throws LDAPException
1488 {
1489 return new DN(s, schema).toNormalizedString();
1490 }
1491
1492
1493
1494 /**
1495 * Compares the provided DN to this DN to determine their relative order in
1496 * a sorted list.
1497 *
1498 * @param dn The DN to compare against this DN. It must not be
1499 * {@code null}.
1500 *
1501 * @return A negative integer if this DN should come before the provided DN
1502 * in a sorted list, a positive integer if this DN should come after
1503 * the provided DN in a sorted list, or zero if the provided DN can
1504 * be considered equal to this DN.
1505 */
1506 public int compareTo(final DN dn)
1507 {
1508 return compare(this, dn);
1509 }
1510
1511
1512
1513 /**
1514 * Compares the provided DN values to determine their relative order in a
1515 * sorted list.
1516 *
1517 * @param dn1 The first DN to be compared. It must not be {@code null}.
1518 * @param dn2 The second DN to be compared. It must not be {@code null}.
1519 *
1520 * @return A negative integer if the first DN should come before the second
1521 * DN in a sorted list, a positive integer if the first DN should
1522 * come after the second DN in a sorted list, or zero if the two DN
1523 * values can be considered equal.
1524 */
1525 public int compare(final DN dn1, final DN dn2)
1526 {
1527 ensureNotNull(dn1, dn2);
1528
1529 // We want the comparison to be in reverse order, so that DNs will be sorted
1530 // hierarchically.
1531 int pos1 = dn1.rdns.length - 1;
1532 int pos2 = dn2.rdns.length - 1;
1533 if (pos1 < 0)
1534 {
1535 if (pos2 < 0)
1536 {
1537 // Both DNs are the null DN, so they are equal.
1538 return 0;
1539 }
1540 else
1541 {
1542 // The first DN is the null DN and the second isn't, so the first DN
1543 // comes first.
1544 return -1;
1545 }
1546 }
1547 else if (pos2 < 0)
1548 {
1549 // The second DN is the null DN, which always comes first.
1550 return 1;
1551 }
1552
1553
1554 while ((pos1 >= 0) && (pos2 >= 0))
1555 {
1556 final int compValue = dn1.rdns[pos1].compareTo(dn2.rdns[pos2]);
1557 if (compValue != 0)
1558 {
1559 return compValue;
1560 }
1561
1562 pos1--;
1563 pos2--;
1564 }
1565
1566
1567 // If we've gotten here, then one of the DNs is equal to or a descendant of
1568 // the other.
1569 if (pos1 < 0)
1570 {
1571 if (pos2 < 0)
1572 {
1573 // They're both the same length, so they should be considered equal.
1574 return 0;
1575 }
1576 else
1577 {
1578 // The first is shorter than the second, so it should come first.
1579 return -1;
1580 }
1581 }
1582 else
1583 {
1584 // The second RDN is shorter than the first, so it should come first.
1585 return 1;
1586 }
1587 }
1588
1589
1590
1591 /**
1592 * Compares the DNs with the provided string representations to determine
1593 * their relative order in a sorted list.
1594 *
1595 * @param s1 The string representation for the first DN to be compared. It
1596 * must not be {@code null}.
1597 * @param s2 The string representation for the second DN to be compared. It
1598 * must not be {@code null}.
1599 *
1600 * @return A negative integer if the first DN should come before the second
1601 * DN in a sorted list, a positive integer if the first DN should
1602 * come after the second DN in a sorted list, or zero if the two DN
1603 * values can be considered equal.
1604 *
1605 * @throws LDAPException If either of the provided strings cannot be parsed
1606 * as a DN.
1607 */
1608 public static int compare(final String s1, final String s2)
1609 throws LDAPException
1610 {
1611 return compare(s1, s2, null);
1612 }
1613
1614
1615
1616 /**
1617 * Compares the DNs with the provided string representations to determine
1618 * their relative order in a sorted list.
1619 *
1620 * @param s1 The string representation for the first DN to be compared.
1621 * It must not be {@code null}.
1622 * @param s2 The string representation for the second DN to be compared.
1623 * It must not be {@code null}.
1624 * @param schema The schema to use to generate the normalized string
1625 * representations of the DNs. It may be {@code null} if no
1626 * schema is available.
1627 *
1628 * @return A negative integer if the first DN should come before the second
1629 * DN in a sorted list, a positive integer if the first DN should
1630 * come after the second DN in a sorted list, or zero if the two DN
1631 * values can be considered equal.
1632 *
1633 * @throws LDAPException If either of the provided strings cannot be parsed
1634 * as a DN.
1635 */
1636 public static int compare(final String s1, final String s2,
1637 final Schema schema)
1638 throws LDAPException
1639 {
1640 return new DN(s1, schema).compareTo(new DN(s2, schema));
1641 }
1642 }