directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From elecha...@apache.org
Subject svn commit: r420301 [1/2] - in /directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name: LdapDN.java LdapDnParser.java Rdn.java RdnParser.java
Date Sun, 09 Jul 2006 15:26:47 GMT
Author: elecharny
Date: Sun Jul  9 08:26:47 2006
New Revision: 420301

URL: http://svn.apache.org/viewvc?rev=420301&view=rev
Log:
Restored the missing files...

Added:
    directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java
    directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java
    directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java   (with props)
    directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/RdnParser.java   (with props)

Added: directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java
URL: http://svn.apache.org/viewvc/directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java?rev=420301&view=auto
==============================================================================
--- directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java (added)
+++ directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java Sun Jul  9 08:26:47 2006
@@ -0,0 +1,1426 @@
+/*
+ *   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.directory.shared.ldap.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.directory.shared.ldap.schema.OidNormalizer;
+import org.apache.directory.shared.ldap.util.StringTools;
+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
+{
+    /** The LoggerFactory used by this class */
+    private static Logger log = LoggerFactory.getLogger( LdapDN.class );
+
+    /**
+     * 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;
+
+    /** 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;
+
+    // ~ Static fields/initializers
+    // -----------------------------------------------------------------
+    /** The RDNs that are elements of the DN */
+    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 static final LdapDN EMPTY_LDAPDN = new LdapDN();
+    
+    // ~ Methods
+    // ------------------------------------------------------------------------------------
+
+    /**
+     * Construct an empty LdapDN object
+     */
+    public LdapDN()
+    {
+        super();
+        upName = "";
+        normName = "";
+    }
+
+
+    /**
+     * Transduces, or copies a Name to an LdapDN.
+     *
+     * @param name composed of String name components.
+     */
+    public LdapDN( Name name ) throws InvalidNameException
+    {
+        if ( ( name != null ) && ( name.size() != 0 ) )
+        {
+            for ( int ii = 0; ii < name.size(); ii++ )
+            {
+                String nameComponent = ( String ) name.get( ii );
+                add( nameComponent );
+            }
+        }
+    }
+
+
+    /**
+     * Creates an ldap name using a list of NameComponents. Each NameComponent
+     * is a String
+     * 
+     * @param list of String name components.
+     */
+    LdapDN( List list ) throws InvalidNameException
+    {
+        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
+    {
+        if ( nameComponents != null )
+        {
+            while ( nameComponents.hasNext() )
+            {
+                String nameComponent = ( String ) nameComponents.next();
+                add( 0, nameComponent );
+            }
+        }
+    }
+
+
+    /**
+     * Parse a String 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
+     *                InvalidNameException is thrown if the buffer does not
+     *                contains a valid DN.
+     */
+    public LdapDN( String upName ) throws InvalidNameException
+    {
+        if ( upName != null )
+        {
+            LdapDnParser.parseInternal( upName, rdns );
+        }
+
+        // Stores the representations of a DN : internal (as a string and as a
+        // byte[]) and external.
+        normalizeInternal();
+        this.upName = upName;
+    }
+
+
+    /**
+     * 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
+     *                InvalidNameException is thrown if the buffer does not
+     *                contains a valid DN.
+     */
+    public LdapDN(byte[] bytes) throws InvalidNameException
+    {
+        try
+        {
+            upName = new String( bytes, "UTF-8" );
+            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 normalizeInternal()
+    {
+        normName = toNormName();
+    }
+
+
+    /**
+     * Build the normalized DN as a String,
+     * 
+     * @return A String representing the normalized DN
+     */
+    public String toNormName()
+    {
+        if ( ( rdns == null ) || ( rdns.size() == 0 ) )
+        {
+            bytes = null;
+            return "";
+        }
+        else
+        {
+            StringBuffer sb = new StringBuffer();
+            boolean isFirst = true;
+
+            for ( int i = 0; i < rdns.size(); i++ )
+            {
+                if ( isFirst )
+                {
+                    isFirst = false;
+                }
+                else
+                {
+                    sb.append( ',' );
+                }
+
+                sb.append( ( ( Rdn ) rdns.get( i ) ) );
+            }
+
+            String newNormName = sb.toString();
+            
+            if ( normName != newNormName )
+            {
+                bytes = StringTools.getBytesUtf8( newNormName );
+                normName = newNormName;
+            }
+
+            return normName;
+        }
+    }
+
+
+    /**
+     * 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
+     */
+    private String toUpName()
+    {
+        if ( ( rdns == null ) || ( rdns.size() == 0 ) )
+        {
+            upName = "";
+        }
+        else
+        {
+            StringBuffer sb = new StringBuffer();
+            boolean isFirst = true;
+
+            for ( int i = 0; i < rdns.size(); i++ )
+            {
+                if ( isFirst )
+                {
+                    isFirst = false;
+                }
+                else
+                {
+                    sb.append( ',' );
+                }
+
+                sb.append( ( ( Rdn ) rdns.get( i ) ).getUpName() );
+            }
+
+            upName = sb.toString();
+        }
+
+        return upName;
+    }
+
+
+    /**
+     * 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 this name.
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode()
+    {
+    	int result = 17;
+    	
+    	if ( ( rdns != null ) || ( rdns.size() == 0 ) )
+    	{
+    		for ( Iterator rdnsIter = rdns.iterator(); rdnsIter.hasNext(); ) 
+    		{
+    			result = result * 37 + rdnsIter.next().hashCode(); 
+    		}
+    	}
+    	
+        return result;
+    }
+
+
+    /**
+     * Get the initial DN (without normalization)
+     * 
+     * @return The DN as a String
+     */
+    public String getUpName()
+    {
+        return ( upName == null ? "" : upName );
+    }
+
+
+    /**
+     * 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
+     */
+    public int size()
+    {
+        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( LdapDN dn )
+    {
+        return dn == null ? null : dn.bytes;
+    }
+
+
+    /**
+     * Determines whether this name starts with a specified prefix. A name
+     * <tt>name</tt> is a prefix if it is equal to
+     * <tt>getPrefix(name.size())</tt>. 
+     * 
+     * Be aware that for a specific DN like :
+     * cn=xxx, ou=yyy 
+     * the startsWith method will return true with ou=yyy, and
+     * false with cn=xxx
+     * 
+     * @param name
+     *            the name to check
+     * @return true if <tt>name</tt> is a prefix of this name, false otherwise
+     */
+    public boolean startsWith( Name name )
+    {
+        if ( name instanceof LdapDN )
+        {
+            LdapDN nameDN = ( LdapDN ) name;
+
+            if ( nameDN.size() == 0 )
+            {
+                return true;
+            }
+
+            if ( nameDN.size() > size() )
+            {
+                // 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-- )
+            {
+                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 if ( name instanceof Name )
+        {
+            if ( name.size() == 0 )
+            {
+                return true;
+            }
+
+            if ( name.size() > size() )
+            {
+                // 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 = name.size() - 1; i >= 0; i-- )
+            {
+                Rdn ldapRdn = ( Rdn ) rdns.get( rdns.size() - i - 1 );
+                Rdn nameRdn = null;
+                try
+                {
+                    nameRdn = new Rdn( ( String ) name.get( name.size() - i - 1 ) );
+                }
+                catch ( InvalidNameException e )
+                {
+                    e.printStackTrace();
+                    log.error( "Failed to parse RDN for name " + name.toString(), e );
+                }
+                
+                if ( nameRdn.compareTo( ldapRdn ) != 0 )
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+        else
+        {
+            // We don't accept a Name which is not a LdapName
+            return name == null;
+        }
+    }
+
+
+    /**
+     * Determines whether this name ends with a specified suffix. A name
+     * <tt>name</tt> is a suffix if it is equal to
+     * <tt>getSuffix(size()-name.size())</tt>. 
+     * 
+     * Be aware that for a specific
+     * DN like : cn=xxx, ou=yyy the endsWith method will return true with
+     * cn=xxx, and false with ou=yyy
+     * 
+     * @param name
+     *            the name to check
+     * @return true if <tt>name</tt> is a suffix of this name, false otherwise
+     */
+    public boolean endsWith( Name name )
+    {
+        if ( name instanceof LdapDN )
+        {
+            LdapDN nameDN = ( LdapDN ) name;
+
+            if ( nameDN.size() == 0 )
+            {
+                return true;
+            }
+
+            if ( nameDN.size() > size() )
+            {
+                // 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++ )
+            {
+                Rdn nameRdn = ( Rdn ) ( nameDN.rdns.get( i ) );
+                Rdn ldapRdn = ( Rdn ) rdns.get( i );
+
+                if ( nameRdn.compareTo( ldapRdn ) != 0 )
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+        else
+        {
+            // We don't accept a Name which is not a LdapName
+            return name == null;
+        }
+    }
+
+
+    /**
+     * Determines whether this name is empty. An empty name is one with zero
+     * components.
+     * 
+     * @return true if this name is empty, false otherwise
+     */
+    public boolean isEmpty()
+    {
+        return ( rdns.size() == 0 );
+    }
+
+
+    /**
+     * 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 String get( int posn )
+    {
+        if ( rdns.size() == 0 )
+        {
+            return "";
+        }
+        else
+        {
+            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 the last component of this name.
+     * 
+     * @return the last component of this DN 
+     */
+    public Rdn getRdn()
+    {
+        if ( rdns.size() == 0 )
+        {
+            return null;
+        }
+        else
+        {
+            Rdn rdn = ( Rdn ) rdns.get( 0 );
+
+            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 as string
+     */
+    public Enumeration getAll()
+    {
+        /*
+         * 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.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 this name will not affect the name that
+     * is returned and vice versa.
+     * 
+     * @param posn
+     *            the 0-based index of the component at which to stop. Must be
+     *            in the range [0,size()].
+     * @return a name consisting of the components at indexes in the range
+     *         [0,posn].
+     * @throws ArrayIndexOutOfBoundsException
+     *             if posn is outside the specified range
+     */
+    public Name getPrefix( int posn )
+    {
+        if ( rdns.size() == 0 )
+        {
+            return EMPTY_LDAPDN;
+        }
+
+        if ( ( posn < 0 ) || ( posn > 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( ( ( Rdn ) rdns.get( i ) ).clone() );
+        }
+
+        newLdapDN.normName = newLdapDN.toNormName();
+        newLdapDN.upName = getUpNamePrefix( posn );
+
+        return newLdapDN;
+    }
+
+
+    /**
+     * Creates a name whose components consist of a suffix of the components in
+     * this name. Subsequent changes to this name do not affect the name that is
+     * returned and vice versa.
+     * 
+     * @param posn
+     *            the 0-based index of the component at which to start. Must be
+     *            in the range [0,size()].
+     * @return a name consisting of the components at indexes in the range
+     *         [posn,size()]. If posn is equal to size(), an empty name is
+     *         returned.
+     * @throws ArrayIndexOutOfBoundsException
+     *             if posn is outside the specified range
+     */
+    public Name getSuffix( int posn )
+    {
+        if ( rdns.size() == 0 )
+        {
+            return EMPTY_LDAPDN;
+        }
+
+        if ( ( posn < 0 ) || ( posn > 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( ( ( Rdn ) rdns.get( i ) ).clone() );
+        }
+
+        newLdapDN.normName = newLdapDN.toNormName();
+        newLdapDN.upName = getUpNameSuffix( posn );
+
+        return newLdapDN;
+    }
+
+
+    /**
+     * Adds the components of a name -- in order -- to the end of this name.
+     * 
+     * @param suffix
+     *            the components to add
+     * @return the updated name (not a new one)
+     * @throws InvalidNameException
+     *             if <tt>suffix</tt> is not a valid name, or if the addition
+     *             of the components would violate the syntax rules of this name
+     */
+    public Name addAll( Name suffix ) throws InvalidNameException
+    {
+        addAll( rdns.size(), suffix );
+        normalizeInternal();
+        toUpName();
+
+        return this;
+    }
+
+
+    /**
+     * Adds the components of a name -- in order -- at a specified position
+     * within this name. Components of this name at or after the index of the
+     * first new component are shifted up (away from 0) to accommodate the new
+     * components.
+     * 
+     * @param name
+     *            the components to add
+     * @param posn
+     *            the index in this name at which to add the new components.
+     *            Must be in the range [0,size()].
+     * @return the updated name (not a new one)
+     * @throws ArrayIndexOutOfBoundsException
+     *             if posn is outside the specified range
+     * @throws InvalidNameException
+     *             if <tt>n</tt> is not a valid name, or if the addition of
+     *             the components would violate the syntax rules of this name
+     */
+    public Name addAll( int posn, Name name ) throws InvalidNameException
+    {
+        if ( name instanceof LdapDN )
+        {
+            if ( ( name == null ) || ( name.size() == 0 ) )
+            {
+                return this;
+            }
+
+            // Concatenate the rdns
+            rdns.addAll( size() - posn, ( ( LdapDN ) name ).rdns );
+
+            // Regenerate the normalized name and the original string
+            normalizeInternal();
+            toUpName();
+
+            return this;
+        }
+        else
+        {
+            log.error( "Not a valid LdapDN suffix : " + name );
+            throw new InvalidNameException( "The suffix is not a LdapDN" );
+        }
+    }
+
+
+    /**
+     * Adds a single component to the end of this name.
+     * 
+     * @param comp
+     *            the component to add
+     * @return the updated name (not a new one)
+     * @throws InvalidNameException
+     *             if adding <tt>comp</tt> would violate the syntax rules of
+     *             this name
+     */
+    public Name add( String comp ) throws InvalidNameException
+    {
+        // We have to parse the nameComponent which is given as an argument
+        Rdn newRdn = new Rdn( comp );
+
+        rdns.add( 0, newRdn );
+        normalizeInternal();
+        toUpName();
+
+        return this;
+    }
+
+    /**
+     * Adds a single component to the end of this name.
+     * 
+     * @param comp
+     *            the component to add
+     * @return the updated name (not a new one)
+     * @throws InvalidNameException
+     *             if adding <tt>comp</tt> would violate the syntax rules of
+     *             this name
+     */
+    public Name add( Rdn newRdn ) throws InvalidNameException
+    {
+        rdns.add( rdns.size() - 1, newRdn );
+        normalizeInternal();
+        toUpName();
+
+        return this;
+    }
+
+    /**
+     * Adds a single component at a specified position within this name.
+     * Components of this name at or after the index of the new component are
+     * shifted up by one (away from index 0) to accommodate the new component.
+     * 
+     * @param comp
+     *            the component to add
+     * @param posn
+     *            the index at which to add the new component. Must be in the
+     *            range [0,size()].
+     * @return the updated name (not a new one)
+     * @throws ArrayIndexOutOfBoundsException
+     *             if posn is outside the specified range
+     * @throws InvalidNameException
+     *             if adding <tt>comp</tt> would violate the syntax rules of
+     *             this name
+     */
+    public Name add( int posn, String comp ) throws InvalidNameException
+    {
+        if ( ( posn < 0 ) || ( posn > 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
+        Rdn newRdn = new Rdn( comp );
+
+        int realPos = size() - posn;
+        rdns.add( realPos, newRdn );
+
+        normalizeInternal();
+        toUpName();
+
+        return this;
+    }
+
+
+    /**
+     * Removes a component from this name. The component of this name at the
+     * specified position is removed. Components with indexes greater than this
+     * position are shifted down (toward index 0) by one.
+     * 
+     * @param posn
+     *            the index of the component to remove. Must be in the range
+     *            [0,size()).
+     * @return the component removed (a String)
+     * @throws ArrayIndexOutOfBoundsException
+     *             if posn is outside the specified range
+     * @throws InvalidNameException
+     *             if deleting the component would violate the syntax rules of
+     *             the name
+     */
+    public Object remove( int posn ) throws InvalidNameException
+    {
+        if ( rdns.size() == 0 )
+        {
+            return EMPTY_LDAPDN;
+        }
+
+        if ( ( posn < 0 ) || ( posn >= 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;
+        Rdn rdn = ( Rdn ) rdns.remove( realPos );
+
+        normalizeInternal();
+        toUpName();
+
+        return rdn;
+    }
+
+
+    /**
+     * Generates a new copy of this name. Subsequent changes to the components
+     * of this name will not affect the new copy, and vice versa.
+     * 
+     * @return a copy of this name
+     * @see Object#clone()
+     */
+    public Object clone()
+    {
+        try
+        {
+            LdapDN dn = ( LdapDN ) super.clone();
+            dn.rdns = new ArrayList();
+
+            for ( int i = 0; i < rdns.size(); i++ )
+            {
+                dn.rdns.add( i, ( ( Rdn ) rdns.get( i ) ).clone() );
+            }
+
+            return dn;
+        }
+        catch ( CloneNotSupportedException cnse )
+        {
+            log.error( "The clone operation has failed" );
+            throw new Error( "Assertion failure : cannot clone the object" );
+        }
+    }
+
+
+    /**
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals( Object obj )
+    {
+        if ( obj instanceof String )
+        {
+            return normName.equals( obj );
+        }
+        else if ( obj instanceof LdapDN )
+        {
+            LdapDN name = ( LdapDN ) obj;
+
+            if ( name.size() != this.size() )
+            {
+                return false;
+            }
+
+            for ( int i = 0; i < size(); i++ )
+            {
+                if ( ( ( Rdn ) name.rdns.get( i ) ).compareTo( rdns.get( i ) ) != 0 )
+                {
+                    return false;
+                }
+            }
+
+            // All components matched so we return true
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+
+    /**
+     * Compares this name with another name for order. Returns a negative
+     * integer, zero, or a positive integer as this name is less than, equal to,
+     * or greater than the given name.
+     * <p>
+     * As with <tt>Object.equals()</tt>, the notion of ordering for names
+     * depends on the class that implements this interface. For example, the
+     * ordering may be based on lexicographical ordering of the name components.
+     * Specific attributes of the name, such as how it treats case, may affect
+     * the ordering. In general, two names of different classes may not be
+     * compared.
+     * 
+     * @param obj
+     *            the non-null object to compare against.
+     * @return a negative integer, zero, or a positive integer as this name is
+     *         less than, equal to, or greater than the given name
+     * @throws ClassCastException
+     *             if obj is not a <tt>Name</tt> of a type that may be
+     *             compared with this name
+     * @see Comparable#compareTo(Object)
+     */
+    public int compareTo( Object obj )
+    {
+        if ( obj instanceof LdapDN )
+        {
+            LdapDN ldapDN = ( LdapDN ) obj;
+
+            if ( ldapDN.size() != size() )
+            {
+                return size() - ldapDN.size();
+            }
+
+            for ( int i = rdns.size(); i > 0; i-- )
+            {
+                Rdn rdn1 = ( Rdn ) rdns.get( i - 1 );
+                Rdn rdn2 = ( Rdn ) ldapDN.rdns.get( i - 1 );
+                int res = rdn1.compareTo( rdn2 );
+
+                if ( res != 0 )
+                {
+                    return res;
+                }
+            }
+
+            return EQUALS;
+        }
+        else
+        {
+            return 1;
+        }
+    }
+
+
+    private static AttributeTypeAndValue atavOidToName( AttributeTypeAndValue atav, Map oidsMap )
+        throws InvalidNameException, NamingException
+    {
+        String type = StringTools.trim( atav.getType() );
+
+        if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) )
+        {
+            type = type.substring( 4 );
+        }
+
+        if ( StringTools.isNotEmpty( StringTools.lowerCase( type ) ) )
+        {
+            if ( oidsMap == null )
+            {
+                return atav;
+            }
+            else
+            {
+                OidNormalizer oidNormalizer = ( OidNormalizer ) oidsMap.get( type );
+    
+                if ( oidNormalizer != null )
+                {
+                    return new AttributeTypeAndValue( oidNormalizer.getAttributeTypeOid(), ( 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 oidsMap ) 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, oidsMap );
+                rdn.addAttributeTypeAndValue( newAtav.getType(), newAtav.getValue() );
+            }
+
+        }
+        else
+        {
+            String type = StringTools.trim( rdn.getType() );
+            
+            if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) )
+            {
+                type = type.substring( 4 );
+            }
+
+            if ( StringTools.isNotEmpty( StringTools.lowerCase( type ) ) )
+            {
+                if ( oidsMap == null )
+                {
+                    return;
+                }
+                else
+                {
+                    OidNormalizer oidNormalizer = ( OidNormalizer ) oidsMap.get( type );
+    
+                    if ( oidNormalizer != null )
+                    {
+                        Rdn rdnCopy = ( Rdn ) rdn.clone();
+                        rdn.clear();
+                        
+                        rdn.addAttributeTypeAndValue( oidNormalizer.getAttributeTypeOid(), ( 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 LdapDN normalize( LdapDN dn, Map oidsMap ) throws InvalidNameException, NamingException
+    {
+        if ( ( dn == null ) || ( dn.size() == 0 ) || ( oidsMap == null ) || ( oidsMap.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, oidsMap );
+            rdn.normalizeString();
+            rdn.setUpName( upName );
+        }
+
+        newDn.normalizeInternal();
+
+        return newDn;
+    }
+    
+    /**
+     * 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 void normalize( Map oidsMap ) throws InvalidNameException, NamingException
+    {
+        if ( ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
+        {
+            return;
+        }
+
+        Enumeration rdns = getAllRdn();
+
+        // Loop on all RDNs
+        while ( rdns.hasMoreElements() )
+        {
+            Rdn rdn = ( Rdn ) rdns.nextElement();
+            String upName = rdn.getUpName();
+            rdnOidToName( rdn, oidsMap );
+            rdn.normalizeString();
+            rdn.setUpName( upName );
+        }
+
+        normalizeInternal();
+    }
+}

Added: directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java
URL: http://svn.apache.org/viewvc/directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java?rev=420301&view=auto
==============================================================================
--- directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java (added)
+++ directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java Sun Jul  9 08:26:47 2006
@@ -0,0 +1,163 @@
+/*
+ *   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.directory.shared.ldap.name;
+
+
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.Name;
+
+import org.apache.directory.shared.ldap.util.DNUtils;
+import org.apache.directory.shared.ldap.util.Position;
+import org.apache.directory.shared.ldap.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
+    {
+        if ( dn.length() == 0 )
+        {
+            // We have an empty DN, just get out of the function.
+            return;
+        }
+
+        Position pos = new Position();
+        pos.start = 0;
+        Rdn rdn = new Rdn();
+
+        // <name> ::= <name-component> <name-components>
+        // <name-components> ::= <spaces> <separator> <spaces> <name-component>
+        // <name-components> | e
+        if ( RdnParser.parse( dn, pos, rdn ) != DNUtils.PARSING_ERROR )
+        {
+            // Now, parse the following nameComponents
+            do
+            {
+                rdns.add( rdn );
+                rdn = new Rdn();
+                
+                if ( ( StringTools.isCharASCII( dn, pos.start, ',' ) == false )
+                    && ( StringTools.isCharASCII( dn, pos.start, ';' ) == false ) )
+                {
+
+                    if ( pos.start != dn.length() )
+                    {
+                        throw new InvalidNameException( "Bad DN : " + dn );
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+                
+                pos.start++;
+            }
+            while ( RdnParser.parse( dn, pos, rdn ) != DNUtils.PARSING_ERROR );
+        }
+        else
+        {
+            throw new InvalidNameException( "Bad DN : " + dn );
+        }
+    }
+
+
+    /**
+     * 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 );
+    }
+}

Added: directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java
URL: http://svn.apache.org/viewvc/directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java?rev=420301&view=auto
==============================================================================
--- directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java (added)
+++ directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java Sun Jul  9 08:26:47 2006
@@ -0,0 +1,1216 @@
+/*
+ *   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.directory.shared.ldap.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.directory.shared.ldap.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();
+    
+    /**
+     * We keep the type for a single valued RDN, to avoid the creation of an HashMap
+     */
+    private String atavType = null;
+
+    /**
+     * 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 = atav.getType() + '=' + 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 = type.toLowerCase();
+        String normalizedValue = value;
+
+        switch ( nbAtavs )
+        {
+            case 0:
+                // This is the first AttributeTypeAndValue. Just stores it.
+                atav = new AttributeTypeAndValue( normalizedType, normalizedValue );
+                nbAtavs = 1;
+                atavType = normalizedType;
+                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 );
+                atavTypes = new MultiHashMap();
+                atavTypes.put( atavType, 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;
+        atavType = 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 = atavTypes;
+                    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();
+        Attribute attribute = null;
+
+        switch ( nbAtavs  )
+        {
+            case 0 :
+                break;
+                
+            case 1 :
+                attribute = new BasicAttribute( atavType, true );
+                attribute.add( atav.getValue() );
+                attributes.put( attribute );
+                break;
+                
+            default :
+                Iterator types = atavTypes.keySet().iterator();
+        
+                while ( types.hasNext() )
+                {
+                    String type = ( String ) types.next();
+                    List values = ( List ) atavTypes.get( type );
+        
+                    attribute = new BasicAttribute( type, true );
+        
+                    Iterator iterValues = values.iterator();
+        
+                    while ( iterValues.hasNext() )
+                    {
+                        AttributeTypeAndValue value = ( AttributeTypeAndValue ) iterValues.next();
+        
+                        attribute.add( value.getValue() );
+                    }
+        
+                    attributes.put( attribute );
+                }
+                
+                break;
+        }
+
+        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 );
+    }
+
+    /**
+     * Gets the hashcode of this rdn.
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode()
+    {
+    	int result = 17;
+    	
+        switch ( nbAtavs )
+        {
+            case 0:
+                // An empty RDN
+                break;
+
+            case 1:
+                // We have a single AttributeTypeAndValue
+                result = result * 37 + atav.hashCode();
+                break;
+
+            default:
+                // We have more than one AttributeTypeAndValue
+
+                for ( Iterator elems = atavs.iterator();elems.hasNext(); )
+                {
+                    AttributeTypeAndValue ata = ( AttributeTypeAndValue ) elems.next();
+                    result = result * 37 + ata.hashCode();
+                }
+        }
+    	
+    	return result;
+    }
+}



Mime
View raw message