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