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