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 *    http://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     * Add a suffix to the Dn. For instance, if the current Dn is "ou=people",
911     * and the suffix "dc=example,dc=com", then the resulting Dn will be
912     * "ou=people,dc=example,dc=com"
913     *
914     * @param suffix the suffix to add
915     * @return The resulting Dn with the additional suffix
916     * @throws LdapInvalidDnException If the resulting Dn is not valid
917     */
918    public Dn add( Dn suffix ) throws LdapInvalidDnException
919    {
920        if ( ( suffix == null ) || ( suffix.size() == 0 ) )
921        {
922            return this;
923        }
924
925        Dn clonedDn = copy();
926
927        // Concatenate the rdns
928        clonedDn.rdns.addAll( 0, suffix.rdns );
929
930        // Regenerate the normalized name and the original string
931        if ( clonedDn.isSchemaAware() && suffix.isSchemaAware() )
932        {
933            if ( clonedDn.size() != 0 )
934            {
935                clonedDn.upName = suffix.getName() + "," + upName;
936            }
937        }
938        else
939        {
940            clonedDn.toUpName();
941        }
942
943        return clonedDn;
944    }
945
946
947    /**
948     * Add a suffix to the Dn. For instance, if the current Dn is "ou=people",
949     * and the suffix "dc=example,dc=com", then the resulting Dn will be
950     * "ou=people,dc=example,dc=com"
951     *
952     * @param comp the suffix to add
953     * @return The resulting Dn with the additional suffix
954     * @throws LdapInvalidDnException If the resulting Dn is not valid
955     */
956    public Dn add( String comp ) throws LdapInvalidDnException
957    {
958        if ( comp.length() == 0 )
959        {
960            return this;
961        }
962
963        Dn clonedDn = copy();
964
965        // We have to parse the nameComponent which is given as an argument
966        Rdn newRdn = new Rdn( schemaManager, comp );
967
968        clonedDn.rdns.add( 0, newRdn );
969
970        clonedDn.toUpName();
971
972        return clonedDn;
973    }
974
975
976    /**
977     * Adds a single Rdn to the (leaf) end of this name.
978     *
979     * @param newRdn the Rdn to add
980     * @return the updated cloned Dn
981     * @throws LdapInvalidDnException If the Dn is invalid
982     */
983    public Dn add( Rdn newRdn ) throws LdapInvalidDnException
984    {
985        if ( ( newRdn == null ) || ( newRdn.size() == 0 ) )
986        {
987            return this;
988        }
989
990        Dn clonedDn = copy();
991
992        clonedDn.rdns.add( 0, new Rdn( schemaManager, newRdn ) );
993        clonedDn.toUpName();
994
995        return clonedDn;
996    }
997
998
999    /**
1000     * Gets the parent Dn of this Dn. Null if this Dn doesn't have a parent, i.e. because it
1001     * is the empty Dn.<br>
1002     * The Parent is the right part of the Dn, when the Rdn has been removed.
1003     *
1004     * @return the parent Dn of this Dn
1005     */
1006    public Dn getParent()
1007    {
1008        if ( isNullOrEmpty( this ) )
1009        {
1010            return this;
1011        }
1012
1013        int posn = rdns.size() - 1;
1014
1015        Dn newDn = new Dn( schemaManager );
1016
1017        for ( int i = rdns.size() - posn; i < rdns.size(); i++ )
1018        {
1019            newDn.rdns.add( rdns.get( i ) );
1020        }
1021
1022        newDn.toUpName();
1023
1024        return newDn;
1025    }
1026
1027
1028    /**
1029     * Create a copy of the current Dn
1030     *
1031     * @return The copied Dn
1032     */
1033    private Dn copy()
1034    {
1035        Dn dn = new Dn( schemaManager );
1036        dn.rdns = new ArrayList<>();
1037
1038        for ( Rdn rdn : rdns )
1039        {
1040            dn.rdns.add( rdn );
1041        }
1042
1043        return dn;
1044    }
1045
1046
1047    /**
1048     * @see java.lang.Object#equals(java.lang.Object)
1049     * @return <code>true</code> if the two instances are equals
1050     */
1051    @Override
1052    public boolean equals( Object obj )
1053    {
1054        Dn other;
1055
1056        if ( obj instanceof String )
1057        {
1058            try
1059            {
1060                other = new Dn( schemaManager, ( String ) obj );
1061            }
1062            catch ( LdapInvalidDnException e )
1063            {
1064                return false;
1065            }
1066        }
1067        else if ( obj instanceof Dn )
1068        {
1069            other = ( Dn ) obj;
1070        }
1071        else
1072        {
1073            return false;
1074        }
1075
1076        if ( other.size() != this.size() )
1077        {
1078            return false;
1079        }
1080
1081        // Shortcut if the Dn is normalized
1082        if ( isSchemaAware() )
1083        {
1084            if ( normName == null )
1085            {
1086                // equals() should never NPE
1087                return other.normName == null;
1088            }
1089            return normName.equals( other.normName );
1090        }
1091
1092        for ( int i = 0; i < this.size(); i++ )
1093        {
1094            if ( !other.rdns.get( i ).equals( rdns.get( i ) ) )
1095            {
1096                return false;
1097            }
1098        }
1099
1100        // All components matched so we return true
1101        return true;
1102    }
1103
1104
1105    /**
1106     * Tells if the Dn is schema aware
1107     *
1108     * @return <code>true</code> if the Dn is schema aware.
1109     */
1110    public boolean isSchemaAware()
1111    {
1112        return schemaManager != null;
1113    }
1114
1115
1116    /**
1117     * Iterate over the inner Rdn. The Rdn are returned from
1118     * the rightmost to the leftmost. For instance, the following code :<br>
1119     * <pre>
1120     * Dn dn = new Dn( "sn=test, dc=apache, dc=org );
1121     *
1122     * for ( Rdn rdn : dn )
1123     * {
1124     *     System.out.println( rdn.toString() );
1125     * }
1126     * </pre>
1127     * will produce this output : <br>
1128     * <pre>
1129     * dc=org
1130     * dc=apache
1131     * sn=test
1132     * </pre>
1133     *
1134     */
1135    @Override
1136    public Iterator<Rdn> iterator()
1137    {
1138        return new RdnIterator();
1139    }
1140
1141
1142    /**
1143     * Check if a DistinguishedName is null or empty.
1144     *
1145     * @param dn The Dn to check
1146     * @return <code>true</code> if the Dn is null or empty, <code>false</code>
1147     * otherwise
1148     */
1149    public static boolean isNullOrEmpty( Dn dn )
1150    {
1151        return ( dn == null ) || dn.isEmpty();
1152    }
1153
1154
1155    /**
1156     * Check if a DistinguishedName is syntactically valid.
1157     *
1158     * @param name The Dn to validate
1159     * @return <code>true</code> if the Dn is valid, <code>false</code>
1160     * otherwise
1161     */
1162    public static boolean isValid( String name )
1163    {
1164        Dn dn = new Dn();
1165
1166        try
1167        {
1168            parseInternal( null, name, dn.rdns );
1169            return true;
1170        }
1171        catch ( LdapInvalidDnException e )
1172        {
1173            return false;
1174        }
1175    }
1176
1177
1178    /**
1179     * Check if a DistinguishedName is syntactically valid.
1180     *
1181     * @param schemaManager The SchemaManager to use
1182     * @param name The Dn to validate
1183     * @return <code>true</code> if the Dn is valid, <code>false</code>
1184     * otherwise
1185     */
1186    public static boolean isValid( SchemaManager schemaManager, String name )
1187    {
1188        Dn dn = new Dn();
1189
1190        try
1191        {
1192            parseInternal( schemaManager, name, dn.rdns );
1193            return true;
1194        }
1195        catch ( LdapInvalidDnException e )
1196        {
1197            return false;
1198        }
1199    }
1200
1201
1202    /**
1203     * Parse a Dn.
1204     *
1205     * @param schemaManager The SchemaManager
1206     * @param name The Dn to be parsed
1207     * @param rdns The list that will contain the RDNs
1208     * @return The nromalized Dn
1209     * @throws LdapInvalidDnException If the Dn is invalid
1210     */
1211    private static String parseInternal( SchemaManager schemaManager, String name, List<Rdn> rdns ) throws LdapInvalidDnException
1212    {
1213        try
1214        {
1215            return FastDnParser.parseDn( schemaManager, name, rdns );
1216        }
1217        catch ( TooComplexDnException e )
1218        {
1219            rdns.clear();
1220            return new ComplexDnParser().parseDn( schemaManager, name, rdns );
1221        }
1222    }
1223
1224
1225    /**
1226     * {@inheritDoc}
1227     */
1228    @Override
1229    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1230    {
1231        // Read the UPName
1232        upName = in.readUTF();
1233
1234        // Read the RDNs. Is it's null, the number will be -1.
1235        int nbRdns = in.readInt();
1236
1237        rdns = new ArrayList<>( nbRdns );
1238
1239        for ( int i = 0; i < nbRdns; i++ )
1240        {
1241            Rdn rdn = new Rdn( schemaManager );
1242            rdn.readExternal( in );
1243            rdns.add( rdn );
1244        }
1245
1246        toUpName();
1247    }
1248
1249
1250    /**
1251     * {@inheritDoc}
1252     */
1253    @Override
1254    public void writeExternal( ObjectOutput out ) throws IOException
1255    {
1256        if ( upName == null )
1257        {
1258            String message = I18n.err( I18n.ERR_13624_CANNOT_SERIALIZE_NULL_DN );
1259            LOG.error( message );
1260            throw new IOException( message );
1261        }
1262
1263        // Write the UPName
1264        out.writeUTF( upName );
1265
1266        // Write the RDNs.
1267        // First the number of RDNs
1268        out.writeInt( size() );
1269
1270        // Loop on the RDNs
1271        for ( Rdn rdn : rdns )
1272        {
1273            rdn.writeExternal( out );
1274        }
1275
1276        out.flush();
1277    }
1278
1279
1280    /**
1281     * Return the user provided Dn as a String. It returns the same value as the
1282     * getName method
1283     *
1284     * @return A String representing the user provided Dn
1285     */
1286    @Override
1287    public String toString()
1288    {
1289        return getName();
1290    }
1291}