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 *    http://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.Collection;
029import java.util.Iterator;
030import java.util.List;
031
032import org.apache.commons.collections.MultiMap;
033import org.apache.commons.collections.map.MultiValueMap;
034import org.apache.directory.api.i18n.I18n;
035import org.apache.directory.api.ldap.model.entry.StringValue;
036import org.apache.directory.api.ldap.model.entry.Value;
037import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
038import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
039import org.apache.directory.api.ldap.model.schema.AttributeType;
040import org.apache.directory.api.ldap.model.schema.SchemaManager;
041import org.apache.directory.api.util.Chars;
042import org.apache.directory.api.util.Hex;
043import org.apache.directory.api.util.Serialize;
044import org.apache.directory.api.util.Strings;
045import org.apache.directory.api.util.Unicode;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049
050/**
051 * This class store the name-component part or the following BNF grammar (as of
052 * RFC2253, par. 3, and RFC1779, fig. 1) : <br> - &lt;name-component&gt; ::=
053 * &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt;
054 * &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; <br> -
055 * &lt;attributeTypeAndValues&gt; ::= &lt;spaces&gt; '+' &lt;spaces&gt;
056 * &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt;
057 * &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; | e <br> -
058 * &lt;attributeType&gt; ::= [a-zA-Z] &lt;keychars&gt; | &lt;oidPrefix&gt; [0-9]
059 * &lt;digits&gt; &lt;oids&gt; | [0-9] &lt;digits&gt; &lt;oids&gt; <br> -
060 * &lt;keychars&gt; ::= [a-zA-Z] &lt;keychars&gt; | [0-9] &lt;keychars&gt; | '-'
061 * &lt;keychars&gt; | e <br> - &lt;oidPrefix&gt; ::= 'OID.' | 'oid.' | e <br> -
062 * &lt;oids&gt; ::= '.' [0-9] &lt;digits&gt; &lt;oids&gt; | e <br> -
063 * &lt;attributeValue&gt; ::= &lt;pairs-or-strings&gt; | '#' &lt;hexstring&gt;
064 * |'"' &lt;quotechar-or-pairs&gt; '"' <br> - &lt;pairs-or-strings&gt; ::= '\'
065 * &lt;pairchar&gt; &lt;pairs-or-strings&gt; | &lt;stringchar&gt;
066 * &lt;pairs-or-strings&gt; | e <br> - &lt;quotechar-or-pairs&gt; ::=
067 * &lt;quotechar&gt; &lt;quotechar-or-pairs&gt; | '\' &lt;pairchar&gt;
068 * &lt;quotechar-or-pairs&gt; | e <br> - &lt;pairchar&gt; ::= ',' | '=' | '+' |
069 * '&lt;' | '&gt;' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F] <br> -
070 * &lt;hexstring&gt; ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; <br> -
071 * &lt;hexpairs&gt; ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; | e <br> -
072 * &lt;digits&gt; ::= [0-9] &lt;digits&gt; | e <br> - &lt;stringchar&gt; ::=
073 * [0x00-0xFF] - [,=+&lt;&gt;#;\"\n\r] <br> - &lt;quotechar&gt; ::= [0x00-0xFF] -
074 * [\"] <br> - &lt;separator&gt; ::= ',' | ';' <br> - &lt;spaces&gt; ::= ' '
075 * &lt;spaces&gt; | e <br>
076 * <br>
077 * A Rdn is a part of a Dn. It can be composed of many types, as in the Rdn
078 * following Rdn :<br>
079 * ou=value + cn=other value<br>
080 * <br>
081 * or <br>
082 * ou=value + ou=another value<br>
083 * <br>
084 * In this case, we have to store an 'ou' and a 'cn' in the Rdn.<br>
085 * <br>
086 * The types are case insensitive. <br>
087 * Spaces before and after types and values are not stored.<br>
088 * Spaces before and after '+' are not stored.<br>
089 * <br>
090 * Thus, we can consider that the following RDNs are equals :<br>
091 * <br>
092 * 'ou=test 1'<br> ' ou=test 1'<br>
093 * 'ou =test 1'<br>
094 * 'ou= test 1'<br>
095 * 'ou=test 1 '<br> ' ou = test 1 '<br>
096 * <br>
097 * So are the following :<br>
098 * <br>
099 * '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 */
116public 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}