View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    https://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  
21  package org.apache.directory.api.ldap.model.name;
22  
23  
24  import java.io.Externalizable;
25  import java.io.IOException;
26  import java.io.ObjectInput;
27  import java.io.ObjectOutput;
28  import java.util.ArrayList;
29  import java.util.Iterator;
30  import java.util.List;
31  
32  import org.apache.commons.collections4.list.UnmodifiableList;
33  import org.apache.directory.api.i18n.I18n;
34  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
35  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
36  import org.apache.directory.api.ldap.model.schema.SchemaManager;
37  import org.apache.directory.api.util.Strings;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  
42  /**
43   * The Dn class contains a Dn (Distinguished Name). This class is immutable.
44   * <br>
45   * Its specification can be found in RFC 2253,
46   * "UTF-8 String Representation of Distinguished Names".
47   * <br>
48   * We will store two representation of a Dn :
49   * <ul>
50   * <li>a user Provider representation, which is the parsed String given by a user</li>
51   * <li>an internal representation.</li>
52   * </ul>
53   *
54   * A Dn is formed of RDNs, in a specific order :<br>
55   *  Rdn[n], Rdn[n-1], ... Rdn[1], Rdn[0]<br>
56   *
57   * It represents a position in a hierarchy, in which the root is the last Rdn (Rdn[0]) and the leaf
58   * is the first Rdn (Rdn[n]).
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   */
62  public class Dn implements Iterable<Rdn>, Externalizable
63  {
64      /** The LoggerFactory used by this class */
65      protected static final Logger LOG = LoggerFactory.getLogger( Dn.class );
66  
67      /**
68       * Declares the Serial Version Uid.
69       *
70       * @see <a
71       *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
72       *      Declare Serial Version Uid</a>
73       */
74      private static final long serialVersionUID = 1L;
75  
76      /** Value returned by the compareTo method if values are not equals */
77      public static final int NOT_EQUAL = -1;
78  
79      /** Value returned by the compareTo method if values are equals */
80      public static final int EQUAL = 0;
81  
82      /**
83       *  The RDNs that are elements of the Dn<br>
84       * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!<br>
85       * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0)
86       * <br>
87       * For instance,if the Dn is "dc=c, dc=b, dc=a", then the RDNs are stored as :
88       * <ul>
89       * <li>[0] : dc=c</li>
90       * <li>[1] : dc=b</li>
91       * <li>[2] : dc=a</li>
92       * </ul>
93       */
94      protected transient List<Rdn> rdns = new ArrayList<>( 5 );
95  
96      /** The user provided name */
97      private String upName;
98  
99      /** The normalized name */
100     private String normName;
101 
102     /** A null Dn */
103     public static final Dn EMPTY_DN = new Dn();
104 
105     /** The rootDSE */
106     public static final Dn ROOT_DSE = new Dn();
107 
108     /** the schema manager */
109     private transient SchemaManager schemaManager;
110     
111     /** Two constants used to trim the DN UpName */
112     private static final boolean LEFT = true;
113     private static final boolean RIGHT = false;
114 
115     /**
116      * An iterator over RDNs
117      */
118     private final class RdnIterator implements Iterator<Rdn>
119     {
120         // The current index
121         int index;
122 
123 
124         private RdnIterator()
125         {
126             index = rdns != null ? rdns.size() - 1 : -1;
127         }
128 
129 
130         /**
131          * {@inheritDoc}
132          */
133         @Override
134         public boolean hasNext()
135         {
136             return index >= 0;
137         }
138 
139 
140         /**
141          * {@inheritDoc}
142          */
143         @Override
144         public Rdn next()
145         {
146             return index >= 0 ? rdns.get( index-- ) : null;
147         }
148 
149 
150         /**
151          * {@inheritDoc}
152          */
153         @Override
154         public void remove()
155         {
156             // Not implemented
157         }
158     }
159 
160 
161     /**
162      * Construct an empty Dn object
163      */
164     public Dn()
165     {
166         this( ( SchemaManager ) null );
167     }
168 
169 
170     /**
171      * Construct an empty Schema aware Dn object
172      *
173      *  @param schemaManager The SchemaManager to use
174      */
175     public Dn( SchemaManager schemaManager )
176     {
177         this.schemaManager = schemaManager;
178         upName = "";
179         normName = "";
180     }
181 
182 
183     /**
184      * Construct an empty Schema aware Dn object
185      *
186      *  @param schemaManager The SchemaManager to use
187      *  @param dn The Dn to use
188      *  @throws LdapInvalidDnException If the Dn is invalid
189      */
190     public Dn( SchemaManager schemaManager, Dn dn ) throws LdapInvalidDnException
191     {
192         this.schemaManager = schemaManager;
193 
194         if ( dn == null )
195         {
196             return;
197         }
198 
199         for ( Rdn rdn : dn.rdns )
200         {
201             this.rdns.add( new Rdn( schemaManager, rdn ) );
202         }
203 
204         upName = toUpName();
205     }
206 
207 
208     /**
209      * Creates a new instance of Dn, using varargs to declare the RDNs. Each
210      * String is either a full Rdn, or a couple of AttributeType DI and a value.
211      * If the String contains a '=' symbol, the the constructor will assume that
212      * the String arg contains afull Rdn, otherwise, it will consider that the
213      * following arg is the value.<br>
214      * The created Dn is Schema aware.
215      * <br><br>
216      * An example of usage would be :
217      * <pre>
218      * String exampleName = "example";
219      * String baseDn = "dc=apache,dc=org";
220      *
221      * Dn dn = new Dn( DefaultSchemaManager.INSTANCE,
222      *     "cn=Test",
223      *     "ou", exampleName,
224      *     baseDn);
225      * </pre>
226      *
227      * @param upRdns The list of String composing the Dn
228      * @throws LdapInvalidDnException If the resulting Dn is invalid
229      */
230     public Dn( String... upRdns ) throws LdapInvalidDnException
231     {
232         this( null, upRdns );
233     }
234 
235 
236     /**
237      * Creates a new instance of schema aware Dn, using varargs to declare the RDNs. Each
238      * String is either a full Rdn, or a couple of AttributeType DI and a value.
239      * If the String contains a '=' symbol, the the constructor will assume that
240      * the String arg contains afull Rdn, otherwise, it will consider that the
241      * following arg is the value.<br>
242      * The created Dn is Schema aware.
243      * <br><br>
244      * An example of usage would be :
245      * <pre>
246      * String exampleName = "example";
247      * String baseDn = "dc=apache,dc=org";
248      *
249      * Dn dn = new Dn( DefaultSchemaManager.INSTANCE,
250      *     "cn=Test",
251      *     "ou", exampleName,
252      *     baseDn);
253      * </pre>
254      *
255      * @param schemaManager the schema manager
256      * @param upRdns The list of String composing the Dn
257      * @throws LdapInvalidDnException If the resulting Dn is invalid
258      */
259     public Dn( SchemaManager schemaManager, String... upRdns ) throws LdapInvalidDnException
260     {
261         StringBuilder sbUpName = new StringBuilder();
262         boolean valueExpected = false;
263         boolean isFirst = true;
264         this.schemaManager = schemaManager;
265 
266         for ( String upRdn : upRdns )
267         {
268             if ( Strings.isEmpty( upRdn ) )
269             {
270                 continue;
271             }
272 
273             if ( isFirst )
274             {
275                 isFirst = false;
276             }
277             else if ( !valueExpected )
278             {
279                 sbUpName.append( ',' );
280             }
281 
282             if ( !valueExpected )
283             {
284                 sbUpName.append( upRdn );
285 
286                 if ( upRdn.indexOf( '=' ) == -1 )
287                 {
288                     valueExpected = true;
289                 }
290             }
291             else
292             {
293                 sbUpName.append( "=" ).append( upRdn );
294 
295                 valueExpected = false;
296             }
297         }
298 
299         if ( !isFirst && valueExpected )
300         {
301             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_13611_VALUE_MISSING_ON_RDN ) );
302         }
303 
304         // Stores the representations of a Dn : internal (as a string and as a
305         // byte[]) and external.
306         upName = sbUpName.toString();
307 
308         try
309         {
310             normName = parseInternal( schemaManager, upName, rdns );
311         }
312         catch ( LdapInvalidDnException e )
313         {
314             if ( schemaManager == null || !schemaManager.isRelaxed() )
315             {
316                 throw e;
317             }
318             // Ignore invalid DN formats in relaxed mode.
319             // This is needed to support unbelievably insane
320             // DN formats such as <GUI=abcd...> format used by
321             // Active Directory
322         }
323     }
324 
325 
326     /**
327      * Creates a Dn from a list of Rdns.
328      *
329      * @param rdns the list of Rdns to be used for the Dn
330      * @throws LdapInvalidDnException If the resulting Dn is invalid
331      */
332     public Dn( Rdn... rdns ) throws LdapInvalidDnException
333     {
334         if ( rdns == null )
335         {
336             return;
337         }
338 
339         for ( Rdn rdn : rdns )
340         {
341             this.rdns.add( rdn );
342         }
343 
344         toUpName();
345     }
346 
347 
348     /**
349      * Creates a Dn concatenating a Rdn and a Dn.
350      *
351      * @param rdn the Rdn to add to the Dn
352      * @param dn the Dn
353      * @throws LdapInvalidDnException If the resulting Dn is invalid
354      */
355     public Dn( Rdn rdn, Dn dn ) throws LdapInvalidDnException
356     {
357         if ( ( dn == null ) || ( rdn == null ) )
358         {
359             throw new IllegalArgumentException( I18n.err( I18n.ERR_13622_DN_OR_RDN_NULL ) );
360         }
361 
362         for ( Rdn rdnParent : dn )
363         {
364             rdns.add( 0, rdnParent );
365         }
366 
367         rdns.add( 0, rdn );
368 
369         toUpName();
370     }
371 
372 
373     /**
374      * Creates a Schema aware Dn from a list of Rdns.
375      *
376      * @param schemaManager The SchemaManager to use
377      * @param rdns the list of Rdns to be used for the Dn
378      * @throws LdapInvalidDnException If the resulting Dn is invalid
379      */
380     public Dn( SchemaManager schemaManager, Rdn... rdns ) throws LdapInvalidDnException
381     {
382         this.schemaManager = schemaManager;
383 
384         if ( rdns == null )
385         {
386             return;
387         }
388 
389         for ( Rdn rdn : rdns )
390         {
391             if ( rdn.isSchemaAware() )
392             {
393                 this.rdns.add( rdn );
394             }
395             else
396             {
397                 this.rdns.add( new Rdn( schemaManager, rdn ) );
398             }
399         }
400 
401         toUpName();
402     }
403 
404 
405     /**
406      * Get the associated SchemaManager if any.
407      *
408      * @return The SchemaManager
409      */
410     public SchemaManager getSchemaManager()
411     {
412         return schemaManager;
413     }
414 
415 
416     /**
417      * Return the User Provided Dn as a String,
418      *
419      * @return A String representing the User Provided Dn
420      */
421     private String toUpName()
422     {
423         if ( rdns.isEmpty() )
424         {
425             upName = "";
426             normName = "";
427         }
428         else
429         {
430             StringBuilder sbUpName = new StringBuilder();
431             StringBuilder sbNormName = new StringBuilder();
432             boolean isFirst = true;
433 
434             for ( Rdn rdn : rdns )
435             {
436                 if ( isFirst )
437                 {
438                     isFirst = false;
439                 }
440                 else
441                 {
442                     sbUpName.append( ',' );
443                     sbNormName.append( ',' );
444                 }
445 
446                 sbUpName.append( rdn.getName() );
447                 sbNormName.append( rdn.getNormName() );
448             }
449 
450             upName = sbUpName.toString();
451             normName = sbNormName.toString();
452         }
453 
454         return upName;
455     }
456 
457 
458     /**
459      * Gets the hash code of this Dn.
460      *
461      * @see java.lang.Object#hashCode()
462      * @return the instance hash code
463      */
464     @Override
465     public int hashCode()
466     {
467         int result = 37;
468 
469         for ( Rdn rdn : rdns )
470         {
471             result = result * 17 + rdn.hashCode();
472         }
473 
474         return result;
475     }
476 
477 
478     /**
479      * Get the user provided Dn
480      *
481      * @return The user provided Dn as a String
482      */
483     public String getName()
484     {
485         return upName == null ? "" : upName;
486     }
487 
488 
489     /**
490      * Get the normalized Dn
491      *
492      * @return The normalized Dn as a String
493      */
494     public String getNormName()
495     {
496         return normName == null ? "" : normName;
497     }
498 
499 
500     /**
501      * @return The RDN as an escaped String
502      */
503     public String getEscaped()
504     {
505         StringBuilder sb = new StringBuilder();
506 
507         boolean isFirst = true;
508 
509         for ( Rdn rdn : rdns )
510         {
511             if ( isFirst )
512             {
513                 isFirst = false;
514             }
515             else
516             {
517                 sb.append( ',' );
518             }
519 
520             sb.append( rdn.getEscaped() );
521         }
522 
523         return sb.toString();
524     }
525 
526 
527     /**
528      * Sets the up name.
529      *
530      * Package private because Dn is immutable, only used by the Dn parser.
531      *
532      * @param upName the new up name
533      */
534     /* No qualifier */void setUpName( String upName )
535     {
536         this.upName = upName;
537     }
538 
539 
540     /**
541      * Sets the normalized name.
542      *
543      * Package private because Dn is immutable, only used by the Dn parser.
544      *
545      * @param normName the new normalized name
546      */
547     /* No qualifier */void setNormName( String normName )
548     {
549         this.normName = normName;
550     }
551 
552 
553     /**
554      * Get the number of RDNs present in the DN
555      * @return The umber of RDNs in the DN
556      */
557     public int size()
558     {
559         return rdns.size();
560     }
561 
562 
563     /**
564      * Tells if the current Dn is a parent of another Dn.<br>
565      * For instance, <b>dc=com</b> is a ancestor
566      * of <b>dc=example, dc=com</b>
567      *
568      * @param dn The child
569      * @return true if the current Dn is a parent of the given Dn
570      */
571     public boolean isAncestorOf( String dn )
572     {
573         try
574         {
575             return isAncestorOf( new Dn( dn ) );
576         }
577         catch ( LdapInvalidDnException lide )
578         {
579             return false;
580         }
581     }
582 
583 
584     /**
585      * Tells if the current Dn is a parent of another Dn.<br>
586      * For instance, <b>dc=com</b> is a ancestor
587      * of <b>dc=example, dc=com</b>
588      *
589      * @param dn The child
590      * @return true if the current Dn is a parent of the given Dn
591      */
592     public boolean isAncestorOf( Dn dn )
593     {
594         if ( dn == null )
595         {
596             return false;
597         }
598 
599         return dn.isDescendantOf( this );
600     }
601 
602 
603     /**
604      * Tells if a Dn is a child of another Dn.<br>
605      * For instance, <b>dc=example, dc=com</b> is a descendant
606      * of <b>dc=com</b>
607      *
608      * @param dn The parent
609      * @return true if the current Dn is a child of the given Dn
610      */
611     public boolean isDescendantOf( String dn )
612     {
613         try
614         {
615             return isDescendantOf( new Dn( schemaManager, dn ) );
616         }
617         catch ( LdapInvalidDnException lide )
618         {
619             return false;
620         }
621     }
622 
623 
624     /**
625      * Tells if a Dn is a child of another Dn.<br>
626      * For instance, <b>dc=example, dc=apache, dc=com</b> is a descendant
627      * of <b>dc=com</b>
628      *
629      * @param dn The parent
630      * @return true if the current Dn is a child of the given Dn
631      */
632     public boolean isDescendantOf( Dn dn )
633     {
634         if ( ( dn == null ) || dn.isRootDse() )
635         {
636             return true;
637         }
638 
639         if ( dn.size() > size() )
640         {
641             // The name is longer than the current Dn.
642             return false;
643         }
644 
645         // Ok, iterate through all the Rdn of the name,
646         // starting a the end of the current list.
647 
648         for ( int i = dn.size() - 1; i >= 0; i-- )
649         {
650             Rdn nameRdn = dn.rdns.get( dn.rdns.size() - i - 1 );
651             Rdn ldapRdn = rdns.get( rdns.size() - i - 1 );
652 
653             if ( !nameRdn.equals( ldapRdn ) )
654             {
655                 return false;
656             }
657         }
658 
659         return true;
660     }
661 
662 
663     /**
664      * Tells if the Dn contains no Rdn
665      *
666      * @return <code>true</code> if the Dn is empty
667      */
668     public boolean isEmpty()
669     {
670         return rdns.isEmpty();
671     }
672 
673 
674     /**
675      * Tells if the Dn is the RootDSE Dn (ie, an empty Dn)
676      *
677      * @return <code>true</code> if the Dn is the RootDSE's Dn
678      */
679     public boolean isRootDse()
680     {
681         return rdns.isEmpty();
682     }
683 
684 
685     /**
686      * Retrieves a component of this name.
687      *
688      * @param posn the 0-based index of the component to retrieve. Must be in the
689      *            range [0,size()).
690      * @return the component at index posn
691      * @throws ArrayIndexOutOfBoundsException
692      *             if posn is outside the specified range
693      */
694     public Rdn getRdn( int posn )
695     {
696         if ( rdns.isEmpty() )
697         {
698             return null;
699         }
700 
701         if ( ( posn < 0 ) || ( posn >= rdns.size() ) )
702         {
703             throw new IllegalArgumentException( I18n.err( I18n.ERR_13623_INVALID_POSITION, posn ) );
704         }
705 
706         return rdns.get( posn );
707     }
708 
709 
710     /**
711      * Retrieves the last (leaf) component of this name.
712      *
713      * @return the last component of this Dn
714      */
715     public Rdn getRdn()
716     {
717         if ( isNullOrEmpty( this ) )
718         {
719             return Rdn.EMPTY_RDN;
720         }
721 
722         return rdns.get( 0 );
723     }
724 
725 
726     /**
727      * Retrieves all the components of this name.
728      *
729      * @return All the components
730      */
731     public List<Rdn> getRdns()
732     {
733         return UnmodifiableList.unmodifiableList( rdns );
734     }
735 
736 
737     /**
738      * Get the descendant of a given DN, using the ancestr DN. Assuming that
739      * a DN has two parts :<br>
740      * DN = [descendant DN][ancestor DN]<br>
741      * To get back the descendant from the full DN, you just pass the ancestor DN
742      * as a parameter. Here is a working example :
743      * <pre>
744      * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
745      *
746      * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" );
747      *
748      * // At this point, the descendant contains cn=test, dc=server, dc=directory"
749      * </pre>
750      *
751      * @param ancestor The parent DN
752      * @return The part of the DN that is the descendant
753      * @throws LdapInvalidDnException If the Dn is invalid
754      */
755     public Dn getDescendantOf( String ancestor ) throws LdapInvalidDnException
756     {
757         return getDescendantOf( new Dn( schemaManager, ancestor ) );
758     }
759 
760 
761     /**
762      * Get the descendant of a given DN, using the ancestor DN. Assuming that
763      * a DN has two parts :<br>
764      * DN = [descendant DN][ancestor DN]<br>
765      * To get back the descendant from the full DN, you just pass the ancestor DN
766      * as a parameter. Here is a working example :
767      * <pre>
768      * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
769      *
770      * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" );
771      *
772      * // At this point, the descendant contains cn=test, dc=server, dc=directory"
773      * </pre>
774      *
775      * @param ancestor The parent DN
776      * @return The part of the DN that is the descendant
777      * @throws LdapInvalidDnException If the Dn is invalid
778      */
779     public Dn getDescendantOf( Dn ancestor ) throws LdapInvalidDnException
780     {
781         if ( ( ancestor == null ) || ( ancestor.size() == 0 ) )
782         {
783             return this;
784         }
785 
786         if ( rdns.isEmpty() )
787         {
788             return EMPTY_DN;
789         }
790 
791         int length = ancestor.size();
792 
793         if ( length > rdns.size() )
794         {
795             String message = I18n.err( I18n.ERR_13612_POSITION_NOT_IN_RANGE, length, rdns.size() );
796             LOG.error( message );
797             throw new ArrayIndexOutOfBoundsException( message );
798         }
799 
800         Dn newDn = new Dn( schemaManager );
801         List<Rdn> rdnsAncestor = ancestor.getRdns();
802 
803         for ( int i = 0; i < ancestor.size(); i++ )
804         {
805             Rdn rdn = rdns.get( size() - 1 - i );
806             Rdn rdnDescendant = rdnsAncestor.get( ancestor.size() - 1 - i );
807 
808             if ( !rdn.equals( rdnDescendant ) )
809             {
810                 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX );
811             }
812         }
813 
814         // Short cut: if the last RDNs are equal, return ""
815         if ( rdns.get( 0 ).equals( rdnsAncestor.get( 0 ) ) )
816         {
817             return newDn;
818         }
819         
820         for ( int i = 0; i < rdns.size() - length; i++ )
821         {
822             newDn.rdns.add( rdns.get( i ) );
823         }
824 
825         newDn.toUpName();
826 
827         return newDn;
828     }
829 
830 
831     /**
832      * Get the ancestor of a given DN, using the descendant DN. Assuming that
833      * a DN has two parts :<br>
834      * DN = [descendant DN][ancestor DN]<br>
835      * To get back the ancestor from the full DN, you just pass the descendant DN
836      * as a parameter. Here is a working example :
837      * <pre>
838      * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
839      *
840      * Dn ancestor = dn.getAncestorOf( "cn=test, dc=server, dc=directory" );
841      *
842      * // At this point, the ancestor contains "dc=apache, dc=org"
843      * </pre>
844      *
845      * @param descendant The child DN
846      * @return The part of the DN that is the ancestor
847      * @throws LdapInvalidDnException If the Dn is invalid
848      */
849     public Dn getAncestorOf( String descendant ) throws LdapInvalidDnException
850     {
851         return getAncestorOf( new Dn( schemaManager, descendant ) );
852     }
853 
854 
855     /**
856      * Get the ancestor of a given DN, using the descendant DN. Assuming that
857      * a DN has two parts :<br>
858      * DN = [descendant DN][ancestor DN]<br>
859      * To get back the ancestor from the full DN, you just pass the descendant DN
860      * as a parameter. Here is a working example :
861      * <pre>
862      * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
863      *
864      * Dn ancestor = dn.getAncestorOf( new Dn( "cn=test, dc=server, dc=directory" ) );
865      *
866      * // At this point, the ancestor contains "dc=apache, dc=org"
867      * </pre>
868      *
869      * @param descendant The child DN
870      * @return The part of the DN that is the ancestor
871      * @throws LdapInvalidDnException If the Dn is invalid
872      */
873     public Dn getAncestorOf( Dn descendant ) throws LdapInvalidDnException
874     {
875         int length = descendant.size();
876 
877         if ( ( descendant == null ) || ( length == 0 ) )
878         {
879             return this;
880         }
881 
882         if ( rdns.isEmpty() || length == rdns.size() )
883         {
884             return EMPTY_DN;
885         }
886 
887         if ( length > rdns.size() )
888         {
889             String message = I18n.err( I18n.ERR_13612_POSITION_NOT_IN_RANGE, length, rdns.size() );
890             LOG.error( message );
891             throw new ArrayIndexOutOfBoundsException( message );
892         }
893         
894         Dn newDn = new Dn( schemaManager );
895         List<Rdn> rdnsDescendant = descendant.getRdns();
896 
897         for ( int i = 0; i < descendant.size(); i++ )
898         {
899             Rdn rdn = rdns.get( i );
900             Rdn rdnDescendant = rdnsDescendant.get( i );
901 
902             if ( !rdn.equals( rdnDescendant ) )
903             {
904                 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX );
905             }
906         }
907         
908         for ( int i = length; i < rdns.size(); i++ )
909         {
910             newDn.rdns.add( rdns.get( i ) );
911         }
912         
913         newDn.toUpName();
914 
915         //newDn.upName = upName.substring( descendant.upName.length() + 1 );
916 
917         return newDn;
918     }
919 
920 
921     /**
922      * Adds all RDNs of the provided DN to the (leaf) end of this name.
923      * For instance, if the current Dn is "dc=example,dc=com",
924      * and the rdns "ou=people", then the resulting Dn will be
925      * "ou=people,dc=example,dc=com".
926      *
927      * @param rdns the RDNs to add
928      * @return the updated cloned Dn
929      * @throws LdapInvalidDnException If the resulting Dn is not valid
930      */
931     public Dn add( Dn rdns ) throws LdapInvalidDnException
932     {
933         if ( ( rdns == null ) || ( rdns.size() == 0 ) )
934         {
935             return this;
936         }
937 
938         Dn clonedDn = copy();
939 
940         // Concatenate the rdns
941         clonedDn.rdns.addAll( 0, rdns.rdns );
942 
943         // Regenerate the normalized name and the original string
944         if ( clonedDn.isSchemaAware() && rdns.isSchemaAware() )
945         {
946             if ( clonedDn.size() != 0 )
947             {
948                 clonedDn.upName = rdns.getName() + "," + upName;
949             }
950         }
951         else
952         {
953             clonedDn.toUpName();
954         }
955 
956         return clonedDn;
957     }
958 
959 
960     /**
961      * Adds a single Rdn to the (leaf) end of this name.
962      * For instance, if the current Dn is "dc=example,dc=com",
963      * and the rdn "ou=people", then the resulting Dn will be
964      * "ou=people,dc=example,dc=com".
965      *
966      * @param rdn the Rdn to add
967      * @return the updated cloned Dn
968      * @throws LdapInvalidDnException If the resulting Dn is not valid
969      */
970     public Dn add( String rdn ) throws LdapInvalidDnException
971     {
972         if ( rdn.length() == 0 )
973         {
974             return this;
975         }
976 
977         Dn clonedDn = copy();
978 
979         // We have to parse the nameComponent which is given as an argument
980         Rdn newRdn = new Rdn( schemaManager, rdn );
981 
982         clonedDn.rdns.add( 0, newRdn );
983 
984         clonedDn.toUpName();
985 
986         return clonedDn;
987     }
988 
989 
990     /**
991      * Adds a single Rdn to the (leaf) end of this name.
992      *
993      * @param newRdn the Rdn to add
994      * @return the updated cloned Dn
995      * @throws LdapInvalidDnException If the Dn is invalid
996      */
997     public Dn add( Rdn newRdn ) throws LdapInvalidDnException
998     {
999         if ( ( newRdn == null ) || ( newRdn.size() == 0 ) )
1000         {
1001             return this;
1002         }
1003 
1004         Dn clonedDn = copy();
1005 
1006         clonedDn.rdns.add( 0, new Rdn( schemaManager, newRdn ) );
1007         clonedDn.toUpName();
1008 
1009         return clonedDn;
1010     }
1011 
1012 
1013     /**
1014      * Gets the parent Dn of this Dn. Null if this Dn doesn't have a parent, i.e. because it
1015      * is the empty Dn.<br>
1016      * The Parent is the right part of the Dn, when the Rdn has been removed.
1017      *
1018      * @return the parent Dn of this Dn
1019      */
1020     public Dn getParent()
1021     {
1022         if ( isNullOrEmpty( this ) )
1023         {
1024             return this;
1025         }
1026 
1027         int posn = rdns.size() - 1;
1028 
1029         Dn newDn = new Dn( schemaManager );
1030 
1031         for ( int i = rdns.size() - posn; i < rdns.size(); i++ )
1032         {
1033             newDn.rdns.add( rdns.get( i ) );
1034         }
1035 
1036         newDn.toUpName();
1037 
1038         return newDn;
1039     }
1040     
1041     
1042     private String removeUpName( String removedUpName, boolean fromLeft )
1043     {
1044         int removedSize = removedUpName.length();
1045         
1046         if ( fromLeft )
1047         {
1048             for ( int i = removedSize; i < upName.length(); i++ )
1049             {
1050                 if ( upName.charAt( i ) == ',' )
1051                 {
1052                     return upName.substring( i + 1 );
1053                 }
1054             }
1055         }
1056         else
1057         {
1058              for ( int i = upName.length() - removedSize; i > 0; i-- )
1059              {
1060                  if ( upName.charAt( i ) == ',' )
1061                  {
1062                      return upName.substring( 0, i - 1 );
1063                  }
1064              }
1065         }
1066         
1067         // Nothing left
1068         return Strings.EMPTY_STRING;
1069     }
1070 
1071 
1072     /**
1073      * Create a copy of the current Dn
1074      *
1075      * @return The copied Dn
1076      */
1077     private Dn copy()
1078     {
1079         Dn dn = new Dn( schemaManager );
1080         dn.rdns = new ArrayList<>();
1081 
1082         for ( Rdn rdn : rdns )
1083         {
1084             dn.rdns.add( rdn );
1085         }
1086 
1087         return dn;
1088     }
1089 
1090 
1091     /**
1092      * @see java.lang.Object#equals(java.lang.Object)
1093      * @return <code>true</code> if the two instances are equals
1094      */
1095     @Override
1096     public boolean equals( Object obj )
1097     {
1098         Dn other;
1099 
1100         if ( obj instanceof String )
1101         {
1102             try
1103             {
1104                 other = new Dn( schemaManager, ( String ) obj );
1105             }
1106             catch ( LdapInvalidDnException e )
1107             {
1108                 return false;
1109             }
1110         }
1111         else if ( obj instanceof Dn )
1112         {
1113             other = ( Dn ) obj;
1114         }
1115         else
1116         {
1117             return false;
1118         }
1119 
1120         if ( other.size() != this.size() )
1121         {
1122             return false;
1123         }
1124 
1125         // Shortcut if the Dn is normalized
1126         if ( isSchemaAware() )
1127         {
1128             if ( normName == null )
1129             {
1130                 // equals() should never NPE
1131                 return other.normName == null;
1132             }
1133             return normName.equals( other.normName );
1134         }
1135 
1136         for ( int i = 0; i < this.size(); i++ )
1137         {
1138             if ( !other.rdns.get( i ).equals( rdns.get( i ) ) )
1139             {
1140                 return false;
1141             }
1142         }
1143 
1144         // All components matched so we return true
1145         return true;
1146     }
1147 
1148 
1149     /**
1150      * Tells if the Dn is schema aware
1151      *
1152      * @return <code>true</code> if the Dn is schema aware.
1153      */
1154     public boolean isSchemaAware()
1155     {
1156         return schemaManager != null;
1157     }
1158 
1159 
1160     /**
1161      * Iterate over the inner Rdn. The Rdn are returned from
1162      * the rightmost to the leftmost. For instance, the following code :<br>
1163      * <pre>
1164      * Dn dn = new Dn( "sn=test, dc=apache, dc=org );
1165      *
1166      * for ( Rdn rdn : dn )
1167      * {
1168      *     System.out.println( rdn.toString() );
1169      * }
1170      * </pre>
1171      * will produce this output : <br>
1172      * <pre>
1173      * dc=org
1174      * dc=apache
1175      * sn=test
1176      * </pre>
1177      *
1178      */
1179     @Override
1180     public Iterator<Rdn> iterator()
1181     {
1182         return new RdnIterator();
1183     }
1184 
1185 
1186     /**
1187      * Check if a DistinguishedName is null or empty.
1188      *
1189      * @param dn The Dn to check
1190      * @return <code>true</code> if the Dn is null or empty, <code>false</code>
1191      * otherwise
1192      */
1193     public static boolean isNullOrEmpty( Dn dn )
1194     {
1195         return ( dn == null ) || dn.isEmpty();
1196     }
1197 
1198 
1199     /**
1200      * Check if a DistinguishedName is syntactically valid.
1201      *
1202      * @param name The Dn to validate
1203      * @return <code>true</code> if the Dn is valid, <code>false</code>
1204      * otherwise
1205      */
1206     public static boolean isValid( String name )
1207     {
1208         Dn dn = new Dn();
1209 
1210         try
1211         {
1212             parseInternal( null, name, dn.rdns );
1213             return true;
1214         }
1215         catch ( LdapInvalidDnException e )
1216         {
1217             return false;
1218         }
1219     }
1220 
1221 
1222     /**
1223      * Check if a DistinguishedName is syntactically valid.
1224      *
1225      * @param schemaManager The SchemaManager to use
1226      * @param name The Dn to validate
1227      * @return <code>true</code> if the Dn is valid, <code>false</code>
1228      * otherwise
1229      */
1230     public static boolean isValid( SchemaManager schemaManager, String name )
1231     {
1232         Dn dn = new Dn();
1233 
1234         try
1235         {
1236             parseInternal( schemaManager, name, dn.rdns );
1237             return true;
1238         }
1239         catch ( LdapInvalidDnException e )
1240         {
1241             return false;
1242         }
1243     }
1244 
1245 
1246     /**
1247      * Parse a Dn.
1248      *
1249      * @param schemaManager The SchemaManager
1250      * @param name The Dn to be parsed
1251      * @param rdns The list that will contain the RDNs
1252      * @return The nromalized Dn
1253      * @throws LdapInvalidDnException If the Dn is invalid
1254      */
1255     private static String parseInternal( SchemaManager schemaManager, String name, List<Rdn> rdns ) throws LdapInvalidDnException
1256     {
1257         try
1258         {
1259             return FastDnParser.parseDn( schemaManager, name, rdns );
1260         }
1261         catch ( TooComplexDnException e )
1262         {
1263             rdns.clear();
1264             return new ComplexDnParser().parseDn( schemaManager, name, rdns );
1265         }
1266     }
1267 
1268 
1269     /**
1270      * {@inheritDoc}
1271      */
1272     @Override
1273     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1274     {
1275         // Read the UPName
1276         upName = in.readUTF();
1277 
1278         // Read the RDNs. Is it's null, the number will be -1.
1279         int nbRdns = in.readInt();
1280 
1281         rdns = new ArrayList<>( nbRdns );
1282 
1283         for ( int i = 0; i < nbRdns; i++ )
1284         {
1285             Rdn rdn = new Rdn( schemaManager );
1286             rdn.readExternal( in );
1287             rdns.add( rdn );
1288         }
1289 
1290         toUpName();
1291     }
1292 
1293 
1294     /**
1295      * {@inheritDoc}
1296      */
1297     @Override
1298     public void writeExternal( ObjectOutput out ) throws IOException
1299     {
1300         if ( upName == null )
1301         {
1302             String message = I18n.err( I18n.ERR_13624_CANNOT_SERIALIZE_NULL_DN );
1303             LOG.error( message );
1304             throw new IOException( message );
1305         }
1306 
1307         // Write the UPName
1308         out.writeUTF( upName );
1309 
1310         // Write the RDNs.
1311         // First the number of RDNs
1312         out.writeInt( size() );
1313 
1314         // Loop on the RDNs
1315         for ( Rdn rdn : rdns )
1316         {
1317             rdn.writeExternal( out );
1318         }
1319 
1320         out.flush();
1321     }
1322 
1323 
1324     /**
1325      * Return the user provided Dn as a String. It returns the same value as the
1326      * getName method
1327      *
1328      * @return A String representing the user provided Dn
1329      */
1330     @Override
1331     public String toString()
1332     {
1333         return getName();
1334     }
1335 }