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