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