Return-Path: Delivered-To: apmail-directory-commits-archive@www.apache.org Received: (qmail 57967 invoked from network); 20 Aug 2006 22:55:26 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 20 Aug 2006 22:55:26 -0000 Received: (qmail 15965 invoked by uid 500); 20 Aug 2006 22:55:22 -0000 Delivered-To: apmail-directory-commits-archive@directory.apache.org Received: (qmail 15904 invoked by uid 500); 20 Aug 2006 22:55:22 -0000 Mailing-List: contact commits-help@directory.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@directory.apache.org Delivered-To: mailing list commits@directory.apache.org Received: (qmail 15780 invoked by uid 99); 20 Aug 2006 22:55:21 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 20 Aug 2006 15:55:21 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Received: from [140.211.166.113] (HELO eris.apache.org) (140.211.166.113) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 20 Aug 2006 15:55:17 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id 82BA61A9820; Sun, 20 Aug 2006 15:54:57 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r433074 [3/3] - in /directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name: AttributeTypeAndValue.java LdapDN.java LdapDnParser.java Rdn.java RdnParser.java Date: Sun, 20 Aug 2006 22:54:55 -0000 To: commits@directory.apache.org From: akarasulu@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060820225457.82BA61A9820@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Modified: directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java?rev=433074&r1=433073&r2=433074&view=diff ============================================================================== --- directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java (original) +++ directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java Sun Aug 20 15:54:54 2006 @@ -1,5 +1,5 @@ /* - * Copyright 2005 The Apache Software Foundation + * Copyright 2006 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. @@ -18,7 +18,6 @@ import java.io.Serializable; -import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -33,6 +32,8 @@ import org.apache.commons.collections.MultiHashMap; import org.apache.directory.shared.ldap.message.LockableAttributesImpl; import org.apache.directory.shared.ldap.util.StringTools; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -96,1122 +97,1116 @@ * 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 Apache Directory Project */ public class Rdn implements Cloneable, Comparable, Serializable { - /** - * Declares the Serial Version Uid. - * - * @see Always - * Declare Serial Version Uid - */ - 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 LockableAttributesImpl(); - 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 form, a - * LDAP string representation asserted value can be obtained by replacing - * (left-to-right, non-recursively) each appearing in the as - * follows: replace with ; replace with - * ; replace with the octet indicated by the - * If in form, a BER representation can be obtained - * from converting each of the to the octet indicated - * by the - * - * @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; - } + /** + * Declares the Serial Version Uid. + * + * @see Always + * Declare Serial Version Uid + */ + private static final long serialVersionUID = 1L; + + /** The LoggerFactory used by this class */ + private static Logger log = LoggerFactory.getLogger( Rdn.class ); + + /** The User Provided RDN */ + private String upName = null; + + /** The normalized RDN */ + private String string = null; + + /** The starting position of this RDN in the given string from which + * we have extracted the upName */ + private int start; + + /** The length of this RDN upName */ + private int length; + + /** + * 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 representing a RDN. + * + * The input String must be UTF-8 encoded + * + * @param rdn The String containing the RDN to parse + * @throws InvalidNameException If the RDN is invalid + */ + public Rdn( String rdn ) throws InvalidNameException + { + start = 0; + + if ( StringTools.isNotEmpty( rdn ) ) + { + // Parse the string. The Rdn will be updated. + RdnParser.parse( rdn, this ); + + // create the internal normalized form + // and store the user provided form + normalizeString(); + upName = rdn; + length = rdn.length(); + } + else + { + upName = ""; + string = ""; + length = 0; + } + } + + + /** + * 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; + start = 0; + length = upName.length(); + // create the internal normalized form + 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() ); + this.start = rdn.start; + this.length = rdn.length; + + 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. + if ( atav.getValue() instanceof String ) + { + string = atav.getType() + '=' + (String)atav.getValue(); + } + else + { + string = atav.getType() + "=#" + StringTools.dumpHexPairs( (byte[])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, Object value ) throws InvalidNameException + { + // First, let's normalize the type + String normalizedType = type.toLowerCase(); + Object 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 = ""; + start = -1; + length = 0; + } + + + /** + * 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 Object 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 Object 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( true ); + 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 form, a + * LDAP string representation asserted value can be obtained by replacing + * (left-to-right, non-recursively) each appearing in the as + * follows: replace with ; replace with + * ; replace with the octet indicated by the + * If in form, a BER representation can be obtained + * from converting each of the to the octet indicated + * by the + * + * @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; + } } Modified: directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/RdnParser.java URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/RdnParser.java?rev=433074&r1=433073&r2=433074&view=diff ============================================================================== --- directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/RdnParser.java (original) +++ directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/name/RdnParser.java Sun Aug 20 15:54:54 2006 @@ -1,5 +1,5 @@ /* - * Copyright 2005 The Apache Software Foundation + * Copyright 2006 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. @@ -17,6 +17,8 @@ package org.apache.directory.shared.ldap.name; +import java.io.UnsupportedEncodingException; + import javax.naming.InvalidNameException; import org.apache.directory.shared.ldap.util.DNUtils; @@ -79,7 +81,7 @@ * 'ou=test 1'
* because we have more than one spaces inside the value.
*
- * + * * @author Apache Directory Project */ public class RdnParser @@ -89,7 +91,7 @@ *

* <oidValue> ::= [0-9] <digits> <oids> *

- * + * * @param chars * The char array to parse * @param pos @@ -101,7 +103,7 @@ { pos.start += pos.length; pos.end = pos.start; - + // ::= [0-9] if ( StringTools.isDigit( string, pos.start ) == false ) { @@ -142,7 +144,7 @@ pos.end++; } } - + } while ( StringTools.isCharASCII( string, pos.end, '.' ) ); @@ -157,7 +159,7 @@ *

* <oidPrefix> ::= 'OID.' | 'oid.' | e *

- * + * * @param bytes * The buffer to parse * @param pos @@ -188,7 +190,7 @@ * [0-9] <digits> <oids> | [0-9] <digits> <oids> *

* The string *MUST* be an ASCII string, not an unicode string. - * + * * @param chars * The char array to parse * @param pos @@ -249,7 +251,7 @@ * <quotechar-or-pairs> | '\' <pairchar> * <quotechar-or-pairs> | e
*

- * + * * @param chars * The char array to parse * @param pos @@ -257,25 +259,38 @@ * @return The new position in the char array, or PARSING_ERROR if the rule * does not apply to the char array */ - private static String parseAttributeValue( String string, Position pos ) + private static Object parseAttributeValue( String string, Position pos ) { + StringBuffer sb = new StringBuffer(); char c = StringTools.charAt( string, pos.start ); - + if ( c == '#' ) { pos.start++; + int nbHex = 0; + int currentPos = pos.start; + + // First, we will count the number of hexPairs + while ( DNUtils.parseHexPair( string, currentPos ) >= 0 ) + { + nbHex++; + currentPos += DNUtils.TWO_CHARS; + } + byte[] hexValue = new byte[nbHex]; + + // Now, convert the value // ::= '#' - if ( DNUtils.parseHexString( string, pos ) == DNUtils.PARSING_ERROR ) + if ( DNUtils.parseHexString( string, hexValue, pos ) == DNUtils.PARSING_ERROR ) { return null; } - pos.start --; + pos.start--; StringTools.trimRight( string, pos ); pos.length = pos.end - pos.start; - - return string.substring( pos.start, pos.end ); + + return hexValue; } else if ( c == '"' ) { @@ -286,7 +301,7 @@ // ::= '"' '"' // ::= | '\' - // | e + // | e while ( true ) { if ( StringTools.isCharASCII( string, pos.end, '\\' ) ) @@ -326,6 +341,9 @@ } else { + int escapedSpace = -1; + boolean hasPairChar = false; + while ( true ) { if ( StringTools.isCharASCII( string, pos.end, '\\' ) ) @@ -340,6 +358,28 @@ } else { + if ( nbChars == 1 ) + { + sb.append( string.charAt( pos.end ) ); + } + else + { + if ( hasPairChar == false ) + { + hasPairChar = true; + } + + byte b = ( byte ) ( ( StringTools.HEX_VALUE[string.charAt( pos.end )] << 4 ) + StringTools.HEX_VALUE[string + .charAt( pos.end + 1 )] ); + + sb.append( (char)(b & 0x00FF) ); + } + + if ( string.charAt( pos.end ) == ' ' ) + { + escapedSpace = sb.length(); + } + pos.end += nbChars; } } @@ -353,7 +393,7 @@ // A special case : if we have some spaces before the // '+' character, // we MUST skip them. - if ( StringTools.isCharASCII( string, pos.end, ' ' ) ) + if ( StringTools.isCharASCII( string, pos.end, '+' ) ) { //StringTools.trimLeft( string, pos ); @@ -361,24 +401,44 @@ && ( StringTools.isCharASCII( string, pos.end, '\\' ) == false ) ) { // Ok, we are done with the stringchar. - return string.substring( pos.start, pos.start + pos.length ); + String result = string.substring( pos.start, pos.start + pos.length ); + + if ( hasPairChar ) + { + return unescapeValue( result ); + } + else + { + return result; + } } else { + sb.append( string.charAt( pos.end ) ); pos.end++; } } else { - // An unicode char could be more than one byte long + sb.append( string.charAt( pos.end ) ); pos.end += nbChars; } } else { pos.length = pos.end - pos.start; - String value = string.substring( pos.start, pos.end ); - return StringTools.trimRight( value ); + String value = sb.toString(); + String result = StringTools.trimRight( value, escapedSpace ); + + if ( hasPairChar ) + { + return unescapeValue( result ); + } + else + { + return result; + } + } } } @@ -393,7 +453,7 @@ * <attributeType> <spaces> '=' <spaces> * <attributeValue> <nameComponents> | e *

- * + * * @param chars * The char buffer to parse * @param pos @@ -405,7 +465,7 @@ { int newStart = 0; String type = null; - String value = null; + Object value = null; while ( true ) { @@ -424,13 +484,13 @@ StringTools.trimLeft( string, pos ); - if ( (type = parseAttributeType( string, pos ) ) == null ) + if ( ( type = parseAttributeType( string, pos ) ) == null ) { return DNUtils.PARSING_ERROR; } pos.start = pos.end; - + StringTools.trimLeft( string, pos ); if ( StringTools.isCharASCII( string, pos.end, '=' ) ) @@ -452,7 +512,7 @@ { if ( rdn != null ) { - rdn.addAttributeTypeAndValue( type, StringTools.trimRight( value ) ); + rdn.addAttributeTypeAndValue( type, value ); } } @@ -463,138 +523,33 @@ /** - * Parse this rule :
- *

- * <attributeValue> ::= <pairs-or-strings> | '#' - * <hexstring> |'"' <quotechar-or-pairs> '"'
- * <pairs-or-strings> ::= '\' <pairchar> - * <pairs-or-strings> | <stringchar> <pairs-or-strings> | | - * e
- * <quotechar-or-pairs> ::= <quotechar> - * <quotechar-or-pairs> | '\' <pairchar> - * <quotechar-or-pairs> | e
- *

+ * Unescape pairChars. * - * @param chars - * The char array to parse + * A PairChar can be a char if it's + * + * @param value The value to modify * @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 + private static Object unescapeValue( String value ) throws IllegalArgumentException { - char[] chars = value.toCharArray(); + byte[] bytes = new byte[value.length()]; int pos = 0; - if ( StringTools.isCharASCII( chars, pos, '#' ) ) + for ( int i = 0; i < value.length(); i++ ) { - pos++; - - // ::= '#' - if ( ( pos = DNUtils.parseHexString( chars, pos ) ) == DNUtils.PARSING_ERROR ) - { - - throw new IllegalArgumentException(); - } - - return StringTools.trimLeft( chars, pos ); + bytes[pos++] = (byte)value.charAt( i ); } - else if ( StringTools.isCharASCII( chars, pos, '"' ) ) + + try { - pos++; - int nbBytes = 0; - - // ::= '"' '"' - // ::= | '\' - // | 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; - } + return new String( bytes, "UTF-8" ); } - else + catch ( UnsupportedEncodingException uee ) { - while ( true ) - { - if ( StringTools.isCharASCII( chars, pos, '\\' ) ) - { - // '\' - pos++; - - if ( DNUtils.isPairChar( chars, pos ) == false ) - { - return DNUtils.PARSING_ERROR; - } - else - { - pos++; - } - } - else - { - int nbChars = 0; - - // - 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, ' ' ) ) - { - 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; - } - } - } + return bytes; } } @@ -605,7 +560,7 @@ * <name-component> ::= <attributeType> <spaces> '=' * <spaces> <attributeValue> <nameComponents> *

- * + * * @param bytes * The buffer to parse * @param pos @@ -616,14 +571,13 @@ public static int parse( String dn, Position pos, Rdn rdn ) throws InvalidNameException { String type = null; - String value = null; + Object value = null; int start = pos.start; StringTools.trimLeft( dn, pos ); pos.end = pos.start; pos.length = 0; - if ( ( type = parseAttributeType( dn, pos ) ) == null ) { return DNUtils.PARSING_ERROR; @@ -648,7 +602,7 @@ StringTools.trimLeft( dn, pos ); pos.end = pos.start; - + if ( ( value = parseAttributeValue( dn, pos ) ) == null ) { return DNUtils.PARSING_ERROR; @@ -658,14 +612,14 @@ { rdn.addAttributeTypeAndValue( type, value ); rdn.normalizeString(); - + pos.start = pos.end; pos.length = 0; } parseNameComponents( dn, pos, rdn ); - rdn.setUpName( dn.substring( start, pos.end ) ); + rdn.setUpName( dn.substring( start, pos.end ) ); pos.start = pos.end; return DNUtils.PARSING_OK; } @@ -677,7 +631,7 @@ * <name-component> ::= <attributeType> <spaces> '=' * <spaces> <attributeValue> <nameComponents> *

- * + * * @param string * The buffer to parse * @param rdn