You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2005/12/25 20:23:47 UTC

svn commit: r359002 - /directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java

Author: elecharny
Date: Sun Dec 25 11:23:42 2005
New Revision: 359002

URL: http://svn.apache.org/viewcvs?rev=359002&view=rev
Log:
Totally reworked version of LdapRdn class, renamed Rdn.
It is compatible with the JDK 1.5 javax.naming.directory.Rdn
class.

Added:
    directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java
      - copied, changed from r357844, directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java

Copied: directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java (from r357844, directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java)
URL: http://svn.apache.org/viewcvs/directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java?p2=directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java&p1=directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java&r1=357844&r2=359002&rev=359002&view=diff
==============================================================================
--- directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java (original)
+++ directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java Sun Dec 25 11:23:42 2005
@@ -17,17 +17,19 @@
 package org.apache.ldap.common.name;
 
 import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
+import java.util.TreeSet;
 
 import javax.naming.InvalidNameException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
 
 import org.apache.asn1.codec.util.StringUtils;
 import org.apache.commons.collections.MultiHashMap;
-import org.apache.ldap.common.LdapString;
 
 /**
  * This class store the name-component part or the following BNF grammar (as of RFC2253, par. 3, 
@@ -54,6 +56,10 @@
  * following RDN :<br>
  *   ou=value + cn=other value<br>
  * <br>  
+ * or
+ * <br>
+ *   ou=value + ou=another value<br>
+ *   <br> 
  * In this case, we have to store an 'ou' and a 'cn' in the RDN.<br>
  * <br>
  * The types are case insensitive. <br>
@@ -82,52 +88,64 @@
  * because we have more than one spaces inside the value.<br>
  * <br>
  * 
+ * The Rdn is composed of one or more AttributeTypeAndValue (atav)
+ * Those atavs are ordered in the alphabetical natural order :
+ * a < b < c ... < z
+ * 
+ * As the type are not case sensitive, we can say that a = A
+ * 
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  *
  */
-public class LdapRDN extends LdapString implements Cloneable, Comparable
+public class Rdn implements Cloneable, Comparable
 {
 	/** The User Provided RDN */
 	private String upName = null;
 	
+	/** The normalized RDN */
+	private String string = null;
+	
     /** 
      * Stores all couple type = value. We may have more than one type,
-     * if the '+' character appears in the AttributeTypeAndValue. The key is
-     * the type, the value is a AttributeTypeAndValue.
+     * if the '+' character appears in the AttributeTypeAndValue. This is 
+     * a TreeSet, because we want the ATAVs to be sorted.
+     * An atav may contain more than one value. In this case, the values
+     * are String stored in a List.
      */
-    private MultiHashMap atavs;
+    private TreeSet atavs = null;
     
-    /** Stores the lowest element of the RDN */
-    private transient String lowest;
+    /**
+     * We also keep a set of types, in order to use manipulations. A type is
+     * connected with the atav it represents.
+     */
+    private Map atavTypes = new MultiHashMap();
     
     /** 
-     * A simple AttributeTypeAndValue is used to store the LdapRDN for the simple
+     * A simple AttributeTypeAndValue is used to store the Rdn for the simple
      * case where we only have a single type=value. This will be 99.99%
      * the case. This avoids the creation of a HashMap.
      */
-    private AttributeTypeAndValue atav;
+    private AttributeTypeAndValue atav = null;
     
-    private transient int nbAtavs;
+    /** The number of atavs. We store this number here to avoid complex
+     * manipulation of atav and atavs */
+    private transient int nbAtavs = 0;
     
-    /** Value returned by the compareTo method if values are not equals */
-    public final static int NOT_EQUALS = -1;
-
-    /** Value returned by the compareTo method if values are equals */
-    public final static int EQUALS = 0;
+    /** CompareTo() results */
+    public static final int UNDEFINED = Integer.MAX_VALUE;
+    public static final int SUPERIOR = 1;
+    public static final int INFERIOR = -1;
+    public static final int EQUALS = 0;
     
     /**
      * A empty constructor.
      */
-    public LdapRDN()
+    public Rdn()
     {
-        super();
-        
         // Don't waste space... This is not so often we have multiple
-        // name-components in a RDN... So we won't initialize the Map.
-        atavs = null;
-        atav = null;
+        // name-components in a RDN... So we won't initialize the Map and the treeSet.
         upName = "";
-        nbAtavs = 0;
+        string = "";
     }
     
     /**
@@ -136,10 +154,8 @@
      * @param rdn The String containing the RDN to parse
      * @throws InvalidNameException If the RDN is invalid
      */
-    public LdapRDN( String rdn ) throws InvalidNameException
+    public Rdn( String rdn ) throws InvalidNameException
     {
-        super();
-        
         if ( StringUtils.isNotEmpty( rdn ) )
         {
             try
@@ -152,9 +168,15 @@
                 throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
             }
 
-            // Parse the string and normalize the RDN
-            RDNParser.parse( rdn, this );
-            normalize();
+            // Parse the string. The Rdn will be updated.
+            RdnParser.parse( rdn, this );
+            normalizeString();
+            // The upName is set by the RdnParser
+        }
+        else
+        {
+            upName = "";
+            string = "";
         }
     }
     
