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 2006/01/09 02:03:39 UTC

svn commit: r367149 [2/4] - in /directory/trunk/ldap-common/src: main/java/org/apache/ldap/common/codec/ main/java/org/apache/ldap/common/codec/add/ main/java/org/apache/ldap/common/codec/bind/ main/java/org/apache/ldap/common/codec/compare/ main/java/...

Copied: directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDN.java (from r366272, directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/codec/util/LdapDN.java)
URL: http://svn.apache.org/viewcvs/directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDN.java?p2=directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDN.java&p1=directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/codec/util/LdapDN.java&r1=366272&r2=367149&rev=367149&view=diff
==============================================================================
--- directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/codec/util/LdapDN.java (original)
+++ directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDN.java Sun Jan  8 17:02:49 2006
@@ -14,142 +14,50 @@
  *   limitations under the License.
  *
  */
-/*package org.apache.asn1new.ldap.codec.primitives;
 
-import java.io.UnsupportedEncodingException;
-
-import org.apache.asn1.codec.DecoderException;
-import org.apache.asn1new.util.StringTools;
-
-/**
- * This class parses a DN. 
- * 
- * The DN MUST respect this BNF grammar (as of RFC2253, par. 3, and RFC1779, fig. 1) <br>
- * 
- * <p>
- *-    &lt;distinguishedName&gt;      ::= &lt;name&gt; | e <br>
- *-    &lt;name&gt;                   ::= &lt;name-component&gt; &lt;name-components&gt; <br>
- *-    &lt;name-components&gt;        ::= &lt;spaces&gt; &lt;separator&gt; &lt;spaces&gt; &lt;name-component&gt; &lt;name-components&gt; | e <br>
- *-    &lt;name-component&gt;         ::= &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; <br>
- *-    &lt;attributeTypeAndValues&gt; ::= &lt;spaces&gt; '+' &lt;spaces&gt; &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; | e <br>
- *-    &lt;attributeType&gt;          ::= [a-zA-Z] &lt;keychars&gt; | &lt;oidPrefix&gt; [0-9] &lt;digits&gt; &lt;oids&gt; | [0-9] &lt;digits&gt; &lt;oids&gt; <br>
- *-    &lt;keychars&gt;               ::= [a-zA-Z] &lt;keychars&gt; | [0-9] &lt;keychars&gt; | '-' &lt;keychars&gt; | e <br>
- *-    &lt;oidPrefix&gt;              ::= 'OID.' | 'oid.' | e <br>
- *-    &lt;oids&gt;                   ::= '.' [0-9] &lt;digits&gt; &lt;oids&gt; | e <br>
- *-    &lt;attributeValue&gt;         ::= &lt;pairs-or-strings&gt; | '#' &lt;hexstring&gt; |'"' &lt;quotechar-or-pairs&gt; '"' <br>
- *-    &lt;pairs-or-strings&gt;       ::= '\' &lt;pairchar&gt; &lt;pairs-or-strings&gt; | &lt;stringchar&gt; &lt;pairs-or-strings&gt; | e <br>
- *-    &lt;quotechar-or-pairs&gt;     ::= &lt;quotechar&gt; &lt;quotechar-or-pairs&gt; | '\' &lt;pairchar&gt; &lt;quotechar-or-pairs&gt; | e <br>
- *-    &lt;pairchar&gt;               ::= ',' | '=' | '+' | '&lt;' | '&gt;' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F]  <br>
- *-    &lt;hexstring&gt;              ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; <br>
- *-    &lt;hexpairs&gt;               ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; | e <br>
- *-    &lt;digits&gt;                 ::= [0-9] &lt;digits&gt; | e <br>
- *-    &lt;stringchar&gt;             ::= [0x00-0xFF] - [,=+&lt;&gt;#;\"\n\r] <br>
- *-    &lt;quotechar&gt;              ::= [0x00-0xFF] - [\"] <br>
- *-    &lt;separator&gt;              ::= ',' | ';' <br>
- *-    &lt;spaces&gt;                 ::= ' ' &lt;spaces&gt; | e <br>
- * </p>
- *
- * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
- *
-public class LdapDN extends RelativeLdapDN
-{
-    //~ Static fields/initializers -----------------------------------------------------------------
-
-    /** A null LdapDN *
-    public transient static final LdapDN EMPTY_STRING = new LdapDN();
-
-    //~ Methods ------------------------------------------------------------------------------------
-
-    /**
-     * Construct an empty LdapDN object
-     *
-    public LdapDN()
-    {
-        super(0, false);
-    }
-    
-    /**
-     * Parse a buffer and checks that it is a valid DN <br>
-     * <p>
-     * &lt;distinguishedName&gt;     ::= &lt;name&gt; | e <br>
-     * &lt;name&gt;                ::= &lt;name-component&gt; &lt;name-components&gt; <br>
-     * &lt;name-components&gt;    ::= &lt;spaces&gt; &lt;separator&gt; &lt;spaces&gt; &lt;name-component&gt; &lt;name-components&gt; | e <br>
-     * </p>
-     * 
-     * @param bytes The byte buffer that contains the DN
-     * @exception A DecoderException is thrown if the buffer does not contains a valid DN.
-     *
-    public LdapDN( byte[] bytes ) throws DecoderException
-    {
-        if ( bytes == null || bytes.length == 0)
-        {
-            return;
-        }
-        
-        int pos = 0;
-
-        // <name>             ::= <name-component> <name-components>
-        // <name-components> ::= <spaces> <separator> <spaces> <name-component> <name-components> | e
-        if ( ( pos = parseNameComponent( bytes, pos ) ) != -1 )
-        {
-
-            do
-            {
-
-                if ( ( StringTools.isCharASCII( bytes, pos, ',' ) == false ) &&
-                        ( StringTools.isCharASCII( bytes, pos, ';' ) == false ) )
-                {
-
-                    break;
-                }
-
-                bytes[pos] = ',';
-                pos++;
-
-                pos = parseSpaces( bytes, pos );
-            }
-            while ( ( pos = parseNameComponent( bytes, pos ) ) != -1 );
-        }
-        else
-        {
-            try 
-            {
-                throw new DecoderException( "Bad DN : " + new String( bytes, "UTF-8" ) );
-            }
-            catch ( UnsupportedEncodingException uee )
-            {
-                throw new DecoderException( "Bad DN : " + StringTools.dumpBytes( bytes ) );
-            }
-            
-        }
-
-        setData(bytes);
-    }
-}
-*/
-
-package org.apache.ldap.common.codec.util;
+package org.apache.ldap.common.name;
 
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 
 import javax.naming.InvalidNameException;
 import javax.naming.Name;
+import javax.naming.NamingException;
 
+import org.apache.ldap.common.codec.util.LdapString;
+import org.apache.ldap.common.codec.util.LdapStringEncodingException;
+import org.apache.ldap.common.name.Rdn;
+import org.apache.ldap.common.schema.OidNormalizer;
 import org.apache.ldap.common.util.StringTools;
