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  package org.apache.directory.api.ldap.model.name;
21  
22  
23  import java.io.Externalizable;
24  import java.io.IOException;
25  import java.io.ObjectInput;
26  import java.io.ObjectOutput;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Iterator;
30  import java.util.List;
31  
32  import org.apache.commons.collections.MultiMap;
33  import org.apache.commons.collections.map.MultiValueMap;
34  import org.apache.directory.api.i18n.I18n;
35  import org.apache.directory.api.ldap.model.entry.StringValue;
36  import org.apache.directory.api.ldap.model.entry.Value;
37  import org.apache.directory.api.ldap.model.exception.LdapException;
38  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
39  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
40  import org.apache.directory.api.ldap.model.schema.AttributeType;
41  import org.apache.directory.api.ldap.model.schema.SchemaManager;
42  import org.apache.directory.api.ldap.model.schema.normalizers.OidNormalizer;
43  import org.apache.directory.api.util.Chars;
44  import org.apache.directory.api.util.Hex;
45  import org.apache.directory.api.util.Serialize;
46  import org.apache.directory.api.util.StringConstants;
47  import org.apache.directory.api.util.Strings;
48  import org.apache.directory.api.util.Unicode;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  
53  /**
54   * This class store the name-component part or the following BNF grammar (as of
55   * RFC2253, par. 3, and RFC1779, fig. 1) : <br> - &lt;name-component&gt; ::=
56   * &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt;
57   * &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; <br> -
58   * &lt;attributeTypeAndValues&gt; ::= &lt;spaces&gt; '+' &lt;spaces&gt;
59   * &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt;
60   * &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; | e <br> -
61   * &lt;attributeType&gt; ::= [a-zA-Z] &lt;keychars&gt; | &lt;oidPrefix&gt; [0-9]
62   * &lt;digits&gt; &lt;oids&gt; | [0-9] &lt;digits&gt; &lt;oids&gt; <br> -
63   * &lt;keychars&gt; ::= [a-zA-Z] &lt;keychars&gt; | [0-9] &lt;keychars&gt; | '-'
64   * &lt;keychars&gt; | e <br> - &lt;oidPrefix&gt; ::= 'OID.' | 'oid.' | e <br> -
65   * &lt;oids&gt; ::= '.' [0-9] &lt;digits&gt; &lt;oids&gt; | e <br> -
66   * &lt;attributeValue&gt; ::= &lt;pairs-or-strings&gt; | '#' &lt;hexstring&gt;
67   * |'"' &lt;quotechar-or-pairs&gt; '"' <br> - &lt;pairs-or-strings&gt; ::= '\'
68   * &lt;pairchar&gt; &lt;pairs-or-strings&gt; | &lt;stringchar&gt;
69   * &lt;pairs-or-strings&gt; | e <br> - &lt;quotechar-or-pairs&gt; ::=
70   * &lt;quotechar&gt; &lt;quotechar-or-pairs&gt; | '\' &lt;pairchar&gt;
71   * &lt;quotechar-or-pairs&gt; | e <br> - &lt;pairchar&gt; ::= ',' | '=' | '+' |
72   * '&lt;' | '&gt;' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F] <br> -
73   * &lt;hexstring&gt; ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; <br> -
74   * &lt;hexpairs&gt; ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; | e <br> -
75   * &lt;digits&gt; ::= [0-9] &lt;digits&gt; | e <br> - &lt;stringchar&gt; ::=
76   * [0x00-0xFF] - [,=+&lt;&gt;#;\"\n\r] <br> - &lt;quotechar&gt; ::= [0x00-0xFF] -
77   * [\"] <br> - &lt;separator&gt; ::= ',' | ';' <br> - &lt;spaces&gt; ::= ' '
78   * &lt;spaces&gt; | e <br>
79   * <br>
80   * A Rdn is a part of a Dn. It can be composed of many types, as in the Rdn
81   * following Rdn :<br>
82   * ou=value + cn=other value<br>
83   * <br>
84   * or <br>
85   * ou=value + ou=another value<br>
86   * <br>
87   * In this case, we have to store an 'ou' and a 'cn' in the Rdn.<br>
88   * <br>
89   * The types are case insensitive. <br>
90   * Spaces before and after types and values are not stored.<br>
91   * Spaces before and after '+' are not stored.<br>
92   * <br>
93   * Thus, we can consider that the following RDNs are equals :<br>
94   * <br>
95   * 'ou=test 1'<br> ' ou=test 1'<br>
96   * 'ou =test 1'<br>
97   * 'ou= test 1'<br>
98   * 'ou=test 1 '<br> ' ou = test 1 '<br>
99   * <br>
100  * So are the following :<br>
101  * <br>
102  * 'ou=test 1+cn=test 2'<br>
103  * 'ou = test 1 + cn = test 2'<br> ' ou =test 1+ cn =test 2 ' <br>
104  * 'cn = test 2 +ou = test 1'<br>
105  * <br>
106  * but the following are not equal :<br>
107  * 'ou=test 1' <br>
108  * 'ou=test 1'<br>
109  * because we have more than one spaces inside the value.<br>
110  * <br>
111  * The Rdn is composed of one or more Ava. Those Avas
112  * are ordered in the alphabetical natural order : a < b < c ... < z As the type
113  * are not case sensitive, we can say that a = A
114  * <br>
115  * This class is immutable.
116  *
117  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
118  */
119 public class Rdn implements Cloneable, Externalizable, Iterable<Ava>, Comparable<Rdn>
120 {
121     /** The LoggerFactory used by this class */
122     protected static final Logger LOG = LoggerFactory.getLogger( Rdn.class );
123 
124     /** An empty Rdn */
125     public static final Rdn EMPTY_RDN = new Rdn();
126 
127     /**
128     * Declares the Serial Version Uid.
129     *
130     * @see <a
131     *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
132     *      Declare Serial Version Uid</a>
133     */
134     private static final long serialVersionUID = 1L;
135 
136     /** The User Provided Rdn */
137     private String upName = null;
138 
139     /** The normalized Rdn */
140     private String normName = null;
141 
142     /**
143      * Stores all couple type = value. We may have more than one type, if the
144      * '+' character appears in the Ava. This is a TreeSet,
145      * because we want the Avas to be sorted. An Ava may contain more than one
146      * value. In this case, the values are String stored in a List.
147      */
148     private List<Ava> avas = null;
149 
150     /**
151      * We also keep a set of types, in order to use manipulations. A type is
152      * connected with the Ava it represents.
153      *
154      * Note : there is no Generic available classes in commons-collection...
155      */
156     private MultiMap avaTypes = new MultiValueMap();
157 
158     /**
159      * We keep the type for a single valued Rdn, to avoid the creation of an HashMap
160      */
161     private String avaType = null;
162 
163     /**
164      * A simple Ava is used to store the Rdn for the simple
165      * case where we only have a single type=value. This will be 99.99% the
166      * case. This avoids the creation of a HashMap.
167      */
168     protected Ava ava = null;
169 
170     /**
171      * The number of Avas. We store this number here to avoid complex
172      * manipulation of Ava and Avas
173      */
174     private int nbAvas = 0;
175 
176     /** CompareTo() results */
177     public static final int UNDEFINED = Integer.MAX_VALUE;
178 
179     /** Constant used in comparisons */
180     public static final int SUPERIOR = 1;
181 
182     /** Constant used in comparisons */
183     public static final int INFERIOR = -1;
184 
185     /** Constant used in comparisons */
186     public static final int EQUAL = 0;
187 
188     /** A flag used to tell if the Rdn has been normalized */
189     private boolean normalized = false;
190 
191     /** the schema manager */
192     private SchemaManager schemaManager;
193 
194     /** The computed hashcode */
195     private volatile int h;
196 
197 
198     /**
199      * A empty constructor.
200      */
201     public Rdn()
202     {
203         this( ( SchemaManager ) null );
204     }
205 
206 
207     /**
208      *
209      * Creates a new schema aware instance of Rdn.
210      *
211      * @param schemaManager the schema manager
212      */
213     public Rdn( SchemaManager schemaManager )
214     {
215         // Don't waste space... This is not so often we have multiple
216         // name-components in a Rdn... So we won't initialize the Map and the
217         // treeSet.
218         this.schemaManager = schemaManager;
219         upName = "";
220         normName = "";
221         normalized = false;
222         h = 0;
223     }
224 
225 
226     /**
227      *  A constructor that parse a String representing a schema aware Rdn.
228      *
229      * @param schemaManager the schema manager
230      * @param rdn the String containing the Rdn to parse
231      * @throws LdapInvalidDnException if the Rdn is invalid
232      */
233     public Rdn( SchemaManager schemaManager, String rdn ) throws LdapInvalidDnException
234     {
235         if ( Strings.isNotEmpty( rdn ) )
236         {
237             // Parse the string. The Rdn will be updated.
238             parse( rdn, this );
239 
240             // create the internal normalized form
241             // and store the user provided form
242             if ( schemaManager != null )
243             {
244                 this.schemaManager = schemaManager;
245                 apply( schemaManager );
246                 normalized = true;
247             }
248             else
249             {
250                 normalize();
251                 normalized = false;
252             }
253 
254             if ( upName.length() < rdn.length() )
255             {
256                 throw new LdapInvalidDnException( "Invalid RDN" );
257             }
258 
259             upName = rdn;
260         }
261         else
262         {
263             upName = "";
264             normName = "";
265             normalized = false;
266         }
267 
268         hashCode();
269     }
270 
271 
272     /**
273      * A constructor that parse a String representing a Rdn.
274      *
275      * @param rdn the String containing the Rdn to parse
276      * @throws LdapInvalidDnException if the Rdn is invalid
277      */
278     public Rdn( String rdn ) throws LdapInvalidDnException
279     {
280         this( ( SchemaManager ) null, rdn );
281     }
282 
283 
284     /**
285      * A constructor that constructs a schema aware Rdn from a type and a value.
286      * <p>
287      * The string attribute values are not interpreted as RFC 414 formatted Rdn
288      * strings. That is, the values are used literally (not parsed) and assumed
289      * to be un-escaped.
290       *
291      * @param schemaManager the schema manager
292      * @param upType the user provided type of the Rdn
293      * @param upValue the user provided value of the Rdn
294      * @throws LdapInvalidDnException if the Rdn is invalid
295      */
296     public Rdn( SchemaManager schemaManager, String upType, String upValue ) throws LdapInvalidDnException
297     {
298         addAVA( schemaManager, upType, upType, new StringValue( upValue ), new StringValue( upValue ) );
299 
300         upName = upType + '=' + upValue;
301 
302         if ( schemaManager != null )
303         {
304             this.schemaManager = schemaManager;
305             apply( schemaManager );
306             normalized = true;
307         }
308         else
309         {
310             // create the internal normalized form
311             normalize();
312 
313             // As strange as it seems, the Rdn is *not* normalized against the schema at this point
314             normalized = false;
315         }
316 
317         hashCode();
318     }
319 
320 
321     /**
322      * A constructor that constructs a Rdn from a type and a value.
323      *
324      * @param upType the user provided type of the Rdn
325      * @param upValue the user provided value of the Rdn
326      * @throws LdapInvalidDnException if the Rdn is invalid
327      * @see #Rdn( SchemaManager, String, String )
328      */
329     public Rdn( String upType, String upValue ) throws LdapInvalidDnException
330     {
331         this( null, upType, upValue );
332     }
333 
334 
335     public Rdn( SchemaManager schemaManager, Ava... avas ) throws LdapInvalidDnException
336     {
337         StringBuilder upName = new StringBuilder();
338         for ( int i = 0; i < avas.length; i++ )
339         {
340             if ( i > 0 )
341             {
342                 upName.append( '+' );
343             }
344             addAVA( schemaManager, avas[i] );
345             upName.append( avas[i].getName() );
346         }
347         setUpName( upName.toString() );
348         normalize();
349     }
350 
351 
352     public Rdn( Ava... avas ) throws LdapInvalidDnException
353     {
354         this( null, avas );
355     }
356 
357 
358     /**
359      * Constructs an Rdn from the given rdn. The content of the rdn is simply
360      * copied into the newly created Rdn.
361      *
362      * @param rdn The non-null Rdn to be copied.
363      */
364     public Rdn( Rdn rdn )
365     {
366         nbAvas = rdn.size();
367         this.normName = rdn.normName;
368         this.upName = rdn.getName();
369         normalized = rdn.normalized;
370         schemaManager = rdn.schemaManager;
371 
372         switch ( rdn.size() )
373         {
374             case 0:
375                 hashCode();
376 
377                 return;
378 
379             case 1:
380                 this.ava = rdn.ava.clone();
381                 hashCode();
382 
383                 return;
384 
385             default:
386                 // We must duplicate the treeSet and the hashMap
387                 avas = new ArrayList<Ava>();
388                 avaTypes = new MultiValueMap();
389 
390                 for ( Ava currentAva : rdn.avas )
391                 {
392                     avas.add( currentAva.clone() );
393                     avaTypes.put( currentAva.getNormType(), currentAva );
394                 }
395 
396                 hashCode();
397 
398                 return;
399         }
400     }
401 
402 
403     /**
404      * Transform the external representation of the current Rdn to an internal
405      * normalized form where :
406      * - types are trimmed and lower cased
407      * - values are trimmed and lower cased
408      */
409     // WARNING : The protection level is left unspecified on purpose.
410     // We need this method to be visible from the DnParser class, but not
411     // from outside this package.
412     /* Unspecified protection */void normalize()
413     {
414         switch ( nbAvas )
415         {
416             case 0:
417                 // An empty Rdn
418                 normName = "";
419                 break;
420 
421             case 1:
422                 // We have a single Ava
423                 // We will trim and lowercase type and value.
424                 if ( ava.getNormValue().isHumanReadable() )
425                 {
426                     normName = ava.getNormName();
427                 }
428                 else
429                 {
430                     normName = ava.getNormType() + "=#" + Strings.dumpHexPairs( ava.getNormValue().getBytes() );
431                 }
432 
433                 break;
434 
435             default:
436                 // We have more than one Ava
437                 StringBuffer sb = new StringBuffer();
438 
439                 boolean isFirst = true;
440 
441                 for ( Ava ata : avas )
442                 {
443                     if ( isFirst )
444                     {
445                         isFirst = false;
446                     }
447                     else
448                     {
449                         sb.append( '+' );
450                     }
451 
452                     sb.append( ata.getNormName() );
453                 }
454 
455                 normName = sb.toString();
456                 break;
457         }
458 
459         hashCode();
460     }
461 
462 
463     /**
464      * Transform a Rdn by changing the value to its OID counterpart and
465      * normalizing the value accordingly to its type. The modified Rdn is
466      * a new instance, as the Rdn class is immutable.
467      *
468      * @param schemaManager the SchemaManager
469      * @return this Rdn, normalized
470      * @throws LdapInvalidDnException if the Rdn is invalid
471      */
472     public Rdn apply( SchemaManager schemaManager ) throws LdapInvalidDnException
473     {
474         if ( normalized )
475         {
476             return this;
477         }
478 
479         String savedUpName = getName();
480         Dn.rdnOidToName( this, schemaManager );
481         normalize();
482         this.upName = savedUpName;
483         normalized = true;
484         this.schemaManager = schemaManager;
485         hashCode();
486 
487         return this;
488     }
489 
490 
491     /**
492      * Add an Ava to the current Rdn
493      *
494      * @param upType The user provided type of the added Rdn.
495      * @param type The normalized provided type of the added Rdn.
496      * @param upValue The user provided value of the added Rdn
497      * @param value The normalized provided value of the added Rdn
498      * @throws LdapInvalidDnException
499      *             If the Rdn is invalid
500      */
501     private void addAVA( SchemaManager schemaManager, String upType, String type, Value<?> upValue,
502         Value<?> value ) throws LdapInvalidDnException
503     {
504         // First, let's normalize the type
505         Value<?> normalizedValue = value;
506         String normalizedType = Strings.lowerCaseAscii( type );
507         this.schemaManager = schemaManager;
508 
509         if ( schemaManager != null )
510         {
511             OidNormalizer oidNormalizer = schemaManager.getNormalizerMapping().get( normalizedType );
512             normalizedType = oidNormalizer.getAttributeTypeOid();
513 
514             try
515             {
516                 normalizedValue = oidNormalizer.getNormalizer().normalize( value );
517             }
518             catch ( LdapException e )
519             {
520                 throw new LdapInvalidDnException( e.getMessage(), e );
521             }
522         }
523 
524         switch ( nbAvas )
525         {
526             case 0:
527                 // This is the first Ava. Just stores it.
528                 ava = new Ava( schemaManager, upType, normalizedType, upValue, normalizedValue );
529                 nbAvas = 1;
530                 avaType = normalizedType;
531                 hashCode();
532 
533                 return;
534 
535             case 1:
536                 // We already have an Ava. We have to put it in the HashMap
537                 // before adding a new one.
538                 // First, create the HashMap,
539                 avas = new ArrayList<Ava>();
540 
541                 // and store the existing Ava into it.
542                 avas.add( ava );
543                 avaTypes = new MultiValueMap();
544                 avaTypes.put( avaType, ava );
545 
546                 ava = null;
547 
548                 // Now, fall down to the commmon case
549                 // NO BREAK !!!
550 
551             default:
552                 // add a new Ava
553                 Ava newAva = new Ava( schemaManager, upType, normalizedType, upValue, normalizedValue );
554                 avas.add( newAva );
555                 avaTypes.put( normalizedType, newAva );
556                 nbAvas++;
557                 hashCode();
558 
559                 return;
560 
561         }
562     }
563 
564 
565     /**
566      * Replace an Ava into a Rdn at a given position
567      *
568      * @param value The modified Ava
569      * @param pos The position of the Ava in the Rdn
570      * @exception LdapInvalidDnException If the position is not valid
571      */
572     // WARNING : The protection level is left unspecified intentionally.
573     // We need this method to be visible from the DnParser class, but not
574     // from outside this package.
575     /* Unspecified protection */void replaceAva( Ava value, int pos ) throws LdapInvalidDnException
576     {
577         if ( ( pos < 0 ) || ( pos > nbAvas ) )
578         {
579             throw new LdapInvalidDnException( "Cannot set the AVA at position " + pos );
580         }
581 
582         String normalizedType = value.getNormType();
583 
584         switch ( nbAvas )
585         {
586             case 1:
587                 // This is the first Ava. Just stores it.
588                 ava = value;
589                 avaType = normalizedType;
590 
591                 break;
592 
593             default:
594                 Ava oldAva = avas.get( pos );
595                 avas.set( pos, value );
596                 avaTypes.remove( oldAva.getType() );
597                 avaTypes.put( normalizedType, value );
598 
599                 break;
600         }
601 
602         h = 0;
603         hashCode();
604     }
605 
606 
607     /**
608      * Add an Ava to the current schema aware Rdn
609      *
610      * @param value The added Ava
611      */
612     // WARNING : The protection level is left unspecified intentionally.
613     // We need this method to be visible from the DnParser class, but not
614     // from outside this package.
615     /* Unspecified protection */void addAVA( SchemaManager schemaManager, Ava value ) throws LdapInvalidDnException
616     {
617         this.schemaManager = schemaManager;
618         String normalizedType = value.getNormType();
619 
620         switch ( nbAvas )
621         {
622             case 0:
623                 // This is the first Ava. Just stores it.
624                 ava = value;
625                 nbAvas = 1;
626                 avaType = normalizedType;
627                 hashCode();
628 
629                 return;
630 
631             case 1:
632                 // We already have an Ava. We have to put it in the HashMap
633                 // before adding a new one.
634                 // Check that the first AVA is not for the same attribute
635                 if ( avaType.equals( normalizedType ) )
636                 {
637                     throw new LdapInvalidDnException( "Invalid RDN: the " + normalizedType
638                         + " is already present in the RDN" );
639                 }
640 
641                 // First, create the HashMap,
642                 avas = new ArrayList<Ava>();
643 
644                 // and store the existing Ava into it.
645                 avas.add( ava );
646                 avaTypes = new MultiValueMap();
647                 avaTypes.put( avaType, ava );
648 
649                 this.ava = null;
650 
651                 // Now, fall down to the commmon case
652                 // NO BREAK !!!
653 
654             default:
655                 // Check that the AT is not already present
656                 if ( avaTypes.containsKey( normalizedType ) )
657                 {
658                     throw new LdapInvalidDnException( "Invalid RDN: the " + normalizedType
659                         + " is already present in the RDN" );
660                 }
661 
662                 // add a new Ava
663                 avas.add( value );
664                 avaTypes.put( normalizedType, value );
665                 nbAvas++;
666                 hashCode();
667 
668                 break;
669         }
670     }
671 
672 
673     /**
674      * Clear the Rdn, removing all the Avas.
675      */
676     // WARNING : The protection level is left unspecified intentionally.
677     // We need this method to be visible from the DnParser class, but not
678     // from outside this package.
679     /* No protection */void clear()
680     {
681         ava = null;
682         avas = null;
683         avaType = null;
684         avaTypes.clear();
685         nbAvas = 0;
686         normName = "";
687         upName = "";
688         normalized = false;
689         h = 0;
690     }
691 
692 
693     /**
694      * Get the Value of the Ava which type is given as an
695      * argument.
696      *
697      * @param type the type of the NameArgument
698      * @return the Value to be returned, or null if none found.
699      * @throws LdapInvalidDnException if the Rdn is invalid
700      */
701     public Object getValue( String type ) throws LdapInvalidDnException
702     {
703         // First, let's normalize the type
704         String normalizedType = Strings.lowerCaseAscii( Strings.trim( type ) );
705 
706         if ( schemaManager != null )
707         {
708             AttributeType attributeType = schemaManager.getAttributeType( normalizedType );
709 
710             if ( attributeType != null )
711             {
712                 normalizedType = attributeType.getOid();
713             }
714         }
715 
716         switch ( nbAvas )
717         {
718             case 0:
719                 return "";
720 
721             case 1:
722                 if ( Strings.equals( ava.getNormType(), normalizedType ) )
723                 {
724                     return ava.getNormValue().getValue();
725                 }
726 
727                 return "";
728 
729             default:
730                 if ( avaTypes.containsKey( normalizedType ) )
731                 {
732                     @SuppressWarnings("unchecked")
733                     Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType );
734                     StringBuffer sb = new StringBuffer();
735                     boolean isFirst = true;
736 
737                     for ( Ava elem : atavList )
738                     {
739                         if ( isFirst )
740                         {
741                             isFirst = false;
742                         }
743                         else
744                         {
745                             sb.append( ',' );
746                         }
747 
748                         sb.append( elem.getNormValue() );
749                     }
750 
751                     return sb.toString();
752                 }
753 
754                 return "";
755         }
756     }
757 
758 
759     /**
760      * Get the Ava which type is given as an argument. If we
761      * have more than one value associated with the type, we will return only
762      * the first one.
763      *
764      * @param type
765      *            The type of the NameArgument to be returned
766      * @return The Ava, of null if none is found.
767      */
768     public Ava getAva( String type )
769     {
770         // First, let's normalize the type
771         String normalizedType = Strings.lowerCaseAscii( Strings.trim( type ) );
772 
773         switch ( nbAvas )
774         {
775             case 0:
776                 return null;
777 
778             case 1:
779                 if ( ava.getNormType().equals( normalizedType ) )
780                 {
781                     return ava;
782                 }
783 
784                 return null;
785 
786             default:
787                 if ( avaTypes.containsKey( normalizedType ) )
788                 {
789                     @SuppressWarnings("unchecked")
790                     Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType );
791                     return atavList.iterator().next();
792                 }
793 
794                 return null;
795         }
796     }
797 
798 
799     /**
800      * Retrieves the components of this Rdn as an iterator of Avas.
801      * The effect on the iterator of updates to this Rdn is undefined. If the
802      * Rdn has zero components, an empty (non-null) iterator is returned.
803      *
804      * @return an iterator of the components of this Rdn, each an Ava
805      */
806     public Iterator<Ava> iterator()
807     {
808         if ( nbAvas == 1 || nbAvas == 0 )
809         {
810             return new Iterator<Ava>()
811             {
812                 private boolean hasMoreElement = nbAvas == 1;
813 
814 
815                 public boolean hasNext()
816                 {
817                     return hasMoreElement;
818                 }
819 
820 
821                 public Ava next()
822                 {
823                     Ava obj = ava;
824                     hasMoreElement = false;
825                     return obj;
826                 }
827 
828 
829                 public void remove()
830                 {
831                     // nothing to do
832                 }
833             };
834         }
835         else
836         {
837             return avas.iterator();
838         }
839     }
840 
841 
842     /**
843      * Clone the Rdn
844      *
845      * @return A clone of the current Rdn
846      */
847     public Rdn clone()
848     {
849         try
850         {
851             Rdn rdn = ( Rdn ) super.clone();
852             rdn.normalized = normalized;
853 
854             // The Ava is immutable. We won't clone it
855 
856             switch ( rdn.size() )
857             {
858                 case 0:
859                     break;
860 
861                 case 1:
862                     rdn.ava = this.ava.clone();
863                     rdn.avaTypes = avaTypes;
864                     break;
865 
866                 default:
867                     // We must duplicate the treeSet and the hashMap
868                     rdn.avaTypes = new MultiValueMap();
869                     rdn.avas = new ArrayList<Ava>();
870 
871                     for ( Ava currentAva : this.avas )
872                     {
873                         rdn.avas.add( currentAva.clone() );
874                         rdn.avaTypes.put( currentAva.getNormType(), currentAva );
875                     }
876 
877                     break;
878             }
879 
880             return rdn;
881         }
882         catch ( CloneNotSupportedException cnse )
883         {
884             throw new Error( "Assertion failure" );
885         }
886     }
887 
888 
889     /**
890      * @return the user provided name
891      */
892     public String getName()
893     {
894         return upName;
895     }
896 
897 
898     /**
899      * @return The normalized name
900      */
901     public String getNormName()
902     {
903         return normName == null ? "" : normName;
904     }
905 
906 
907     /**
908      * Set the User Provided Name.
909      *
910      * Package private because Rdn is immutable, only used by the Dn parser.
911      *
912      * @param upName the User Provided dame
913      */
914     void setUpName( String upName )
915     {
916         this.upName = upName;
917     }
918 
919 
920     /**
921      * Return the unique Ava, or the first one of we have more
922      * than one
923      *
924      * @return The first Ava of this Rdn
925      */
926     public Ava getAva()
927     {
928         switch ( nbAvas )
929         {
930             case 0:
931                 return null;
932 
933             case 1:
934                 return ava;
935 
936             default:
937                 return avas.get( 0 );
938         }
939     }
940 
941 
942     /**
943      * Return the user provided type, or the first one of we have more than one (the lowest)
944      *
945      * @return The first user provided type of this Rdn
946      */
947     public String getType()
948     {
949         switch ( nbAvas )
950         {
951             case 0:
952                 return null;
953 
954             case 1:
955                 return ava.getType();
956 
957             default:
958                 return avas.get( 0 ).getType();
959         }
960     }
961 
962 
963     /**
964      * Return the normalized type, or the first one of we have more than one (the lowest)
965      *
966      * @return The first normalized type of this Rdn
967      */
968     public String getNormType()
969     {
970         switch ( nbAvas )
971         {
972             case 0:
973                 return null;
974 
975             case 1:
976                 return ava.getNormType();
977 
978             default:
979                 return avas.get( 0 ).getNormType();
980         }
981     }
982 
983 
984     /**
985      * Return the User Provided value
986      *
987      * @return The first User provided value of this Rdn
988      */
989     public Value<?> getValue()
990     {
991         switch ( nbAvas )
992         {
993             case 0:
994                 return null;
995 
996             case 1:
997                 return ava.getValue();
998 
999             default:
1000                 return avas.get( 0 ).getValue();
1001         }
1002     }
1003 
1004 
1005     /**
1006      * Return the normalized value, or the first one of we have more than one (the lowest)
1007      *
1008      * @return The first normalized value of this Rdn
1009      */
1010     public Value<?> getNormValue()
1011     {
1012         switch ( nbAvas )
1013         {
1014             case 0:
1015                 return null;
1016 
1017             case 1:
1018                 return ava.getNormValue();
1019 
1020             default:
1021                 return avas.get( 0 ).getNormValue();
1022         }
1023     }
1024 
1025 
1026     /**
1027      * Compares the specified Object with this Rdn for equality. Returns true if
1028      * the given object is also a Rdn and the two Rdns represent the same
1029      * attribute type and value mappings. The order of components in
1030      * multi-valued Rdns is not significant.
1031      *
1032      * @param rdn
1033      *            Rdn to be compared for equality with this Rdn
1034      * @return true if the specified object is equal to this Rdn
1035      */
1036     public boolean equals( Object that )
1037     {
1038         if ( this == that )
1039         {
1040             return true;
1041         }
1042 
1043         if ( !( that instanceof Rdn ) )
1044         {
1045             return false;
1046         }
1047 
1048         Rdn rdn = ( Rdn ) that;
1049 
1050         // Short cut : compare the normalized Rdn
1051         if ( normName.equals( rdn.normName ) )
1052         {
1053             return true;
1054         }
1055 
1056         // Short cut : compare the normalized Rdn
1057         if ( normName.equals( rdn.normName ) )
1058         {
1059             return true;
1060         }
1061 
1062         if ( rdn.nbAvas != nbAvas )
1063         {
1064             // We don't have the same number of Avas. The Rdn which
1065             // has the higher number of Ava is the one which is
1066             // superior
1067             return false;
1068         }
1069 
1070         switch ( nbAvas )
1071         {
1072             case 0:
1073                 return true;
1074 
1075             case 1:
1076                 return ava.equals( rdn.ava );
1077 
1078             default:
1079                 // We have more than one value. We will
1080                 // go through all of them.
1081 
1082                 // the types are already normalized and sorted in the Avas Map
1083                 // so we could compare the first element with all of the second
1084                 // Ava elemnts, etc.
1085                 Iterator<Ava> localIterator = avas.iterator();
1086 
1087                 while ( localIterator.hasNext() )
1088                 {
1089                     Iterator<Ava> paramIterator = rdn.avas.iterator();
1090 
1091                     Ava localAva = localIterator.next();
1092                     boolean equals = false;
1093 
1094                     while ( paramIterator.hasNext() )
1095                     {
1096                         Ava paramAva = paramIterator.next();
1097 
1098                         if ( localAva.equals( paramAva ) )
1099                         {
1100                             equals = true;
1101                             break;
1102                         }
1103                     }
1104 
1105                     if ( !equals )
1106                     {
1107                         return false;
1108                     }
1109                 }
1110 
1111                 return true;
1112         }
1113     }
1114 
1115 
1116     /**
1117      * Get the number of Avas of this Rdn
1118      *
1119      * @return The number of Avas in this Rdn
1120      */
1121     public int size()
1122     {
1123         return nbAvas;
1124     }
1125 
1126 
1127     /**
1128      * Unescape the given string according to RFC 2253 If in <string> form, a
1129      * LDAP string representation asserted value can be obtained by replacing
1130      * (left-to-right, non-recursively) each <pair> appearing in the <string> as
1131      * follows: replace <ESC><ESC> with <ESC>; replace <ESC><special> with
1132      * <special>; replace <ESC><hexpair> with the octet indicated by the
1133      * <hexpair> If in <hexstring> form, a BER representation can be obtained
1134      * from converting each <hexpair> of the <hexstring> to the octet indicated
1135      * by the <hexpair>
1136      *
1137      * @param value The value to be unescaped
1138      * @return Returns a string value as a String, and a binary value as a byte
1139      *         array.
1140      * @throws IllegalArgumentException When an Illegal value is provided.
1141      */
1142     public static Object unescapeValue( String value ) throws IllegalArgumentException
1143     {
1144         if ( Strings.isEmpty( value ) )
1145         {
1146             return "";
1147         }
1148 
1149         char[] chars = value.toCharArray();
1150 
1151         // If the value is contained into double quotes, return it as is.
1152         if ( ( chars[0] == '\"' ) && ( chars[chars.length - 1] == '\"' ) )
1153         {
1154             return value;
1155         }
1156 
1157         if ( chars[0] == '#' )
1158         {
1159             if ( chars.length == 1 )
1160             {
1161                 // The value is only containing a #
1162                 return StringConstants.EMPTY_BYTES;
1163             }
1164 
1165             if ( ( chars.length % 2 ) != 1 )
1166             {
1167                 throw new IllegalArgumentException( I18n.err( I18n.ERR_04213 ) );
1168             }
1169 
1170             // HexString form
1171             byte[] hexValue = new byte[( chars.length - 1 ) / 2];
1172             int pos = 0;
1173 
1174             for ( int i = 1; i < chars.length; i += 2 )
1175             {
1176                 if ( Chars.isHex( chars, i ) && Chars.isHex( chars, i + 1 ) )
1177                 {
1178                     hexValue[pos++] = Hex.getHexValue( chars[i], chars[i + 1] );
1179                 }
1180                 else
1181                 {
1182                     throw new IllegalArgumentException( I18n.err( I18n.ERR_04214 ) );
1183                 }
1184             }
1185 
1186             return hexValue;
1187         }
1188         else
1189         {
1190             boolean escaped = false;
1191             boolean isHex = false;
1192             byte pair = -1;
1193             int pos = 0;
1194 
1195             byte[] bytes = new byte[chars.length * 6];
1196 
1197             for ( int i = 0; i < chars.length; i++ )
1198             {
1199                 if ( escaped )
1200                 {
1201                     escaped = false;
1202 
1203                     switch ( chars[i] )
1204                     {
1205                         case '\\':
1206                         case '"':
1207                         case '+':
1208                         case ',':
1209                         case ';':
1210                         case '<':
1211                         case '>':
1212                         case '#':
1213                         case '=':
1214                         case ' ':
1215                             bytes[pos++] = ( byte ) chars[i];
1216                             break;
1217 
1218                         default:
1219                             if ( Chars.isHex( chars, i ) )
1220                             {
1221                                 isHex = true;
1222                                 pair = ( ( byte ) ( Hex.getHexValue( chars[i] ) << 4 ) );
1223                             }
1224 
1225                             break;
1226                     }
1227                 }
1228                 else
1229                 {
1230                     if ( isHex )
1231                     {
1232                         if ( Chars.isHex( chars, i ) )
1233                         {
1234                             pair += Hex.getHexValue( chars[i] );
1235                             bytes[pos++] = pair;
1236                             isHex = false;
1237                         }
1238                     }
1239                     else
1240                     {
1241                         switch ( chars[i] )
1242                         {
1243                             case '\\':
1244                                 escaped = true;
1245                                 break;
1246 
1247                             // We must not have a special char
1248                             // Specials are : '"', '+', ',', ';', '<', '>', ' ',
1249                             // '#' and '='
1250                             case '"':
1251                             case '+':
1252                             case ',':
1253                             case ';':
1254                             case '<':
1255                             case '>':
1256                             case '#':
1257                                 if ( i != 0 )
1258                                 {
1259                                     // '#' are allowed if not in first position
1260                                     bytes[pos++] = '#';
1261                                     break;
1262                                 }
1263                             case '=':
1264                                 throw new IllegalArgumentException( I18n.err( I18n.ERR_04215 ) );
1265 
1266                             case ' ':
1267                                 if ( ( i == 0 ) || ( i == chars.length - 1 ) )
1268                                 {
1269                                     throw new IllegalArgumentException( I18n.err( I18n.ERR_04215 ) );
1270                                 }
1271                                 else
1272                                 {
1273                                     bytes[pos++] = ' ';
1274                                     break;
1275                                 }
1276 
1277                             default:
1278                                 if ( ( chars[i] >= 0 ) && ( chars[i] < 128 ) )
1279                                 {
1280                                     bytes[pos++] = ( byte ) chars[i];
1281                                 }
1282                                 else
1283                                 {
1284                                     byte[] result = Unicode.charToBytes( chars[i] );
1285                                     System.arraycopy( result, 0, bytes, pos, result.length );
1286                                     pos += result.length;
1287                                 }
1288 
1289                                 break;
1290                         }
1291                     }
1292                 }
1293             }
1294 
1295             return Strings.utf8ToString( bytes, pos );
1296         }
1297     }
1298 
1299 
1300     /**
1301      * Transform a value in a String, accordingly to RFC 2253
1302      *
1303      * @param value The attribute value to be escaped
1304      * @return The escaped string value.
1305      */
1306     public static String escapeValue( String value )
1307     {
1308         if ( Strings.isEmpty( value ) )
1309         {
1310             return "";
1311         }
1312 
1313         char[] chars = value.toCharArray();
1314         char[] newChars = new char[chars.length * 3];
1315         int pos = 0;
1316 
1317         for ( int i = 0; i < chars.length; i++ )
1318         {
1319             switch ( chars[i] )
1320             {
1321                 case ' ':
1322                     if ( ( i > 0 ) && ( i < chars.length - 1 ) )
1323                     {
1324                         newChars[pos++] = chars[i];
1325                     }
1326                     else
1327                     {
1328                         newChars[pos++] = '\\';
1329                         newChars[pos++] = chars[i];
1330                     }
1331 
1332                     break;
1333 
1334                 case '#':
1335                     if ( i != 0 )
1336                     {
1337                         newChars[pos++] = chars[i];
1338                     }
1339                     else
1340                     {
1341                         newChars[pos++] = '\\';
1342                         newChars[pos++] = chars[i];
1343                     }
1344 
1345                     break;
1346 
1347                 case '"':
1348                 case '+':
1349                 case ',':
1350                 case ';':
1351                 case '=':
1352                 case '<':
1353                 case '>':
1354                 case '\\':
1355                     newChars[pos++] = '\\';
1356                     newChars[pos++] = chars[i];
1357                     break;
1358 
1359                 case 0x7F:
1360                     newChars[pos++] = '\\';
1361                     newChars[pos++] = '7';
1362                     newChars[pos++] = 'F';
1363                     break;
1364 
1365                 case 0x00:
1366                 case 0x01:
1367                 case 0x02:
1368                 case 0x03:
1369                 case 0x04:
1370                 case 0x05:
1371                 case 0x06:
1372                 case 0x07:
1373                 case 0x08:
1374                 case 0x09:
1375                 case 0x0A:
1376                 case 0x0B:
1377                 case 0x0C:
1378                 case 0x0D:
1379                 case 0x0E:
1380                 case 0x0F:
1381                     newChars[pos++] = '\\';
1382                     newChars[pos++] = '0';
1383                     newChars[pos++] = Strings.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
1384                     break;
1385 
1386                 case 0x10:
1387                 case 0x11:
1388                 case 0x12:
1389                 case 0x13:
1390                 case 0x14:
1391                 case 0x15:
1392                 case 0x16:
1393                 case 0x17:
1394                 case 0x18:
1395                 case 0x19:
1396                 case 0x1A:
1397                 case 0x1B:
1398                 case 0x1C:
1399                 case 0x1D:
1400                 case 0x1E:
1401                 case 0x1F:
1402                     newChars[pos++] = '\\';
1403                     newChars[pos++] = '1';
1404                     newChars[pos++] = Strings.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
1405                     break;
1406 
1407                 default:
1408                     newChars[pos++] = chars[i];
1409                     break;
1410             }
1411         }
1412 
1413         return new String( newChars, 0, pos );
1414     }
1415 
1416 
1417     /**
1418      * Transform a value in a String, accordingly to RFC 2253
1419      *
1420      * @param attrValue
1421      *            The attribute value to be escaped
1422      * @return The escaped string value.
1423      */
1424     public static String escapeValue( byte[] attrValue )
1425     {
1426         if ( Strings.isEmpty( attrValue ) )
1427         {
1428             return "";
1429         }
1430 
1431         String value = Strings.utf8ToString( attrValue );
1432 
1433         return escapeValue( value );
1434     }
1435 
1436 
1437     /**
1438      * Tells if the Rdn is schema aware.
1439      *
1440      * @return <code>true</code> if the Rdn is schema aware
1441      */
1442     public boolean isSchemaAware()
1443     {
1444         return schemaManager != null;
1445     }
1446 
1447 
1448     /**
1449      * Validate a NameComponent : <br>
1450      * <p>
1451      * &lt;name-component&gt; ::= &lt;attributeType&gt; &lt;spaces&gt; '='
1452      * &lt;spaces&gt; &lt;attributeValue&gt; &lt;nameComponents&gt;
1453      * </p>
1454      *
1455      * @param dn The string to parse
1456      * @return <code>true</code> if the Rdn is valid
1457      */
1458     public static boolean isValid( String dn )
1459     {
1460         Rdn rdn = new Rdn();
1461         
1462         try
1463         {
1464             parse( dn, rdn );
1465             
1466             return true;
1467         }
1468         catch ( LdapInvalidDnException e )
1469         {
1470             return false;
1471         }
1472     }
1473 
1474 
1475     /**
1476      * Parse a NameComponent : <br>
1477      * <p>
1478      * &lt;name-component&gt; ::= &lt;attributeType&gt; &lt;spaces&gt; '='
1479      * &lt;spaces&gt; &lt;attributeValue&gt; &lt;nameComponents&gt;
1480      * </p>
1481      *
1482      * @param dn The String to parse
1483      * @param rdn The Rdn to fill. Beware that if the Rdn is not empty, the new
1484      *            AttributeTypeAndValue will be added.
1485      * @throws LdapInvalidDnException If the NameComponent is invalid
1486      */
1487     private static void parse( String dn, Rdn rdn ) throws LdapInvalidDnException
1488     {
1489         try
1490         {
1491             FastDnParser.parseRdn( dn, rdn );
1492         }
1493         catch ( TooComplexDnException e )
1494         {
1495             rdn.clear();
1496             new ComplexDnParser().parseRdn( dn, rdn );
1497         }
1498     }
1499 
1500 
1501     /**
1502       * Gets the hashcode of this rdn.
1503       *
1504       * @see java.lang.Object#hashCode()
1505       * @return the instance's hash code
1506       */
1507     public int hashCode()
1508     {
1509         if ( h == 0 )
1510         {
1511             h = 37;
1512 
1513             switch ( nbAvas )
1514             {
1515                 case 0:
1516                     // An empty Rdn
1517                     break;
1518 
1519                 case 1:
1520                     // We have a single Ava
1521                     h = h * 17 + ava.hashCode();
1522                     break;
1523 
1524                 default:
1525                     // We have more than one Ava
1526 
1527                     for ( Ava ata : avas )
1528                     {
1529                         h = h * 17 + ata.hashCode();
1530                     }
1531 
1532                     break;
1533             }
1534         }
1535 
1536         return h;
1537     }
1538 
1539 
1540     /**
1541      * Serialize a RDN into a byte[]
1542      * 
1543      * @return a byte[] containing a RDN
1544      */
1545     public int serialize( byte[] buffer, int pos ) throws IOException
1546     {
1547         int length = 4 + 4; // The nbAvas and the HashCode length
1548 
1549         // The NnbAvas
1550         pos = Serialize.serialize( nbAvas, buffer, pos );
1551 
1552         // The upName
1553         byte[] upNameBytes = Strings.getBytesUtf8( upName );
1554         length += 4 + upNameBytes.length;
1555 
1556         byte[] normNameBytes = Strings.EMPTY_BYTES;
1557         length += 4;
1558 
1559         if ( !upName.equals( normName ) )
1560         {
1561             normNameBytes = Strings.getBytesUtf8( normName );
1562             length += 4 + normNameBytes.length;
1563         }
1564 
1565         // Check that we will be able to store the data in the buffer
1566         if ( buffer.length - pos < length )
1567         {
1568             throw new ArrayIndexOutOfBoundsException();
1569         }
1570 
1571         // Write the upName
1572         pos = Serialize.serialize( upNameBytes, buffer, pos );
1573 
1574         // Write the normName
1575         pos = Serialize.serialize( normNameBytes, buffer, pos );
1576 
1577         // Write the AVAs
1578         switch ( nbAvas )
1579         {
1580             case 0:
1581                 break;
1582 
1583             case 1:
1584                 pos = ava.serialize( buffer, pos );
1585 
1586                 break;
1587 
1588             default:
1589                 for ( Ava localAva : avas )
1590                 {
1591                     pos = localAva.serialize( buffer, pos );
1592                 }
1593 
1594                 break;
1595         }
1596 
1597         // The hash code
1598         pos = Serialize.serialize( h, buffer, pos );
1599 
1600         return pos;
1601     }
1602 
1603 
1604     /**
1605      * Deserialize a RDN from a byte[], starting at a given position
1606      * 
1607      * @param buffer The buffer containing the RDN
1608      * @param pos The position in the buffer
1609      * @return The new position
1610      * @throws IOException If the serialized value is not a RDN
1611      * @throws LdapInvalidAttributeValueException If the serialized RDN is invalid
1612      */
1613     public int deserialize( byte[] buffer, int pos ) throws IOException, LdapInvalidAttributeValueException
1614     {
1615         if ( ( pos < 0 ) || ( pos >= buffer.length ) )
1616         {
1617             throw new ArrayIndexOutOfBoundsException();
1618         }
1619 
1620         // Read the nbAvas
1621         nbAvas = Serialize.deserializeInt( buffer, pos );
1622         pos += 4;
1623 
1624         // Read the upName
1625         byte[] upNameBytes = Serialize.deserializeBytes( buffer, pos );
1626         pos += 4 + upNameBytes.length;
1627         upName = Strings.utf8ToString( upNameBytes );
1628 
1629         // Read the normName
1630         byte[] normNameBytes = Serialize.deserializeBytes( buffer, pos );
1631         pos += 4 + normNameBytes.length;
1632 
1633         if ( normNameBytes.length == 0 )
1634         {
1635             normName = upName;
1636         }
1637         else
1638         {
1639             normName = Strings.utf8ToString( normNameBytes );
1640         }
1641 
1642         // Read the AVAs
1643         switch ( nbAvas )
1644         {
1645             case 0:
1646                 break;
1647 
1648             case 1:
1649                 ava = new Ava( schemaManager );
1650                 pos = ava.deserialize( buffer, pos );
1651                 avaType = ava.getNormType();
1652 
1653                 break;
1654 
1655             default:
1656                 avas = new ArrayList<Ava>();
1657 
1658                 avaTypes = new MultiValueMap();
1659 
1660                 for ( int i = 0; i < nbAvas; i++ )
1661                 {
1662                     Ava ava = new Ava( schemaManager );
1663                     pos = ava.deserialize( buffer, pos );
1664                     avas.add( ava );
1665                     avaTypes.put( ava.getNormType(), ava );
1666                 }
1667 
1668                 ava = null;
1669                 avaType = null;
1670 
1671                 break;
1672         }
1673 
1674         // Read the hashCode
1675         h = Serialize.deserializeInt( buffer, pos );
1676         pos += 4;
1677 
1678         return pos;
1679     }
1680 
1681 
1682     /**
1683      * A Rdn is composed of on to many Avas (AttributeType And Value).
1684      * We should write all those Avas sequencially, following the
1685      * structure :
1686      * <ul>
1687      *   <li>
1688      *     <b>parentId</b> The parent entry's Id
1689      *   </li>
1690      *   <li>
1691      *     <b>nbAvas</b> The number of Avas to write. Can't be 0.
1692      *   </li>
1693      *   <li>
1694      *     <b>upName</b> The User provided Rdn
1695      *   </li>
1696      *   <li>
1697      *     <b>normName</b> The normalized Rdn. It can be empty if the normalized
1698      * name equals the upName.
1699      *   </li>
1700      *   <li>
1701      *     <b>Avas</b>
1702      *   </li>
1703      * </ul>
1704      * <br/>
1705      * For each Ava :
1706      * <ul>
1707      *   <li>
1708      *     <b>start</b> The position of this Ava in the upName string
1709      *   </li>
1710      *   <li>
1711      *     <b>length</b> The Ava user provided length
1712      *   </li>
1713      *   <li>
1714      *     <b>Call the Ava write method</b> The Ava itself
1715      *   </li>
1716      * </ul>
1717      *
1718      * @see Externalizable#readExternal(ObjectInput)
1719      * @param out The stream into which the serialized Rdn will be put
1720      * @throws IOException If the stream can't be written
1721      */
1722     public void writeExternal( ObjectOutput out ) throws IOException
1723     {
1724         out.writeInt( nbAvas );
1725         out.writeUTF( upName );
1726 
1727         if ( upName.equals( normName ) )
1728         {
1729             out.writeUTF( "" );
1730         }
1731         else
1732         {
1733             out.writeUTF( normName );
1734         }
1735 
1736         switch ( nbAvas )
1737         {
1738             case 0:
1739                 break;
1740 
1741             case 1:
1742                 ava.writeExternal( out );
1743                 break;
1744 
1745             default:
1746                 for ( Ava localAva : avas )
1747                 {
1748                     localAva.writeExternal( out );
1749                 }
1750 
1751                 break;
1752         }
1753 
1754         out.writeInt( h );
1755 
1756         out.flush();
1757     }
1758 
1759 
1760     /**
1761      * We read back the data to create a new RDB. The structure
1762      * read is exposed in the {@link Rdn#writeExternal(ObjectOutput)}
1763      * method
1764      *
1765      * @see Externalizable#readExternal(ObjectInput)
1766      * @param in The input stream from which the Rdn will be read
1767      * @throws IOException If we can't read from the input stream
1768      * @throws ClassNotFoundException If we can't create a new Rdn
1769      */
1770     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1771     {
1772         // Read the Ava number
1773         nbAvas = in.readInt();
1774 
1775         // Read the UPName
1776         upName = in.readUTF();
1777 
1778         // Read the normName
1779         normName = in.readUTF();
1780 
1781         if ( Strings.isEmpty( normName ) )
1782         {
1783             normName = upName;
1784         }
1785 
1786         switch ( nbAvas )
1787         {
1788             case 0:
1789                 ava = null;
1790                 break;
1791 
1792             case 1:
1793                 ava = new Ava( schemaManager );
1794                 ava.readExternal( in );
1795                 avaType = ava.getNormType();
1796 
1797                 break;
1798 
1799             default:
1800                 avas = new ArrayList<Ava>();
1801 
1802                 avaTypes = new MultiValueMap();
1803 
1804                 for ( int i = 0; i < nbAvas; i++ )
1805                 {
1806                     Ava ava = new Ava( schemaManager );
1807                     ava.readExternal( in );
1808                     avas.add( ava );
1809                     avaTypes.put( ava.getNormType(), ava );
1810                 }
1811 
1812                 ava = null;
1813                 avaType = null;
1814 
1815                 break;
1816         }
1817 
1818         h = in.readInt();
1819     }
1820 
1821 
1822     public int compareTo( Rdn arg0 )
1823     {
1824         // TODO Auto-generated method stub
1825         return 0;
1826     }
1827 
1828 
1829     /**
1830      * @return a String representation of the Rdn. The caller will get back the user
1831      * provided Rdn
1832      */
1833     public String toString()
1834     {
1835         return upName == null ? "" : upName;
1836     }
1837 }