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