-import org.apache.ldap.common.codec.util.DNParser;
-
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
+ * The LdapDN class contains a DN (Distinguished Name). Its specification can be found in RFC 2253, 
+ * "UTF-8 String Representation of Distinguished Names".
+ * 
+ * We will store two representation of a DN :
+ *  - a user Provider represeentation, which is the parsed String given by a user
+ *  - an internal representation.
+ *  
+ * A DN is formed of RDNs, in a specific order :
+ *  RDN[n], RDN[n-1], ... RDN[1], RDN[0]
+ *  
+ * It represents a tree, in which the root is the last RDN (RDN[0]) and the leaf is the
+ * first RDN (RDN[n]).
  *
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  */
-public class LdapDN extends LdapString implements Name
+public class LdapDN /*extends LdapString*/ implements Name
 {
+    /** The LoggerFactory used by this class */
+    private static Logger log = LoggerFactory.getLogger( LdapDN.class );
+
     /**
      * Declares the Serial Version Uid.
      * 
@@ -165,13 +73,19 @@
 
     //~ Static fields/initializers -----------------------------------------------------------------
     /** The RDNs that are elements of the DN */
-    private List rdns = new ArrayList(5);
-
+    private List rdns = new ArrayList(5); 
+    
     /** The user provided name */
     private String upName;
-
+    
+    /** The normalized name */
+    private String normName;
+    
+    /** The bytes representation of the normName */
+    private byte[] bytes;
+    
     /** A null LdapDN */
-    public transient static final LdapDN EMPTY_LDAPDN = new LdapDN();
+    public static final LdapDN EMPTY_LDAPDN = new LdapDN();
 
     //~ Methods ------------------------------------------------------------------------------------
 
@@ -182,6 +96,48 @@
     {
         super();
         upName = "";
+        normName = ""; 
+    }
+    
+    /**
+     * Creates an ldap name using a list of NameComponents. Each NameComponent
+     * is a String
+     *
+     * @param a_list of String name components.
+     */
+    LdapDN( List list ) throws InvalidNameException
+    {
+    	super();
+    	
+    	if ( ( list != null ) && ( list.size() != 0 ) )
+    	{
+	    	Iterator nameComponents = list.iterator();
+	    	
+	    	while ( nameComponents.hasNext() )
+	    	{
+	    		String nameComponent = (String)nameComponents.next();
+	    		add( 0, nameComponent );
+	    	}
+    	}
+    }
+
+    /**
+     * Creates an ldap name using a list of name components.
+     *
+     * @param nameComponents List of String name components.
+     */
+    LdapDN( Iterator nameComponents ) throws InvalidNameException
+    {
+    	super();
+    	
+    	if ( nameComponents != null )
+    	{
+	    	while ( nameComponents.hasNext() )
+	    	{
+	    		String nameComponent = (String)nameComponents.next();
+	    		add( 0, nameComponent );
+	    	}
+    	}
     }
 
     /**
@@ -195,30 +151,17 @@
      * @param bytes The byte buffer that contains the DN
      * @exception A InvalidNameException is thrown if the buffer does not contains a valid DN.
      */
-    public LdapDN( String string ) throws InvalidNameException
+    public LdapDN( String upName ) throws InvalidNameException
     {
-        if ( StringTools.isNotEmpty( string ) )
-        {
-            try
-            {
-                string.getBytes( "UTF-8" );
-            }
-            catch ( UnsupportedEncodingException uee )
-            {
-                throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
-            }
-
-            DNParser.parse( string, rdns );
-        }
-        else
+        if ( StringTools.isNotEmpty( upName ) )
         {
-            this.string = "";
+            LdapDnParser.parseInternal( upName, rdns );
         }
-
-        normalize();
-        upName = string;
+        
+        // Stores the representations of a DN : internal (as a string and as a byte[]) and external.
+        normalize( upName );
     }
-
+    
     /**
      * Parse a buffer and checks that it is a valid DN <br>
      * <p>
@@ -235,68 +178,80 @@
         try
         {
             upName = new String( bytes, "UTF-8" );
-            DNParser.parse( upName, rdns );
-            string = toString();
-
-            normalize();
+            LdapDnParser.parseInternal( upName, rdns );
+            this.normName = toNormName();
         }
         catch ( UnsupportedEncodingException uee )
         {
+        	log.error( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
             throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
         }
     }
-
+    
 
     /**
      * Normalize the DN by triming useless spaces and lowercasing names.
      * @return a normalized form of the DN
      */
-    private void normalize()
+    private void normalize( String upName )
     {
-        StringBuffer sb = new StringBuffer();
-
-        if ( rdns == null )
+    	normName = toNormName();
+        this.upName = upName == null ? "" : upName ;
+    }
+    
+    /**
+     * Build the normalized DN as a String,
+     * @return A String representing the normalized DN
+     */
+    public String toNormName()
+    {
+        if ( ( rdns == null ) || ( rdns.size() == 0 ) )
         {
-            string = "";
+        	bytes = null;
+            return "";
         }
-
-        Iterator elements = rdns.iterator();
-        boolean isFirst = true;
-
-        while ( elements.hasNext() )
+        else
         {
-            LdapRDN rdn = (LdapRDN)elements.next();
-
-            if ( isFirst )
-            {
-                isFirst = false;
-            }
-            else
+            StringBuffer sb = new StringBuffer();
+            boolean isFirst = true;
+            
+            for ( int i = 0; i < rdns.size(); i++ )
             {
-                sb.append(',');
+                if ( isFirst )
+                {
+                    isFirst = false;
+                }
+                else
+                {
+                    sb.append( ',' );
+                }
+                
+                sb.append( ( (Rdn)rdns.get( i ) ) );
             }
-
-            sb.append( rdn.toString() );
-        }
-
-        string = sb.toString();
-
-        try
-        {
-            bytes = string.getBytes( "UTF-8" );
-        }
-        catch ( UnsupportedEncodingException uee )
-        {
-            // We can't reach this point
+            
+            normName = sb.toString(); 
+            bytes = StringTools.getBytesUtf8( normName );
+            
+            return normName;
         }
     }
-
+    
     /**
-     * Return the normalized DN as a String,
+     * Return the normalized DN as a String. It returns the same value as the
+     * getNormName method
      * @return A String representing the normalized DN
      */
     public String toString()
     {
+    	return normName == null ? "" : normName;
+    }
+    
+    /**
+     * Return the User Provided DN as a String,
+     * @return A String representing the User Provided DN
+     */
+    public String toUpName()
+    {
         if ( ( rdns == null ) || ( rdns.size() == 0 ) )
         {
             return "";
@@ -305,7 +260,7 @@
         {
             StringBuffer sb = new StringBuffer();
             boolean isFirst = true;
-
+            
             for ( int i = 0; i < rdns.size(); i++ )
             {
                 if ( isFirst )
@@ -316,13 +271,122 @@
                 {
                     sb.append( ',' );
                 }
-
-                sb.append( ( (LdapRDN)rdns.get( i ) ) );
+                
+                sb.append(( (Rdn)rdns.get( i ) ).getUpName() );
             }
-
+            
             return sb.toString();
         }
     }
+    
+    /**
+     * Return the User Provided prefix representation of the DN starting at the posn position.
+     * 
+     * If posn = 0, return an empty string.
+     * 
+     * for DN : sn=smith, dc=apache, dc=org
+     * 
+     * getUpname(0) -> ""
+     * getUpName(1) -> "dc=org"
+     * getUpname(3) -> "sn=smith, dc=apache, dc=org"
+     * getUpName(4) -> ArrayOutOfBoundException
+     * 
+     * Warning ! The returned String is not exactly the user provided DN, as spaces
+     * before and after each RDNs have been trimmed.
+     * 
+     * @param posn The starting position
+     * @return The truncated DN
+     */
+    private String getUpNamePrefix( int posn )
+    {
+    	if ( posn == 0 )
+    	{
+    		return "";
+    	}
+    	
+    	if ( posn > rdns.size() )
+    	{
+    		String message = "Impossible to get the position " + posn + ", the DN only has " + rdns.size() + " RDNs";
+    		log.error( message );
+    		throw new ArrayIndexOutOfBoundsException( message );
+    	}
+    
+    	int start = rdns.size() - posn;
+    	StringBuffer sb = new StringBuffer();
+    	boolean isFirst = true;
+    	
+    	for ( int i = start; i < rdns.size(); i++ )
+    	{
+    		if ( isFirst )
+    		{
+    			isFirst = false;
+    		}
+    		else
+    		{
+    			sb.append( ',' );
+    		}
+    		
+    		sb.append( ( (Rdn)rdns.get( i ) ).getUpName() );
+    	}
+    	
+    	return sb.toString();
+    }
+
+    /**
+     * Return the User Provided suffix representation of the DN starting at the posn position.
+     * 
+     * If posn = 0, return an empty string.
+     * 
+     * for DN : sn=smith, dc=apache, dc=org
+     * 
+     * getUpname(0) -> "sn=smith, dc=apache, dc=org"
+     * getUpName(1) -> "sn=smith, dc=apache"
+     * getUpname(3) -> "sn=smith"
+     * getUpName(4) -> ""
+     * 
+     * Warning ! The returned String is not exactly the user provided DN, as spaces
+     * before and after each RDNs have been trimmed.
+     * 
+     * @param posn The starting position
+     * @return The truncated DN
+     */
+    private String getUpNameSuffix( int posn )
+    {
+    	if ( posn > rdns.size() )
+    	{
+    		return "";
+    	}
+    	
+    	int end = rdns.size() - posn;
+    	StringBuffer sb = new StringBuffer();
+    	boolean isFirst = true;
+    	
+    	for ( int i = 0; i < end; i++ )
+    	{
+    		if ( isFirst )
+    		{
+    			isFirst = false;
+    		}
+    		else
+    		{
+    			sb.append( ',' );
+    		}
+    		
+    		sb.append( ( (Rdn)rdns.get( i ) ).getUpName() );
+    	}
+    	
+    	return sb.toString();
+    }
+    
+    /**
+     * Gets the hashcode of the string representation of this name.
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode()
+    {
+        return upName.hashCode() ;
+    }
+    
 
     /**
      * Get the initial DN (without normalization) 
@@ -334,6 +398,15 @@
     }
 
     /**
+     * Get the initial DN (without normalization) 
+     * @return The DN as a String
+     */
+    /*public String getNormName()
+    {
+        return ( normName == null ? "" : normName );
+    }*/
+
+    /**
      * Get the number of NameComponent conatained in this LdapDN
      * 
      * @return The number of NameComponent conatained in this LdapDN
@@ -342,7 +415,26 @@
     {
         return rdns.size();
     }
-
+    
+    /**
+     * Get the number of bytes necessary to store this DN
+     * @return A integer, which is the size of the UTF-8 byte array
+     */
+    public static int getNbBytes( Name dn )
+    {
+    	LdapDN ldapDn = (LdapDN)dn;
+    	return ldapDn.bytes == null ? 0 : ldapDn.bytes.length;
+    }
+    
+    /**
+     * Get an UTF-8 representation of the normalized form of the DN
+     * @return A byte[] representation of the DN
+     */
+    public static byte[] getBytes( Name dn )
+    {
+    	return ((LdapDN)dn).bytes;
+    }
+    
     /**
      * Determines whether this name starts with a specified prefix.
      * A name <tt>name</tt> is a prefix if it is equal to
@@ -363,7 +455,7 @@
         if ( name instanceof LdapDN )
         {
             LdapDN nameDN = (LdapDN)name;
-
+            
             if ( nameDN.size() == 0 )
             {
                 return true;
@@ -374,21 +466,21 @@
                 // The name is longer than the current LdapDN.
                 return false;
             }
-
+            
             // Ok, iterate through all the RDN of the name,
             // starting a the end of the current list.
-
+            
             for ( int i = nameDN.size() - 1; i >= 0; i-- )
             {
-                LdapRDN nameRdn = (LdapRDN)(nameDN.rdns.get( nameDN.rdns.size() - i - 1 ));
-                LdapRDN ldapRdn = (LdapRDN)rdns.get( rdns.size() - i - 1 );
-
+            	Rdn nameRdn = (Rdn)(nameDN.rdns.get( nameDN.rdns.size() - i - 1 ));
+            	Rdn ldapRdn = (Rdn)rdns.get( rdns.size() - i - 1 );
+                
                 if ( nameRdn.compareTo(ldapRdn) != 0 )
                 {
                     return false;
                 }
             }
-
+            
             return true;
         }
         else
@@ -418,7 +510,7 @@
         if ( name instanceof LdapDN )
         {
             LdapDN nameDN = (LdapDN)name;
-
+            
             if ( nameDN.size() == 0 )
             {
                 return true;
@@ -429,19 +521,19 @@
                 // The name is longer than the current LdapDN.
                 return false;
             }
-
+            
             // Ok, iterate through all the RDN of the name
             for ( int i = 0; i < nameDN.size(); i++ )
             {
-                LdapRDN nameRdn = (LdapRDN)(nameDN.rdns.get( i ));
-                LdapRDN ldapRdn = (LdapRDN)rdns.get( i );
-
+            	Rdn nameRdn = (Rdn)(nameDN.rdns.get( i ));
+            	Rdn ldapRdn = (Rdn)rdns.get( i );
+                
                 if ( nameRdn.compareTo(ldapRdn) != 0 )
                 {
                     return false;
                 }
             }
-
+            
             return true;
         }
         else
@@ -450,7 +542,7 @@
             return name == null;
         }
     }
-
+    
     /**
      * Determines whether this name is empty.
      * An empty name is one with zero components.
@@ -461,7 +553,7 @@
     {
         return ( rdns.size() == 0 );
     }
-
+    
     /**
      * Retrieves a component of this name.
      *
@@ -480,19 +572,61 @@
         }
         else
         {
-            LdapRDN rdn = (LdapRDN)rdns.get( rdns.size() - posn - 1 );
-
+        	Rdn rdn = (Rdn)rdns.get( rdns.size() - posn - 1 );
+        
             return rdn.toString();
         }
     }
-
+    
+    /**
+     * Retrieves a component of this name.
+     *
+     * @param posn
+     *      the 0-based index of the component to retrieve.
+     *      Must be in the range [0,size()).
+     * @return  the component at index posn
+     * @throws  ArrayIndexOutOfBoundsException
+     *      if posn is outside the specified range
+     */
+    public Rdn getRdn( int posn )
+    {
+        if ( rdns.size() == 0 )
+        {
+            return null;
+        }
+        else
+        {
+        	Rdn rdn = (Rdn)rdns.get( rdns.size() - posn - 1 );
+        
+            return rdn;
+        }
+    }
+    
+    /**
+     * Retrieves all the components of this name.
+     *
+     * @return  All the components
+     */
+    public List getRdns()
+    {
+        List newRdns = new ArrayList();
+        
+        // We will clone the list, to avoid user modifications
+        for ( int i = 0; i < rdns.size(); i++ )
+        {
+            newRdns.add( i, ((Rdn)rdns.get( i ) ).clone() );
+        }
+        
+    	return newRdns;
+    }
+    
     /**
      * Retrieves the components of this name as an enumeration
      * of strings.  The effect on the enumeration of updates to
      * this name is undefined.  If the name has zero components,
      * an empty (non-null) enumeration is returned.
      *
-     * @return  an enumeration of the components of this name, each a string
+     * @return  an enumeration of the components of this name, each as string
      */
     public Enumeration getAll()
     {
@@ -502,7 +636,7 @@
          * right to left with increasing index values.  LdapName.get() does the
          * index translation on m_list for us. 
          */
-        return new Enumeration()
+        return new Enumeration() 
         {
             private int pos ;
 
@@ -513,18 +647,59 @@
 
             public Object nextElement()
             {
-                if ( pos >= rdns.size() )
+                if ( pos >= rdns.size() ) 
                 {
+                	log.error( "Exceeded number of elements in the current object" );
                     throw new NoSuchElementException() ;
                 }
 
-                Object obj = get( pos ) ;
+                Object obj = rdns.get( rdns.size() - pos - 1 );
                 pos++ ;
-                return obj ;
+                return obj.toString();
             }
         };
     }
+    
+    /**
+     * Retrieves the components of this name as an enumeration
+     * of strings.  The effect on the enumeration of updates to
+     * this name is undefined.  If the name has zero components,
+     * an empty (non-null) enumeration is returned.
+     *
+     * @return  an enumeration of the components of this name, as Rdn
+     */
+    public Enumeration getAllRdn()
+    {
+        /*
+         * Note that by accessing the name component using the get() method on
+         * the name rather than get() on the list we are reading components from
+         * right to left with increasing index values.  LdapName.get() does the
+         * index translation on m_list for us. 
+         */
+        return new Enumeration() 
+        {
+            private int pos ;
+
+            public boolean hasMoreElements()
+            {
+                return pos < rdns.size() ;
+            }
+
+            public Object nextElement()
+            {
+                if ( pos >= rdns.size() ) 
+                {
+                	log.error( "Exceeded number of elements in the current object" );
+                    throw new NoSuchElementException() ;
+                }
 
+                Object obj = rdns.get( rdns.size() - pos - 1 );
+                pos++ ;
+                return obj ;
+            }
+        };
+    }
+    
     /**
      * Creates a name whose components consist of a prefix of the
      * components of this name.  Subsequent changes to
@@ -544,24 +719,25 @@
         {
             return EMPTY_LDAPDN;
         }
-
+        
         if ( ( posn < 0 ) || ( posn > rdns.size() ) )
         {
-            throw new ArrayIndexOutOfBoundsException("The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]");
+        	String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
+        	log.error( message );
+            throw new ArrayIndexOutOfBoundsException( message );
         }
-
+        
         LdapDN newLdapDN = new LdapDN();
-
+        
         for (int i = rdns.size() - posn; i < rdns.size(); i++ )
         {
             // Don't forget to clone the rdns !
-            newLdapDN.rdns.add( ( (LdapRDN)rdns.get( i ) ).clone() );
+            newLdapDN.rdns.add( ( (Rdn)rdns.get( i ) ).clone() );
         }
 
-        newLdapDN.normalize();
-        newLdapDN.string = newLdapDN.toString();
-        newLdapDN.upName = newLdapDN.string;
-
+        newLdapDN.normName = newLdapDN.toNormName();
+        newLdapDN.upName = getUpNamePrefix( posn );
+        
         return newLdapDN;
     }
 
@@ -585,26 +761,28 @@
         {
             return EMPTY_LDAPDN;
         }
-
+        
         if ( ( posn < 0 ) || ( posn > rdns.size() ) )
         {
-            throw new ArrayIndexOutOfBoundsException("The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]");
+            String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
+            log.error( message );
+            throw new ArrayIndexOutOfBoundsException( message );
         }
-
+        
         LdapDN newLdapDN = new LdapDN();
-
+        
         for (int i = 0; i < size() - posn; i++ )
         {
             // Don't forget to clone the rdns !
-            newLdapDN.rdns.add( ( (LdapRDN)rdns.get( i ) ).clone() );
+            newLdapDN.rdns.add( ( (Rdn)rdns.get( i ) ).clone() );
         }
 
-        newLdapDN.normalize();
-        newLdapDN.string = newLdapDN.toString();
-        newLdapDN.upName = newLdapDN.string;
+        newLdapDN.normName = newLdapDN.toNormName();
+        newLdapDN.upName = getUpNameSuffix( posn );
 
         return newLdapDN;
     }
+    
     /**
      * Adds the components of a name -- in order -- to the end of this name.
      *
@@ -619,7 +797,7 @@
     public Name addAll( Name suffix ) throws InvalidNameException
     {
         addAll( rdns.size(), suffix );
-
+        
         return this;
     }
 
@@ -651,19 +829,18 @@
             {
                 return this;
             }
-
+            
             // Concatenate the rdns
             rdns.addAll( size() - posn, ((LdapDN)name).rdns );
 
             // Regenerate the normalized name and the original string
-            normalize();
-
-            upName = toString();
-
+            normalize( toUpName() );
+            
             return this;
         }
         else
         {
+        	log.error( "Not a valid LdapDN suffix : " + name );
             throw new InvalidNameException( "The suffix is not a LdapDN" );
         }
     }
@@ -681,12 +858,10 @@
     public Name add(String comp) throws InvalidNameException
     {
         // We have to parse the nameComponent which is given as an argument
-        LdapRDN newRdn = new LdapRDN( comp );
-
+    	Rdn newRdn = new Rdn( comp );
+        
         rdns.add( 0, newRdn );
-        normalize();
-
-        upName = toString();
+        normalize( toUpName() );
 
         return this;
     }
@@ -713,18 +888,19 @@
     {
         if ( ( posn < 0 ) || ( posn > size() ) )
         {
-            throw new ArrayIndexOutOfBoundsException("The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]");
+        	String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
+        	log.error( message );
+            throw new ArrayIndexOutOfBoundsException( message ); 
         }
 
         // We have to parse the nameComponent which is given as an argument
-        LdapRDN newRdn = new LdapRDN( comp );
-
+        Rdn newRdn = new Rdn( comp );
+        
         int realPos = size() - posn;
         rdns.add( realPos, newRdn );
-        normalize();
-
-        upName = toString();
-
+        
+        normalize( toUpName() );
+        
         return this;
     }
 
@@ -750,18 +926,19 @@
         {
             return EMPTY_LDAPDN;
         }
-
+        
         if ( ( posn < 0 ) || ( posn >= rdns.size() ) )
         {
-            throw new ArrayIndexOutOfBoundsException("The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]");
+        	String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
+        	log.error ( message );
+            throw new ArrayIndexOutOfBoundsException( message );
         }
-
+        
         int realPos = size() - posn - 1;
-        LdapRDN rdn = (LdapRDN)rdns.remove( realPos );
-        normalize();
-
-        upName = toString();
-
+        Rdn rdn = (Rdn)rdns.remove( realPos );
+        
+        normalize( toUpName() );
+        
         return rdn;
     }
 
@@ -779,17 +956,19 @@
         try
         {
             LdapDN dn = (LdapDN)super.clone();
-
+            dn.rdns = new ArrayList();
+            
             for ( int i = 0; i < rdns.size(); i++ )
             {
-                dn.rdns.set( i, ((LdapRDN)rdns.get( i ) ).clone() );
+                dn.rdns.add( i, ((Rdn)rdns.get( i ) ).clone() );
             }
-
+            
             return dn;
         }
         catch ( CloneNotSupportedException cnse )
         {
-            throw new Error( "Assertion failure" );
+        	log.error( "The clone operation has failed" );
+            throw new Error( "Assertion failure : cannot clone the object" );
         }
     }
 
@@ -800,8 +979,8 @@
     {
         if ( obj instanceof String )
         {
-            return toString().equals( obj ) ;
-        }
+            return normName.equals( obj ) ;
+        } 
         else if ( obj instanceof LdapDN )
         {
             LdapDN name = ( LdapDN ) obj ;
@@ -811,9 +990,9 @@
                 return false ;
             }
 
-            for ( int i = 0; i < size(); i++ )
+            for ( int i = 0; i < size(); i++ ) 
             {
-                if ( ( (LdapRDN)name.rdns.get( i ) ).compareTo( rdns.get( i ) ) == LdapRDN.NOT_EQUALS)
+                if ( ( (Rdn)name.rdns.get( i ) ).compareTo( rdns.get( i ) ) != 0)
                 {
                     return false;
                 }
@@ -821,8 +1000,8 @@
 
             // All components matched so we return true
             return true ;
-        }
-        else
+        } 
+        else 
         {
             return false ;
         }
@@ -854,31 +1033,220 @@
         if ( obj instanceof LdapDN )
         {
             LdapDN ldapDN = (LdapDN)obj;
-
+            
             if ( ldapDN.size() != size() )
             {
-                return NOT_EQUALS;
+                return size() - ldapDN.size();
             }
 
-            Iterator dn1Iter = rdns.iterator();
-            Iterator dn2Iter = ldapDN.rdns.iterator();
-
-            while ( dn1Iter.hasNext() && dn2Iter.hasNext() )
+            for ( int i = rdns.size(); i > 0; i-- )
             {
-                LdapRDN rdn1 = (LdapRDN)dn1Iter.next();
-                LdapRDN rdn2 = (LdapRDN)dn2Iter.next();
-
-                if ( rdn1.compareTo( rdn2 ) == LdapRDN.NOT_EQUALS )
+            	Rdn rdn1 = (Rdn) rdns.get( i - 1 );
+            	Rdn rdn2 = (Rdn) ldapDN.rdns.get( i - 1 );
+                int res = rdn1.compareTo( rdn2 );
+                
+                if ( res != 0 )
                 {
-                    return NOT_EQUALS;
+                    return res;
                 }
             }
-
+            
             return EQUALS;
         }
         else
         {
-            return NOT_EQUALS;
+            return 1;
         }
     }
+    
+    private static AttributeTypeAndValue atavOidToName( AttributeTypeAndValue atav, Map oids ) throws InvalidNameException, NamingException
+    {
+		 String type = StringTools.trim( atav.getType() );
+	    	
+		 if ( StringTools.isNotEmpty( StringTools.lowerCase( type ) ) )
+		 {
+			OidNormalizer oidNormalizer = (OidNormalizer)oids.get( type );
+				 
+			 if ( oidNormalizer != null )
+			 {
+				 return new AttributeTypeAndValue( oidNormalizer.getName(), 
+						 (String)oidNormalizer.getNormalizer().normalize( atav.getValue() ) );
+				 
+			 }
+			 else
+			 {
+				 // We don't have a normalizer for this OID : just do nothing.
+				 return atav;
+			 }
+		 }
+		 else
+		 {
+			 // The type is empty : this is not possible... 
+			 log.error( "Empty type not allowed in a DN" );
+			 throw new InvalidNameException( "Empty type not allowed in a DN" );
+		 }
+    	
+    }
+    
+    /**
+     * Transform a RDN by changing the value to its OID counterpart and normalizing
+     * the value accordingly to its type.
+     * @param rdn The RDN to modify
+     * @param oids The map of all existing oids and normalizer 
+     * @throws InvalidNameException If 
+     * @throws NamingException
+     */
+    private static void rdnOidToName( Rdn rdn, Map oids ) throws InvalidNameException, NamingException
+    {
+		 if ( rdn.getNbAtavs() > 1 )
+		 {
+			 // We have more than one ATAV for this RDN. We will loop on all ATAVs
+			 Rdn rdnCopy = (Rdn)rdn.clone();
+			 rdn.clear();
+			 
+			 Iterator atavs = rdnCopy.iterator();
+			 
+			 while ( atavs.hasNext() )
+			 {
+				 Object val = atavs.next();
+				 AttributeTypeAndValue newAtav = atavOidToName( (AttributeTypeAndValue)val, oids );
+				 rdn.addAttributeTypeAndValue( newAtav.getType(), newAtav.getValue() );
+			 }
+			 
+			 
+		 }
+		 else
+		 {
+			 String type = StringTools.trim( rdn.getType() );
+		    	
+			 if ( StringTools.isNotEmpty( StringTools.lowerCase( type ) ) )
+			 {
+				OidNormalizer oidNormalizer = (OidNormalizer)oids.get( type );
+					 
+				 if ( oidNormalizer != null )
+				 {
+					 Rdn rdnCopy = (Rdn)rdn.clone();
+					 rdn.clear();
+					 
+					 rdn.addAttributeTypeAndValue( oidNormalizer.getName(), 
+							 (String)oidNormalizer.getNormalizer().normalize( rdnCopy.getValue() ) );
+					 
+				 }
+				 else
+				 {
+					 // We don't have a normalizer for this OID : just do nothing.
+					 return;
+				 }
+			 }
+			 else
+			 {
+				 // The type is empty : this is not possible... 
+				 log.error( "We should not have an empty DN" );
+				 throw new InvalidNameException( "Empty type not allowed in a DN" );
+			 }
+		 }
+    }
+
+    /**
+     * Change the internal DN, using the first alias instead of oids or other aliases. 
+     * As we still have the UP name of each RDN, we will be able to provide both 
+     * representation of the DN.
+     * 
+     * example :
+     * 
+     *  dn: 2.5.4.3=People, dc=example, domainComponent=com
+     *  
+     * will be transformed to :
+     * 
+     *  cn=People, dc=example, dc=com
+     *  
+     *  because 2.5.4.3 is the OID for cn and dc is the first alias
+     *  of the couple of aliases (dc, domaincomponent).  
+     *  
+     * This is really important do have such a representation, as 'cn' and 'commonname'
+     * share the same OID.
+     *  
+     * @param dn The DN to transform
+     * @param oids The mapping between names and oids. 
+     * @return A normalized form of the DN
+     * @throws InvalidNameException If the DN is invalid
+     */
+    public static Name normalize( Name dn, Map oids ) throws InvalidNameException, NamingException
+    {
+    	if ( ( dn == null ) || ( dn.size() == 0 ) || ( oids== null ) || ( oids.size() == 0 ) )
+    	{
+    		return dn;
+    	}
+    	
+    	LdapDN newDn = (LdapDN)dn.clone();
+    	
+    	Enumeration rdns = newDn.getAllRdn();
+    	
+    	// Loop on all RDNs
+    	while ( rdns.hasMoreElements() )
+    	{
+    		Rdn rdn = (Rdn)rdns.nextElement();
+    		String upName = rdn.getUpName();
+    		rdnOidToName( rdn, oids );
+    		rdn.normalizeString();
+    		rdn.setUpName( upName );
+    	}
+    	
+    	newDn.normalize( newDn.upName );
+    	
+    	return newDn;
+    }
+
+    public static Name normalize( Name dn ) throws InvalidNameException, NamingException
+    {
+    	return normalize( dn, DnOidContainer.getOids() );
+    }
+
+    /**
+     * Substitute OIDs and aliases for the simplest alias
+     * @param tlv The TLV which contains the data
+     * @return A simple attribute
+     * @throws LdapStringEncodingException If the attribute is not valid
+     */
+    public static LdapString normalizeAttribute( byte[] data ) throws LdapStringEncodingException
+    {
+    	LdapString type = new LdapString( data );
+    	
+    	OidNormalizer oidNormalizer = (OidNormalizer)DnOidContainer.getOids().get( type.getString() );
+    	
+    	if ( oidNormalizer != null )
+    	{
+    		type = new LdapString( StringTools.getBytesUtf8( oidNormalizer.getName() ) );
+    	}
+    	
+    	return type;
+    }
+
+    /*
+    public Name toLdapName() throws InvalidNameException
+    {
+    	Name name = new LdapName();
+    	
+    	Enumeration rdns = getAll();
+    	
+    	while ( rdns.hasMoreElements() )
+    	{
+    		name.add( (String)rdns.nextElement() );
+    	}
+    	
+    	return name;
+    }
+    
+    public LdapDN( LdapName name ) throws InvalidNameException
+    {
+    	Name newName = new LdapDN();
+    	
+    	Enumeration comps = name.getAll();
+
+    	while ( comps.hasMoreElements() )
+    	{
+    		newName.add( (String)comps.nextElement() );
+    	}
+    }
+    */
 }

Propchange: directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDN.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDnParser.java
URL: http://svn.apache.org/viewcvs/directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDnParser.java?rev=367149&view=auto
==============================================================================
--- directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDnParser.java (added)
+++ directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapDnParser.java Sun Jan  8 17:02:49 2006
@@ -0,0 +1,140 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.ldap.common.name;
+
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.Name;
+
+import org.apache.ldap.common.util.DNUtils;
+import org.apache.ldap.common.util.StringTools;
+
+import javax.naming.NameParser ;
+
+/**
+ * This class parses a DN. 
+ * 
+ * The DN MUST respect this BNF grammar (as of RFC2253, par. 3, and RFC1779, fig. 1) <br>
+ * 
+ * <p>
+ *-    &lt;distinguishedName&gt;      ::= &lt;name&gt; | e <br>
+ *-    &lt;name&gt;                   ::= &lt;name-component&gt; &lt;name-components&gt; <br>
+ *-    &lt;name-components&gt;        ::= &lt;spaces&gt; &lt;separator&gt; &lt;spaces&gt; &lt;name-component&gt; &lt;name-components&gt; | e <br>
+ *-    &lt;name-component&gt;         ::= &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; <br>
+ *-    &lt;attributeTypeAndValues&gt; ::= &lt;spaces&gt; '+' &lt;spaces&gt; &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; | e <br>
+ *-    &lt;attributeType&gt;          ::= [a-zA-Z] &lt;keychars&gt; | &lt;oidPrefix&gt; [0-9] &lt;digits&gt; &lt;oids&gt; | [0-9] &lt;digits&gt; &lt;oids&gt; <br>
+ *-    &lt;keychars&gt;               ::= [a-zA-Z] &lt;keychars&gt; | [0-9] &lt;keychars&gt; | '-' &lt;keychars&gt; | e <br>
+ *-    &lt;oidPrefix&gt;              ::= 'OID.' | 'oid.' | e <br>
+ *-    &lt;oids&gt;                   ::= '.' [0-9] &lt;digits&gt; &lt;oids&gt; | e <br>
+ *-    &lt;attributeValue&gt;         ::= &lt;pairs-or-strings&gt; | '#' &lt;hexstring&gt; |'"' &lt;quotechar-or-pairs&gt; '"' <br>
+ *-    &lt;pairs-or-strings&gt;       ::= '\' &lt;pairchar&gt; &lt;pairs-or-strings&gt; | &lt;stringchar&gt; &lt;pairs-or-strings&gt; | e <br>
+ *-    &lt;quotechar-or-pairs&gt;     ::= &lt;quotechar&gt; &lt;quotechar-or-pairs&gt; | '\' &lt;pairchar&gt; &lt;quotechar-or-pairs&gt; | e <br>
+ *-    &lt;pairchar&gt;               ::= ',' | '=' | '+' | '&lt;' | '&gt;' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F]  <br>
+ *-    &lt;hexstring&gt;              ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; <br>
+ *-    &lt;hexpairs&gt;               ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; | e <br>
+ *-    &lt;digits&gt;                 ::= [0-9] &lt;digits&gt; | e <br>
+ *-    &lt;stringchar&gt;             ::= [0x00-0xFF] - [,=+&lt;&gt;#;\"\n\r] <br>
+ *-    &lt;quotechar&gt;              ::= [0x00-0xFF] - [\"] <br>
+ *-    &lt;separator&gt;              ::= ',' | ';' <br>
+ *-    &lt;spaces&gt;                 ::= ' ' &lt;spaces&gt; | e <br>
+ * </p>
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class LdapDnParser implements NameParser
+{
+	private static LdapDnParser instance = new LdapDnParser();
+	
+	/**
+	 * A private constructor. It's useless, as this object is totally stateless,
+	 * but we need to expose a NameParser.
+	 *
+	 */
+	private LdapDnParser()
+	{
+	}
+	
+	/**
+	 * Get a reference to the NameParser. Needed to be compliant with the JNDI API
+	 * @return An instance of the NameParser
+	 */
+	public static NameParser getNameParser()
+	{
+		return instance;
+	}
+	
+	/** 
+	 * Parse a DN 
+	 * @param dn The DN to be parsed
+	 * @param rdns The list that will contain the RDNs
+	 * @throws InvalidNameException If the DN is invalid
+	 */
+    public static void parseInternal( String dn, List rdns ) throws InvalidNameException
+    {
+        // We won't decode the LdapDN using the bytes.
+        char[] chars = dn.trim().toCharArray();
+        
+        if ( chars.length == 0 )
+        {
+            // We have an empty DN, just get out of the function.
+            return;
+        }
+        
+        int pos = 0;
+        Rdn rdn = new Rdn();
+
+        // <name>             ::= <name-component> <name-components>
+        // <name-components> ::= <spaces> <separator> <spaces> <name-component> <name-components> | e
+        if ( ( pos = RdnParser.parse( chars, pos, rdn ) ) != DNUtils.PARSING_ERROR )
+        {
+            do
+            {
+                rdns.add( rdn.clone() );
+                rdn.clear();
+
+                if ( ( StringTools.isCharASCII( chars, pos, ',' ) == false ) &&
+                     ( StringTools.isCharASCII( chars, pos, ';' ) == false ) )
+                {
+
+                    break;
+                }
+
+                chars[pos] = ',';
+                pos++;
+
+                //pos = StringUtils.trimLeft( chars, pos );
+            }
+            while ( ( pos = RdnParser.parse( chars, pos, rdn ) ) != DNUtils.PARSING_ERROR );
+        }
+        else
+        {
+            throw new InvalidNameException( "Bad DN : " + new String( chars ) );
+        }
+    }
+    
+    /**
+     * Parse a String and return a LdapDN if the String is a valid DN
+     * @param dn The DN to parse
+     * @return A LdapDN
+     * @throws InvalidNameException If the String is not a valid DN
+     */
+    public Name parse( String dn ) throws InvalidNameException
+    {
+        return new LdapDN( dn );
+    }
+}

