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