@@ -167,15 +189,13 @@
      * @throws InvalidNameException If the RDN is invalid
      */
     
-    public LdapRDN( byte[] bytes ) throws InvalidNameException
+    public Rdn( byte[] bytes ) throws InvalidNameException
     {
-        super();
-        
         try
         {
-            RDNParser.parse( new String( bytes, "UTF-8" ), this );
-            normalize();
-            upName = StringUtils.utf8ToString( bytes );
+            RdnParser.parse( new String( bytes, "UTF-8" ), this );
+            normalizeString();
+            // The upName is set by the RdnParser
         }
         catch ( UnsupportedEncodingException uee )
         {
@@ -184,21 +204,62 @@
     }
     
     /**
-     * A constructor that constructs a RDN from a type and a value
+     * A constructor that constructs a RDN from a type and a value.
+     * 
+     * Constructs an Rdn from the given attribute type and value. The string attribute 
+     * values are not interpretted as RFC 2253  formatted RDN strings. That is, the 
+     * values are used literally (not parsed) and assumed to be unescaped.
+     * 
      * @param type The type of the RDN
      * @param value The value of the RDN
      * @throws InvalidNameException If the RDN is invalid
      */
-    public LdapRDN( String type, String value ) throws InvalidNameException
+    public Rdn( String type, String value ) throws InvalidNameException
     {
         super();
-        
-        // Don't waste space... This is not so often we have multiple
-        // name-components in a RDN... So we won't initialize the Map.
-        atavs = null;
+
         addAttributeTypeAndValue( type, value );
-        normalize();
+        
         upName = type + '=' + value;
+        normalizeString();
+    }
+    
+    /**
+     * Constructs an Rdn from the given rdn. The contents of the rdn are simply 
+     * copied into the newly created 
+     * 
+     * @param rdn The non-null Rdn to be copied.
+     */
+    public Rdn( Rdn rdn )
+    {
+    	super();
+		nbAtavs = rdn.getNbAtavs();
+    	this.string = new String( rdn.string );
+		this.upName = new String( rdn.getUpName() );
+    	
+		switch ( rdn.getNbAtavs() )
+		{
+			case 0 :
+				return;
+				
+			case 1 :
+	    		this.atav = (AttributeTypeAndValue)rdn.atav.clone();
+	    		return;
+
+			default :
+				// We must duplicate the treeSet and the hashMap
+				Iterator iter = rdn.atavs.iterator();
+			
+				atavs = new TreeSet();
+				atavTypes = new MultiHashMap();
+			
+				while ( iter.hasNext() )
+				{
+					AttributeTypeAndValue currentAtav = (AttributeTypeAndValue)iter.next();
+					atavs.add( currentAtav.clone() );
+					atavTypes.put( currentAtav.getType(), currentAtav );
+				}
+		}
     }
 
     /**
@@ -207,7 +268,10 @@
      * - types are trimmed and lowercased
      * - values are trimmed and lowercased
      */
-    private void normalize()
+    // WARNING : The protection level is left unspecified intentionnaly.
+    // We need this method to be visible from the DnParser class, but not 
+    // from outside this package.
+    /* Unspecified protection */ void normalizeString()
     {
         switch ( nbAtavs )
         {
@@ -227,7 +291,7 @@
                 // We have more than one AttributeTypeAndValue
                 StringBuffer sb = new StringBuffer();
 
-                Iterator elems = atavs.values().iterator();
+                Iterator elems = atavs.iterator();
                 boolean isFirst = true;
                 
                 while ( elems.hasNext() )
@@ -250,15 +314,6 @@
                 string = sb.toString();
                 break;
         }
-
-        try
-        {
-            bytes = string.getBytes( "UTF-8" );
-        }
-        catch ( UnsupportedEncodingException uee )
-        {
-            // We can't reach this point.
-        }
     }
 
     /**
@@ -268,138 +323,61 @@
      * @param value The value of the added RDN
      * @throws InvalidNameException If the RDN is invalid
      */
-    public void addAttributeTypeAndValue( String type, String value) throws InvalidNameException
+    // WARNING : The protection level is left unspecified intentionnaly.
+    // We need this method to be visible from the DnParser class, but not 
+    // from outside this package.
+    /* Unspecified protection */ void addAttributeTypeAndValue( String type, String value) throws InvalidNameException
     {
         // First, let's normalize the type
         String normalizedType = StringUtils.lowerCase( StringUtils.trim( type ) );
         String normalizedValue = StringUtils.trim( value );
         
-        boolean isLowest = true;
-        
         switch ( nbAtavs )
         {
             case 0 :
                 // This is the first AttributeTypeAndValue. Just stores it.
                 atav = new AttributeTypeAndValue( normalizedType, normalizedValue );
-                lowest = normalizedType;
                 nbAtavs = 1;
-                break;
+                atavTypes.put( normalizedType, atav );
+                return;
                 
             case 1 :
                 // We already have an atav. We have to put it in the HashMap
                 // before adding a new one.
                 // First, create the HashMap,
-                atavs = new MultiHashMap();
+                atavs = new TreeSet();
                 
                 // and store the existing AttributeTypeAndValue into it.
-                atavs.put( atav.getType(), atav );
+                atavs.add( atav );
+
                 atav = null;
 
                 // Now, fall down to the commmon case
                 // NO BREAK !!!
 
             default :
-            	// Before adding the new AttributeTypeAndValue, we want to compare
-            	// it to the first element : we must the first element to be the
-            	// lowest of all the elements.
-            	
-            	if ( normalizedType.compareTo( lowest ) < 0 )
-            	{
-            		lowest = normalizedType;
-            	}
-            	else
-            	{
-            		isLowest = false;
-            	}
-            
                 // add a new AttributeTypeAndValue
-                atavs.put( normalizedType, new AttributeTypeAndValue( normalizedType, normalizedValue ) );
+            	AttributeTypeAndValue newAtav = new AttributeTypeAndValue( normalizedType, normalizedValue );
+            	atavs.add( newAtav );
+                atavTypes.put( normalizedType, newAtav );
             
                 nbAtavs++;
                 break;
                     
         }
-        
-        if ( StringUtils.isEmpty( string ) )
-        {
-            string = normalizedType + '=' + normalizedValue;
-        }
-        else
-        {
-        	if ( isLowest )
-        	{
-        		string = normalizedType + '=' + normalizedValue + '+' + string;
-        	}
-        	else
-        	{
-        		string = string + '+' + normalizedType + '=' + normalizedValue;
-        	}
-        }
     }
 
     /**
-     * Remove a Name from a RDN
-     * 
-     * @param type The nome to remove
-     * @throws InvalidNameException If the name does not exists or if the RDN is empty
-     */
-    public void removeAttributeTypeAndValue( String type ) throws InvalidNameException
-    {
-        if ( StringUtils.isEmpty( type ) )
-        {
-            return;
-        }
-        
-        // First, let's normalize the type
-        String normalizedType = StringUtils.lowerCase( StringUtils.trim( type ) );
-        
-        switch ( nbAtavs )
-        {
-            case 0 :
-                throw new InvalidNameException( "Cannot remove a AttributeTypeAndValue form an empty RDN" );
-                
-            case 1 :
-                if ( normalizedType.equals( atav.getType() ) )
-                {
-                    atav = null;
-                    string = "";
-                    nbAtavs--;
-                }
-                else
-                {
-                    throw new InvalidNameException( "Name '" + normalizedType + "' is not valid for the RDN '" + this.toString() + "'");
-                }
-                
-                break;
-                
-            default :
-                if ( atavs.containsKey( normalizedType ) )
-                {
-                    atavs.remove( normalizedType );
-                    normalize();
-                    nbAtavs --;
-                }
-                else
-                {
-                    throw new InvalidNameException( "Name '" + normalizedType + "' is not valid for the RDN '" + this.toString() + "'");
-                }
-            
-                break;
-            
-        }
-    }
-    
-    /**
      * Clear the RDN, removing all the AttributeTypeAndValues.
      */
     public void clear()
     {
         atav = null;
         atavs = null;
+        atavTypes.clear();
         nbAtavs = 0;
         string = "";
         upName = "";
-        bytes = EMPTY_BYTES;
     }
     
     /**
@@ -429,27 +407,41 @@
                 }
                 
             default :
-                if ( atavs.containsKey( normalizedType ) )
+                if ( atavTypes.containsKey( normalizedType ) )
                 {
-                    List avas = (ArrayList)atavs.get( normalizedType );
-                    StringBuffer sb = new StringBuffer();
-                    boolean isFirst = true;
+                    Object obj = atavTypes.get( normalizedType );
                     
-                    for ( int i = 0; i < avas.size(); i++ )
+                    if ( obj instanceof AttributeTypeAndValue )
                     {
-                        if ( isFirst )
-                        {
-                            isFirst = false;
-                        }
-                        else
+                    	return ((AttributeTypeAndValue)obj).getValue();
+                    }
+                    else if ( obj instanceof List )
+                    {
+                        StringBuffer sb = new StringBuffer();
+                        boolean isFirst = true;
+                        
+                        for ( int i = 0; i < ((List)obj).size(); i++ )
                         {
-                            sb.append( ',' );
+                        	AttributeTypeAndValue elem = (AttributeTypeAndValue)((List)obj).get( i ); 
+                        	
+                            if ( isFirst )
+                            {
+                                isFirst = false;
+                            }
+                            else
+                            {
+                                sb.append( ',' );
+                            }
+                            
+                            sb.append( elem.getValue() );
                         }
                         
-                        sb.append( ( (AttributeTypeAndValue)avas.get( i ) ).getValue() );
+                        return sb.toString();
+                    }
+                    else
+                    {
+                    	throw new InvalidNameException( "Bad object stored in the RDN" );
                     }
-                    
-                    return sb.toString();
                 }
                 else
                 {
@@ -487,11 +479,9 @@
                 }
                 
             default :
-                if ( atavs.containsKey( normalizedType ) )
+                if ( atavTypes.containsKey( normalizedType ) )
                 {
-                    List values =  (ArrayList)atavs.get( normalizedType );
-                    
-                    return (AttributeTypeAndValue)values.get( 0 );
+                    return (AttributeTypeAndValue)atavTypes.get( normalizedType );
                 }
                 else
                 {
@@ -536,34 +526,48 @@
         }
         else
         {
-        	return atavs.values().iterator();
+        	return atavs.iterator();
         }
     }
     
     /**
-     * Clone the LdapRDN
+     * Clone the Rdn
      */
     public Object clone()
     {
         try
         {
-            LdapRDN rdn = (LdapRDN)super.clone();
+            Rdn rdn = (Rdn)super.clone();
             
             // The AttributeTypeAndValue is immutable. We won't clone it
             
-            if ( atavs != null )
-            {
-                rdn.atavs = new MultiHashMap( nbAtavs );
-                
-                Iterator values = atavs.values().iterator();
-                
-                while ( values.hasNext() )
-                {
-                    AttributeTypeAndValue ava = (AttributeTypeAndValue)values.next();
-                    
-                    rdn.atavs.put( ava.getType(), ava );
-                }
-            }
+    		switch ( rdn.getNbAtavs() )
+    		{
+    			case 0 :
+    				break;
+    				
+    			case 1 :
+    	    		rdn.atav = (AttributeTypeAndValue)this.atav.clone();
+    	            rdn.atavTypes = new MultiHashMap();
+    	    		rdn.atavTypes.put( rdn.atav.getType(), rdn.atav );
+    	    		break;
+
+    			default :
+    				// We must duplicate the treeSet and the hashMap
+    	            rdn.atavTypes = new MultiHashMap();
+    				rdn.atavs = new TreeSet();
+    				
+    				Iterator iter = this.atavs.iterator();
+    			
+    				while ( iter.hasNext() )
+    				{
+    					AttributeTypeAndValue currentAtav = (AttributeTypeAndValue)iter.next();
+    					rdn.atavs.add( currentAtav.clone() );
+    					rdn.atavTypes.put( currentAtav.getType(), currentAtav );
+    				}
+    				
+    				break;
+    		}
             
             return rdn;
         }
@@ -574,30 +578,35 @@
     }
     
     /**
-     * Compares two RDN. They are equals if :
+     * Compares two RDNs. They are equals if :
      * - their have the same number of NC (AttributeTypeAndValue)
-     * - for each NC in object, their is one NC which is equal
-     * - comparizon of type are done case insensitive
-     * - each value is equel, case sensitive
+     * - each ATAVs are equals
+     *   - comparizon of type are done case insensitive
+     *   - each value is equel, case sensitive
+     * - Order of ATAV is not important
      * 
      * If the RDNs are not equals, a positive number is returned if the 
      * first RDN is greated, negative otherwise
      * @param object
-     * @return
+     * @return 0 if both rdn are equals. -1 if the current RDN is inferior, 1 if
+     * teh current Rdn is superioir, UNDIFIED otherwise. 
      */
     public int compareTo( Object object )
     {
-        if ( object instanceof LdapRDN )
+        if ( object instanceof Rdn )
         {
-            LdapRDN rdn = (LdapRDN)object;
+        	Rdn rdn = (Rdn)object;
 
             if ( rdn == null )
             {
-                return 1;
+                return SUPERIOR;
             }
             
             if ( rdn.nbAtavs != nbAtavs )
             {
+            	// We don't have the same number of ATAVs. The Rdn which
+            	// has the higher number of Atav is the one which is
+            	// superior
             	return nbAtavs - rdn.nbAtavs;
             }
             
@@ -612,58 +621,64 @@
                 default :
                     // We have more than one value. We will
                     // go through all of them.
-                    Iterator keys = ((MultiHashMap)atavs).keySet().iterator();
+                    Iterator keys = atavs.iterator();
                     
                     while ( keys.hasNext() )
                     {
-                        String type = (String)keys.next();
+                    	AttributeTypeAndValue current = (AttributeTypeAndValue)keys.next();
+                        String type = current.getType();
                         
-                        if ( ((MultiHashMap)rdn.atavs).containsKey( type ) )
+                        if ( rdn.atavTypes.containsKey( type ) )
                         {
-                            List atavList = (List)((MultiHashMap)atavs).get( type );
-                            List atavList2 = (List)((MultiHashMap)rdn.atavs).get( type );
-                            
-                            // Ok, let's go for ugliness : 
-                            // We are not supposed to have a lot of multi-valued RDN
-                            // with a type that contains multiple values. In fact,
-                            // I don't think that we will ever have this kind of RDN :
-                            // "ou=test+ou=test2".
-                            // And if we do, I won't put a cent on an application that
-                            // have this kind of RDNs, especially if the number of AVA
-                            // is higher than 2...
-                            Iterator atavIter1 = atavList.iterator();
-                            Set atavSet = new HashSet();
-                            
-                            // We build a set of value from the local RDN
-                            while ( atavIter1.hasNext() )
-                            {
-                                AttributeTypeAndValue atavValue = (AttributeTypeAndValue)atavIter1.next();
-                                atavSet.add( StringUtils.lowerCase( atavValue.getValue() ) );
-                            }
-                            
-                            Iterator atavIter2 = atavList2.iterator();
-                            
-                            // Now, we compare each NC from the second RDN to the NC of the first RDN
-                            while ( atavIter2.hasNext() )
-                            {
-                                AttributeTypeAndValue atavValue = (AttributeTypeAndValue)atavIter2.next();
-                                
-                                if ( atavSet.contains( StringUtils.lowerCase( atavValue.getValue() ) ) )
-                                {
-                                    atavSet.remove( StringUtils.lowerCase( atavValue.getValue() ) );
-                                }
-                                else
-                                {
-                                    return NOT_EQUALS;
-                                }
-                            }
-                            
-                            // Last, not least, the Set must be empty for
-                            // both RDN to be equals
-                            if ( atavSet.size() != 0 )
-                            {
-                                return NOT_EQUALS;
-                            }
+                        	List atavLocalList = (List)atavTypes.get( type );
+                        	List atavParamList = (List)rdn.atavTypes.get( type );
+                        	
+                    		if ( atavLocalList.size() == 1 )
+                    		{
+                    			// We have only one ATAV
+                    			AttributeTypeAndValue atavLocal = (AttributeTypeAndValue)atavLocalList.get(0);
+                    			AttributeTypeAndValue atavParam = (AttributeTypeAndValue)atavParamList.get(0);
+                    			
+                    			return atavLocal.compareTo( atavParam );
+                    		}
+                    		else
+                    		{
+	                            // We have to verify that each value of the first list are present in 
+	                            // the second list
+	                            Iterator atavLocals = atavLocalList.iterator();
+	                            
+	                            while ( atavLocals.hasNext() )
+	                            {
+	                            	AttributeTypeAndValue atavLocal = (AttributeTypeAndValue)atavLocals.next();
+	                            	
+	                                Iterator atavParams = atavParamList.iterator();
+	                                boolean found = false;
+	                                
+	                                while ( atavParams.hasNext() )
+	                                {
+	                                	AttributeTypeAndValue atavParam = (AttributeTypeAndValue)atavParams.next();
+	                                	
+	                                	if ( atavLocal.compareTo( atavParam ) == EQUALS )
+	                                	{
+	                                		found = true;
+	                                		break;
+	                                	}
+	                                }
+	                                
+	                                if ( !found )
+	                                {
+	                                	// The ATAV does not exist in the second RDN
+	                                	return SUPERIOR;
+	                                }
+	                            }
+                    		}
+                    		
+                            return EQUALS;
+                        }
+                        else
+                        {
+                        	// We can't find an atav in the rdn : the current one is superior
+                        	return SUPERIOR;
                         }
                     }
                     
@@ -672,7 +687,7 @@
         }
         else
         {
-            return NOT_EQUALS;
+            return object != null ? UNDEFINED : SUPERIOR;
         }
     }
 
@@ -714,17 +729,345 @@
      */
     public AttributeTypeAndValue getAtav()
     {
-    	if ( nbAtavs == 0 )
+    	switch ( nbAtavs )
     	{
-    		return null;
+    		case 0 :
+    			return null;
+    			
+    		case 1 :
+    			return atav;
+    			
+    		default :
+    			return (AttributeTypeAndValue)atavs.first(); 
     	}
-    	else if ( nbAtavs == 1 )
+    }
+    
+    /**
+     * Return the type, or the first one of we have more than one (the lowest)
+     * @return The first type of this RDN
+     */
+    public String getType()
+    {
+    	switch ( nbAtavs )
     	{
-    		return atav;
+    		case 0 :
+    			return null;
+    			
+    		case 1 :
+    			return atav.getType();
+    			
+    		default :
+    			return ((AttributeTypeAndValue)atavs.first()).getType(); 
     	}
-    	else
+    }
+
+    /**
+     * Return the value, or the first one of we have more than one (the lowest)
+     * @return The first value of this RDN
+     */
+    public String getValue()
+    {
+    	switch ( nbAtavs )
     	{
-    		return (AttributeTypeAndValue)atavs.get( lowest ); 
+    		case 0 :
+    			return null;
+    			
+    		case 1 :
+    			return atav.getValue();
+    			
+    		default :
+    			return ((AttributeTypeAndValue)atavs.first()).getValue(); 
     	}
     }
+    
+    /**
+     * Compares the specified Object with this Rdn for equality. Returns true 
+     * if the given object is also a Rdn and the two Rdns represent the same 
+     * attribute type and value mappings. The order of components in multi-valued 
+     * Rdns is not significant.
+     * @param rdn Rdn to be compared for equality with this Rdn
+     * @return true if the specified object is equal to this Rdn
+     */
+	public boolean equals( Object rdn )
+	{
+		if ( this == rdn )
+		{
+			return true;
+		}
+		
+		if ( !(rdn instanceof Rdn ) )
+		{
+			return false;
+		}
+
+		return compareTo( (Rdn)rdn ) == EQUALS;
+	}
+	
+	/**
+	 * Returns the hash code of this RDN. Two RDNs that are equal (according 
+	 * to the equals method) will have the same hash code.
+	 * 
+	 * @returnAn int representing the hash code of this Rdn 
+	 */
+	public int hashcode()
+	{
+		// We compute the hashcode using the string, which is a 
+		// normalized form of a rdn. unescapeValue
+		return 37*17 + string.hashCode();
+	}
+	
+	/**
+	 * Get the number of Attribute type and value of this Rdn
+	 * @return The number of ATAVs in this Rdn
+	 */
+	public int size()
+	{
+		return nbAtavs;
+	}
+	
+	/**
+	 * Transform the Rdn into an javax.naming.directory.Attributes
+	 * 
+	 * @return An attributes structure containing all the ATAVs
+	 */
+	public Attributes toAttributes()
+	{
+		Attributes attributes = new BasicAttributes();
+		
+		Iterator types = atavTypes.keySet().iterator();
+		
+		while ( types.hasNext() )
+		{
+			String type = (String)types.next();
+			List values = (List)atavTypes.get( type ); 
+			
+			Attribute attribute = new BasicAttribute( type, true );
+			
+			Iterator iterValues = values.iterator();
+			
+			while ( iterValues.hasNext() )
+			{
+				AttributeTypeAndValue value = (AttributeTypeAndValue)iterValues.next();
+				
+				attribute.add( value.getValue() );
+			}
+			
+			attributes.put( attribute );
+		}
+		
+		return attributes;
+	}
+	
+	/**
+	 * Unescape the given string according to RFC 2253
+	 * If in <string> form, a LDAP string representation asserted value can
+     * be obtained by replacing (left-to-right, non-recursively) each <pair>
+     * appearing in the <string> as follows:
+     *   replace <ESC><ESC> with <ESC>;
+     *   replace <ESC><special> with <special>;
+     *   replace <ESC><hexpair> with the octet indicated by the <hexpair>
+     *   
+     * If in <hexstring> form, a BER representation can be obtained from
+     * converting each <hexpair> of the <hexstring> to the octet indicated by
+     * the <hexpair>
+     * 
+	 * @param value The value to be unescaped
+	 * @return Returns a string value as a String, and a binary value as a byte array.
+	 * @throws IllegalArgumentException - When an Illegal value is provided.
+	 */
+	public static Object unescapeValue( String value ) throws IllegalArgumentException
+	{
+		if ( StringUtils.isEmpty( value ) )
+		{
+			return "";
+		}
+		
+		char[] chars = value.toCharArray();
+		
+		if ( chars[0] == '#' )
+		{
+			if ( chars.length == 1 )
+			{
+				// The value is only containing a #
+				return StringUtils.EMPTY_BYTES;
+			}
+			
+			if ( ( chars.length % 2 ) != 1 )
+			{
+				throw new IllegalArgumentException("This value is not in hex form, we have an odd number of hex chars" );
+			}
+			
+			// HexString form
+			byte[] hexValue = new byte[ (chars.length - 1)/2];
+			int pos = 0;
+			
+			for ( int i = 1; i < chars.length; i += 2 )
+			{
+				if ( StringUtils.isHex( chars, i ) && StringUtils.isHex( chars, i + 1 ) ) 
+				{
+					hexValue[ pos++ ] = (byte)((StringUtils.HEX_VALUE[ chars[i] ] << 4) + StringUtils.HEX_VALUE[ chars[i + 1] ]);
+				}
+				else
+				{
+					throw new IllegalArgumentException("This value is not in hex form" );
+				}
+			}
+			
+			return hexValue;
+		}
+		else
+		{
+			boolean escaped = false;
+			boolean isHex = false;
+			byte pair = -1;
+			int pos = 0;
+			
+			byte[] bytes = new byte[ chars.length * 6];
+			
+			for ( int i = 0; i < chars.length; i++ )
+			{
+				if ( escaped )
+				{
+					escaped = false;
+					
+					switch ( chars[i] )
+					{
+					case '\\' :
+					case '"' :
+					case '+' :
+					case ',' :
+					case ';' :
+					case '<' :
+					case '>' :
+					case '#' :
+					case '=' :
+					case ' ' :
+						bytes[pos++] = (byte)chars[i];
+						break;
+						
+					default :
+						if ( StringUtils.isHex( chars, i ) )
+						{
+							isHex = true;
+							pair = ((byte)(StringUtils.HEX_VALUE[ chars[i] ] << 4));
+						}
+					}
+				}
+				else
+				{
+					if ( isHex )
+					{
+						if ( StringUtils.isHex( chars, i ) )
+						{
+							pair += (byte)StringUtils.HEX_VALUE[ chars[i] ];
+							bytes[pos++] = pair;
+						}
+					}
+					else
+					{
+						switch ( chars[i] )
+						{
+						case '\\' :
+							escaped = true;
+							break;
+							
+							// We must not have a special char
+							// Specials are : '"', '+', ',', ';', '<', '>', ' ', '#' and '=' 
+						case '"' :
+						case '+' :
+						case ',' :
+						case ';' :
+						case '<' :
+						case '>' :
+						case '#' :
+						case '=' :
+						case ' ' :
+							throw new IllegalArgumentException( "Unescaped special characters are not allowed" );
+							
+						default :
+							byte[] result = StringUtils.charToBytes( chars[i] );
+							
+							for ( int j = 0; j < result.length; j++ )
+							{
+								bytes[pos++] = result[j];
+							}
+						}
+					}
+				}
+			}
+			
+			return StringUtils.utf8ToString( bytes, pos );
+		}
+	}
+	
+	/**
+	 * Transform a value in a String, accordingly to RFC 2253
+	 * 
+	 * @param attrValue The attribute value to be escaped
+	 * @return The escaped string value.
+	 */
+	public static String escapeValue( Object attrValue)
+	{
+		if (StringUtils.isEmpty( (byte[])attrValue) )
+		{
+			return "";
+		}
+		
+		String value = StringUtils.utf8ToString( (byte[])attrValue );
+		
+		char[] chars = value.toCharArray();
+		char[] newChars = new char[chars.length * 3];
+		int pos = 0;
+		
+		for ( int i = 0; i < chars.length; i++ )
+		{
+			switch ( chars[i] )
+			{
+				case ' ' :
+				case '"' :
+				case '#' :
+				case '+' :
+				case ',' :
+				case ';' :
+				case '=' :
+				case '<' :
+				case '>' :
+				case '\\' :
+					newChars[pos++] = '\\';
+					newChars[pos++] = chars[i];
+					break;
+					
+				case 0x7F :
+					newChars[pos++] = '\\';
+					newChars[pos++] = '7';
+					newChars[pos++] = 'F';
+					break;
+					
+				case 0x00 :	case 0x01 :	case 0x02 :	case 0x03 :
+				case 0x04 :	case 0x05 :	case 0x06 :	case 0x07 :
+				case 0x08 :	case 0x09 :	case 0x0A :	case 0x0B :
+				case 0x0C :	case 0x0D :	case 0x0E :	case 0x0F :
+					newChars[pos++] = '\\';
+					newChars[pos++] = '0';
+					newChars[pos++] = StringUtils.dumpHex( (byte)(chars[i] & 0x0F) );
+					break;
+					
+				case 0x10 :	case 0x11 :	case 0x12 :	case 0x13 :
+				case 0x14 :	case 0x15 :	case 0x16 :	case 0x17 :
+				case 0x18 :	case 0x19 :	case 0x1A :	case 0x1B :
+				case 0x1C :	case 0x1D :	case 0x1E :	case 0x1F :
+					newChars[pos++] = '\\';
+					newChars[pos++] = '1';
+					newChars[pos++] = StringUtils.dumpHex( (byte)(chars[i] & 0x0F) );
+					break;
+					
+
+				default :
+					newChars[pos++] = chars[i];
+					
+			}
+		}
+		
+		return new String( newChars, 0, pos);
+	}
 }