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