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