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.shared.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.Arrays;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033
034import org.apache.commons.collections.list.UnmodifiableList;
035import org.apache.directory.shared.i18n.I18n;
036import org.apache.directory.shared.ldap.model.exception.LdapException;
037import org.apache.directory.shared.ldap.model.exception.LdapInvalidDnException;
038import org.apache.directory.shared.ldap.model.message.ResultCodeEnum;
039import org.apache.directory.shared.ldap.model.schema.SchemaManager;
040import org.apache.directory.shared.ldap.model.schema.normalizers.OidNormalizer;
041import org.apache.directory.shared.util.Strings;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045
046/**
047 * The Dn class contains a Dn (Distinguished Name). This class is immutable.
048 * <br/>
049 * Its specification can be found in RFC 2253,
050 * "UTF-8 String Representation of Distinguished Names".
051 * <br/>
052 * We will store two representation of a Dn :
053 * <ul>
054 * <li>a user Provider representation, which is the parsed String given by a user</li>
055 * <li>an internal representation.</li>
056 * </ul>
057 *
058 * A Dn is formed of RDNs, in a specific order :<br/>
059 *  Rdn[n], Rdn[n-1], ... Rdn[1], Rdn[0]<br/>
060 *
061 * It represents a position in a hierarchy, in which the root is the last Rdn (Rdn[0]) and the leaf
062 * is the first Rdn (Rdn[n]).
063 *
064 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
065 */
066public class Dn implements Iterable<Rdn>, Externalizable
067{
068    /** The LoggerFactory used by this class */
069    protected static final Logger LOG = LoggerFactory.getLogger( Dn.class );
070
071    /**
072     * Declares the Serial Version Uid.
073     *
074     * @see <a
075     *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
076     *      Declare Serial Version Uid</a>
077     */
078    private static final long serialVersionUID = 1L;
079
080    /** Value returned by the compareTo method if values are not equals */
081    public static final int NOT_EQUAL = -1;
082
083    /** Value returned by the compareTo method if values are equals */
084    public static final int EQUAL = 0;
085
086    /**
087     *  The RDNs that are elements of the Dn<br/>
088     * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!<br/>
089     * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0)
090     * <br>
091     * For instance,if the Dn is "dc=c, dc=b, dc=a", then the RDNs are stored as :
092     * <ul>
093     * <li>[0] : dc=c</li>
094     * <li>[1] : dc=b</li>
095     * <li>[2] : dc=a</li>
096     * </ul>
097     */
098    protected List<Rdn> rdns = new ArrayList<Rdn>( 5 );
099
100    /** The user provided name */
101    private String upName;
102
103    /** The normalized name */
104    private String normName;
105
106    /** The bytes representation of the normName */
107    private byte[] bytes;
108
109    /** A null Dn */
110    public static final Dn EMPTY_DN = new Dn();
111
112    /** The rootDSE */
113    public static final Dn ROOT_DSE = new Dn();
114
115    /** the schema manager */
116    private SchemaManager schemaManager;
117    
118    /**
119     * An iterator over RDNs
120     */
121    private final class RdnIterator implements Iterator<Rdn>
122    {
123        // The current index
124        int index;
125
126        private RdnIterator()
127        {
128            index = rdns != null ? rdns.size() - 1 : -1;
129        }
130
131
132        /**
133         * {@inheritDoc}
134         */
135        public boolean hasNext()
136        {
137            return index >= 0;
138        }
139
140
141        /**
142         * {@inheritDoc}
143         */
144        public Rdn next()
145        {
146            return index >= 0 ? rdns.get( index-- ) : null;
147        }
148
149
150        /**
151         * {@inheritDoc}
152         */
153        public void remove()
154        {
155            // Not implemented
156        }
157    }
158
159
160    /**
161     * Construct an empty Dn object
162     */
163    public Dn()
164    {
165        this( ( SchemaManager ) null );
166    }
167
168
169    /**
170     * Construct an empty Schema aware Dn object
171     * 
172     *  @param schemaManager The SchemaManager to use
173     */
174    public Dn( SchemaManager schemaManager )
175    {
176        this.schemaManager = schemaManager;
177        upName = "";
178        normName = "";
179    }
180
181
182    /**
183     * Creates a new instance of Dn, using varargs to declare the RDNs. Each
184     * String is either a full Rdn, or a couple of AttributeType DI and a value.
185     * If the String contains a '=' symbol, the the constructor will assume that
186     * the String arg contains afull Rdn, otherwise, it will consider that the
187     * following arg is the value.<br/>
188     * The created Dn is Schema aware.
189     * <br/><br/>
190     * An example of usage would be :
191     * <pre>
192     * String exampleName = "example";
193     * String baseDn = "dc=apache,dc=org";
194     *
195     * Dn dn = new Dn( DefaultSchemaManager.INSTANCE,
196     *     "cn=Test",
197     *     "ou", exampleName,
198     *     baseDn);
199     * </pre>
200     * 
201     * @param schemaManager the schema manager
202     * @param upRdns The list of String composing the Dn
203     * @throws LdapInvalidDnException If the resulting Dn is invalid
204     */
205    public Dn(String... upRdns) throws LdapInvalidDnException
206    {
207        this( null, upRdns );
208    }
209
210
211    /**
212     * Creates a new instance of schema aware Dn, using varargs to declare the RDNs. Each
213     * String is either a full Rdn, or a couple of AttributeType DI and a value.
214     * If the String contains a '=' symbol, the the constructor will assume that
215     * the String arg contains afull Rdn, otherwise, it will consider that the
216     * following arg is the value.<br/>
217     * The created Dn is Schema aware.
218     * <br/><br/>
219     * An example of usage would be :
220     * <pre>
221     * String exampleName = "example";
222     * String baseDn = "dc=apache,dc=org";
223     *
224     * Dn dn = new Dn( DefaultSchemaManager.INSTANCE,
225     *     "cn=Test",
226     *     "ou", exampleName,
227     *     baseDn);
228     * </pre>
229     * 
230     * @param schemaManager the schema manager
231     * @param upRdns The list of String composing the Dn
232     * @throws LdapInvalidDnException If the resulting Dn is invalid
233     */
234    public Dn( SchemaManager schemaManager, String... upRdns ) throws LdapInvalidDnException
235    {
236        StringBuilder sb = new StringBuilder();
237        boolean valueExpected = false;
238        boolean isFirst = true;
239
240        for ( String upRdn : upRdns )
241        {
242            if ( Strings.isEmpty( upRdn ) )
243            {
244                continue;
245            }
246            
247            if ( isFirst )
248            {
249                isFirst = false;
250            }
251            else if ( !valueExpected )
252            {
253                sb.append( ',' );
254            }
255
256            if ( !valueExpected )
257            {
258                sb.append( upRdn );
259
260                if ( upRdn.indexOf( '=' ) == -1 )
261                {
262                    valueExpected = true;
263                }
264            }
265            else
266            {
267                sb.append( "=" ).append( upRdn );
268
269                valueExpected = false;
270            }
271        }
272        
273        if ( !isFirst && valueExpected )
274        {
275            throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04202 ) );
276        }
277
278        // Stores the representations of a Dn : internal (as a string and as a
279        // byte[]) and external.
280        upName = sb.toString();
281        parseInternal( upName, rdns );
282
283        apply( schemaManager );
284    }
285
286
287    /**
288     * Create a schema aware Dn while deserializing it.
289     * <br/>
290     * Note : this constructor is used only by the deserialization method.
291     * 
292     * @param schemaManager the schema manager
293     * @param upName The user provided name
294     * @param normName the normalized name
295     * @param rdns the list of RDNs for this Dn
296     */
297    /* No protection */ Dn( SchemaManager schemaManager, String upName, String normName, Rdn... rdns )
298    {
299        this.schemaManager = schemaManager;
300        this.upName = upName;
301        this.normName = normName;
302        bytes = Strings.getBytesUtf8( upName );
303        this.rdns = Arrays.asList( rdns );
304    }
305
306
307    /**
308     * Creates a Dn from a list of Rdns.
309     *
310     * @param rdns the list of Rdns to be used for the Dn
311     * @throws LdapInvalidDnException If the resulting Dn is invalid
312     */
313    public Dn( Rdn... rdns ) throws LdapInvalidDnException
314    {
315        if ( rdns == null )
316        {
317            return;
318        }
319        
320        for ( Rdn rdn : rdns)
321        {
322            this.rdns.add( rdn.clone() );
323        }
324
325        apply( null );
326        toUpName();
327    }
328
329
330    /**
331     * Creates a Dn concatenating a Rdn and a Dn.
332     *
333     * @param rdn the Rdn to add to the Dn
334     * @param dn the Dn 
335     * @throws LdapInvalidDnException If the resulting Dn is invalid
336     */
337    public Dn( Rdn rdn, Dn dn ) throws LdapInvalidDnException
338    {
339        if ( ( dn == null ) || ( rdn == null ) )
340        {
341            throw new IllegalArgumentException( "Either the dn or the rdn is null" );
342        }
343        
344        for ( Rdn rdnParent : dn )
345        {
346            rdns.add( 0, rdnParent );
347        }
348        
349        rdns.add( 0, rdn );
350
351        apply( dn.schemaManager );
352        toUpName();
353    }
354
355
356    /**
357     * Creates a Schema aware Dn from a list of Rdns.
358     *
359     * @param schemaManager The SchemaManager to use
360     * @param rdns the list of Rdns to be used for the Dn
361     * @throws LdapInvalidDnException If the resulting Dn is invalid
362     */
363    public Dn( SchemaManager schemaManager, Rdn... rdns ) throws LdapInvalidDnException
364    {
365        if ( rdns == null )
366        {
367            return;
368        }
369        
370        for ( Rdn rdn : rdns)
371        {
372            this.rdns.add( rdn.clone() );
373        }
374
375        apply( schemaManager );
376    }
377
378
379    /**
380     * Get the associated SchemaManager if any.
381     * 
382     * @return The SchemaManager
383     */
384    public SchemaManager getSchemaManager()
385    {
386        return schemaManager;
387    }
388    
389    
390    /**
391     * Return the User Provided Dn as a String,
392     *
393     * @return A String representing the User Provided Dn
394     */
395    private String toUpName()
396    {
397        if ( rdns.size() == 0 )
398        {
399            upName = "";
400        }
401        else
402        {
403            StringBuffer sb = new StringBuffer();
404            boolean isFirst = true;
405
406            for ( Rdn rdn : rdns )
407            {
408                if ( isFirst )
409                {
410                    isFirst = false;
411                }
412                else
413                {
414                    sb.append( ',' );
415                }
416
417                sb.append( rdn.getName() );
418            }
419
420            upName = sb.toString();
421        }
422
423        return upName;
424    }
425    
426    
427    /**
428     * Gets the hash code of this Dn.
429     *
430     * @see java.lang.Object#hashCode()
431     * @return the instance hash code
432     */
433    @Override
434    public int hashCode()
435    {
436        int result = 37;
437
438        for ( Rdn rdn : rdns )
439        {
440            result = result * 17 + rdn.hashCode();
441        }
442
443        return result;
444    }
445
446
447    /**
448     * Get the user provided Dn
449     *
450     * @return The user provided Dn as a String
451     */
452    public String getName()
453    {
454        return ( upName == null ? "" : upName );
455    }
456
457
458    /**
459     * Sets the up name.
460     *
461     * Package private because Dn is immutable, only used by the Dn parser.
462     *
463     * @param upName the new up name
464     */
465    /* No qualifier */ void setUpName( String upName )
466    {
467        this.upName = upName;
468    }
469
470
471    /**
472     * Get the normalized Dn. If the Dn is schema aware, the AttributeType
473     * will be represented using its OID :<br/>
474     * <pre>
475     * Dn dn = new Dn( schemaManager, "ou = Example , ou = com" );
476     * assert( "2.5.4.11=example,2.5.4.11=com".equals( dn.getNormName ) );
477     * </pre>
478     * Otherwise, it will return a Dn with the AttributeType in lower case
479     * and the value trimmed : <br/>
480     * <pre>
481     * Dn dn = new Dn( " CN = A   Test " );
482     * assertEquals( "cn=A   Test", dn.getNormName() );
483     * </pre>
484     *
485     * @return The normalized Dn as a String
486     */
487    public String getNormName()
488    {
489        return normName;
490    }
491
492
493    /**
494     * Get the number of RDNs present in the DN
495     * @return The umber of RDNs in the DN
496     */
497    public int size()
498    {
499        return rdns.size();
500    }
501
502
503    /**
504     * Get the number of bytes necessary to store this Dn
505
506     * @param dn The Dn.
507     * @return A integer, which is the size of the UTF-8 byte array
508     */
509    public static int getNbBytes( Dn dn )
510    {
511        return dn.bytes == null ? 0 : dn.bytes.length;
512    }
513
514
515    /**
516     * Get an UTF-8 representation of the normalized form of the Dn
517     *
518     * @param dn The Dn.
519     * @return A byte[] representation of the Dn
520     */
521    public static byte[] getBytes( Dn dn )
522    {
523        return dn == null ? null : dn.bytes;
524    }
525
526
527    /**
528     * Tells if the current Dn is a parent of another Dn.<br>
529     * For instance, <b>dc=com</b> is a ancestor
530     * of <b>dc=example, dc=com</b>
531     *
532     * @param dn The child
533     * @return true if the current Dn is a parent of the given Dn
534     */
535    public boolean isAncestorOf( String dn )
536    {
537        try
538        {
539            return isAncestorOf( new Dn( dn ) );
540        }
541        catch ( LdapInvalidDnException lide )
542        {
543            return false;
544        }
545    }
546
547
548    /**
549     * Tells if the current Dn is a parent of another Dn.<br>
550     * For instance, <b>dc=com</b> is a ancestor 
551     * of <b>dc=example, dc=com</b>
552     *
553     * @param dn The child
554     * @return true if the current Dn is a parent of the given Dn
555     */
556    public boolean isAncestorOf( Dn dn )
557    {
558        if ( dn == null )
559        {
560            return false;
561        }
562
563        return dn.isDescendantOf( this );
564    }
565
566
567    /**
568     * Tells if a Dn is a child of another Dn.<br>
569     * For instance, <b>dc=example, dc=com</b> is a descendant
570     * of <b>dc=com</b>
571     *
572     * @param dn The parent
573     * @return true if the current Dn is a child of the given Dn
574     */
575    public boolean isDescendantOf( String dn )
576    {
577        try
578        {
579            return isDescendantOf( new Dn( schemaManager, dn ) );
580        }
581        catch ( LdapInvalidDnException lide )
582        {
583            return false;
584        }
585    }
586
587
588    /**
589     * Tells if a Dn is a child of another Dn.<br>
590     * For instance, <b>dc=example, dc=apache, dc=com</b> is a descendant
591     * of <b>dc=com</b>
592     *
593     * @param dn The parent
594     * @return true if the current Dn is a child of the given Dn
595     */
596    public boolean isDescendantOf( Dn dn )
597    {
598        if ( ( dn == null ) || dn.isRootDSE() )
599        {
600            return true;
601        }
602
603        if ( dn.size() > size() )
604        {
605            // The name is longer than the current Dn.
606            return false;
607        }
608
609        // Ok, iterate through all the Rdn of the name,
610        // starting a the end of the current list.
611
612        for ( int i = dn.size() - 1; i >= 0; i-- )
613        {
614            Rdn nameRdn = dn.rdns.get( dn.rdns.size() - i - 1 );
615            Rdn ldapRdn = rdns.get( rdns.size() - i - 1 );
616
617            if ( !nameRdn.equals( ldapRdn ) )
618            {
619                return false;
620            }
621        }
622
623        return true;
624    }
625
626
627    /**
628     * Tells if the Dn contains no Rdn
629     *
630     * @return <code>true</code> if the Dn is empty
631     */
632    public boolean isEmpty()
633    {
634        return ( rdns.size() == 0 );
635    }
636
637
638    /**
639     * Tells if the Dn is the RootDSE Dn (ie, an empty Dn)
640     *
641     * @return <code>true</code> if the Dn is the RootDSE's Dn
642     */
643    public boolean isRootDSE()
644    {
645        return ( rdns.size() == 0 );
646    }
647
648
649    /**
650     * Retrieves a component of this name.
651     *
652     * @param posn the 0-based index of the component to retrieve. Must be in the
653     *            range [0,size()).
654     * @return the component at index posn
655     * @throws ArrayIndexOutOfBoundsException
656     *             if posn is outside the specified range
657     */
658    public Rdn getRdn( int posn )
659    {
660        if ( rdns.size() == 0 )
661        {
662            return null;
663        }
664        
665        if ( ( posn < 0 ) || ( posn >= rdns.size() ) )
666        {
667            throw new IllegalArgumentException( "Invalid position : " + posn );
668        }
669
670        Rdn rdn = rdns.get( posn );
671
672        return rdn.clone();
673    }
674
675
676    /**
677     * Retrieves the last (leaf) component of this name.
678     *
679     * @return the last component of this Dn
680     */
681    public Rdn getRdn()
682    {
683        if ( isNullOrEmpty( this ) )
684        {
685            return Rdn.EMPTY_RDN;
686        }
687        
688        return rdns.get( 0 ).clone();
689    }
690
691
692    /**
693     * Retrieves all the components of this name.
694     *
695     * @return All the components
696     */
697    @SuppressWarnings("unchecked")
698    public List<Rdn> getRdns()
699    {
700        return UnmodifiableList.decorate( rdns );
701    }
702
703
704    /**
705     * Get the descendant of a given DN, using the ancestr DN. Assuming that
706     * a DN has two parts :<br/>
707     * DN = [descendant DN][ancestor DN]<br/>
708     * To get back the descendant from the full DN, you just pass the ancestor DN
709     * as a parameter. Here is a working example :
710     * <pre>
711     * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
712     * 
713     * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" );
714     * 
715     * // At this point, the descendant contains cn=test, dc=server, dc=directory"
716     * </pre> 
717     */
718    public Dn getDescendantOf( String ancestor ) throws LdapInvalidDnException
719    {
720        return getDescendantOf( new Dn( schemaManager, ancestor ) );
721    }
722    
723
724    
725    /**
726     * Get the descendant of a given DN, using the ancestr DN. Assuming that
727     * a DN has two parts :<br/>
728     * DN = [descendant DN][ancestor DN]<br/>
729     * To get back the descendant from the full DN, you just pass the ancestor DN
730     * as a parameter. Here is a working example :
731     * <pre>
732     * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
733     * 
734     * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" );
735     * 
736     * // At this point, the descendant contains cn=test, dc=server, dc=directory"
737     * </pre> 
738     */
739    public Dn getDescendantOf( Dn ancestor ) throws LdapInvalidDnException
740    {
741        if ( ( ancestor == null ) || ( ancestor.size() == 0 ) )
742        {
743            return this;
744        }
745        
746        if ( rdns.size() == 0 )
747        {
748            return EMPTY_DN;
749        }
750        
751        int length = ancestor.size();
752        
753        if ( length > rdns.size() )
754        {
755            String message = I18n.err( I18n.ERR_04206, length, rdns.size() );
756            LOG.error( message );
757            throw new ArrayIndexOutOfBoundsException( message );
758        }
759
760        Dn newDn = new Dn( schemaManager );
761        List<Rdn> rdnsAncestor = ancestor.getRdns();
762        
763        for ( int i = 0; i < ancestor.size(); i++ )
764        {
765            Rdn rdn = rdns.get( size() -1 - i );
766            Rdn rdnDescendant = rdnsAncestor.get( ancestor.size() - 1 - i );
767            
768            if ( !rdn.equals( rdnDescendant ) )
769            {
770                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX );
771            }
772        }
773
774        for ( int i = 0; i < rdns.size() - length; i++ )
775        {
776            // Don't forget to clone the rdns !
777            newDn.rdns.add( rdns.get( i ).clone() );
778        }
779
780        newDn.toUpName();
781        newDn.apply( schemaManager );
782
783        return newDn;
784    }
785
786    /**
787     * Get the ancestor of a given DN, using the descendant DN. Assuming that
788     * a DN has two parts :<br/>
789     * DN = [descendant DN][ancestor DN]<br/>
790     * To get back the ancestor from the full DN, you just pass the descendant DN
791     * as a parameter. Here is a working example :
792     * <pre>
793     * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
794     * 
795     * Dn ancestor = dn.getAncestorOf( "cn=test, dc=server, dc=directory" );
796     * 
797     * // At this point, the ancestor contains "dc=apache, dc=org"
798     * </pre> 
799     */
800    public Dn getAncestorOf( String descendant ) throws LdapInvalidDnException
801    {
802        return getAncestorOf( new Dn( schemaManager, descendant ) );
803    }
804    
805
806    /**
807     * Get the ancestor of a given DN, using the descendant DN. Assuming that
808     * a DN has two parts :<br/>
809     * DN = [descendant DN][ancestor DN]<br/>
810     * To get back the ancestor from the full DN, you just pass the descendant DN
811     * as a parameter. Here is a working example :
812     * <pre>
813     * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" );
814     * 
815     * Dn ancestor = dn.getAncestorOf( new Dn( "cn=test, dc=server, dc=directory" ) );
816     * 
817     * // At this point, the ancestor contains "dc=apache, dc=org"
818     * </pre> 
819     */
820    public Dn getAncestorOf( Dn descendant ) throws LdapInvalidDnException
821    {
822        if ( ( descendant == null ) || ( descendant.size() == 0 ) )
823        {
824            return this;
825        }
826        
827        if ( rdns.size() == 0 )
828        {
829            return EMPTY_DN;
830        }
831        
832        int length = descendant.size();
833        
834        if ( length > rdns.size() )
835        {
836            String message = I18n.err( I18n.ERR_04206, length, rdns.size() );
837            LOG.error( message );
838            throw new ArrayIndexOutOfBoundsException( message );
839        }
840
841        Dn newDn = new Dn( schemaManager );
842        List<Rdn> rdnsDescendant = descendant.getRdns();
843        
844        for ( int i = 0; i < descendant.size(); i++ )
845        {
846            Rdn rdn = rdns.get( i );
847            Rdn rdnDescendant = rdnsDescendant.get( i );
848            
849            if ( !rdn.equals( rdnDescendant ) )
850            {
851                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX );
852            }
853        }
854
855        for ( int i = length; i < rdns.size(); i++ )
856        {
857            // Don't forget to clone the rdns !
858            newDn.rdns.add( rdns.get( i ).clone() );
859        }
860
861        newDn.toUpName();
862        newDn.apply( schemaManager );
863
864        return newDn;
865    }
866
867
868    /**
869     * {@inheritDoc}
870     */
871    public Dn add( Dn suffix ) throws LdapInvalidDnException
872    {
873        if ( ( suffix == null ) || ( suffix.size() == 0 ) )
874        {
875            return this;
876        }
877
878        Dn clonedDn = copy();
879
880        // Concatenate the rdns
881        clonedDn.rdns.addAll( 0, suffix.rdns );
882
883        // Regenerate the normalized name and the original string
884        if ( clonedDn.isSchemaAware() && suffix.isSchemaAware() )
885        {
886            if ( clonedDn.size() != 0 )
887            {
888                clonedDn.normName = suffix.getNormName() + "," + normName;
889                clonedDn.bytes = Strings.getBytesUtf8(normName);
890                clonedDn.upName = suffix.getName() + "," + upName;
891            }
892        }
893        else
894        {
895            clonedDn.apply( schemaManager );
896            clonedDn.toUpName();
897        }
898
899        return clonedDn;
900    }
901
902
903    /**
904     * {@inheritDoc}
905     */
906    public Dn add( String comp ) throws LdapInvalidDnException
907    {
908        if ( comp.length() == 0 )
909        {
910            return this;
911        }
912
913        Dn clonedDn = copy();
914
915        // We have to parse the nameComponent which is given as an argument
916        Rdn newRdn = new Rdn( schemaManager, comp );
917
918        clonedDn.rdns.add( 0, newRdn );
919
920        clonedDn.apply( schemaManager );
921        clonedDn.toUpName();
922
923        return clonedDn;
924    }
925
926
927    /**
928     * Adds a single Rdn to the (leaf) end of this name.
929     *
930     * @param newRdn the Rdn to add
931     * @return the updated cloned Dn
932     */
933    public Dn add( Rdn newRdn )  throws LdapInvalidDnException
934    {
935        if ( ( newRdn == null ) || ( newRdn.size() == 0 ) )
936        {
937            return this;
938        }
939        
940        Dn clonedDn = copy();
941
942        clonedDn.rdns.add( 0, newRdn.clone() );
943        clonedDn.apply( schemaManager );
944        clonedDn.toUpName();
945
946        return clonedDn;
947    }
948
949
950    /**
951     * Gets the parent Dn of this Dn. Null if this Dn doesn't have a parent, i.e. because it
952     * is the empty Dn.<br/>
953     * The Parent is the right part of the Dn, when the Rdn has been removed.
954     *
955     * @return the parent Dn of this Dn
956     */
957    public Dn getParent()
958    {
959        if ( isNullOrEmpty( this ) )
960        {
961            return this;
962        }
963        
964        int posn = rdns.size() - 1;
965
966        Dn newDn = new Dn( schemaManager );
967
968        for ( int i = rdns.size() - posn; i < rdns.size(); i++ )
969        {
970            // Don't forget to clone the rdns !
971            newDn.rdns.add( rdns.get( i ).clone() );
972        }
973
974        try
975        {
976            newDn.apply( schemaManager );
977        }
978        catch ( LdapInvalidDnException e )
979        {
980            LOG.error( e.getMessage(), e );
981        }
982
983        newDn.toUpName();
984
985        return newDn;
986    }
987
988
989    /**
990     * Create a copy of the current Dn
991     */
992    private Dn copy()
993    {
994        Dn dn = new Dn( schemaManager );
995        dn.rdns = new ArrayList<Rdn>();
996
997        for ( Rdn rdn : rdns )
998        {
999            dn.rdns.add( rdn.clone() );
1000        }
1001
1002        return dn;
1003    }
1004
1005
1006    /**
1007     * @see java.lang.Object#equals(java.lang.Object)
1008     * @return <code>true</code> if the two instances are equals
1009     */
1010    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS",
1011                justification = "String is a special case")
1012    @Override
1013    public boolean equals( Object obj )
1014    {
1015        if ( obj instanceof String )
1016        {
1017            return normName.equals( obj );
1018        }
1019        else if ( obj instanceof Dn)
1020        {
1021            Dn name = (Dn) obj;
1022
1023            if ( name.size() != this.size() )
1024            {
1025                return false;
1026            }
1027
1028            for ( int i = 0; i < this.size(); i++ )
1029            {
1030                if ( !name.rdns.get( i ).equals( rdns.get( i ) ) )
1031                {
1032                    return false;
1033                }
1034            }
1035
1036            // All components matched so we return true
1037            return true;
1038        }
1039        else
1040        {
1041            return false;
1042        }
1043    }
1044
1045
1046    /**
1047     * Normalize the Ava
1048     */
1049    private static Ava atavOidToName( Ava atav, SchemaManager schemaManager )
1050        throws LdapInvalidDnException
1051    {
1052        Map<String, OidNormalizer> oidsMap = schemaManager.getNormalizerMapping();
1053        String type = Strings.trim( atav.getNormType() );
1054
1055        if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) )
1056        {
1057            type = type.substring( 4 );
1058        }
1059
1060        if ( Strings.isNotEmpty( type ) )
1061        {
1062            if ( oidsMap == null )
1063            {
1064                return atav;
1065            }
1066            
1067            type = Strings.toLowerCase( type );
1068            
1069            // Check that we have an existing AttributeType for this type
1070            if ( !oidsMap.containsKey( type ) )
1071            {
1072                // No AttributeType : this is an error
1073                String msg = I18n.err( I18n.ERR_04268_OID_NOT_FOUND, atav.getUpType() );
1074                LOG.error( msg );
1075                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, msg );
1076            }
1077
1078            OidNormalizer oidNormalizer = oidsMap.get( type );
1079
1080            if ( oidNormalizer != null )
1081            {
1082                try
1083                {
1084                    Ava newAva = new Ava(
1085                        atav.getUpType(),
1086                        oidNormalizer.getAttributeTypeOid(),
1087                        atav.getUpValue(),
1088                        oidNormalizer.getNormalizer().normalize( atav.getNormValue() ),
1089                        atav.getUpName() );
1090                    newAva.apply( schemaManager );
1091                    
1092                    return newAva;
1093                }
1094                catch ( LdapException le )
1095                {
1096                    throw new LdapInvalidDnException( le.getMessage(), le );
1097                }
1098            }
1099            else
1100            {
1101                // We don't have a normalizer for this OID : just do nothing.
1102                return atav;
1103            }
1104        }
1105        else
1106        {
1107            // The type is empty : this is not possible...
1108            String msg = I18n.err( I18n.ERR_04209_EMPTY_TYPE_NOT_ALLOWED );
1109            LOG.error( msg );
1110            throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, msg );
1111        }
1112    }
1113
1114
1115    /**
1116     * Transform a Rdn by changing the value to its OID counterpart and
1117     * normalizing the value accordingly to its type.
1118     *
1119     * @param rdn The Rdn to modify.
1120     * @param SchemaManager The schema manager
1121     * @throws LdapInvalidDnException If the Rdn is invalid.
1122     */
1123    /** No qualifier */
1124    static void rdnOidToName( Rdn rdn, SchemaManager schemaManager ) throws LdapInvalidDnException
1125    {
1126        // We have more than one ATAV for this Rdn. We will loop on all
1127        // ATAVs
1128        Rdn rdnCopy = rdn.clone();
1129        rdn.clear();
1130
1131        for ( Ava val : rdnCopy )
1132        {
1133            Ava newAtav = atavOidToName( val, schemaManager );
1134            rdn.addAVA( schemaManager, newAtav );
1135        }
1136    }
1137
1138
1139    /**
1140     * Normalizes the Dn using the given the schema manager
1141     *
1142     * @param schemaManager The schemaManagerto use to normalize the Dn
1143     * @return The normalized Dn
1144     * @throws LdapInvalidDnException If the Dn is invalid.
1145     */
1146    public Dn apply( SchemaManager schemaManager ) throws LdapInvalidDnException
1147    {
1148        this.schemaManager = schemaManager;
1149
1150        if ( this.schemaManager != null )
1151        {
1152            synchronized ( this )
1153            {
1154                if ( size() == 0 )
1155                {
1156                    bytes = null;
1157                    normName = "";
1158                    
1159                    return this;
1160                }
1161
1162                StringBuilder sb = new StringBuilder();
1163                boolean isFirst = true;
1164
1165                for ( Rdn rdn : rdns )
1166                {
1167                    rdn.apply( schemaManager );
1168
1169                    if ( isFirst )
1170                    {
1171                        isFirst = false;
1172                    }
1173                    else
1174                    {
1175                        sb.append( ',' );
1176                    }
1177
1178                    sb.append( rdn.getNormName() );
1179                }
1180
1181                String newNormName = sb.toString();
1182
1183                if ( ( normName == null ) || !normName.equals( newNormName ) )
1184                {
1185                    bytes = Strings.getBytesUtf8(newNormName);
1186                    normName = newNormName;
1187                }
1188
1189                return this;
1190            }
1191        }
1192        else
1193        {
1194            if ( rdns.size() == 0 )
1195            {
1196                bytes = null;
1197                normName = "";
1198            }
1199            else
1200            {
1201                StringBuffer sb = new StringBuffer();
1202                boolean isFirst = true;
1203
1204                for ( Rdn rdn : rdns )
1205                {
1206                    if ( isFirst )
1207                    {
1208                        isFirst = false;
1209                    }
1210                    else
1211                    {
1212                        sb.append( ',' );
1213                    }
1214
1215                    sb.append( rdn.getNormName() );
1216                }
1217
1218                String newNormName = sb.toString();
1219
1220                if ( ( normName == null ) || !normName.equals( newNormName ) )
1221                {
1222                    bytes = Strings.getBytesUtf8(newNormName);
1223                    normName = newNormName;
1224                }
1225            }
1226        }
1227        
1228        return this;
1229    }
1230
1231
1232    /**
1233     * Tells if the Dn is schema aware
1234     *
1235     * @return <code>true</code> if the Dn is schema aware.
1236     */
1237    public boolean isSchemaAware()
1238    {
1239        return schemaManager != null;
1240    }
1241
1242
1243    /**
1244     * Iterate over the inner Rdn. The Rdn are returned from 
1245     * the rightmost to the leftmost. For instance, the following code :<br/>
1246     * <pre>
1247     * Dn dn = new Dn( "sn=test, dc=apache, dc=org );
1248     * 
1249     * for ( Rdn rdn : dn )
1250     * {
1251     *     System.out.println( rdn.toString() );
1252     * }
1253     * </pre>
1254     * will produce this output : <br/>
1255     * <pre>
1256     * dc=org
1257     * dc=apache
1258     * sn=test
1259     * </pre>
1260     * 
1261     */
1262    public Iterator<Rdn> iterator()
1263    {
1264        return new RdnIterator();
1265    }
1266
1267
1268    /**
1269     * Check if a DistinguishedName is null or empty.
1270     *
1271     * @param dn The Dn to check
1272     * @return <code>true></code> if the Dn is null or empty, <code>false</code>
1273     * otherwise
1274     */
1275    public static boolean isNullOrEmpty( Dn dn )
1276    {
1277        return ( dn == null ) || dn.isEmpty();
1278    }
1279    
1280    
1281    /**
1282     * Check if a DistinguishedName is syntactically valid.
1283     *
1284     * @param dn The Dn to validate
1285     * @return <code>true></code> if the Dn is valid, <code>false</code>
1286     * otherwise
1287     */
1288    public static boolean isValid( String name )
1289    {
1290        Dn dn = new Dn();
1291        
1292        try
1293        {
1294            parseInternal( name, dn.rdns );
1295            return true;
1296        }
1297        catch ( LdapInvalidDnException e )
1298        {
1299            return false;
1300        }
1301    }
1302
1303    
1304    /**
1305     * Parse a Dn.
1306     *
1307     * @param name The Dn to be parsed
1308     * @param rdns The list that will contain the RDNs
1309     * @throws LdapInvalidDnException If the Dn is invalid
1310     */
1311    private static void parseInternal( String name, List<Rdn> rdns ) throws LdapInvalidDnException
1312    {
1313        try
1314        {
1315            FastDnParser.parseDn( name, rdns );
1316        }
1317        catch ( TooComplexException e )
1318        {
1319            rdns.clear();
1320            new ComplexDnParser().parseDn( name, rdns );
1321        }
1322    }
1323
1324
1325    /**
1326     * {@inheritDoc}
1327     */
1328    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1329    {
1330        // Read the UPName
1331        upName = in.readUTF();
1332
1333        // Read the NormName
1334        normName = in.readUTF();
1335
1336        if ( normName.length() == 0 )
1337        {
1338            // As the normName is equal to the upName,
1339            // we didn't saved the nbnormName on disk.
1340            // restore it by copying the upName.
1341            normName = upName;
1342        }
1343
1344        // Read the RDNs. Is it's null, the number will be -1.
1345        int nbRdns = in.readInt();
1346        
1347        rdns = new ArrayList<Rdn>( nbRdns );
1348        
1349        for ( int i = 0; i < nbRdns; i++ )
1350        {
1351            Rdn rdn = new Rdn( schemaManager );
1352            rdn.readExternal( in );
1353            rdns.add( rdn );
1354        }
1355    }
1356    
1357    
1358    /**
1359     * {@inheritDoc}
1360     */
1361    public void writeExternal( ObjectOutput out ) throws IOException
1362    {
1363        if ( upName == null )
1364        {
1365            String message = "Cannot serialize a NULL Dn";
1366            LOG.error( message );
1367            throw new IOException( message );
1368        }
1369
1370        // Write the UPName
1371        out.writeUTF( upName );
1372
1373        // Write the NormName if different
1374        if ( upName.equals( normName ) )
1375        {
1376            out.writeUTF( "" );
1377        }
1378        else
1379        {
1380            out.writeUTF( normName );
1381        }
1382
1383        // Write the RDNs.
1384        // First the number of RDNs
1385        out.writeInt( size() );
1386
1387        // Loop on the RDNs
1388        for ( Rdn rdn : rdns )
1389        {
1390            rdn.writeExternal( out );
1391        }
1392        
1393        out.flush();
1394    }
1395    
1396    
1397    /**
1398     * Return the user provided Dn as a String. It returns the same value as the
1399     * getName method
1400     *
1401     * @return A String representing the user provided Dn
1402     */
1403    @Override
1404    public String toString()
1405    {
1406        return getName();
1407    }
1408}