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/05 10:03:52 UTC

svn commit: r366127 [2/3] - in /directory/branches/DN-refactoring/ldap-common/src: main/antlr/ main/java/org/apache/ldap/common/aci/ main/java/org/apache/ldap/common/codec/ main/java/org/apache/ldap/common/codec/add/ main/java/org/apache/ldap/common/co...

Added: directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java
URL: http://svn.apache.org/viewcvs/directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java?rev=366127&view=auto
==============================================================================
--- directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java (added)
+++ directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java Thu Jan  5 01:02:51 2006
@@ -0,0 +1,1082 @@
+/*
+ *   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.codec.AttributeTypeAndValue;
+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/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/Rdn.java
------------------------------------------------------------------------------
    svn:executable = *

Added: directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/RdnParser.java
URL: http://svn.apache.org/viewcvs/directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/RdnParser.java?rev=366127&view=auto
==============================================================================
--- directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/RdnParser.java (added)
+++ directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/RdnParser.java Thu Jan  5 01:02:51 2006
@@ -0,0 +1,615 @@
+/*
+ *   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 javax.naming.InvalidNameException;
+
+import org.apache.ldap.common.util.DnUtils;
+import org.apache.ldap.common.util.StringTools;
+
+/**
+ * This class parse 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>  
+ * 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>
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class RdnParser
+{
+    /**
+     * Parse this rule : <br>
+     * <p>
+     * &lt;oidValue&gt; ::= [0-9] &lt;digits&gt; &lt;oids&gt; 
+     * </p>
+     * 
+     * @param chars The char array to parse
+     * @param pos The current position in the byte buffer
+     * @return The new position in the char array, or PARSING_ERROR if the rule does not apply to the char array
+     */
+    private static int parseOidValue(char[] chars, int pos)
+    {
+        // <attributType> ::= [0-9] <digits> <oids>
+        if ( StringTools.isDigit( chars, pos ) == false )
+        {
+            // Nope... An error
+            return DnUtils.PARSING_ERROR;
+        }
+        else
+        {
+            // Let's process an oid
+            pos++;
+
+            while ( StringTools.isDigit( chars, pos ) )
+            {
+                pos++;
+            }
+
+            // <oids> ::= '.' [0-9] <digits> <oids> | e
+            if ( StringTools.isCharASCII( chars, pos, '.' ) == false )
+            {
+                return pos;
+            }
+            else
+            {
+                do
+                {
+                    pos++;
+
+                    if ( StringTools.isDigit( chars, pos ) == false )
+                    {
+                        return DnUtils.PARSING_ERROR;
+                    }
+                    else
+                    {
+                        pos++;
+
+                        while ( StringTools.isDigit( chars, pos ) )
+                        {
+                            pos++;
+                        }
+                    }
+                }
+                while ( StringTools.isCharASCII( chars, pos, '.' ) );
+
+                return pos;
+            }
+        }
+    }
+
+    /**
+     * Parse this rule : <br>
+     * <p>
+     * &lt;oidPrefix&gt; ::= 'OID.' | 'oid.' | e
+     * </p>
+     * 
+     * @param bytes The buffer to parse
+     * @param pos The current position in the char array
+     * @return The new position in the char array, or PARSING_ERROR if the rule does not apply to the char array
+     */
+    private static int parseOidPrefix( char[] chars, int pos )
+    {
+        if ( ( StringTools.areEquals( chars, pos, DnUtils.OID_LOWER ) == DnUtils.PARSING_ERROR ) &&
+             ( StringTools.areEquals( chars, pos, DnUtils.OID_UPPER ) == DnUtils.PARSING_ERROR ) )
+        {
+            return DnUtils.PARSING_ERROR;
+        }
+        else
+        {
+            pos += DnUtils.OID_LOWER.length;
+
+            return pos;
+        }
+    }
+    
+    /**
+     * Parse this rule : <br>
+     * <p>
+     * &lt;attributType&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;
+     * </p>
+     * 
+     * The string *MUST* be an ASCII string, not an unicode string.
+     * 
+     * @param chars The char array to parse
+     * @param pos The current position in the char array
+     * @return The new position in the char array, or PARSING_ERROR if the rule does not apply to the char array
+     */
+    private static int parseAttributeType( char[] chars, int pos )
+    {
+        // <attributType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9] <digits> <oids> | [0-9] <digits> <oids>
+        
+        if ( StringTools.isAlphaASCII( chars, pos ))
+        {
+            // <attributType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9] <digits> <oids> 
+
+            // We have got an Alpha char, it may be the begining of an OID ?
+            int oldPos = pos;
+            
+            if ( ( pos = parseOidPrefix( chars, oldPos ) ) != DnUtils.PARSING_ERROR ) 
+            {
+                return parseOidValue(chars, pos);
+            }
+            else 
+            {
+                // It's not an oid, it's a String (ASCII)
+                // <attributType> ::= [a-zA-Z] <keychars>
+                // <keychars>       ::= [a-zA-Z] <keychar> | [0-9] <keychar> | '-' <keychar> | e
+                pos = oldPos + 1;
+
+                while ( StringTools.isAlphaDigitMinus( chars, pos ) )
+                {
+                    pos++;
+                }
+
+                return pos;
+            }
+        }
+        else
+        {
+
+            // An oid
+            // <attributType> ::= [0-9] <digits> <oids> 
+            return parseOidValue(chars, pos);
+        }
+    }
+
+    /**
+     * Parse this rule : <br>
+     * <p>
+     * &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>
+     * </p>
+     * 
+     * @param chars The char array to parse
+     * @param pos The current position in the char array
+     * @return The new position in the char array, or PARSING_ERROR if the rule does not apply to the char array
+     */
+    private static int parseAttributeValue( char[] chars, int pos )
+    {
+        if ( StringTools.isCharASCII( chars, pos, '#' ) )
+        {
+            pos++;
+
+            // <attributeValue> ::= '#' <hexstring>
+            if ( ( pos = DnUtils.parseHexString( chars, pos ) ) == DnUtils.PARSING_ERROR )
+            {
+
+                return DnUtils.PARSING_ERROR;
+            }
+
+            return StringTools.trimLeft( chars, pos );
+        }
+        else if ( StringTools.isCharASCII( chars, pos, '"' ) )
+        {
+            pos++;
+            int nbBytes = 0;
+
+            // <attributeValue>     ::= '"' <quotechar-or-pair> '"'
+            // <quotechar-or-pairs>    ::= <quotechar> <quotechar-or-pairs> | '\' <pairchar> <quotechar-or-pairs> | e
+            while ( true )
+            {
+                if ( StringTools.isCharASCII( chars, pos, '\\' ) )
+                {
+                    pos++;
+
+                    if ( DnUtils.isPairChar( chars, pos ) )
+                    {
+                        pos++;
+                    }
+                    else
+                    {
+                        return DnUtils.PARSING_ERROR;
+                    }
+                }
+                else if ( (nbBytes = DnUtils.isQuoteChar( chars, pos ) ) != DnUtils.PARSING_ERROR )
+                {
+                    pos += nbBytes;
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            if ( StringTools.isCharASCII( chars, pos, '"' ) )
+            {
+                pos++;
+
+                return StringTools.trimLeft( chars, pos );
+            }
+            else
+            {
+                return DnUtils.PARSING_ERROR;
+            }
+        }
+        else
+        {
+            while ( true )
+            {
+                if ( StringTools.isCharASCII( chars, pos, '\\' ) )
+                {
+                    // '\' <pairchar> <pairs-or-strings>
+                    pos++;
+
+                    if ( DnUtils.isPairChar( chars, pos ) == false )
+                    {
+                        return DnUtils.PARSING_ERROR;
+                    }
+                    else
+                    {
+                        pos++;
+                    }
+                }
+                else
+                {
+                    int nbChars = 0;
+                    
+                    // <stringchar> <pairs-or-strings>
+                    if ( ( nbChars = DnUtils.isStringChar( chars, pos )) != DnUtils.PARSING_ERROR )
+                    {
+                        // A special case : if we have some spaces before the '+' character,
+                        // we MUST skip them.
+                        if ( StringTools.isCharASCII( chars, pos, ' ') )
+                        {
+                            pos = StringTools.trimLeft( chars, pos );
+
+                            if ( ( DnUtils.isStringChar( chars, pos ) == DnUtils.PARSING_ERROR ) &&
+                                    ( StringTools.isCharASCII( chars, pos, '\\' ) == false ) )
+                            {
+                                // Ok, we are done with the stringchar.
+                                return pos;
+                            }
+                        }
+                        else
+                        {
+                            // An unicode char could be more than one byte long 
+                            pos += nbChars;
+                        }
+                    }
+                    else
+                    {
+                        return pos;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parse this rule : <br>
+     * <p>
+     * &lt;nameComponents&gt;    ::= &lt;spaces&gt; '+' &lt;spaces&gt; &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;nameComponents&gt; | e
+     * </p>
+     * 
+     * @param chars The char buffer to parse
+     * @param pos The current position in the byte buffer
+     * @return The new position in the char buffer, or PARSING_ERROR if the rule does not apply to the char buffer
+     */
+    private static int parseNameComponents( char[] chars, int pos, Rdn rdn ) throws InvalidNameException
+    {
+        int newPos = 0;
+        String type = null;
+        String value = null;
+
+        while ( true )
+        {
+            pos = StringTools.trimLeft( chars, pos );
+
+            if ( StringTools.isCharASCII( chars, pos, '+' ) )
+            {
+                pos++;
+            }
+            else
+            {
+                // <attributeTypeAndValues> ::= e
+                return pos;
+            }
+
+            pos = StringTools.trimLeft( chars, pos );
+
+            if ( ( newPos = parseAttributeType( chars, pos ) ) == DnUtils.PARSING_ERROR )
+            {
+                return DnUtils.PARSING_ERROR;
+            }
+
+            if ( rdn != null )
+            {
+                type = new String( chars, pos, newPos - pos );
+            }
+
+            pos = StringTools.trimLeft( chars, newPos );
+
+            if ( StringTools.isCharASCII( chars, pos, '=' ) )
+            {
+                pos++;
+            }
+            else
+            {
+                return DnUtils.PARSING_ERROR;
+            }
+
+            pos = StringTools.trimLeft( chars, pos );
+
+            newPos = parseAttributeValue( chars, pos );
+            
+            if ( newPos != DnUtils.PARSING_ERROR )
+            {
+                if ( rdn != null )
+                {
+                    newPos = StringTools.trimRight( chars, newPos );
+                    value = new String( chars, pos, newPos - pos );
+                    
+                    rdn.addAttributeTypeAndValue( type, value );
+                }
+            }
+            
+            pos = newPos;
+        }
+    }
+
+    /**
+     * Parse this rule : <br>
+     * <p>
+     * &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>
+     * </p>
+     * 
+     * @param chars The char array to parse
+     * @param pos The current position in the char array
+     * @return The new position in the char array, or PARSING_ERROR if the rule does not apply to the char array
+     */
+    public static int unescapeValue( String value ) throws IllegalArgumentException
+    {
+    	char[] chars = value.toCharArray();
+    	int pos = 0;
+    	
+        if ( StringTools.isCharASCII( chars, pos, '#' ) )
+        {
+            pos++;
+
+            // <attributeValue> ::= '#' <hexstring>
+            if ( ( pos = DnUtils.parseHexString( chars, pos ) ) == DnUtils.PARSING_ERROR )
+            {
+
+                throw new IllegalArgumentException();
+            }
+
+            return StringTools.trimLeft( chars, pos );
+        }
+        else if ( StringTools.isCharASCII( chars, pos, '"' ) )
+        {
+            pos++;
+            int nbBytes = 0;
+
+            // <attributeValue>     ::= '"' <quotechar-or-pair> '"'
+            // <quotechar-or-pairs>    ::= <quotechar> <quotechar-or-pairs> | '\' <pairchar> <quotechar-or-pairs> | e
+            while ( true )
+            {
+                if ( StringTools.isCharASCII( chars, pos, '\\' ) )
+                {
+                    pos++;
+
+                    if ( DnUtils.isPairChar( chars, pos ) )
+                    {
+                        pos++;
+                    }
+                    else
+                    {
+                        return DnUtils.PARSING_ERROR;
+                    }
+                }
+                else if ( (nbBytes = DnUtils.isQuoteChar( chars, pos ) ) != DnUtils.PARSING_ERROR )
+                {
+                    pos += nbBytes;
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            if ( StringTools.isCharASCII( chars, pos, '"' ) )
+            {
+                pos++;
+
+                return StringTools.trimLeft( chars, pos );
+            }
+            else
+            {
+                return DnUtils.PARSING_ERROR;
+            }
+        }
+        else
+        {
+            while ( true )
+            {
+                if ( StringTools.isCharASCII( chars, pos, '\\' ) )
+                {
+                    // '\' <pairchar> <pairs-or-strings>
+                    pos++;
+
+                    if ( DnUtils.isPairChar( chars, pos ) == false )
+                    {
+                        return DnUtils.PARSING_ERROR;
+                    }
+                    else
+                    {
+                        pos++;
+                    }
+                }
+                else
+                {
+                    int nbChars = 0;
+                    
+                    // <stringchar> <pairs-or-strings>
+                    if ( ( nbChars = DnUtils.isStringChar( chars, pos )) != DnUtils.PARSING_ERROR )
+                    {
+                        // A special case : if we have some spaces before the '+' character,
+                        // we MUST skip them.
+                        if ( StringTools.isCharASCII( chars, pos, ' ') )
+                        {
+                            pos = StringTools.trimLeft( chars, pos );
+
+                            if ( ( DnUtils.isStringChar( chars, pos ) == DnUtils.PARSING_ERROR ) &&
+                                    ( StringTools.isCharASCII( chars, pos, '\\' ) == false ) )
+                            {
+                                // Ok, we are done with the stringchar.
+                                return pos;
+                            }
+                        }
+                        else
+                        {
+                            // An unicode char could be more than one byte long 
+                            pos += nbChars;
+                        }
+                    }
+                    else
+                    {
+                        return pos;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parse a NameComponent : <br>
+     * <p>
+     * &lt;name-component&gt;    ::= &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;nameComponents&gt;
+     * </p>
+     * 
+     * @param bytes The buffer to parse
+     * @param pos The current position in the buffer
+     * @return The new position in the char array, or PARSING_ERROR if the rule does not apply to the char array 
+     */
+    public static int parse( char[] chars, int pos, Rdn rdn ) throws InvalidNameException
+    {
+        int newPos = 0;
+        String type = null;
+        String value = null;
+        int start = pos;
+        
+        pos = StringTools.trimLeft( chars, pos );
+        
+        if ( ( newPos = parseAttributeType( chars, pos ) ) == DnUtils.PARSING_ERROR )
+        {
+            return DnUtils.PARSING_ERROR;
+        }
+        
+        if ( rdn != null )
+        {
+            type = new String( chars, pos, newPos - pos );
+        }
+        
+        pos = StringTools.trimLeft( chars, newPos );
+
+        if ( StringTools.isCharASCII( chars, pos, '=' ) == false )
+        {
+            return DnUtils.PARSING_ERROR;
+        }
+        else
+        {
+            pos++;
+        }
+
+        pos = StringTools.trimLeft( chars, pos );
+
+        if ( ( newPos = parseAttributeValue( chars, pos ) ) == DnUtils.PARSING_ERROR )
+        {
+            return DnUtils.PARSING_ERROR;
+        }
+        
+        if ( rdn != null )
+        {
+            newPos = StringTools.trimRight( chars, newPos );
+            value = new String( chars, pos, newPos - pos );
+            
+            rdn.addAttributeTypeAndValue( type, value );
+        }
+
+        int end = parseNameComponents( chars, newPos, rdn );
+        rdn.setUpName( new String( chars, start, end - start ) );
+        rdn.normalizeString();
+        return end;
+    }
+
+    /**
+     * Parse a NameComponent : <br>
+     * <p>
+     * &lt;name-component&gt;    ::= &lt;attributeType&gt; &lt;spaces&gt; '=' &lt;spaces&gt; &lt;attributeValue&gt; &lt;nameComponents&gt;
+     * </p>
+     * 
+     * @param string The buffer to parse
+     * @param rdn The RDN to fill. Beware that if the RDN is not empty, the new AttributeTypeAndValue will be added. 
+     */
+    public static void parse( String string, Rdn rdn ) throws InvalidNameException
+    {
+        parse( string.toCharArray(), 0, rdn );
+        rdn.normalizeString();
+    }
+}

Propchange: directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/name/RdnParser.java
------------------------------------------------------------------------------
    svn:executable = *

Added: directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/schema/OidNormalizer.java
URL: http://svn.apache.org/viewcvs/directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/schema/OidNormalizer.java?rev=366127&view=auto
==============================================================================
--- directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/schema/OidNormalizer.java (added)
+++ directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/schema/OidNormalizer.java Thu Jan  5 01:02:51 2006
@@ -0,0 +1,92 @@
+/*
+ *   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.schema;
+
+/**
+ * The OidNomalizer class contains a couple : and OID with its Normalizer
+ *  
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class OidNormalizer {
+	/** The oid */
+	private String oid;
+	
+	/** The normalizer to be used with this OID */
+	private Normalizer normalizer;
+	
+	/**
+	 * A constructor which accept two parameters
+	 * @param oid The oid
+	 * @param normalizer The associated normalizer
+	 */
+	public OidNormalizer( String oid, Normalizer normalizer )
+	{
+		this.oid = oid;
+		this.normalizer = normalizer;
+	}
+	
+	/**
+	 * A copy constructor. 
+	 * @param oidNormalizer The OidNormalizer to copy from
+	 */
+	public OidNormalizer( OidNormalizer oidNormalizer )
+	{
+		oid = oidNormalizer.oid;
+		normalizer = oidNormalizer.normalizer;
+	}
+
+	/**
+	 * Get the normalizer
+	 * @return The normalizer associated to the current OID
+	 */
+	public Normalizer getNormalizer() {
+		return normalizer;
+	}
+
+	/**
+	 * Set the normalizer
+	 * @param The normalizer to be associated to the current OID
+	 */
+	public void setNormalizer(Normalizer normalizer) {
+		this.normalizer = normalizer;
+	}
+
+	/**
+	 * Get the current OID
+	 * @return The current OID
+	 */
+	public String getOid() {
+		return oid;
+	}
+
+	/**
+	 * Set the current OID
+	 * @param The current OID
+	 */
+	public void setOid(String oid) {
+		this.oid = oid;
+	}
+	
+	/**
+	 * Return a String representation of this class
+	 */
+	public String toString()
+	{
+		return "OidNormalizer : { " + oid + ", " + normalizer.toString() + "}";
+	}
+}

Propchange: directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/schema/OidNormalizer.java
------------------------------------------------------------------------------
    svn:executable = *

Added: directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/util/DnUtils.java
URL: http://svn.apache.org/viewcvs/directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/util/DnUtils.java?rev=366127&view=auto
==============================================================================
--- directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/util/DnUtils.java (added)
+++ directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/util/DnUtils.java Thu Jan  5 01:02:51 2006
@@ -0,0 +1,616 @@
+/*
+ *   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.util;
+
+/**
+ * Utility class used by the LdapDN Parser.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class DnUtils
+{
+    //~ Static fields/initializers -----------------------------------------------------------------
+
+    /** <safe-init-char>    ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F] */
+    private static final boolean[] SAFE_INIT_CHAR =
+    {
+        false, true, true, true, true, true, true, true, true, true, false, true, true, false,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, false, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, false,
+        true, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true
+    };
+
+    /** <safe-char>        ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F] */
+    private static final boolean[] SAFE_CHAR =
+    {
+        false, true, true, true, true, true, true, true, true, true, false, true, true, false,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true
+    };
+
+    /** <base64-char>    ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-0x5A] | [0x61-0x7A] */
+    private static final boolean[] BASE64_CHAR =
+    {
+        false, false, false, false, false, false, false, false, false, false, false, false, false,
+        false, false, false, false, false, false, false, false, false, false, false, false, false,
+        false, false, false, false, false, false, false, false, false, false, false, false, false,
+        false, false, false, false, true, false, false, false, true, true, true, true, true, true,
+        true, true, true, true, true, false, false, false, true, false, false, false, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, false, false, false, false, false,
+        false, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+        true, true, true, true, true, true, true, true, true, true, true, true, false, false,
+        false, false, false
+    };
+
+    /** '"'  | '#'  | '+'  | ','  | [0-9] | ';'  | '<'  | '='  | '>'  | [A-F] | '\' | [a-f] 
+     * 0x22 | 0x23 | 0x2B | 0x2C | [0x30-0x39] | 0x3B | 0x3C | 0x3D | 0x3E | [0x41-0x46] | 0x5C | [0x61-0x66] */
+    private static final boolean[] PAIR_CHAR =
+    {
+        false, false, false, false, false, false, false, false, false, false, false, false, false,
+        false, false, false, false, false, false, false, false, false, false, false, false, false,
+        false, false, false, false, false, false, false, false, true, true, false, false, false,
+        false, false, false, false, true, true, false, false, false, true, true, true, true, true,
+        true, true, true, true, true, false, true, true, true, true, false, false, true, true,
+        true, true, true, true, false, false, false, false, false, false, false, false, false,
+        false, false, false, false, false, false, false, false, false, false, false, false, true,
+        false, false, false, false, true, true, true, true, true, true, false, false, false, false,
+        false, false, false, false, false, false, false, false, false, false, false, false, false,
+        false, false, false, false, false, false, false, false
+    };
+
+    /** "oid." static */
+    public static final char[] OID_LOWER = new char[] { 'o', 'i', 'd', '.' };
+
+    /** "OID." static */
+    public static final char[] OID_UPPER = new char[] { 'O', 'I', 'D', '.' };
+    
+    /** "oid." static */
+    public static final byte[] OID_LOWER_BYTES = new byte[] { 'o', 'i', 'd', '.' };
+
+    /** "OID." static */
+    public static final byte[] OID_UPPER_BYTES = new byte[] { 'O', 'I', 'D', '.' };
+
+    /** A value if we got an error while parsing */
+    public static final int PARSING_ERROR = -1;
+
+    /** If an hex pair contains only one char, this value is returned */ 
+    public static final int BAD_HEX_PAIR = -2;
+    
+    /** A constant representing one char length */
+    public static final int ONE_CHAR = 1;
+
+    /** A constant representing two chars length */
+    public static final int TWO_CHARS = 2;
+
+    /** A constant representing one byte length */
+    public static final int ONE_BYTE = 1;
+
+    /** A constant representing two bytes length */
+    public static final int TWO_BYTES = 2;
+
+    //~ Methods ------------------------------------------------------------------------------------
+
+    /**
+     * Walk the buffer while characters are Safe String characters :
+     *  <safe-string>    ::= <safe-init-char> <safe-chars>
+     *  <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
+     *  <safe-chars>     ::= <safe-char> <safe-chars> |
+     *  <safe-char>      ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
+     *
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The position of the first character which is not a Safe Char
+     */
+    public static int parseSafeString( byte[] byteArray, int index )
+    {
+        if ( ( byteArray == null ) || ( byteArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= byteArray.length ) )
+        {
+            return -1;
+        }
+        else
+        {
+            byte c = byteArray[index];
+
+            if ( ( c > 127 ) || ( SAFE_INIT_CHAR[c] == false ) )
+            {
+                return -1;
+            }
+
+            index++;
+
+            while ( index < byteArray.length )
+            {
+                c = byteArray[index];
+
+                if ( ( c > 127 ) || ( SAFE_CHAR[c] == false ) )
+                {
+                    break;
+                }
+
+                index++;
+            }
+
+            return index;
+        }
+    }
+
+    /**
+     * Walk the buffer while characters are Alpha characters :
+     *  <alpha>    ::= [0x41-0x5A] | [0x61-0x7A]
+     * 
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The position of the first character which is not an Alpha Char
+     */
+    public static int parseAlphaASCII( byte[] byteArray, int index )
+    {
+        if ( ( byteArray == null ) || ( byteArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= byteArray.length ) )
+        {
+            return -1;
+        }
+        else
+        {
+            byte c = byteArray[index++];
+
+            if ( ( c > 127 ) || ( StringTools.ALPHA[c] == false ) )
+            {
+                return -1;
+            }
+            else
+            {
+                return index;
+            }
+        }
+    }
+
+    /**
+     * Walk the buffer while characters are Alpha characters :
+     *  <alpha>    ::= [0x41-0x5A] | [0x61-0x7A]
+     * 
+     * @param charArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The position of the first character which is not an Alpha Char
+     */
+    public static int parseAlphaASCII( char[] charArray, int index )
+    {
+        if ( ( charArray == null ) || ( charArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= charArray.length ) )
+        {
+            return PARSING_ERROR;
+        }
+        else
+        {
+            char c = charArray[index++];
+
+            if ( ( c > 127 ) || ( StringTools.ALPHA[c] == false ) )
+            {
+                return PARSING_ERROR;
+            }
+            else
+            {
+                return index;
+            }
+        }
+    }
+
+    /**
+     * Check if the current character is a Pair Char
+     *  <pairchar>    ::= ',' | '=' | '+' | '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F]
+     *  
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return <code>true</code> if the current character is a Pair Char
+     */
+    public static boolean isPairChar( byte[] byteArray, int index )
+    {
+        if ( ( byteArray == null ) || ( byteArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= byteArray.length ) )
+        {
+            return false;
+        }
+        else
+        {
+            byte c = byteArray[index];
+
+            if ( ( c > 127 ) || ( PAIR_CHAR[c] == false ) )
+            {
+                return false;
+            }
+            else
+            {
+                if ( StringTools.isHex( byteArray, index++ ) )
+                {
+                    return StringTools.isHex( byteArray, index );
+                }
+                else
+                {
+                    return true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Check if the current character is a Pair Char
+     *  <pairchar>    ::= ',' | '=' | '+' | '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F]
+     *  
+     * @param charArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return <code>true</code> if the current character is a Pair Char
+     */
+    public static boolean isPairChar( char[] charArray, int index )
+    {
+        if ( ( charArray == null ) || ( charArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= charArray.length ) )
+        {
+            return false;
+        }
+        else
+        {
+            char c = charArray[index];
+
+            if ( ( c > 127 ) || ( PAIR_CHAR[c] == false ) )
+            {
+                return false;
+            }
+            else
+            {
+                if ( StringTools.isHex( charArray, index++ ) )
+                {
+                    return StringTools.isHex( charArray, index );
+                }
+                else
+                {
+                    return true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Check if the current character is a String Char.
+     * Chars are Unicode, not ASCII.
+     *  <stringchar>    ::= [0x00-0xFFFF] - [,=+<>#;\"\n\r]
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The current char if it is a String Char, or '#' (this is
+     * simpler than throwing an exception :)
+     */
+    public static int isStringChar( byte[] byteArray, int index )
+    {
+        if ( ( byteArray == null ) || ( byteArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= byteArray.length ) )
+        {
+            return -1;
+        }
+        else
+        {
+            byte c = byteArray[index];
+
+            if ( ( c == 0x0A ) ||
+                    ( c == 0x0D ) ||
+                    ( c == '"' ) ||
+                    ( c == '#' ) ||
+                    ( c == '+' ) ||
+                    ( c == ',' ) ||
+                    ( c == ';' ) ||
+                    ( c == '<' ) ||
+                    ( c == '=' ) ||
+                    ( c == '>' ) )
+            {
+                return -1;
+            }
+            else
+            {
+                return StringTools.countBytesPerChar(byteArray, index);
+            }
+        }
+    }
+
+    /**
+     * Check if the current character is a String Char.
+     * Chars are Unicode, not ASCII.
+     *  <stringchar>    ::= [0x00-0xFFFF] - [,=+<>#;\"\n\r]
+     * @param charArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The current char if it is a String Char, or '#' (this is
+     * simpler than throwing an exception :)
+     */
+    public static int isStringChar( char[] charArray, int index )
+    {
+        if ( ( charArray == null ) || ( charArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= charArray.length ) )
+        {
+            return PARSING_ERROR;
+        }
+        else
+        {
+            char c = charArray[index];
+
+            if ( ( c == 0x0A ) ||
+                    ( c == 0x0D ) ||
+                    ( c == '"' ) ||
+                    ( c == '#' ) ||
+                    ( c == '+' ) ||
+                    ( c == ',' ) ||
+                    ( c == ';' ) ||
+                    ( c == '<' ) ||
+                    ( c == '=' ) ||
+                    ( c == '>' ) )
+            {
+                return PARSING_ERROR;
+            }
+            else
+            {
+                return ONE_CHAR;
+            }
+        }
+    }
+
+    /**
+     * Check if the current character is a Quote Char
+     * We are testing Unicode chars
+     *  <quotechar>    ::= [0x00-0xFFFF] - [\"]
+     * 
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return <code>true</code> if the current character is a Quote Char
+     */
+    public static int isQuoteChar( byte[] byteArray, int index )
+    {
+        if ( ( byteArray == null ) || ( byteArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= byteArray.length ) )
+        {
+            return -1;
+        }
+        else
+        {
+            byte c = byteArray[index];
+
+            if ( ( c == '\\' ) || ( c == '"' ) )
+            {
+                return -1;
+            }
+            else
+            {
+                return StringTools.countBytesPerChar(byteArray, index);
+            }
+        }
+    }
+
+    /**
+     * Check if the current character is a Quote Char
+     * We are testing Unicode chars
+     *  <quotechar>    ::= [0x00-0xFFFF] - [\"]
+     * 
+     * @param charArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return <code>true</code> if the current character is a Quote Char
+     */
+    public static int isQuoteChar( char[] charArray, int index )
+    {
+        if ( ( charArray == null ) || ( charArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= charArray.length ) )
+        {
+            return PARSING_ERROR;
+        }
+        else
+        {
+            char c = charArray[index];
+
+            if ( ( c == '\\' ) || ( c == '"' ) )
+            {
+                return PARSING_ERROR;
+            }
+            else
+            {
+                return ONE_CHAR;
+            }
+        }
+    }
+
+    /**
+     * Parse an hex pair
+     *   <hexpair>    ::= <hex> <hex>
+     *
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The new position, -1 if the buffer does not contain an HexPair, -2 if the
+     * buffer contains an hex byte but not two.
+     */
+    public static int parseHexPair( byte[] byteArray, int index )
+    {
+        if ( StringTools.isHex( byteArray, index ) )
+        {
+            if ( StringTools.isHex( byteArray, index + 1 ) )
+            {
+                return index + 2;
+            }
+            else
+            {
+                return -2;
+            }
+        }
+        else
+        {
+            return -1;
+        }
+    }
+
+    /**
+     * Parse an hex pair
+     *   <hexpair>    ::= <hex> <hex>
+     *
+     * @param charArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The new position, -1 if the buffer does not contain an HexPair, -2 if the
+     * buffer contains an hex byte but not two.
+     */
+    public static int parseHexPair( char[] charArray, int index )
+    {
+        if ( StringTools.isHex( charArray, index ) )
+        {
+            if ( StringTools.isHex( charArray, index + 1 ) )
+            {
+                return index + TWO_CHARS;
+            }
+            else
+            {
+                return BAD_HEX_PAIR;
+            }
+        }
+        else
+        {
+            return PARSING_ERROR;
+        }
+    }
+
+    /**
+     * Parse an hex string, which is a list of hex pairs
+     *  <hexstring>    ::= <hexpair> <hexpairs>
+     *  <hexpairs>    ::= <hexpair> <hexpairs> | e
+     *
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return Return the first position which is not an hex pair, or -1 if there is no
+     * hexpair at the beginning or if an hexpair is invalid (if we have only one hex instead of 2)
+     */
+    public static int parseHexString( byte[] byteArray, int index )
+    {
+        int result = parseHexPair( byteArray, index );
+
+        if ( result < 0 )
+        {
+            return -1;
+        }
+        else
+        {
+            index += 2;
+        }
+
+        while ( ( result = parseHexPair( byteArray, index ) ) >= 0 )
+        {
+            index += 2;
+        }
+
+        return ( ( result == -2 ) ? -1 : index );
+    }
+
+    /**
+     * Parse an hex string, which is a list of hex pairs
+     *  <hexstring>    ::= <hexpair> <hexpairs>
+     *  <hexpairs>    ::= <hexpair> <hexpairs> | e
+     *
+     * @param charArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return Return the first position which is not an hex pair, or -1 if there is no
+     * hexpair at the beginning or if an hexpair is invalid (if we have only one hex instead of 2)
+     */
+    public static int parseHexString( char[] charArray, int index )
+    {
+        int result = parseHexPair( charArray, index );
+
+        if ( result < 0 )
+        {
+            return PARSING_ERROR;
+        }
+        else
+        {
+            index += TWO_CHARS;
+        }
+
+        while ( ( result = parseHexPair( charArray, index ) ) >= 0 )
+        {
+            index += TWO_CHARS;
+        }
+
+        return ( ( result == BAD_HEX_PAIR ) ? PARSING_ERROR : index );
+    }
+
+    /**
+     * Walk the buffer while characters are Base64 characters : 
+     *     <base64-string>      ::= <base64-char> <base64-chars>
+     *  <base64-chars>       ::= <base64-char> <base64-chars> |
+     *  <base64-char>        ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-0x5A] | [0x61-0x7A]
+     *
+     * @param byteArray The buffer which contains the data
+     * @param index Current position in the buffer
+     *
+     * @return The position of the first character which is not a Base64 Char
+     */
+    public static int parseBase64String( byte[] byteArray, int index )
+    {
+        if ( ( byteArray == null ) || ( byteArray.length == 0 ) || ( index < 0 ) ||
+                ( index >= byteArray.length ) )
+        {
+            return -1;
+        }
+        else
+        {
+            byte c = byteArray[index];
+
+            if ( ( c > 127 ) || ( BASE64_CHAR[c] == false ) )
+            {
+                return -1;
+            }
+
+            index++;
+
+            while ( index < byteArray.length )
+            {
+                c = byteArray[index];
+
+                if ( ( c > 127 ) || ( BASE64_CHAR[c] == false ) )
+                {
+                    break;
+                }
+
+                index++;
+            }
+
+            return index;
+        }
+    }
+
+}

Propchange: directory/branches/DN-refactoring/ldap-common/src/main/java/org/apache/ldap/common/util/DnUtils.java
------------------------------------------------------------------------------
    svn:executable = *