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    
021    package org.apache.directory.shared.ldap.name;
022    
023    
024    import java.io.Externalizable;
025    import java.io.IOException;
026    import java.io.ObjectInput;
027    import java.io.ObjectOutput;
028    import java.util.ArrayList;
029    import java.util.Enumeration;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.NoSuchElementException;
033    
034    import javax.naming.InvalidNameException;
035    import javax.naming.Name;
036    import javax.naming.NamingException;
037    
038    import org.apache.directory.shared.i18n.I18n;
039    import org.apache.directory.shared.ldap.schema.normalizers.OidNormalizer;
040    import org.apache.directory.shared.ldap.util.StringTools;
041    import org.slf4j.Logger;
042    import org.slf4j.LoggerFactory;
043    
044    
045    /**
046     * The DN class contains a DN (Distinguished Name).
047     *
048     * Its specification can be found in RFC 2253,
049     * "UTF-8 String Representation of Distinguished Names".
050     *
051     * We will store two representation of a DN :
052     * - a user Provider representation, which is the parsed String given by a user
053     * - an internal representation.
054     *
055     * A DN is formed of RDNs, in a specific order :
056     *  RDN[n], RDN[n-1], ... RDN[1], RDN[0]
057     *
058     * It represents a tree, in which the root is the last RDN (RDN[0]) and the leaf
059     * is the first RDN (RDN[n]).
060     *
061     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
062     * @version $Rev: 921600 $, $Date: 2010-03-10 23:37:30 +0100 (Mer, 10 mar 2010) $
063     */
064    public class DN implements Name, Externalizable
065    {
066        /** The LoggerFactory used by this class */
067        protected static final Logger LOG = LoggerFactory.getLogger( DN.class );
068    
069        /**
070         * Declares the Serial Version Uid.
071         *
072         * @see <a
073         *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
074         *      Declare Serial Version Uid</a>
075         */
076        private static final long serialVersionUID = 1L;
077    
078        /** Value returned by the compareTo method if values are not equals */
079        public static final int NOT_EQUAL = -1;
080    
081        /** Value returned by the compareTo method if values are equals */
082        public static final int EQUAL = 0;
083    
084        /** A flag used to tell if the DN has been normalized */
085        private boolean normalized;
086    
087        // ~ Static fields/initializers
088        // -----------------------------------------------------------------
089        /**
090         *  The RDNs that are elements of the DN
091         * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!
092         * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0)
093         */
094        protected List<RDN> rdns = new ArrayList<RDN>( 5 );
095    
096        /** The user provided name */
097        private String upName;
098    
099        /** The normalized name */
100        private String normName;
101    
102        /** The bytes representation of the normName */
103        private byte[] bytes;
104    
105        /** A null DN */
106        public static final DN EMPTY_DN = new DN();
107    
108    
109        // ~ Methods
110        // ------------------------------------------------------------------------------------
111    
112        /**
113         * Construct an empty DN object
114         */
115        public DN()
116        {
117            upName = "";
118            normName = "";
119            normalized = true;
120        }
121    
122    
123        /**
124         * Transduces, or copies a Name to an DN.
125         *
126         * @param name composed of String name components.
127         * @throws InvalidNameException If the Name is invalid.
128         */
129        public DN( Name name ) throws InvalidNameException
130        {
131            if ( ( name != null ) && ( name.size() != 0 ) )
132            {
133                for ( int ii = 0; ii < name.size(); ii++ )
134                {
135                    String nameComponent = name.get( ii );
136                    add( nameComponent );
137                }
138            }
139    
140            normalized = false;
141    
142        }
143    
144    
145        /**
146         * Parse a String and checks that it is a valid DN <br>
147         * <p>
148         * &lt;distinguishedName&gt; ::= &lt;name&gt; | e <br>
149         * &lt;name&gt; ::= &lt;name-component&gt; &lt;name-components&gt; <br>
150         * &lt;name-components&gt; ::= &lt;spaces&gt; &lt;separator&gt;
151         * &lt;spaces&gt; &lt;name-component&gt; &lt;name-components&gt; | e <br>
152         * </p>
153         *
154         * @param upName The String that contains the DN.
155         * @throws InvalidNameException if the String does not contain a valid DN.
156         */
157        public DN( String upName ) throws InvalidNameException
158        {
159            if ( upName != null )
160            {
161                DnParser.parseInternal( upName, rdns );
162            }
163    
164            // Stores the representations of a DN : internal (as a string and as a
165            // byte[]) and external.
166            normalizeInternal();
167            normalized = false;
168    
169            this.upName = upName;
170        }
171    
172    
173        /**
174         * Creates a new instance of DN, using varargs to declare the RDNs. Each
175         * String is either a full RDN, or a couple of AttributeType DI and a value.
176         * If the String contains a '=' symbol, the the constructor will assume that
177         * the String arg contains afull RDN, otherwise, it will consider that the 
178         * following arg is the value.
179         * An example of usage would be :
180         * <pre>
181         * String exampleName = "example";
182         * String baseDn = "dc=apache,dc=org";
183         * 
184         * DN dn = new DN(
185         *     "cn=Test",
186         *     "ou", exampleName,
187         *     baseDn);
188         * </pre>
189         *
190         * @param upNames
191         * @throws InvalidNameException
192         */
193        public DN( String... upRdns ) throws InvalidNameException
194        {
195            StringBuilder sb = new StringBuilder();
196            boolean valueExpected = false;
197            boolean isFirst = true;
198            
199            for ( String upRdn : upRdns )
200            {
201                if ( isFirst )
202                {
203                    isFirst = false;
204                }
205                else if ( !valueExpected )
206                {
207                    sb.append( ',' );
208                }
209                
210                if ( !valueExpected )
211                {
212                    sb.append( upRdn );
213                    
214                    if ( upRdn.indexOf( '=' ) == -1 )
215                    {
216                        valueExpected = true;
217                    }
218                }
219                else
220                {
221                    sb.append( "=" ).append( upRdn );
222                    
223                    valueExpected = false;
224                }
225            }
226            
227            if ( valueExpected )
228            {
229                throw new InvalidNameException( I18n.err( I18n.ERR_04202 ) );
230            }
231    
232            // Stores the representations of a DN : internal (as a string and as a
233            // byte[]) and external.
234            upName = sb.toString();
235            DnParser.parseInternal( upName, rdns );
236            normalizeInternal();
237            normalized = false;
238        }
239        
240        /**
241         * Create a DN when deserializing it.
242         * 
243         * Note : this constructor is used only by the deserialization method.
244         * @param upName The user provided name
245         * @param normName the normalized name
246         * @param bytes the name as a byte[]
247         */
248        DN( String upName, String normName, byte[] bytes )
249        {
250            normalized = true;
251            this.upName = upName;
252            this.normName = normName;
253            this.bytes = bytes;
254        }
255    
256    
257        /**
258         * Static factory which creates a normalized DN from a String and a Map of OIDs.
259         *
260         * @param name The DN as a String
261         * @param oidsMap The OID mapping
262         * @return A valid DN
263         * @throws InvalidNameException If the DN is invalid.
264         * @throws NamingException If something went wrong.
265         */
266        public static Name normalize( String name, Map<String, OidNormalizer> oidsMap ) throws InvalidNameException,
267            NamingException
268        {
269            if ( ( name == null ) || ( name.length() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
270            {
271                return DN.EMPTY_DN;
272            }
273    
274            try
275            {
276                DN newDn = new DN( name );
277    
278                Enumeration<RDN> rdns = newDn.getAllRdn();
279    
280                // Loop on all RDNs
281                while ( rdns.hasMoreElements() )
282                {
283                    RDN rdn = rdns.nextElement();
284                    String upName = rdn.getUpName();
285                    rdnOidToName( rdn, oidsMap );
286                    rdn.normalize();
287                    rdn.setUpName( upName );
288                }
289    
290                newDn.normalizeInternal();
291                newDn.normalized = true;
292    
293                return newDn;
294            }
295            catch ( NamingException ne )
296            {
297                throw new InvalidNameException( ne.getMessage() );
298            }
299        }
300    
301    
302        /**
303         * Normalize the DN by triming useless spaces and lowercasing names.
304         */
305        void normalizeInternal()
306        {
307            normName = toNormName();
308        }
309    
310    
311        /**
312         * Build the normalized DN as a String,
313         *
314         * @return A String representing the normalized DN
315         */
316        public String toNormName()
317        {
318            if ( rdns.size() == 0 )
319            {
320                bytes = null;
321                return "";
322            }
323            else
324            {
325                StringBuffer sb = new StringBuffer();
326                boolean isFirst = true;
327    
328                for ( RDN rdn : rdns )
329                {
330                    if ( isFirst )
331                    {
332                        isFirst = false;
333                    }
334                    else
335                    {
336                        sb.append( ',' );
337                    }
338    
339                    sb.append( rdn.getNormName() );
340                }
341    
342                String newNormName = sb.toString();
343    
344                if ( ( normName == null ) || !normName.equals( newNormName ) )
345                {
346                    bytes = StringTools.getBytesUtf8( newNormName );
347                    normName = newNormName;
348                }
349    
350                return normName;
351            }
352        }
353    
354    
355        /**
356         * Return the normalized DN as a String. It returns the same value as the
357         * getNormName method
358         *
359         * @return A String representing the normalized DN
360         */
361        public String toString()
362        {
363            return getName();
364        }
365    
366    
367        /**
368         * Return the User Provided DN as a String,
369         *
370         * @return A String representing the User Provided DN
371         */
372        private String toUpName()
373        {
374            if ( rdns.size() == 0 )
375            {
376                upName = "";
377            }
378            else
379            {
380                StringBuffer sb = new StringBuffer();
381                boolean isFirst = true;
382    
383                for ( RDN rdn : rdns )
384                {
385                    if ( isFirst )
386                    {
387                        isFirst = false;
388                    }
389                    else
390                    {
391                        sb.append( ',' );
392                    }
393    
394                    sb.append( rdn.getUpName() );
395                }
396    
397                upName = sb.toString();
398            }
399    
400            return upName;
401        }
402    
403    
404        /**
405         * Return the User Provided prefix representation of the DN starting at the
406         * posn position.
407         *
408         * If posn = 0, return an empty string.
409         *
410         * for DN : sn=smith, dc=apache, dc=org
411         * getUpname(0) -> ""
412         * getUpName(1) -> "dc=org"
413         * getUpname(3) -> "sn=smith, dc=apache, dc=org"
414         * getUpName(4) -> ArrayOutOfBoundException
415         *
416         * Warning ! The returned String is not exactly the
417         * user provided DN, as spaces before and after each RDNs have been trimmed.
418         *
419         * @param posn
420         *            The starting position
421         * @return The truncated DN
422         */
423        private String getUpNamePrefix( int posn )
424        {
425            if ( posn == 0 )
426            {
427                return "";
428            }
429    
430            if ( posn > rdns.size() )
431            {
432                String message = I18n.err( I18n.ERR_04203, posn, rdns.size() );
433                LOG.error( message );
434                throw new ArrayIndexOutOfBoundsException( message );
435            }
436    
437            int start = rdns.size() - posn;
438            StringBuffer sb = new StringBuffer();
439            boolean isFirst = true;
440    
441            for ( int i = start; i < rdns.size(); i++ )
442            {
443                if ( isFirst )
444                {
445                    isFirst = false;
446                }
447                else
448                {
449                    sb.append( ',' );
450                }
451    
452                sb.append( rdns.get( i ).getUpName() );
453            }
454    
455            return sb.toString();
456        }
457    
458    
459        /**
460         * Return the User Provided suffix representation of the DN starting at the
461         * posn position.
462         * If posn = 0, return an empty string.
463         *
464         * for DN : sn=smith, dc=apache, dc=org
465         * getUpname(0) -> "sn=smith, dc=apache, dc=org"
466         * getUpName(1) -> "sn=smith, dc=apache"
467         * getUpname(3) -> "sn=smith"
468         * getUpName(4) -> ""
469         *
470         * Warning ! The returned String is not exactly the user
471         * provided DN, as spaces before and after each RDNs have been trimmed.
472         *
473         * @param posn The starting position
474         * @return The truncated DN
475         */
476        private String getUpNameSuffix( int posn )
477        {
478            if ( posn > rdns.size() )
479            {
480                return "";
481            }
482    
483            int end = rdns.size() - posn;
484            StringBuffer sb = new StringBuffer();
485            boolean isFirst = true;
486    
487            for ( int i = 0; i < end; i++ )
488            {
489                if ( isFirst )
490                {
491                    isFirst = false;
492                }
493                else
494                {
495                    sb.append( ',' );
496                }
497    
498                sb.append( rdns.get( i ).getUpName() );
499            }
500    
501            return sb.toString();
502        }
503    
504    
505        /**
506         * Gets the hash code of this name.
507         *
508         * @see java.lang.Object#hashCode()
509         * @return the instance hash code
510         */
511        public int hashCode()
512        {
513            int result = 37;
514    
515            for ( RDN rdn : rdns )
516            {
517                result = result * 17 + rdn.hashCode();
518            }
519    
520            return result;
521        }
522    
523    
524        /**
525         * Get the initial DN
526         *
527         * @return The DN as a String
528         */
529        public String getName()
530        {
531            return ( upName == null ? "" : upName );
532        }
533    
534    
535        /**
536         * Sets the up name.
537         * 
538         * @param upName the new up name
539         */
540        void setUpName( String upName )
541        {
542            this.upName = upName;
543        }
544    
545    
546        /**
547         * Get the initial DN (without normalization)
548         *
549         * @return The DN as a String
550         */
551        public String getNormName()
552        {
553            return ( normName == null ? "" : normName );
554        }
555    
556    
557        /**
558         * {@inheritDoc}
559         */
560        public int size()
561        {
562            return rdns.size();
563        }
564    
565    
566        /**
567         * Get the number of bytes necessary to store this DN
568    
569         * @param dn The DN.
570         * @return A integer, which is the size of the UTF-8 byte array
571         */
572        public static int getNbBytes( Name dn )
573        {
574            DN newDn = ( DN ) dn;
575            return newDn.bytes == null ? 0 : newDn.bytes.length;
576        }
577    
578    
579        /**
580         * Get an UTF-8 representation of the normalized form of the DN
581         * 
582         * @param dn The DN.
583         * @return A byte[] representation of the DN
584         */
585        public static byte[] getBytes( DN dn )
586        {
587            return dn == null ? null : dn.bytes;
588        }
589    
590    
591        /**
592         * {@inheritDoc}
593         */
594        public boolean startsWith( Name name )
595        {
596            if ( name == null )
597            {
598                return true;
599            }
600            else if ( name instanceof DN )
601            {
602                DN nameDN = ( DN ) name;
603    
604                if ( nameDN.size() == 0 )
605                {
606                    return true;
607                }
608    
609                if ( nameDN.size() > size() )
610                {
611                    // The name is longer than the current DN.
612                    return false;
613                }
614    
615                // Ok, iterate through all the RDN of the name,
616                // starting a the end of the current list.
617    
618                for ( int i = nameDN.size() - 1; i >= 0; i-- )
619                {
620                    RDN nameRdn = nameDN.rdns.get( nameDN.rdns.size() - i - 1 );
621                    RDN ldapRdn = rdns.get( rdns.size() - i - 1 );
622    
623                    if ( nameRdn.compareTo( ldapRdn ) != 0 )
624                    {
625                        return false;
626                    }
627                }
628    
629                return true;
630            }
631            else
632            {
633                if ( name.size() == 0 )
634                {
635                    return true;
636                }
637    
638                if ( name.size() > size() )
639                {
640                    // The name is longer than the current DN.
641                    return false;
642                }
643    
644                // Ok, iterate through all the RDN of the name,
645                // starting a the end of the current list.
646                int starting = size() - name.size();
647    
648                for ( int i = name.size() - 1; i >= 0; i-- )
649                {
650                    RDN ldapRdn = rdns.get( i + starting );
651                    RDN nameRdn = null;
652    
653                    try
654                    {
655                        nameRdn = new RDN( name.get( name.size() - i - 1 ) );
656                    }
657                    catch ( InvalidNameException e )
658                    {
659                        LOG.error( I18n.err( I18n.ERR_04204, name.toString() ), e );
660                        return false;
661                    }
662    
663                    if ( nameRdn.compareTo( ldapRdn ) != 0 )
664                    {
665                        return false;
666                    }
667                }
668    
669                return true;
670            }
671        }
672    
673    
674        /*
675         * Determines whether this name ends with a specified suffix. A name
676         * <tt>name</tt> is a suffix if it is equal to
677         * <tt>getSuffix(size()-name.size())</tt>.
678         *
679         * Be aware that for a specific
680         * DN like : cn=xxx, ou=yyy the endsWith method will return true with
681         * cn=xxx, and false with ou=yyy
682         *
683         * @param name
684         *            the name to check
685         * @return true if <tt>name</tt> is a suffix of this name, false otherwise
686         */
687        /**
688         * {@inheritDoc}
689         */
690        public boolean endsWith( Name name )
691        {
692            if ( name == null )
693            {
694                return true;
695            }
696            
697            if ( name instanceof DN )
698            {
699                DN nameDN = ( DN ) name;
700    
701                if ( nameDN.size() == 0 )
702                {
703                    return true;
704                }
705    
706                if ( nameDN.size() > size() )
707                {
708                    // The name is longer than the current DN.
709                    return false;
710                }
711    
712                // Ok, iterate through all the RDN of the name
713                for ( int i = 0; i < nameDN.size(); i++ )
714                {
715                    RDN nameRdn = nameDN.rdns.get( i );
716                    RDN ldapRdn = rdns.get( i );
717    
718                    if ( nameRdn.compareTo( ldapRdn ) != 0 )
719                    {
720                        return false;
721                    }
722                }
723    
724                return true;
725            }
726            else
727            {
728                if ( name.size() == 0 )
729                {
730                    return true;
731                }
732    
733                if ( name.size() > size() )
734                {
735                    // The name is longer than the current DN.
736                    return false;
737                }
738    
739                // Ok, iterate through all the RDN of the name
740                int nameSize = name.size();
741                
742                for ( int i = name.size() - 1; i >= 0; i-- )
743                {
744                    RDN ldapRdn = rdns.get( nameSize - i - 1 );
745                    RDN nameRdn = null;
746    
747                    try
748                    {
749                        nameRdn = new RDN( name.get( i ) );
750                    }
751                    catch ( InvalidNameException e )
752                    {
753                        LOG.error( I18n.err( I18n.ERR_04204, name.toString() ), e );
754                        return false;
755                    }
756    
757                    if ( nameRdn.compareTo( ldapRdn ) != 0 )
758                    {
759                        return false;
760                    }
761                }
762    
763                return true;
764            }
765        }
766    
767    
768        /**
769         * {@inheritDoc}
770         */
771        public boolean isEmpty()
772        {
773            return ( rdns.size() == 0 );
774        }
775    
776    
777        /**
778         * {@inheritDoc}
779         */
780        public String get( int posn )
781        {
782            if ( rdns.size() == 0 )
783            {
784                return "";
785            }
786            else
787            {
788                RDN rdn = rdns.get( rdns.size() - posn - 1 );
789    
790                return rdn.toString();
791            }
792        }
793    
794    
795        /**
796         * Retrieves a component of this name.
797         *
798         * @param posn
799         *            the 0-based index of the component to retrieve. Must be in the
800         *            range [0,size()).
801         * @return the component at index posn
802         * @throws ArrayIndexOutOfBoundsException
803         *             if posn is outside the specified range
804         */
805        public RDN getRdn( int posn )
806        {
807            if ( rdns.size() == 0 )
808            {
809                return null;
810            }
811            else
812            {
813                RDN rdn = rdns.get( rdns.size() - posn - 1 );
814    
815                return rdn;
816            }
817        }
818    
819    
820        /**
821         * Retrieves the last (leaf) component of this name.
822         *
823         * @return the last component of this DN
824         */
825        public RDN getRdn()
826        {
827            if ( rdns.size() == 0 )
828            {
829                return null;
830            }
831            else
832            {
833                return rdns.get( 0 );
834            }
835        }
836    
837    
838        /**
839         * Retrieves all the components of this name.
840         *
841         * @return All the components
842         */
843        public List<RDN> getRdns()
844        {
845            List<RDN> newRdns = new ArrayList<RDN>();
846    
847            // We will clone the list, to avoid user modifications
848            for ( RDN rdn : rdns )
849            {
850                newRdns.add( ( RDN ) rdn.clone() );
851            }
852    
853            return newRdns;
854        }
855    
856    
857        /**
858         * {@inheritDoc}
859         */
860        public Enumeration<String> getAll()
861        {
862            /*
863             * Note that by accessing the name component using the get() method on
864             * the name rather than get() on the list we are reading components from
865             * right to left with increasing index values. LdapName.get() does the
866             * index translation on m_list for us.
867             */
868            return new Enumeration<String>()
869            {
870                private int pos;
871    
872    
873                public boolean hasMoreElements()
874                {
875                    return pos < rdns.size();
876                }
877    
878    
879                public String nextElement()
880                {
881                    if ( pos >= rdns.size() )
882                    {
883                        LOG.error( I18n.err( I18n.ERR_04205 ) );
884                        throw new NoSuchElementException();
885                    }
886    
887                    RDN rdn = rdns.get( rdns.size() - pos - 1 );
888                    pos++;
889                    return rdn.toString();
890                }
891            };
892        }
893    
894    
895        /**
896         * Retrieves the components of this name as an enumeration of strings. The
897         * effect on the enumeration of updates to this name is undefined. If the
898         * name has zero components, an empty (non-null) enumeration is returned.
899         * This starts at the root (rightmost) rdn.
900         *
901         * @return an enumeration of the components of this name, as Rdn
902         */
903        public Enumeration<RDN> getAllRdn()
904        {
905            /*
906             * Note that by accessing the name component using the get() method on
907             * the name rather than get() on the list we are reading components from
908             * right to left with increasing index values. LdapName.get() does the
909             * index translation on m_list for us.
910             */
911            return new Enumeration<RDN>()
912            {
913                private int pos;
914    
915    
916                public boolean hasMoreElements()
917                {
918                    return pos < rdns.size();
919                }
920    
921    
922                public RDN nextElement()
923                {
924                    if ( pos >= rdns.size() )
925                    {
926                        LOG.error( I18n.err( I18n.ERR_04205 ) );
927                        throw new NoSuchElementException();
928                    }
929    
930                    RDN rdn = rdns.get( rdns.size() - pos - 1 );
931                    pos++;
932                    return rdn;
933                }
934            };
935        }
936    
937    
938        /**
939         * {@inheritDoc}
940         */
941        public Name getPrefix( int posn )
942        {
943            if ( rdns.size() == 0 )
944            {
945                return EMPTY_DN;
946            }
947    
948            if ( ( posn < 0 ) || ( posn > rdns.size() ) )
949            {
950                String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
951                LOG.error( message );
952                throw new ArrayIndexOutOfBoundsException( message );
953            }
954    
955            DN newDN = new DN();
956    
957            for ( int i = rdns.size() - posn; i < rdns.size(); i++ )
958            {
959                // Don't forget to clone the rdns !
960                newDN.rdns.add( ( RDN ) rdns.get( i ).clone() );
961            }
962    
963            newDN.normName = newDN.toNormName();
964            newDN.upName = getUpNamePrefix( posn );
965    
966            return newDN;
967        }
968    
969    
970        /**
971         * {@inheritDoc}
972         */
973        public Name getSuffix( int posn )
974        {
975            if ( rdns.size() == 0 )
976            {
977                return EMPTY_DN;
978            }
979    
980            if ( ( posn < 0 ) || ( posn > rdns.size() ) )
981            {
982                String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
983                LOG.error( message );
984                throw new ArrayIndexOutOfBoundsException( message );
985            }
986    
987            DN newDN = new DN();
988    
989            for ( int i = 0; i < size() - posn; i++ )
990            {
991                // Don't forget to clone the rdns !
992                newDN.rdns.add( ( RDN ) rdns.get( i ).clone() );
993            }
994    
995            newDN.normName = newDN.toNormName();
996            newDN.upName = getUpNameSuffix( posn );
997    
998            return newDN;
999        }
1000    
1001    
1002        /**
1003         * Adds the components of a name -- in order -- at a specified position
1004         * within this name. Components of this name at or after the index of the
1005         * first new component are shifted up (away from 0) to accommodate the new
1006         * components. Compoenents are supposed to be normalized.
1007         *
1008         * @param posn the index in this name at which to add the new components.
1009         *            Must be in the range [0,size()]. Note this is from the opposite end as rnds.get(posn)
1010         * @param name the components to add
1011         * @return the updated name (not a new one)
1012         * @throws ArrayIndexOutOfBoundsException
1013         *             if posn is outside the specified range
1014         * @throws InvalidNameException
1015         *             if <tt>n</tt> is not a valid name, or if the addition of
1016         *             the components would violate the syntax rules of this name
1017         */
1018        public Name addAllNormalized( int posn, Name name ) throws InvalidNameException
1019        {
1020            if ( name instanceof DN )
1021            {
1022                DN dn = (DN)name;
1023                
1024                if ( ( dn == null ) || ( dn.size() == 0 ) )
1025                {
1026                    return this;
1027                }
1028    
1029                // Concatenate the rdns
1030                rdns.addAll( size() - posn, dn.rdns );
1031    
1032                if ( StringTools.isEmpty( normName ) )
1033                {
1034                    normName = dn.normName;
1035                    bytes = dn.bytes;
1036                    upName = dn.upName;
1037                }
1038                else
1039                {
1040                    normName = dn.normName + "," + normName;
1041                    bytes = StringTools.getBytesUtf8( normName );
1042                    upName = dn.upName + "," + upName;
1043                }
1044            }
1045            else
1046            {
1047                if ( ( name == null ) || ( name.size() == 0 ) )
1048                {
1049                    return this;
1050                }
1051    
1052                for ( int i = name.size() - 1; i >= 0; i-- )
1053                {
1054                    RDN rdn = new RDN( name.get( i ) );
1055                    rdns.add( size() - posn, rdn );
1056                }
1057    
1058                normalizeInternal();
1059                toUpName();
1060            }
1061    
1062            return this;
1063        }
1064    
1065        /**
1066         * {@inheritDoc}
1067         */
1068        public Name addAll( Name suffix ) throws InvalidNameException
1069        {
1070            addAll( rdns.size(), suffix );
1071            normalizeInternal();
1072            toUpName();
1073    
1074            return this;
1075        }
1076    
1077    
1078        /**
1079         * {@inheritDoc}
1080         */
1081        public Name addAll( int posn, Name name ) throws InvalidNameException
1082        {
1083            if ( name instanceof DN )
1084            {
1085                DN dn = (DN)name;
1086                
1087                if ( ( dn == null ) || ( dn.size() == 0 ) )
1088                {
1089                    return this;
1090                }
1091    
1092                // Concatenate the rdns
1093                rdns.addAll( size() - posn, dn.rdns );
1094    
1095                // Regenerate the normalized name and the original string
1096                if ( this.isNormalized() && dn.isNormalized() )
1097                {
1098                    if ( this.size() != 0 )
1099                    {
1100                        normName = dn.getNormName() + "," + normName;
1101                        bytes = StringTools.getBytesUtf8( normName );
1102                        upName = dn.getName() + "," + upName;
1103                    }
1104                }
1105                else
1106                {
1107                    normalizeInternal();
1108                    toUpName();
1109                }
1110            }
1111            else
1112            {
1113                if ( ( name == null ) || ( name.size() == 0 ) )
1114                {
1115                    return this;
1116                }
1117    
1118                for ( int i = name.size() - 1; i >= 0; i-- )
1119                {
1120                    RDN rdn = new RDN( name.get( i ) );
1121                    rdns.add( size() - posn, rdn );
1122                }
1123    
1124                normalizeInternal();
1125                toUpName();
1126            }
1127    
1128            return this;
1129        }
1130    
1131    
1132        /**
1133         * {@inheritDoc}
1134         */
1135        public Name add( String comp ) throws InvalidNameException
1136        {
1137            if ( comp.length() == 0 )
1138            {
1139                return this;
1140            }
1141    
1142            // We have to parse the nameComponent which is given as an argument
1143            RDN newRdn = new RDN( comp );
1144    
1145            rdns.add( 0, newRdn );
1146            normalizeInternal();
1147            toUpName();
1148    
1149            return this;
1150        }
1151    
1152    
1153        /**
1154         * Adds a single RDN to the (leaf) end of this name.
1155         *
1156         * @param newRdn the RDN to add
1157         * @return the updated name (not a new one)
1158         */
1159        public Name add( RDN newRdn )
1160        {
1161            rdns.add( 0, newRdn );
1162            
1163            normalizeInternal();
1164            toUpName();
1165    
1166            return this;
1167        }
1168    
1169    
1170        /**
1171         * Adds a single RDN to a specific position.
1172         *
1173         * @param newRdn the RDN to add
1174         * @param pos The position where we want to add the Rdn
1175         * @return the updated name (not a new one)
1176         */
1177        public Name add( int pos, RDN newRdn )
1178        {
1179            rdns.add( newRdn );
1180            
1181            normalizeInternal();
1182            toUpName();
1183    
1184            return this;
1185        }
1186    
1187    
1188        /**
1189         * Adds a single normalized RDN to the (leaf) end of this name.
1190         *
1191         * @param newRdn the RDN to add
1192         * @return the updated name (not a new one)
1193         */
1194        public Name addNormalized( RDN newRdn )
1195        {
1196            rdns.add( 0, newRdn );
1197            
1198            // Avoid a call to the toNormName() method which
1199            // will iterate through all the rdns, when we only
1200            // have to build a new normName by using the current
1201            // RDN normalized name. The very same for upName.
1202            if (rdns.size() == 1 )
1203            {
1204                normName = newRdn.toString();
1205                upName = newRdn.getUpName();
1206            }
1207            else
1208            {
1209                normName = newRdn + "," + normName;
1210                upName = newRdn.getUpName() + "," + upName;
1211            }
1212            
1213            bytes = StringTools.getBytesUtf8( normName );
1214    
1215            return this;
1216        }
1217    
1218    
1219        /**
1220         * {@inheritDoc}
1221         */
1222        public Name add( int posn, String comp ) throws InvalidNameException
1223        {
1224            if ( ( posn < 0 ) || ( posn > size() ) )
1225            {
1226                String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
1227                LOG.error( message );
1228                throw new ArrayIndexOutOfBoundsException( message );
1229            }
1230    
1231            // We have to parse the nameComponent which is given as an argument
1232            RDN newRdn = new RDN( comp );
1233    
1234            int realPos = size() - posn;
1235            rdns.add( realPos, newRdn );
1236    
1237            normalizeInternal();
1238            toUpName();
1239    
1240            return this;
1241        }
1242    
1243    
1244        /**
1245         * {@inheritDoc}
1246         */
1247        public Object remove( int posn ) throws InvalidNameException
1248        {
1249            if ( rdns.size() == 0 )
1250            {
1251                return EMPTY_DN;
1252            }
1253    
1254            if ( ( posn < 0 ) || ( posn >= rdns.size() ) )
1255            {
1256                String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
1257                LOG.error( message );
1258                throw new ArrayIndexOutOfBoundsException( message );
1259            }
1260    
1261            int realPos = size() - posn - 1;
1262            RDN rdn = rdns.remove( realPos );
1263    
1264            normalizeInternal();
1265            toUpName();
1266    
1267            return rdn;
1268        }
1269    
1270    
1271        /**
1272         * {@inheritDoc}
1273         */
1274        public Object clone()
1275        {
1276            try
1277            {
1278                DN dn = ( DN ) super.clone();
1279                dn.rdns = new ArrayList<RDN>();
1280    
1281                for ( RDN rdn : rdns )
1282                {
1283                    dn.rdns.add( ( RDN ) rdn.clone() );
1284                }
1285    
1286                return dn;
1287            }
1288            catch ( CloneNotSupportedException cnse )
1289            {
1290                LOG.error( I18n.err( I18n.ERR_04207 ) );
1291                throw new Error( I18n.err( I18n.ERR_04208 ) );
1292            }
1293        }
1294    
1295    
1296        /**
1297         * @see java.lang.Object#equals(java.lang.Object)
1298         * @return <code>true</code> if the two instances are equals
1299         */
1300        public boolean equals( Object obj )
1301        {
1302            if ( obj instanceof String )
1303            {
1304                return normName.equals( obj );
1305            }
1306            else if ( obj instanceof DN )
1307            {
1308                DN name = ( DN ) obj;
1309    
1310                if ( name.size() != this.size() )
1311                {
1312                    return false;
1313                }
1314    
1315                for ( int i = 0; i < this.size(); i++ )
1316                {
1317                    if ( name.rdns.get( i ).compareTo( rdns.get( i ) ) != 0 )
1318                    {
1319                        return false;
1320                    }
1321                }
1322    
1323                // All components matched so we return true
1324                return true;
1325            }
1326            else
1327            {
1328                return false;
1329            }
1330        }
1331    
1332    
1333        /**
1334         * {@inheritDoc}
1335         */
1336        public int compareTo( Object obj )
1337        {
1338            if ( obj instanceof DN )
1339            {
1340                DN dn = ( DN ) obj;
1341    
1342                if ( dn.size() != size() )
1343                {
1344                    return size() - dn.size();
1345                }
1346    
1347                for ( int i = rdns.size(); i > 0; i-- )
1348                {
1349                    RDN rdn1 = rdns.get( i - 1 );
1350                    RDN rdn2 = dn.rdns.get( i - 1 );
1351                    int res = rdn1.compareTo( rdn2 );
1352    
1353                    if ( res != 0 )
1354                    {
1355                        return res;
1356                    }
1357                }
1358    
1359                return EQUAL;
1360            }
1361            else
1362            {
1363                return 1;
1364            }
1365        }
1366    
1367    
1368        private static AVA atavOidToName( AVA atav, Map<String, OidNormalizer> oidsMap )
1369            throws InvalidNameException, NamingException
1370        {
1371            String type = StringTools.trim( atav.getNormType() );
1372    
1373            if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) )
1374            {
1375                type = type.substring( 4 );
1376            }
1377    
1378            if ( StringTools.isNotEmpty( type ) )
1379            {
1380                if ( oidsMap == null )
1381                {
1382                    return atav;
1383                }
1384                else
1385                {
1386                    OidNormalizer oidNormalizer = oidsMap.get( type.toLowerCase() );
1387    
1388                    if ( oidNormalizer != null )
1389                    {
1390                        return new AVA( 
1391                            atav.getUpType(), 
1392                            oidNormalizer.getAttributeTypeOid(), 
1393                            atav.getUpValue(),
1394                            oidNormalizer.getNormalizer().normalize( atav.getNormValue() ),
1395                            atav.getUpName() );
1396                    }
1397                    else
1398                    {
1399                        // We don't have a normalizer for this OID : just do nothing.
1400                        return atav;
1401                    }
1402                }
1403            }
1404            else
1405            {
1406                // The type is empty : this is not possible...
1407                LOG.error( I18n.err( I18n.ERR_04209 ) );
1408                throw new InvalidNameException( I18n.err( I18n.ERR_04209 ) );
1409            }
1410        }
1411    
1412    
1413        /**
1414         * Transform a RDN by changing the value to its OID counterpart and
1415         * normalizing the value accordingly to its type.
1416         *
1417         * @param rdn The RDN to modify.
1418         * @param oidsMap The map of all existing oids and normalizer.
1419         * @throws InvalidNameException If the RDN is invalid.
1420         * @throws NamingException If something went wrong.
1421         */
1422        /** No qualifier */ static void rdnOidToName( RDN rdn, Map<String, OidNormalizer> oidsMap ) throws InvalidNameException,
1423            NamingException
1424        {
1425            if ( rdn.getNbAtavs() > 1 )
1426            {
1427                // We have more than one ATAV for this RDN. We will loop on all
1428                // ATAVs
1429                RDN rdnCopy = ( RDN ) rdn.clone();
1430                rdn.clear();
1431    
1432                for ( AVA val:rdnCopy )
1433                {
1434                    AVA newAtav = atavOidToName( val, oidsMap );
1435                    rdn.addAttributeTypeAndValue( newAtav );
1436                }
1437            }
1438            else
1439            {
1440                AVA val = rdn.getAtav();
1441                rdn.clear();
1442                AVA newAtav = atavOidToName( val, oidsMap );
1443                rdn.addAttributeTypeAndValue( newAtav );
1444            }
1445        }
1446    
1447    
1448        /**
1449         * Change the internal DN, using the OID instead of the first name or other
1450         * aliases. As we still have the UP name of each RDN, we will be able to
1451         * provide both representation of the DN. example : dn: 2.5.4.3=People,
1452         * dc=example, domainComponent=com will be transformed to : 2.5.4.3=People,
1453         * 0.9.2342.19200300.100.1.25=example, 0.9.2342.19200300.100.1.25=com 
1454         * because 2.5.4.3 is the OID for cn and dc is the first
1455         * alias of the couple of aliases (dc, domaincomponent), which OID is 
1456         * 0.9.2342.19200300.100.1.25. 
1457         * This is really important do have such a representation, as 'cn' and 
1458         * 'commonname' share the same OID.
1459         * 
1460         * @param dn The DN to transform.
1461         * @param oidsMap The mapping between names and oids.
1462         * @return A normalized form of the DN.
1463         * @throws NamingException If something went wrong.
1464         */
1465        public static DN normalize( DN dn, Map<String, OidNormalizer> oidsMap ) throws NamingException
1466        {
1467            if ( ( dn == null ) || ( dn.size() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
1468            {
1469                return dn;
1470            }
1471    
1472            Enumeration<RDN> rdns = dn.getAllRdn();
1473    
1474            // Loop on all RDNs
1475            while ( rdns.hasMoreElements() )
1476            {
1477                RDN rdn = rdns.nextElement();
1478                String upName = rdn.getUpName();
1479                rdnOidToName( rdn, oidsMap );
1480                rdn.normalize();
1481                rdn.setUpName( upName );
1482            }
1483    
1484            dn.normalizeInternal();
1485    
1486            dn.normalized = true;
1487            return dn;
1488        }
1489    
1490    
1491        /**
1492         * Change the internal DN, using the OID instead of the first name or other
1493         * aliases. As we still have the UP name of each RDN, we will be able to
1494         * provide both representation of the DN. example : dn: 2.5.4.3=People,
1495         * dc=example, domainComponent=com will be transformed to : 2.5.4.3=People,
1496         * 0.9.2342.19200300.100.1.25=example, 0.9.2342.19200300.100.1.25=com 
1497         * because 2.5.4.3 is the OID for cn and dc is the first
1498         * alias of the couple of aliases (dc, domaincomponent), which OID is 
1499         * 0.9.2342.19200300.100.1.25. 
1500         * This is really important do have such a representation, as 'cn' and 
1501         * 'commonname' share the same OID.
1502         *
1503         * @param oidsMap The mapping between names and oids.
1504         * @throws NamingException If something went wrong.
1505         * @return The normalized DN
1506         */
1507        public DN normalize( Map<String, OidNormalizer> oidsMap ) throws NamingException
1508        {
1509            if ( ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
1510            {
1511                return this;
1512            }
1513    
1514            if ( size() == 0 )
1515            {
1516                normalized = true;
1517                return this;
1518            }
1519    
1520            Enumeration<RDN> localRdns = getAllRdn();
1521    
1522            // Loop on all RDNs
1523            while ( localRdns.hasMoreElements() )
1524            {
1525                RDN rdn = localRdns.nextElement();
1526                String localUpName = rdn.getUpName();
1527                rdnOidToName( rdn, oidsMap );
1528                rdn.normalize();
1529                rdn.setUpName( localUpName );
1530            }
1531    
1532            normalizeInternal();
1533            normalized = true;
1534            return this;
1535        }
1536    
1537    
1538        /**
1539         * Check if a DistinguishedName is syntactically valid.
1540         *
1541         * @param dn The DN to validate
1542         * @return <code>true></code> if the DN is valid, <code>false</code>
1543         * otherwise
1544         */
1545        public static boolean isValid( String dn )
1546        {
1547            return DnParser.validateInternal( dn );
1548        }
1549    
1550        /**
1551         * Tells if the DN has already been normalized or not
1552         *
1553         * @return <code>true</code> if the DN is already normalized.
1554         */
1555        public boolean isNormalized()
1556        {
1557            return normalized;
1558        }
1559    
1560    
1561        /**
1562         * @see Externalizable#readExternal(ObjectInput)<p>
1563         * 
1564         * We have to store a DN data efficiently. Here is the structure :
1565         * 
1566         * <li>upName</li> The User provided DN<p>
1567         * <li>normName</li> May be null if the normName is equaivalent to 
1568         * the upName<p>
1569         * <li>rdns</li> The rdn's List.<p>
1570         * 
1571         * for each rdn :
1572         * <li>call the RDN write method</li>
1573         *
1574         *@param out The stream in which the DN will be serialized
1575         *@throws IOException If the serialization fail
1576         */
1577        public void writeExternal( ObjectOutput out ) throws IOException
1578        {
1579            if ( upName == null )
1580            {
1581                String message = I18n.err( I18n.ERR_04210 );
1582                LOG.error( message );
1583                throw new IOException( message );
1584            }
1585            
1586            // Write the UPName
1587            out.writeUTF( upName );
1588            
1589            // Write the NormName if different
1590            if ( isNormalized() )
1591            {
1592                if ( upName.equals( normName ) )
1593                {
1594                    out.writeUTF( "" );
1595                }
1596                else
1597                {
1598                    out.writeUTF( normName );
1599                }
1600            }
1601            else
1602            {
1603                String message = I18n.err( I18n.ERR_04211 );
1604                LOG.error( message );
1605                throw new IOException( message );
1606            }
1607            
1608            // Should we store the byte[] ???
1609            
1610            // Write the RDNs. Is it's null, the number will be -1. 
1611            out.writeInt( rdns.size() );
1612    
1613            // Loop on the RDNs
1614            for ( RDN rdn:rdns )
1615            {
1616                out.writeObject( rdn );
1617            }
1618        }
1619    
1620    
1621        /**
1622         * @see Externalizable#readExternal(ObjectInput)
1623         * 
1624         * We read back the data to create a new DN. The structure 
1625         * read is exposed in the {@link DN#writeExternal(ObjectOutput)} 
1626         * method<p>
1627         * 
1628         * @param in The stream from which the DN is read
1629         * @throws IOException If the stream can't be read
1630         * @throws ClassNotFoundException If the RDN can't be created 
1631         */
1632        public void readExternal( ObjectInput in ) throws IOException , ClassNotFoundException
1633        {
1634            // Read the UPName
1635            upName = in.readUTF();
1636            
1637            // Read the NormName
1638            normName = in.readUTF();
1639            
1640            if ( normName.length() == 0 )
1641            {
1642                // As the normName is equal to the upName,
1643                // we didn't saved the nbnormName on disk.
1644                // restore it by copying the upName.
1645                normName = upName;
1646            }
1647            
1648            // A serialized DN is always normalized.
1649            normalized = true;
1650                
1651            // Should we read the byte[] ???
1652            bytes = StringTools.getBytesUtf8( upName );
1653            
1654            // Read the RDNs. Is it's null, the number will be -1.
1655            int nbRdns = in.readInt();
1656            rdns = new ArrayList<RDN>( nbRdns );
1657            
1658            for ( int i = 0; i < nbRdns; i++ )
1659            {
1660                RDN rdn = (RDN)in.readObject();
1661                rdns.add( rdn );
1662            }
1663        }
1664    }