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