001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    https://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020
021package org.apache.directory.api.ldap.model.name;
022
023
024import java.io.Externalizable;
025import java.io.IOException;
026import java.io.ObjectInput;
027import java.io.ObjectOutput;
028import java.util.ArrayList;
029import java.util.Iterator;
030import java.util.List;
031
032import org.apache.commons.collections4.list.UnmodifiableList;
033import org.apache.directory.api.i18n.I18n;
034import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
035import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
036import org.apache.directory.api.ldap.model.schema.SchemaManager;
037import org.apache.directory.api.util.Strings;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041
042/**
043 * The Dn class contains a Dn (Distinguished Name). This class is immutable.
044 * <br>
045 * Its specification can be found in RFC 2253,
046 * "UTF-8 String Representation of Distinguished Names".
047 * <br>
048 * We will store two representation of a Dn :
049 * <ul>
050 * <li>a user Provider representation, which is the parsed String given by a user</li>
051 * <li>an internal representation.</li>
052 * </ul>
053 *
054 * A Dn is formed of RDNs, in a specific order :<br>
055 *  Rdn[n], Rdn[n-1], ... Rdn[1], Rdn[0]<br>
056 *
057 * It represents a position in a hierarchy, in which the root is the last Rdn (Rdn[0]) and the leaf
058 * is the first Rdn (Rdn[n]).
059 *
060 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
061 */
062public class Dn implements Iterable<Rdn>, Externalizable
063{
064    /** The LoggerFactory used by this class */
065    protected static final Logger LOG = LoggerFactory.getLogger( Dn.class );
066
067    /**
068     * Declares the Serial Version Uid.
069     *
070     * @see <a
071     *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
072     *      Declare Serial Version Uid</a>
073     */
074    private static final long serialVersionUID = 1L;
075
076    /** Value returned by the compareTo method if values are not equals */
077    public static final int NOT_EQUAL = -1;
078
079    /** Value returned by the compareTo method if values are equals */
080    public static final int EQUAL = 0;
081
082    /**
083     *  The RDNs that are elements of the Dn<br>
084     * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!<br>
085     * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0)
086     * <br>
087     * For instance,if the Dn is "dc=c, dc=b, dc=a", then the RDNs are stored as :
088     * <ul>
089     * <li>[0] : dc=c</li>
090     * <li>[1] : dc=b</li>
091     * <li>[2] : dc=a</li>
092     * </ul>
093     */
094    protected transient List<Rdn> rdns = new ArrayList<>( 5 );
095
096    /** The user provided name */
097    private String upName;
098
099    /** 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}