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