View Javadoc

1   /*
2    * Copyright 2005-2008 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ldap.core;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.Enumeration;
22  import java.util.Iterator;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.ListIterator;
26  
27  import javax.naming.CompositeName;
28  import javax.naming.InvalidNameException;
29  import javax.naming.Name;
30  import javax.naming.ldap.Rdn;
31  
32  import org.apache.commons.lang.StringUtils;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.springframework.ldap.BadLdapGrammarException;
36  import org.springframework.ldap.support.LdapUtils;
37  import org.springframework.ldap.support.ListComparator;
38  import org.springframework.util.Assert;
39  
40  /**
41   * Default implementation of a {@link Name} corresponding to an LDAP path. A
42   * Distinguished Name manipulation implementation is included in JDK1.5
43   * (LdapName), but not in prior releases.
44   * 
45   * A <code>DistinguishedName</code> is particularly useful when building or
46   * modifying an LDAP path dynamically, as escaping will be taken care of.
47   * 
48   * A path is split into several names. The {@link Name} interface specifies that
49   * the most significant part be in position 0.
50   * <p>
51   * Example:
52   * 
53   * <dl>
54   * <dt>The path</dt>
55   * <dd>uid=adam.skogman, ou=People, ou=EU</dd>
56   * <dt>Name[0]</dt>
57   * <dd>ou=EU</dd>
58   * <dt>Name[1]</dt>
59   * <dd>ou=People</dd>
60   * <dt>Name[2]</dt>
61   * <dd>uid=adam.skogman</dd>
62   * </dl>
63   * </p>
64   * <p>
65   * <code>Name</code> instances, and consequently <code>DistinguishedName</code>
66   * instances are naturally mutable, which is useful when constructing
67   * DistinguishedNames. Example:
68   * 
69   * <pre>
70   * DistinguishedName path = new DistinguishedName(&quot;dc=jayway,dc=se&quot;);
71   * path.add(&quot;ou&quot;, &quot;People&quot;);
72   * path.add(&quot;uid&quot;, &quot;adam.skogman&quot;);
73   * String dn = path.toString();
74   * </pre>
75   * 
76   * will render <code>uid=adam.skogman,ou=People,dc=jayway,dc=se</code>.
77   * </p>
78   * <p>
79   * <b>NOTE:</b> The fact that DistinguishedName instances are mutable needs to
80   * be taken into careful account, as this means that they may be modified
81   * involuntarily. This means that whenever a <code>DistinguishedName</code>
82   * instance is kept for reference (e.g. for identification of a domain entry) or
83   * as a constant, you should consider getting an immutable copy of the instance
84   * using {@link #immutableDistinguishedName()} or
85   * {@link #immutableDistinguishedName(String)}.
86   * </p>
87   * <p>
88   * <b>NB:</b>As of version 1.3 the default toString representation of
89   * DistinguishedName now defaults to a compact one, without spaces between the
90   * respective RDNs. For backward compatibility, set the
91   * {@link #SPACED_DN_FORMAT_PROPERTY} ({@value #SPACED_DN_FORMAT_PROPERTY}) to
92   * <code>true</code>.
93   * @author Adam Skogman
94   * @author Mattias Hellborg Arthursson
95   */
96  public class DistinguishedName implements Name {
97  	/**
98  	 * System property that will be inspected to determine whether toString will
99  	 * format the DN with spaces after each comma or use a more compact
100 	 * representation, i.e.:
101 	 * <code>uid=adam.skogman, ou=People, dc=jayway, dc=se</code> rather than
102 	 * <code>uid=adam.skogman,ou=People,dc=jayway,dc=se</code>. Default is
103 	 * compact representation.
104 	 * @since 1.3
105 	 */
106 	public static final String SPACED_DN_FORMAT_PROPERTY = "org.springframework.ldap.core.spacedDnFormat";
107 
108 	private static final Log log = LogFactory.getLog(DistinguishedName.class);
109 
110 	private static final boolean COMPACT = true;
111 
112 	private static final boolean NON_COMPACT = false;
113 
114 	private static final long serialVersionUID = 3514344371999042586L;
115 
116 	/**
117 	 * An empty, unmodifiable DistinguishedName.
118 	 */
119 	public static final DistinguishedName EMPTY_PATH = new DistinguishedName(Collections.EMPTY_LIST);
120 
121 	private List names;
122 
123 	/**
124 	 * Construct a new DistinguishedName with no components.
125 	 */
126 	public DistinguishedName() {
127 		names = new LinkedList();
128 	}
129 
130 	/**
131 	 * Construct a new <code>DistinguishedName</code> from a String.
132 	 * 
133 	 * @param path a String corresponding to a (syntactically) valid LDAP path.
134 	 */
135 	public DistinguishedName(String path) {
136 		if (StringUtils.isBlank(path)) {
137 			names = new LinkedList();
138 		}
139 		else {
140 			parse(path);
141 		}
142 	}
143 
144 	/**
145 	 * Construct a new <code>DistinguishedName</code> from the supplied
146 	 * <code>List</code> of {@link LdapRdn} objects.
147 	 * 
148 	 * @param list the components that this instance will consist of.
149 	 */
150 	public DistinguishedName(List list) {
151 		this.names = list;
152 	}
153 
154 	/**
155 	 * Construct a new <code>DistinguishedName</code> from the supplied
156 	 * {@link Name}. The parts of the supplied {@link Name} must be
157 	 * syntactically correct {@link LdapRdn}s.
158 	 * 
159 	 * @param name the {@link Name} to construct a new
160 	 * <code>DistinguishedName</code> from.
161 	 */
162 	public DistinguishedName(Name name) {
163 		Assert.notNull(name, "name cannot be null");
164 		if (name instanceof CompositeName) {
165 			parse(LdapUtils.convertCompositeNameToString((CompositeName) name));
166 			return;
167 		}
168 		names = new LinkedList();
169 		for (int i = 0; i < name.size(); i++) {
170 			names.add(new LdapRdn(name.get(i)));
171 		}
172 	}
173 
174 	/**
175 	 * Parse the supplied String and make this instance represent the
176 	 * corresponding distinguished name.
177 	 * 
178 	 * @param path the LDAP path to parse.
179 	 */
180 	protected void parse(String path) {
181 		DnParser parser = DefaultDnParserFactory.createDnParser(unmangleCompositeName(path));
182 		DistinguishedName dn;
183 		try {
184 			dn = parser.dn();
185 		}
186 		catch (ParseException e) {
187 			throw new BadLdapGrammarException("Failed to parse DN", e);
188 		}
189 		catch (TokenMgrError e) {
190 			throw new BadLdapGrammarException("Failed to parse DN", e);
191 		}
192 		this.names = dn.names;
193 	}
194 
195 	/**
196 	 * If path is surrounded by quotes, strip them. JNDI considers forward slash
197 	 * ('/') special, but LDAP doesn't. {@link CompositeName#toString()} tends
198 	 * to mangle a {@link Name} with a slash by surrounding it with quotes
199 	 * ('"').
200 	 * 
201 	 * @param path Path to check and possibly strip.
202 	 * @return A String with the possibly stripped path.
203 	 */
204 	private String unmangleCompositeName(String path) {
205 		String tempPath;
206 		// Check if CompositeName has mangled the name with quotes
207 		if (path.startsWith("\"") && path.endsWith("\"")) {
208 			tempPath = path.substring(1, path.length() - 1);
209 		}
210 		else {
211 			tempPath = path;
212 		}
213 		return tempPath;
214 	}
215 
216 	/**
217 	 * Get the {@link LdapRdn} at a specified position.
218 	 * 
219 	 * @param index the {@link LdapRdn} to retrieve.
220 	 * @return the {@link LdapRdn} at the requested position.
221 	 */
222 	public LdapRdn getLdapRdn(int index) {
223 		return (LdapRdn) names.get(index);
224 	}
225 
226 	/**
227 	 * Get the {@link LdapRdn} with the specified key. If there are several
228 	 * {@link Rdn}s with the same key, the first one found (in order of
229 	 * significance) will be returned.
230 	 * 
231 	 * @param key Attribute name of the {@link LdapRdn} to retrieve.
232 	 * @return the {@link LdapRdn} with the requested key.
233 	 * @throws IllegalArgumentException if no Rdn matches the given key.
234 	 */
235 	public LdapRdn getLdapRdn(String key) {
236 		for (Iterator iter = names.iterator(); iter.hasNext();) {
237 			LdapRdn rdn = (LdapRdn) iter.next();
238 			if (StringUtils.equals(rdn.getKey(), key)) {
239 				return rdn;
240 			}
241 		}
242 
243 		throw new IllegalArgumentException("No Rdn with the requested key: '" + key + "'");
244 	}
245 
246 	/**
247 	 * Get the value of the {@link LdapRdnComponent} with the specified key
248 	 * (Attribute value). If there are several Rdns with the same key, the value
249 	 * of the first one found (in order of significance) will be returned.
250 	 * 
251 	 * @param key Attribute name of the {@link LdapRdn} to retrieve.
252 	 * @return the value.
253 	 * @throws IllegalArgumentException if no Rdn matches the given key.
254 	 */
255 	public String getValue(String key) {
256 		return getLdapRdn(key).getValue();
257 	}
258 
259 	/**
260 	 * Get the name <code>List</code>.
261 	 * 
262 	 * @return the list of {@link LdapRdn}s that this
263 	 * <code>DistinguishedName</code> consists of.
264 	 */
265 	public List getNames() {
266 		return names;
267 	}
268 
269 	/**
270 	 * Get the String representation of this <code>DistinguishedName</code>.
271 	 * Depending on the setting of property
272 	 * <code>org.springframework.ldap.core.spacedDnFormat</code> a space will be
273 	 * added after each comma, to make the result more readable. Default is
274 	 * compact representation, i.e. without any spaces.
275 	 * 
276 	 * @return a syntactically correct, properly escaped String representation
277 	 * of the <code>DistinguishedName</code>.
278 	 * @see #SPACED_DN_FORMAT_PROPERTY
279 	 */
280 	public String toString() {
281 		String spacedFormatting = System.getProperty(SPACED_DN_FORMAT_PROPERTY);
282 		if (StringUtils.isBlank(spacedFormatting)) {
283 			return format(COMPACT);
284 		}
285 		else {
286 			return format(NON_COMPACT);
287 		}
288 	}
289 
290 	/**
291 	 * Get the compact String representation of this
292 	 * <code>DistinguishedName</code>. Add no space after each comma, to make it
293 	 * compact.
294 	 * 
295 	 * @return a syntactically correct, properly escaped String representation
296 	 * of the <code>DistinguishedName</code>.
297 	 */
298 	public String toCompactString() {
299 		return format(COMPACT);
300 	}
301 
302 	/**
303 	 * Builds a complete LDAP path, ldap encoded, useful as a DN.
304 	 * 
305 	 * Always uses lowercase, always separates with ", " i.e. comma and a space.
306 	 * 
307 	 * @return the LDAP path.
308 	 */
309 	public String encode() {
310 		return format(NON_COMPACT);
311 	}
312 
313 	private String format(boolean compact) {
314 		// empty path
315 		if (names.size() == 0)
316 			return "";
317 
318 		StringBuffer buffer = new StringBuffer(256);
319 
320 		ListIterator i = names.listIterator(names.size());
321 		while (i.hasPrevious()) {
322 			LdapRdn rdn = (LdapRdn) i.previous();
323 			buffer.append(rdn.getLdapEncoded());
324 
325 			// add comma, except in last iteration
326 			if (i.hasPrevious()) {
327 				if (compact) {
328 					buffer.append(",");
329 				}
330 				else {
331 					buffer.append(", ");
332 
333 				}
334 			}
335 		}
336 
337 		return buffer.toString();
338 	}
339 
340 	/**
341 	 * Builds a complete LDAP path, ldap and url encoded. Separates only with
342 	 * ",".
343 	 * 
344 	 * @return the LDAP path, for use in an url.
345 	 */
346 	public String toUrl() {
347 		StringBuffer buffer = new StringBuffer(256);
348 
349 		for (int i = names.size() - 1; i >= 0; i--) {
350 			LdapRdn n = (LdapRdn) names.get(i);
351 			buffer.append(n.encodeUrl());
352 			if (i > 0) {
353 				buffer.append(",");
354 			}
355 		}
356 		return buffer.toString();
357 	}
358 
359 	/**
360 	 * Determines if this <code>DistinguishedName</code> path contains another
361 	 * path.
362 	 * 
363 	 * @param path the path to check.
364 	 * @return <code>true</code> if the supplied path is conained in this
365 	 * instance, <code>false</code> otherwise.
366 	 */
367 	public boolean contains(DistinguishedName path) {
368 
369 		List shortlist = path.getNames();
370 
371 		// this path must be at least as long
372 		if (getNames().size() < shortlist.size())
373 			return false;
374 
375 		// must have names
376 		if (shortlist.size() == 0)
377 			return false;
378 
379 		Iterator longiter = getNames().iterator();
380 		Iterator shortiter = shortlist.iterator();
381 
382 		LdapRdn longname = (LdapRdn) longiter.next();
383 		LdapRdn shortname = (LdapRdn) shortiter.next();
384 
385 		// find first match
386 		while (!longname.equals(shortname) && longiter.hasNext()) {
387 			longname = (LdapRdn) longiter.next();
388 		}
389 
390 		// Done?
391 		if (!shortiter.hasNext() && longname.equals(shortname))
392 			return true;
393 		if (!longiter.hasNext())
394 			return false;
395 
396 		// compare
397 		while (longname.equals(shortname) && longiter.hasNext() && shortiter.hasNext()) {
398 			longname = (LdapRdn) longiter.next();
399 			shortname = (LdapRdn) shortiter.next();
400 		}
401 
402 		// Done
403 		if (!shortiter.hasNext() && longname.equals(shortname))
404 			return true;
405 		else
406 			return false;
407 
408 	}
409 
410 	/**
411 	 * Add an LDAP path last in this DistinguishedName. E.g.:
412 	 * 
413 	 * <pre>
414 	 * DistinguishedName name1 = new DistinguishedName(&quot;c=SE, dc=jayway, dc=se&quot;);
415 	 * DistinguishedName name2 = new DistinguishedName(&quot;ou=people&quot;);
416 	 * name1.append(name2);
417 	 * </pre>
418 	 * 
419 	 * will result in <code>ou=people, c=SE, dc=jayway, dc=se</code>
420 	 * 
421 	 * @param path the path to append.
422 	 * @return this instance.
423 	 */
424 	public DistinguishedName append(DistinguishedName path) {
425 		getNames().addAll(path.getNames());
426 		return this;
427 	}
428 
429 	/**
430 	 * Append a new {@link LdapRdn} using the supplied key and value.
431 	 * 
432 	 * @param key the key of the {@link LdapRdn}.
433 	 * @param value the value of the {@link LdapRdn}.
434 	 * @return this instance.
435 	 */
436 	public DistinguishedName append(String key, String value) {
437 		add(key, value);
438 		return this;
439 	}
440 
441 	/**
442 	 * Add an LDAP path first in this DistinguishedName. E.g.:
443 	 * 
444 	 * <pre>
445 	 * DistinguishedName name1 = new DistinguishedName(&quot;ou=people&quot;);
446 	 * DistinguishedName name2 = new DistinguishedName(&quot;c=SE, dc=jayway, dc=se&quot;);
447 	 * name1.prepend(name2);
448 	 * </pre>
449 	 * 
450 	 * will result in <code>ou=people, c=SE, dc=jayway, dc=se</code>
451 	 * 
452 	 * @param path the path to prepend.
453 	 */
454 	public void prepend(DistinguishedName path) {
455 		ListIterator i = path.getNames().listIterator(path.getNames().size());
456 		while (i.hasPrevious()) {
457 			names.add(0, i.previous());
458 		}
459 	}
460 
461 	/**
462 	 * Remove the first part of this <code>DistinguishedName</code>.
463 	 * 
464 	 * @return the removed entry.
465 	 */
466 	public LdapRdn removeFirst() {
467 		return (LdapRdn) names.remove(0);
468 	}
469 
470 	/**
471 	 * Remove the supplied path from the beginning of this
472 	 * <code>DistinguishedName</code> if this instance starts with
473 	 * <code>path</code>. Useful for stripping base path suffix from a
474 	 * <code>DistinguishedName</code>.
475 	 * 
476 	 * @param path the path to remove from the beginning of this instance.
477 	 */
478 	public void removeFirst(Name path) {
479 		if (path != null && this.startsWith(path)) {
480 			for (int i = 0; i < path.size(); i++)
481 				this.removeFirst();
482 		}
483 	}
484 
485 	/**
486 	 * @see java.lang.Object#clone()
487 	 */
488 	public Object clone() {
489 		try {
490 			DistinguishedName result = (DistinguishedName) super.clone();
491 			result.names = new LinkedList(names);
492 			return result;
493 		}
494 		catch (CloneNotSupportedException e) {
495 			log.fatal("CloneNotSupported thrown from superclass - this should not happen");
496 			throw new RuntimeException("Fatal error in clone", e);
497 		}
498 	}
499 
500 	/**
501 	 * @see java.lang.Object#equals(java.lang.Object)
502 	 */
503 	public boolean equals(Object obj) {
504 		// A subclass with identical values should NOT be considered equal.
505 		// EqualsBuilder in commons-lang cannot handle subclasses correctly.
506 		if (obj == null || obj.getClass() != this.getClass()) {
507 			return false;
508 		}
509 
510 		DistinguishedName name = (DistinguishedName) obj;
511 
512 		// compare the lists
513 		return getNames().equals(name.getNames());
514 	}
515 
516 	/**
517 	 * @see java.lang.Object#hashCode()
518 	 */
519 	public int hashCode() {
520 		return this.getClass().hashCode() ^ getNames().hashCode();
521 	}
522 
523 	/**
524 	 * Compare this instance to another object. Note that the comparison is done
525 	 * in order of significance, so the most significant Rdn is compared first,
526 	 * then the second and so on.
527 	 * 
528 	 * @see javax.naming.Name#compareTo(java.lang.Object)
529 	 */
530 	public int compareTo(Object obj) {
531 		DistinguishedName that = (DistinguishedName) obj;
532 		ListComparator comparator = new ListComparator();
533 		return comparator.compare(this.names, that.names);
534 	}
535 
536 	public int size() {
537 		return names.size();
538 	}
539 
540 	public boolean isEmpty() {
541 		return names.size() == 0;
542 	}
543 
544 	/*
545 	 * (non-Javadoc)
546 	 * 
547 	 * @see javax.naming.Name#getAll()
548 	 */
549 	public Enumeration getAll() {
550 		LinkedList strings = new LinkedList();
551 		for (Iterator iter = names.iterator(); iter.hasNext();) {
552 			LdapRdn rdn = (LdapRdn) iter.next();
553 			strings.add(rdn.getLdapEncoded());
554 		}
555 
556 		return Collections.enumeration(strings);
557 	}
558 
559 	/*
560 	 * (non-Javadoc)
561 	 * 
562 	 * @see javax.naming.Name#get(int)
563 	 */
564 	public String get(int index) {
565 		LdapRdn rdn = (LdapRdn) names.get(index);
566 		return rdn.getLdapEncoded();
567 	}
568 
569 	/*
570 	 * (non-Javadoc)
571 	 * 
572 	 * @see javax.naming.Name#getPrefix(int)
573 	 */
574 	public Name getPrefix(int index) {
575 		LinkedList newNames = new LinkedList();
576 		for (int i = 0; i < index; i++) {
577 			newNames.add(names.get(i));
578 		}
579 
580 		return new DistinguishedName(newNames);
581 	}
582 
583 	/*
584 	 * (non-Javadoc)
585 	 * 
586 	 * @see javax.naming.Name#getSuffix(int)
587 	 */
588 	public Name getSuffix(int index) {
589 		if (index > names.size()) {
590 			throw new ArrayIndexOutOfBoundsException();
591 		}
592 
593 		LinkedList newNames = new LinkedList();
594 		for (int i = index; i < names.size(); i++) {
595 			newNames.add(names.get(i));
596 		}
597 
598 		return new DistinguishedName(newNames);
599 	}
600 
601 	/*
602 	 * (non-Javadoc)
603 	 * 
604 	 * @see javax.naming.Name#startsWith(javax.naming.Name)
605 	 */
606 	public boolean startsWith(Name name) {
607 		if (name.size() == 0) {
608 			return false;
609 		}
610 
611 		DistinguishedName start = null;
612 		if (name instanceof DistinguishedName) {
613 			start = (DistinguishedName) name;
614 		}
615 		else {
616 			return false;
617 		}
618 
619 		if (start.size() > this.size()) {
620 			return false;
621 		}
622 
623 		Iterator longiter = names.iterator();
624 		Iterator shortiter = start.getNames().iterator();
625 
626 		while (shortiter.hasNext()) {
627 			Object longname = longiter.next();
628 			Object shortname = shortiter.next();
629 
630 			if (!longname.equals(shortname)) {
631 				return false;
632 			}
633 		}
634 
635 		// All names in shortiter matched.
636 		return true;
637 	}
638 
639 	/**
640 	 * Determines if this <code>DistinguishedName</code> ends with a certian
641 	 * path.
642 	 * 
643 	 * If the argument path is empty (no names in path) this method will return
644 	 * <code>false</code>.
645 	 * 
646 	 * @param name The suffix to check for.
647 	 * 
648 	 */
649 	public boolean endsWith(Name name) {
650 		DistinguishedName path = null;
651 		if (name instanceof DistinguishedName) {
652 			path = (DistinguishedName) name;
653 		}
654 		else {
655 			return false;
656 		}
657 
658 		List shortlist = path.getNames();
659 
660 		// this path must be at least as long
661 		if (getNames().size() < shortlist.size())
662 			return false;
663 
664 		// must have names
665 		if (shortlist.size() == 0)
666 			return false;
667 
668 		ListIterator longiter = getNames().listIterator(getNames().size());
669 		ListIterator shortiter = shortlist.listIterator(shortlist.size());
670 
671 		while (shortiter.hasPrevious()) {
672 			LdapRdn longname = (LdapRdn) longiter.previous();
673 			LdapRdn shortname = (LdapRdn) shortiter.previous();
674 
675 			if (!longname.equals(shortname))
676 				return false;
677 		}
678 
679 		// if short list ended, all were equal
680 		return true;
681 
682 	}
683 
684 	/*
685 	 * (non-Javadoc)
686 	 * 
687 	 * @see javax.naming.Name#addAll(javax.naming.Name)
688 	 */
689 	public Name addAll(Name name) throws InvalidNameException {
690 		return addAll(names.size(), name);
691 	}
692 
693 	/*
694 	 * (non-Javadoc)
695 	 * 
696 	 * @see javax.naming.Name#addAll(int, javax.naming.Name)
697 	 */
698 	public Name addAll(int arg0, Name name) throws InvalidNameException {
699 		DistinguishedName distinguishedName = null;
700 		try {
701 			distinguishedName = (DistinguishedName) name;
702 		}
703 		catch (ClassCastException e) {
704 			throw new InvalidNameException("Invalid name type");
705 		}
706 
707 		names.addAll(arg0, distinguishedName.getNames());
708 		return this;
709 	}
710 
711 	/*
712 	 * (non-Javadoc)
713 	 * 
714 	 * @see javax.naming.Name#add(java.lang.String)
715 	 */
716 	public Name add(String string) throws InvalidNameException {
717 		return add(names.size(), string);
718 	}
719 
720 	/*
721 	 * (non-Javadoc)
722 	 * 
723 	 * @see javax.naming.Name#add(int, java.lang.String)
724 	 */
725 	public Name add(int index, String string) throws InvalidNameException {
726 		try {
727 			names.add(index, new LdapRdn(string));
728 		}
729 		catch (BadLdapGrammarException e) {
730 			throw new InvalidNameException("Failed to parse rdn '" + string + "'");
731 		}
732 		return this;
733 	}
734 
735 	/*
736 	 * (non-Javadoc)
737 	 * 
738 	 * @see javax.naming.Name#remove(int)
739 	 */
740 	public Object remove(int arg0) throws InvalidNameException {
741 		LdapRdn rdn = (LdapRdn) names.remove(arg0);
742 		return rdn.getLdapEncoded();
743 	}
744 
745 	/**
746 	 * Remove the last part of this <code>DistinguishedName</code>.
747 	 * 
748 	 * @return the removed {@link LdapRdn}.
749 	 */
750 	public LdapRdn removeLast() {
751 		return (LdapRdn) names.remove(names.size() - 1);
752 	}
753 
754 	/**
755 	 * Add a new {@link LdapRdn} using the supplied key and value.
756 	 * 
757 	 * @param key the key of the {@link LdapRdn}.
758 	 * @param value the value of the {@link LdapRdn}.
759 	 */
760 	public void add(String key, String value) {
761 		names.add(new LdapRdn(key, value));
762 	}
763 
764 	/**
765 	 * Add the supplied {@link LdapRdn} last in the list of Rdns.
766 	 * 
767 	 * @param rdn the {@link LdapRdn} to add.
768 	 */
769 	public void add(LdapRdn rdn) {
770 		names.add(rdn);
771 	}
772 
773 	/**
774 	 * Add the supplied {@link LdapRdn} att the specified index.
775 	 * 
776 	 * @param idx the index at which to add the LdapRdn.
777 	 * @param rdn the LdapRdn to add.
778 	 */
779 	public void add(int idx, LdapRdn rdn) {
780 		names.add(idx, rdn);
781 	}
782 
783 	/**
784 	 * Return an immutable copy of this instance. It will not be possible to add
785 	 * or remove any Rdns to or from the returned instance, and the respective
786 	 * Rdns will also be immutable in turn.
787 	 * 
788 	 * @return a copy of this instance backed by an immutable list.
789 	 * @since 1.2
790 	 */
791 	public DistinguishedName immutableDistinguishedName() {
792 		List listWithImmutableRdns = new ArrayList(names.size());
793 		for (Iterator iterator = names.iterator(); iterator.hasNext();) {
794 			LdapRdn rdn = (LdapRdn) iterator.next();
795 			listWithImmutableRdns.add(rdn.immutableLdapRdn());
796 		}
797 
798 		return new DistinguishedName(Collections.unmodifiableList(listWithImmutableRdns));
799 	}
800 
801 	/**
802 	 * Create an immutable DistinguishedName instance, suitable as a constant.
803 	 * 
804 	 * @param dnString the DN string to parse.
805 	 * @return an immutable DistinguishedName corresponding to the supplied DN
806 	 * string.
807 	 * @since 1.3
808 	 */
809 	public static final DistinguishedName immutableDistinguishedName(String dnString) {
810 		return new DistinguishedName(dnString).immutableDistinguishedName();
811 	}
812 }