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