Modified: directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapName.java
URL: http://svn.apache.org/viewcvs/directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapName.java?rev=367149&r1=367148&r2=367149&view=diff
==============================================================================
--- directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapName.java (original)
+++ directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/LdapName.java Sun Jan  8 17:02:49 2006
@@ -32,11 +32,9 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.NoSuchElementException;
 
 import org.apache.ldap.common.util.NamespaceTools;
-import org.apache.ldap.common.util.StringTools;
 
 import javax.naming.InvalidNameException;
 import javax.naming.Name;
@@ -929,6 +927,7 @@
     }
     
 
+    /*
     public static LdapName toOidName( LdapName dn, Map oids ) throws InvalidNameException
     {
     	if ( ( dn == null ) || ( dn.size() == 0 ) )
@@ -985,5 +984,6 @@
     	
     	return newDn;
     }
+    */
 }
 

Added: directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java
URL: http://svn.apache.org/viewcvs/directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java?rev=367149&view=auto
==============================================================================
--- directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java (added)
+++ directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java Sun Jan  8 17:02:49 2006
@@ -0,0 +1,1081 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.ldap.common.name;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.Iterator;
+import java.util.List;
+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.commons.collections.MultiHashMap;
+import org.apache.ldap.common.util.StringTools;
+
+/**
+ * This class store the name-component part or the following BNF grammar (as of RFC2253, par. 3, 
+ * and RFC1779, fig. 1) : <br>
+ * -    &lt;name-component&gt;         ::= &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; <br>
+ * -    &lt;attributeTypeAndValues&gt; ::= &lt;spaces&gt; '+' &lt;spaces&gt; &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;attributeTypeAndValues&gt; | e <br>
+ * -    &lt;attributeType&gt;          ::= [a-zA-Z] &lt;keychars&gt; | &lt;oidPrefix&gt; [0-9] &lt;digits&gt; &lt;oids&gt; | [0-9] &lt;digits&gt; &lt;oids&gt; <br>
+ * -    &lt;keychars&gt;               ::= [a-zA-Z] &lt;keychars&gt; | [0-9] &lt;keychars&gt; | '-' &lt;keychars&gt; | e <br>
+ * -    &lt;oidPrefix&gt;              ::= 'OID.' | 'oid.' | e <br>
+ * -    &lt;oids&gt;                   ::= '.' [0-9] &lt;digits&gt; &lt;oids&gt; | e <br>
+ * -    &lt;attributeValue&gt;         ::= &lt;pairs-or-strings&gt; | '#' &lt;hexstring&gt; |'"' &lt;quotechar-or-pairs&gt; '"' <br>
+ * -    &lt;pairs-or-strings&gt;       ::= '\' &lt;pairchar&gt; &lt;pairs-or-strings&gt; | &lt;stringchar&gt; &lt;pairs-or-strings&gt; | e <br>
+ * -    &lt;quotechar-or-pairs&gt;     ::= &lt;quotechar&gt; &lt;quotechar-or-pairs&gt; | '\' &lt;pairchar&gt; &lt;quotechar-or-pairs&gt; | e <br>
+ * -    &lt;pairchar&gt;               ::= ',' | '=' | '+' | '&lt;' | '&gt;' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F]  <br>
+ * -    &lt;hexstring&gt;              ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; <br>
+ * -    &lt;hexpairs&gt;               ::= [0-9a-fA-F] [0-9a-fA-F] &lt;hexpairs&gt; | e <br>
+ * -    &lt;digits&gt;                 ::= [0-9] &lt;digits&gt; | e <br>
+ * -    &lt;stringchar&gt;             ::= [0x00-0xFF] - [,=+&lt;&gt;#;\"\n\r] <br>
+ * -    &lt;quotechar&gt;              ::= [0x00-0xFF] - [\"] <br>
+ * -    &lt;separator&gt;              ::= ',' | ';' <br>
+ * -    &lt;spaces&gt;                 ::= ' ' &lt;spaces&gt; | e <br>
+ *<br>
+ * A RDN is a part of a DN. It can be composed of many types, as in the RDN 
+ * 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>
+ * Spaces before and after types and values are not stored.<br>  
+ * Spaces before and after '+' are not stored.<br>
+ * <br>
+ * Thus, we can consider that the following RDNs are equals :<br>
+ * <br>
+ * 'ou=test 1'<br>
+ * '  ou=test 1'<br>
+ * 'ou  =test 1'<br>
+ * 'ou=  test 1'<br>
+ * 'ou=test 1 '<br>
+ * '  ou  =  test 1 '<br>
+ * <br>
+ * So are the following :<br>
+ * <br>
+ * 'ou=test 1+cn=test 2'<br>
+ * 'ou = test 1 + cn = test 2'<br>
+ * '  ou =test 1+  cn =test 2  ' <br>
+ * 'cn = test 2   +ou = test 1'<br>
+ * <br>
+ * but the following are not equal :<br>
+ * 'ou=test  1' <br>
+ * 'ou=test    1'<br> 
+ * 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 Rdn implements Cloneable, Comparable, Serializable
+{
+    /**
+     * Declares the Serial Version Uid.
+     * 
+     * @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a>
+     */
+    private static final long serialVersionUID = 1L;
+
+    /** 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. 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 TreeSet atavs = null;
+    
+    /**
+     * 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 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 = null;
+    
+    /** The number of atavs. We store this number here to avoid complex
+     * manipulation of atav and atavs */
+    private int nbAtavs = 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 Rdn()
+    {
+        // Don't waste space... This is not so often we have multiple
+        // name-components in a RDN... So we won't initialize the Map and the treeSet.
+        upName = "";
+        string = "";
+    }
+    
+    /**
+     * A constructor that parse a String RDN
+     * 
+     * @param rdn The String containing the RDN to parse
+     * @throws InvalidNameException If the RDN is invalid
+     */
+    public Rdn( String rdn ) throws InvalidNameException
+    {
+        if ( StringTools.isNotEmpty( rdn ) )
+        {
+            try
+            {
+                // Check that the String is a Valid UTF-8 string
+                rdn.getBytes( "UTF-8" );
+            }
+            catch ( UnsupportedEncodingException uee )
+            {
+                throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
+            }
+
+            // Parse the string. The Rdn will be updated.
+            RdnParser.parse( rdn, this );
+            normalizeString();
+            // The upName is set by the RdnParser
+        }
+        else
+        {
+            upName = "";
+            string = "";
+        }
+    }
+    
+    /**
+     * A constructor that parse a RDN from a byte array. This method
+     * is called when whe get a LdapMessage which contains a byte
+     * array representing the ASN.1 RelativeRDN
+     * 
+     * @param rdn The byte array containing the RDN to parse
+     * @throws InvalidNameException If the RDN is invalid
+     */
+    
+    public Rdn( byte[] bytes ) throws InvalidNameException
+    {
+        try
+        {
+            RdnParser.parse( new String( bytes, "UTF-8" ), this );
+            normalizeString();
+            // The upName is set by the RdnParser
+        }
+        catch ( UnsupportedEncodingException uee )
+        {
+            throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
+        }
+    }
+    
+    /**
+     * 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 Rdn( String type, String value ) throws InvalidNameException
+    {
+        super();
+
+        addAttributeTypeAndValue( type, value );
+        
+        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 );
+				}
+		}
+    }
+
+    /**
+     * Transform the external representation of the current RDN
+     * to an internal normalized form where :
+     * - types are trimmed and lowercased
+     * - values are trimmed and lowercased
+     */
+    // 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 )
+        {
+            case 0 :
+                // An empty RDN
+                string = "";
+                break;
+                
+            case 1:
+                // We have a single AttributeTypeAndValue
+                // We will trim and lowercase type and value.
+                string = StringTools.lowerCase( StringTools.trim( atav.getType() ) ) +
+                                '=' + StringTools.trim( atav.getValue() );
+                break;
+                
+            default :
+                // We have more than one AttributeTypeAndValue
+                StringBuffer sb = new StringBuffer();
+
+                Iterator elems = atavs.iterator();
+                boolean isFirst = true;
+                
+                while ( elems.hasNext() )
+                {
+                    AttributeTypeAndValue ata = (AttributeTypeAndValue)elems.next();
+                    
+                    if ( isFirst )
+                    {
+                        isFirst = false;
+                    }
+                    else
+                    {
+                        sb.append( '+' );
+                    }
+                    
+                    
+                    sb.append( ata.normalize() );
+                }
+                
+                string = sb.toString();
+                break;
+        }
+    }
+
+    /**
+     * Add a AttributeTypeAndValue to the current RDN
+     * 
+     * @param type The type of the added RDN. 
+     * @param value The value of the added RDN
+     * @throws InvalidNameException If the RDN is invalid
+     */
+    // 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 = StringTools.lowerCase( StringTools.trim( type ) );
+        String normalizedValue = StringTools.trim( value );
+        
+        switch ( nbAtavs )
+        {
+            case 0 :
+                // This is the first AttributeTypeAndValue. Just stores it.
+                atav = new AttributeTypeAndValue( normalizedType, normalizedValue );
+                nbAtavs = 1;
+                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 TreeSet();
+                
+                // and store the existing AttributeTypeAndValue into it.
+                atavs.add( atav );
+
+                atav = null;
+
+                // Now, fall down to the commmon case
+                // NO BREAK !!!
+
+            default :
+                // add a new AttributeTypeAndValue
+            	AttributeTypeAndValue newAtav = new AttributeTypeAndValue( normalizedType, normalizedValue );
+            	atavs.add( newAtav );
+                atavTypes.put( normalizedType, newAtav );
+            
+                nbAtavs++;
+                break;
+                    
+        }
+    }
+
+    /**
+     * Clear the RDN, removing all the AttributeTypeAndValues.
+     */
+    public void clear()
+    {
+        atav = null;
+        atavs = null;
+        atavTypes.clear();
+        nbAtavs = 0;
+        string = "";
+        upName = "";
+    }
+    
+    /**
+     * Get the Value of the AttributeTypeAndValue which type is given as an argument.
+     * 
+     * @param type The type of the NameArgument
+     * @return The Value to be returned, or null if none found.
+     */
+    public String getValue( String type ) throws InvalidNameException
+    {
+        // First, let's normalize the type
+        String normalizedType = StringTools.lowerCase( StringTools.trim( type ) );
+        
+        switch ( nbAtavs )
+        {
+            case 0:
+                return "";
+                
+            case 1:
+                if ( StringTools.equals( atav.getType(), normalizedType ) )
+                {
+                    return atav.getValue();
+                }
+                else
+                {
+                    return "";
+                }
+                
+            default :
+                if ( atavTypes.containsKey( normalizedType ) )
+                {
+                    Object obj = atavTypes.get( normalizedType );
+                    
+                    if ( obj instanceof AttributeTypeAndValue )
+                    {
+                    	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++ )
+                        {
+                        	AttributeTypeAndValue elem = (AttributeTypeAndValue)((List)obj).get( i ); 
+                        	
+                            if ( isFirst )
+                            {
+                                isFirst = false;
+                            }
+                            else
+                            {
+                                sb.append( ',' );
+                            }
+                            
+                            sb.append( elem.getValue() );
+                        }
+                        
+                        return sb.toString();
+                    }
+                    else
+                    {
+                    	throw new InvalidNameException( "Bad object stored in the RDN" );
+                    }
+                }
+                else
+                {
+                    return "";
+                }
+        }
+    }
+
+    /**
+     * Get the AttributeTypeAndValue which type is given as an argument.
+     * If we have more than one value associated with the type, we will
+     * return only the first one.
+     * 
+     * @param type The type of the NameArgument to be returned
+     * @return The AttributeTypeAndValue, of null if none is found.
+     */
+    public AttributeTypeAndValue getAttributeTypeAndValue( String type )
+    {
+        // First, let's normalize the type
+        String normalizedType = StringTools.lowerCase( StringTools.trim( type ) );
+
+        switch ( nbAtavs )
+        {
+            case 0:
+                return null;
+                
+            case 1:
+                if ( atav.getType().equals( normalizedType ) )
+                {
+                    return atav;
+                }
+                else
+                {
+                    return null;
+                }
+                
+            default :
+                if ( atavTypes.containsKey( normalizedType ) )
+                {
+                    return (AttributeTypeAndValue)atavTypes.get( normalizedType );
+                }
+                else
+                {
+                    return null;
+                }
+        }
+    }
+    
+    /**
+     * Retrieves the components of this name as an enumeration
+     * of strings.  The effect on the enumeration of updates to
+     * this name is undefined.  If the name has zero components,
+     * an empty (non-null) enumeration is returned.
+     *
+     * @return  an enumeration of the components of this name, each a string
+     */
+    public Iterator iterator()
+    {
+        if ( nbAtavs == 1 )
+        {
+            return new Iterator() 
+            {
+                private boolean hasMoreElement = true;
+
+                public boolean hasNext()
+                {
+                    return hasMoreElement ;
+                }
+
+                public Object next()
+                {
+                	Object obj = atav ;
+                	hasMoreElement = false ;
+                	return obj ;
+                }
+                
+                public void remove()
+                {
+                	
+                }
+            };
+        }
+        else
+        {
+        	return atavs.iterator();
+        }
+    }
+    
+    /**
+     * Clone the Rdn
+     */
+    public Object clone()
+    {
+        try
+        {
+            Rdn rdn = (Rdn)super.clone();
+            
+            // The AttributeTypeAndValue is immutable. We won't clone it
+            
+    		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;
+        }
+        catch ( CloneNotSupportedException cnse )
+        {
+            throw new Error( "Assertion failure" );
+        }
+    }
+    
+    /**
+     * Compares two RDNs. They are equals if :
+     * - their have the same number of NC (AttributeTypeAndValue)
+     * - 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 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 Rdn )
+        {
+        	Rdn rdn = (Rdn)object;
+
+            if ( rdn == null )
+            {
+                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;
+            }
+            
+            switch ( nbAtavs )
+            {
+                case 0:
+                    return EQUALS;
+                    
+                case 1:
+                    return atav.compareTo( rdn.atav );
+                    
+                default :
+                    // We have more than one value. We will
+                    // go through all of them.
+                    Iterator keys = atavs.iterator();
+                    
+                    while ( keys.hasNext() )
+                    {
+                    	AttributeTypeAndValue current = (AttributeTypeAndValue)keys.next();
+                        String type = current.getType();
+                        
+                        if ( rdn.atavTypes.containsKey( type ) )
+                        {
+                        	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;
+                        }
+                    }
+                    
+                    return EQUALS;
+            }
+        }
+        else
+        {
+            return object != null ? UNDEFINED : SUPERIOR;
+        }
+    }
+
+    /**
+     * Returns a String representation of the RDN
+     */
+    public String toString()
+    {
+        return string;
+    }
+
+    /**
+     * Returns a String representation of the RDN
+     */
+    public String getUpName()
+    {
+        return upName;
+    }
+
+    /**
+     * Set the User Provided Name
+     */
+    public void setUpName( String upName )
+    {
+        this.upName = upName;
+    }
+
+    /**
+     * @return Returns the nbAtavs.
+     */
+    public int getNbAtavs()
+    {
+        return nbAtavs;
+    }
+    
+    /**
+     * Return the unique AttributeTypeAndValue, or the first one of we have more than one
+     * @return The first AttributeTypeAndValue of this RDN
+     */
+    public AttributeTypeAndValue getAtav()
+    {
+    	switch ( nbAtavs )
+    	{
+    		case 0 :
+    			return null;
+    			
+    		case 1 :
+    			return atav;
+    			
+    		default :
+    			return (AttributeTypeAndValue)atavs.first(); 
+    	}
+    }
+    
+    /**
+     * 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 )
+    	{
+    		case 0 :
+    			return null;
+    			
+    		case 1 :
+    			return atav.getType();
+    			
+    		default :
+    			return ((AttributeTypeAndValue)atavs.first()).getType(); 
+    	}
+    }
+
+    /**
+     * 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 )
+    	{
+    		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 ( StringTools.isEmpty( value ) )
+		{
+			return "";
+		}
+		
+		char[] chars = value.toCharArray();
+		
+		if ( chars[0] == '#' )
+		{
+			if ( chars.length == 1 )
+			{
+				// The value is only containing a #
+				return StringTools.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 ( StringTools.isHex( chars, i ) && StringTools.isHex( chars, i + 1 ) ) 
+				{
+					hexValue[ pos++ ] = (byte)((StringTools.HEX_VALUE[ chars[i] ] << 4) + StringTools.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 ( StringTools.isHex( chars, i ) )
+						{
+							isHex = true;
+							pair = ((byte)(StringTools.HEX_VALUE[ chars[i] ] << 4));
+						}
+					}
+				}
+				else
+				{
+					if ( isHex )
+					{
+						if ( StringTools.isHex( chars, i ) )
+						{
+							pair += (byte)StringTools.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 = StringTools.charToBytes( chars[i] );
+							
+							for ( int j = 0; j < result.length; j++ )
+							{
+								bytes[pos++] = result[j];
+							}
+						}
+					}
+				}
+			}
+			
+			return StringTools.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 (StringTools.isEmpty( (byte[])attrValue) )
+		{
+			return "";
+		}
+		
+		String value = StringTools.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++] = StringTools.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++] = StringTools.dumpHex( (byte)(chars[i] & 0x0F) );
+					break;
+					
+
+				default :
+					newChars[pos++] = chars[i];
+					
+			}
+		}
+		
+		return new String( newChars, 0, pos);
+	}
+}

Propchange: directory/trunk/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java
------------------------------------------------------------------------------
    svn:executable = *