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/08/20 22:22:29 UTC
svn commit: r433026 -
/directory/branches/shared/0.9.5/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java
Author: elecharny
Date: Sun Aug 20 13:22:29 2006
New Revision: 433026
URL: http://svn.apache.org/viewvc?rev=433026&view=rev
Log:
Huge modifications all over the code to fix DIRSERVER-631
Modified:
directory/branches/shared/0.9.5/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java
Modified: directory/branches/shared/0.9.5/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java
URL: http://svn.apache.org/viewvc/directory/branches/shared/0.9.5/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java?rev=433026&r1=433025&r2=433026&view=diff
==============================================================================
--- directory/branches/shared/0.9.5/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java (original)
+++ directory/branches/shared/0.9.5/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java Sun Aug 20 13:22:29 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;
@@ -32,6 +31,8 @@
import org.apache.commons.collections.MultiHashMap;
import org.apache.directory.shared.ldap.util.StringTools;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
@@ -95,1122 +96,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 <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class Rdn implements Cloneable, Comparable, Serializable
{
- /**
- * Declares the Serial Version Uid.
- *
- * @see <a
- * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
- * Declare Serial Version Uid</a>
- */
- private static final long serialVersionUID = 1L;
-
- /** The User Provided RDN */
- private String upName = null;
-
- /** The normalized RDN */
- private String string = null;
-
- /**
- * Stores all couple type = value. We may have more than one type, if the
- * '+' character appears in the AttributeTypeAndValue. This is a TreeSet,
- * because we want the ATAVs to be sorted. An atav may contain more than one
- * value. In this case, the values are String stored in a List.
- */
- private TreeSet atavs = null;
-
- /**
- * We also keep a set of types, in order to use manipulations. A type is
- * connected with the atav it represents.
- */
- private Map atavTypes = new MultiHashMap();
-
- /**
- * We keep the type for a single valued RDN, to avoid the creation of an HashMap
- */
- private String atavType = null;
-
- /**
- * A simple AttributeTypeAndValue is used to store the Rdn for the simple
- * case where we only have a single type=value. This will be 99.99% the
- * case. This avoids the creation of a HashMap.
- */
- private AttributeTypeAndValue atav = null;
-
- /**
- * The number of atavs. We store this number here to avoid complex
- * manipulation of atav and atavs
- */
- private int nbAtavs = 0;
-
- /** CompareTo() results */
- public static final int UNDEFINED = Integer.MAX_VALUE;
-
- public static final int SUPERIOR = 1;
-
- public static final int INFERIOR = -1;
-
- public static final int EQUALS = 0;
-
-
- /**
- * A empty constructor.
- */
- public Rdn()
- {
- // Don't waste space... This is not so often we have multiple
- // name-components in a RDN... So we won't initialize the Map and the
- // treeSet.
- upName = "";
- string = "";
- }
-
-
- /**
- * A constructor that parse a String RDN
- *
- * @param rdn
- * The String containing the RDN to parse
- * @throws InvalidNameException
- * If the RDN is invalid
- */
- public Rdn(String rdn) throws InvalidNameException
- {
- if ( StringTools.isNotEmpty( rdn ) )
- {
- try
- {
- // Check that the String is a Valid UTF-8 string
- rdn.getBytes( "UTF-8" );
- }
- catch ( UnsupportedEncodingException uee )
- {
- throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : "
- + uee.getMessage() );
- }
-
- // Parse the string. The Rdn will be updated.
- RdnParser.parse( rdn, this );
- normalizeString();
- // The upName is set by the RdnParser
- }
- else
- {
- upName = "";
- string = "";
- }
- }
-
-
- /**
- * A constructor that parse a RDN from a byte array. This method is called
- * when whe get a LdapMessage which contains a byte array representing the
- * ASN.1 RelativeRDN
- *
- * @param rdn
- * The byte array containing the RDN to parse
- * @throws InvalidNameException
- * If the RDN is invalid
- */
-
- public Rdn(byte[] bytes) throws InvalidNameException
- {
- try
- {
- RdnParser.parse( new String( bytes, "UTF-8" ), this );
- normalizeString();
- // The upName is set by the RdnParser
- }
- catch ( UnsupportedEncodingException uee )
- {
- throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : "
- + uee.getMessage() );
- }
- }
-
-
- /**
- * A constructor that constructs a RDN from a type and a value. Constructs
- * an Rdn from the given attribute type and value. The string attribute
- * values are not interpretted as RFC 2253 formatted RDN strings. That is,
- * the values are used literally (not parsed) and assumed to be unescaped.
- *
- * @param type
- * The type of the RDN
- * @param value
- * The value of the RDN
- * @throws InvalidNameException
- * If the RDN is invalid
- */
- public Rdn(String type, String value) throws InvalidNameException
- {
- super();
-
- addAttributeTypeAndValue( type, value );
-
- upName = type + '=' + value;
- normalizeString();
- }
-
-
- /**
- * Constructs an Rdn from the given rdn. The contents of the rdn are simply
- * copied into the newly created
- *
- * @param rdn
- * The non-null Rdn to be copied.
- */
- public Rdn(Rdn rdn)
- {
- super();
- nbAtavs = rdn.getNbAtavs();
- this.string = new String( rdn.string );
- this.upName = new String( rdn.getUpName() );
-
- switch ( rdn.getNbAtavs() )
- {
- case 0:
- return;
-
- case 1:
- this.atav = ( AttributeTypeAndValue ) rdn.atav.clone();
- return;
-
- default:
- // We must duplicate the treeSet and the hashMap
- Iterator iter = rdn.atavs.iterator();
-
- atavs = new TreeSet();
- atavTypes = new MultiHashMap();
-
- while ( iter.hasNext() )
- {
- AttributeTypeAndValue currentAtav = ( AttributeTypeAndValue ) iter.next();
- atavs.add( currentAtav.clone() );
- atavTypes.put( currentAtav.getType(), currentAtav );
- }
- }
- }
-
-
- /**
- * Transform the external representation of the current RDN to an internal
- * normalized form where : - types are trimmed and lowercased - values are
- * trimmed and lowercased
- */
- // WARNING : The protection level is left unspecified intentionnaly.
- // We need this method to be visible from the DnParser class, but not
- // from outside this package.
- /* Unspecified protection */void normalizeString()
- {
- switch ( nbAtavs )
- {
- case 0:
- // An empty RDN
- string = "";
- break;
-
- case 1:
- // We have a single AttributeTypeAndValue
- // We will trim and lowercase type and value.
- string = atav.getType() + '=' + atav.getValue();
- break;
-
- default:
- // We have more than one AttributeTypeAndValue
- StringBuffer sb = new StringBuffer();
-
- Iterator elems = atavs.iterator();
- boolean isFirst = true;
-
- while ( elems.hasNext() )
- {
- AttributeTypeAndValue ata = ( AttributeTypeAndValue ) elems.next();
-
- if ( isFirst )
- {
- isFirst = false;
- }
- else
- {
- sb.append( '+' );
- }
-
- sb.append( ata.normalize() );
- }
-
- string = sb.toString();
- break;
- }
- }
-
-
- /**
- * Add a AttributeTypeAndValue to the current RDN
- *
- * @param type
- * The type of the added RDN.
- * @param value
- * The value of the added RDN
- * @throws InvalidNameException
- * If the RDN is invalid
- */
- // WARNING : The protection level is left unspecified intentionnaly.
- // We need this method to be visible from the DnParser class, but not
- // from outside this package.
- /* Unspecified protection */void addAttributeTypeAndValue( String type, String value ) throws InvalidNameException
- {
- // First, let's normalize the type
- String normalizedType = type.toLowerCase();
- String normalizedValue = value;
-
- switch ( nbAtavs )
- {
- case 0:
- // This is the first AttributeTypeAndValue. Just stores it.
- atav = new AttributeTypeAndValue( normalizedType, normalizedValue );
- nbAtavs = 1;
- atavType = normalizedType;
- return;
-
- case 1:
- // We already have an atav. We have to put it in the HashMap
- // before adding a new one.
- // First, create the HashMap,
- atavs = new TreeSet();
-
- // and store the existing AttributeTypeAndValue into it.
- atavs.add( atav );
- atavTypes = new MultiHashMap();
- atavTypes.put( atavType, atav );
-
- atav = null;
-
- // Now, fall down to the commmon case
- // NO BREAK !!!
-
- default:
- // add a new AttributeTypeAndValue
- AttributeTypeAndValue newAtav = new AttributeTypeAndValue( normalizedType, normalizedValue );
- atavs.add( newAtav );
- atavTypes.put( normalizedType, newAtav );
-
- nbAtavs++;
- break;
-
- }
- }
-
-
- /**
- * Clear the RDN, removing all the AttributeTypeAndValues.
- */
- public void clear()
- {
- atav = null;
- atavs = null;
- atavType = null;
- atavTypes.clear();
- nbAtavs = 0;
- string = "";
- upName = "";
- }
-
-
- /**
- * Get the Value of the AttributeTypeAndValue which type is given as an
- * argument.
- *
- * @param type
- * The type of the NameArgument
- * @return The Value to be returned, or null if none found.
- */
- public String getValue( String type ) throws InvalidNameException
- {
- // First, let's normalize the type
- String normalizedType = StringTools.lowerCase( StringTools.trim( type ) );
-
- switch ( nbAtavs )
- {
- case 0:
- return "";
-
- case 1:
- if ( StringTools.equals( atav.getType(), normalizedType ) )
- {
- return atav.getValue();
- }
- else
- {
- return "";
- }
-
- default:
- if ( atavTypes.containsKey( normalizedType ) )
- {
- Object obj = atavTypes.get( normalizedType );
-
- if ( obj instanceof AttributeTypeAndValue )
- {
- return ( ( AttributeTypeAndValue ) obj ).getValue();
- }
- else if ( obj instanceof List )
- {
- StringBuffer sb = new StringBuffer();
- boolean isFirst = true;
-
- for ( int i = 0; i < ( ( List ) obj ).size(); i++ )
- {
- AttributeTypeAndValue elem = ( AttributeTypeAndValue ) ( ( List ) obj ).get( i );
-
- if ( isFirst )
- {
- isFirst = false;
- }
- else
- {
- sb.append( ',' );
- }
-
- sb.append( elem.getValue() );
- }
-
- return sb.toString();
- }
- else
- {
- throw new InvalidNameException( "Bad object stored in the RDN" );
- }
- }
- else
- {
- return "";
- }
- }
- }
-
-
- /**
- * Get the AttributeTypeAndValue which type is given as an argument. If we
- * have more than one value associated with the type, we will return only
- * the first one.
- *
- * @param type
- * The type of the NameArgument to be returned
- * @return The AttributeTypeAndValue, of null if none is found.
- */
- public AttributeTypeAndValue getAttributeTypeAndValue( String type )
- {
- // First, let's normalize the type
- String normalizedType = StringTools.lowerCase( StringTools.trim( type ) );
-
- switch ( nbAtavs )
- {
- case 0:
- return null;
-
- case 1:
- if ( atav.getType().equals( normalizedType ) )
- {
- return atav;
- }
- else
- {
- return null;
- }
-
- default:
- if ( atavTypes.containsKey( normalizedType ) )
- {
- return ( AttributeTypeAndValue ) atavTypes.get( normalizedType );
- }
- else
- {
- return null;
- }
- }
- }
-
-
- /**
- * Retrieves the components of this name as an enumeration of strings. The
- * effect on the enumeration of updates to this name is undefined. If the
- * name has zero components, an empty (non-null) enumeration is returned.
- *
- * @return an enumeration of the components of this name, each a string
- */
- public Iterator iterator()
- {
- if ( nbAtavs == 1 )
- {
- return new Iterator()
- {
- private boolean hasMoreElement = true;
-
-
- public boolean hasNext()
- {
- return hasMoreElement;
- }
-
-
- public Object next()
- {
- Object obj = atav;
- hasMoreElement = false;
- return obj;
- }
-
-
- public void remove()
- {
-
- }
- };
- }
- else
- {
- return atavs.iterator();
- }
- }
-
-
- /**
- * Clone the Rdn
- */
- public Object clone()
- {
- try
- {
- Rdn rdn = ( Rdn ) super.clone();
-
- // The AttributeTypeAndValue is immutable. We won't clone it
-
- switch ( rdn.getNbAtavs() )
- {
- case 0:
- break;
-
- case 1:
- rdn.atav = ( AttributeTypeAndValue ) this.atav.clone();
- rdn.atavTypes = atavTypes;
- break;
-
- default:
- // We must duplicate the treeSet and the hashMap
- rdn.atavTypes = new MultiHashMap();
- rdn.atavs = new TreeSet();
-
- Iterator iter = this.atavs.iterator();
-
- while ( iter.hasNext() )
- {
- AttributeTypeAndValue currentAtav = ( AttributeTypeAndValue ) iter.next();
- rdn.atavs.add( currentAtav.clone() );
- rdn.atavTypes.put( currentAtav.getType(), currentAtav );
- }
-
- break;
- }
-
- return rdn;
- }
- catch ( CloneNotSupportedException cnse )
- {
- throw new Error( "Assertion failure" );
- }
- }
-
-
- /**
- * Compares two RDNs. They are equals if : - their have the same number of
- * NC (AttributeTypeAndValue) - each ATAVs are equals - comparizon of type
- * are done case insensitive - each value is equel, case sensitive - Order
- * of ATAV is not important If the RDNs are not equals, a positive number is
- * returned if the first RDN is greated, negative otherwise
- *
- * @param object
- * @return 0 if both rdn are equals. -1 if the current RDN is inferior, 1 if
- * teh current Rdn is superioir, UNDIFIED otherwise.
- */
- public int compareTo( Object object )
- {
- if ( object instanceof Rdn )
- {
- Rdn rdn = ( Rdn ) object;
-
- if ( rdn == null )
- {
- return SUPERIOR;
- }
-
- if ( rdn.nbAtavs != nbAtavs )
- {
- // We don't have the same number of ATAVs. The Rdn which
- // has the higher number of Atav is the one which is
- // superior
- return nbAtavs - rdn.nbAtavs;
- }
-
- switch ( nbAtavs )
- {
- case 0:
- return EQUALS;
-
- case 1:
- return atav.compareTo( rdn.atav );
-
- default:
- // We have more than one value. We will
- // go through all of them.
- Iterator keys = atavs.iterator();
-
- while ( keys.hasNext() )
- {
- AttributeTypeAndValue current = ( AttributeTypeAndValue ) keys.next();
- String type = current.getType();
-
- if ( rdn.atavTypes.containsKey( type ) )
- {
- List atavLocalList = ( List ) atavTypes.get( type );
- List atavParamList = ( List ) rdn.atavTypes.get( type );
-
- if ( atavLocalList.size() == 1 )
- {
- // We have only one ATAV
- AttributeTypeAndValue atavLocal = ( AttributeTypeAndValue ) atavLocalList.get( 0 );
- AttributeTypeAndValue atavParam = ( AttributeTypeAndValue ) atavParamList.get( 0 );
-
- return atavLocal.compareTo( atavParam );
- }
- else
- {
- // We have to verify that each value of the
- // first list are present in
- // the second list
- Iterator atavLocals = atavLocalList.iterator();
-
- while ( atavLocals.hasNext() )
- {
- AttributeTypeAndValue atavLocal = ( AttributeTypeAndValue ) atavLocals.next();
-
- Iterator atavParams = atavParamList.iterator();
- boolean found = false;
-
- while ( atavParams.hasNext() )
- {
- AttributeTypeAndValue atavParam = ( AttributeTypeAndValue ) atavParams.next();
-
- if ( atavLocal.compareTo( atavParam ) == EQUALS )
- {
- found = true;
- break;
- }
- }
-
- if ( !found )
- {
- // The ATAV does not exist in the second
- // RDN
- return SUPERIOR;
- }
- }
- }
-
- return EQUALS;
- }
- else
- {
- // We can't find an atav in the rdn : the current
- // one is superior
- return SUPERIOR;
- }
- }
-
- return EQUALS;
- }
- }
- else
- {
- return object != null ? UNDEFINED : SUPERIOR;
- }
- }
-
-
- /**
- * Returns a String representation of the RDN
- */
- public String toString()
- {
- return string;
- }
-
-
- /**
- * Returns a String representation of the RDN
- */
- public String getUpName()
- {
- return upName;
- }
-
-
- /**
- * Set the User Provided Name
- */
- public void setUpName( String upName )
- {
- this.upName = upName;
- }
-
-
- /**
- * @return Returns the nbAtavs.
- */
- public int getNbAtavs()
- {
- return nbAtavs;
- }
-
-
- /**
- * Return the unique AttributeTypeAndValue, or the first one of we have more
- * than one
- *
- * @return The first AttributeTypeAndValue of this RDN
- */
- public AttributeTypeAndValue getAtav()
- {
- switch ( nbAtavs )
- {
- case 0:
- return null;
-
- case 1:
- return atav;
-
- default:
- return ( AttributeTypeAndValue ) atavs.first();
- }
- }
-
-
- /**
- * Return the type, or the first one of we have more than one (the lowest)
- *
- * @return The first type of this RDN
- */
- public String getType()
- {
- switch ( nbAtavs )
- {
- case 0:
- return null;
-
- case 1:
- return atav.getType();
-
- default:
- return ( ( AttributeTypeAndValue ) atavs.first() ).getType();
- }
- }
-
-
- /**
- * Return the value, or the first one of we have more than one (the lowest)
- *
- * @return The first value of this RDN
- */
- public String getValue()
- {
- switch ( nbAtavs )
- {
- case 0:
- return null;
-
- case 1:
- return atav.getValue();
-
- default:
- return ( ( AttributeTypeAndValue ) atavs.first() ).getValue();
- }
- }
-
-
- /**
- * Compares the specified Object with this Rdn for equality. Returns true if
- * the given object is also a Rdn and the two Rdns represent the same
- * attribute type and value mappings. The order of components in
- * multi-valued Rdns is not significant.
- *
- * @param rdn
- * Rdn to be compared for equality with this Rdn
- * @return true if the specified object is equal to this Rdn
- */
- public boolean equals( Object rdn )
- {
- if ( this == rdn )
- {
- return true;
- }
-
- if ( !( rdn instanceof Rdn ) )
- {
- return false;
- }
-
- return compareTo( ( Rdn ) rdn ) == EQUALS;
- }
-
-
- /**
- * Returns the hash code of this RDN. Two RDNs that are equal (according to
- * the equals method) will have the same hash code.
- *
- * @returnAn int representing the hash code of this Rdn
- */
- public int hashcode()
- {
- // We compute the hashcode using the string, which is a
- // normalized form of a rdn. unescapeValue
- return 37 * 17 + string.hashCode();
- }
-
-
- /**
- * Get the number of Attribute type and value of this Rdn
- *
- * @return The number of ATAVs in this Rdn
- */
- public int size()
- {
- return nbAtavs;
- }
-
-
- /**
- * Transform the Rdn into an javax.naming.directory.Attributes
- *
- * @return An attributes structure containing all the ATAVs
- */
- public Attributes toAttributes()
- {
- Attributes attributes = new BasicAttributes( 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 <string> form, a
- * LDAP string representation asserted value can be obtained by replacing
- * (left-to-right, non-recursively) each <pair> appearing in the <string> as
- * follows: replace <ESC><ESC> with <ESC>; replace <ESC><special> with
- * <special>; replace <ESC><hexpair> with the octet indicated by the
- * <hexpair> If in <hexstring> form, a BER representation can be obtained
- * from converting each <hexpair> of the <hexstring> to the octet indicated
- * by the <hexpair>
- *
- * @param value
- * The value to be unescaped
- * @return Returns a string value as a String, and a binary value as a byte
- * array.
- * @throws IllegalArgumentException -
- * When an Illegal value is provided.
- */
- public static Object unescapeValue( String value ) throws IllegalArgumentException
- {
- if ( StringTools.isEmpty( value ) )
- {
- return "";
- }
-
- char[] chars = value.toCharArray();
-
- if ( chars[0] == '#' )
- {
- if ( chars.length == 1 )
- {
- // The value is only containing a #
- return StringTools.EMPTY_BYTES;
- }
-
- if ( ( chars.length % 2 ) != 1 )
- {
- throw new IllegalArgumentException( "This value is not in hex form, we have an odd number of hex chars" );
- }
-
- // HexString form
- byte[] hexValue = new byte[( chars.length - 1 ) / 2];
- int pos = 0;
-
- for ( int i = 1; i < chars.length; i += 2 )
- {
- if ( StringTools.isHex( chars, i ) && StringTools.isHex( chars, i + 1 ) )
- {
- hexValue[pos++] = ( byte ) ( ( StringTools.HEX_VALUE[chars[i]] << 4 ) + StringTools.HEX_VALUE[chars[i + 1]] );
- }
- else
- {
- throw new IllegalArgumentException( "This value is not in hex form" );
- }
- }
-
- return hexValue;
- }
- else
- {
- boolean escaped = false;
- boolean isHex = false;
- byte pair = -1;
- int pos = 0;
-
- byte[] bytes = new byte[chars.length * 6];
-
- for ( int i = 0; i < chars.length; i++ )
- {
- if ( escaped )
- {
- escaped = false;
-
- switch ( chars[i] )
- {
- case '\\':
- case '"':
- case '+':
- case ',':
- case ';':
- case '<':
- case '>':
- case '#':
- case '=':
- case ' ':
- bytes[pos++] = ( byte ) chars[i];
- break;
-
- default:
- if ( StringTools.isHex( chars, i ) )
- {
- isHex = true;
- pair = ( ( byte ) ( StringTools.HEX_VALUE[chars[i]] << 4 ) );
- }
- }
- }
- else
- {
- if ( isHex )
- {
- if ( StringTools.isHex( chars, i ) )
- {
- pair += ( byte ) StringTools.HEX_VALUE[chars[i]];
- bytes[pos++] = pair;
- }
- }
- else
- {
- switch ( chars[i] )
- {
- case '\\':
- escaped = true;
- break;
-
- // We must not have a special char
- // Specials are : '"', '+', ',', ';', '<', '>', ' ',
- // '#' and '='
- case '"':
- case '+':
- case ',':
- case ';':
- case '<':
- case '>':
- case '#':
- case '=':
- case ' ':
- throw new IllegalArgumentException( "Unescaped special characters are not allowed" );
-
- default:
- byte[] result = StringTools.charToBytes( chars[i] );
-
- for ( int j = 0; j < result.length; j++ )
- {
- bytes[pos++] = result[j];
- }
- }
- }
- }
- }
-
- return StringTools.utf8ToString( bytes, pos );
- }
- }
-
-
- /**
- * Transform a value in a String, accordingly to RFC 2253
- *
- * @param attrValue
- * The attribute value to be escaped
- * @return The escaped string value.
- */
- public static String escapeValue( Object attrValue )
- {
- if ( StringTools.isEmpty( ( byte[] ) attrValue ) )
- {
- return "";
- }
-
- String value = StringTools.utf8ToString( ( byte[] ) attrValue );
-
- char[] chars = value.toCharArray();
- char[] newChars = new char[chars.length * 3];
- int pos = 0;
-
- for ( int i = 0; i < chars.length; i++ )
- {
- switch ( chars[i] )
- {
- case ' ':
- case '"':
- case '#':
- case '+':
- case ',':
- case ';':
- case '=':
- case '<':
- case '>':
- case '\\':
- newChars[pos++] = '\\';
- newChars[pos++] = chars[i];
- break;
-
- case 0x7F:
- newChars[pos++] = '\\';
- newChars[pos++] = '7';
- newChars[pos++] = 'F';
- break;
-
- case 0x00:
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- case 0x08:
- case 0x09:
- case 0x0A:
- case 0x0B:
- case 0x0C:
- case 0x0D:
- case 0x0E:
- case 0x0F:
- newChars[pos++] = '\\';
- newChars[pos++] = '0';
- newChars[pos++] = StringTools.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
- break;
-
- case 0x10:
- case 0x11:
- case 0x12:
- case 0x13:
- case 0x14:
- case 0x15:
- case 0x16:
- case 0x17:
- case 0x18:
- case 0x19:
- case 0x1A:
- case 0x1B:
- case 0x1C:
- case 0x1D:
- case 0x1E:
- case 0x1F:
- newChars[pos++] = '\\';
- newChars[pos++] = '1';
- newChars[pos++] = StringTools.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
- break;
-
- default:
- newChars[pos++] = chars[i];
-
- }
- }
-
- return new String( newChars, 0, pos );
- }
-
- /**
- * Gets the hashcode of this rdn.
- *
- * @see java.lang.Object#hashCode()
- */
- public int hashCode()
- {
- int result = 17;
-
- switch ( nbAtavs )
- {
- case 0:
- // An empty RDN
- break;
-
- case 1:
- // We have a single AttributeTypeAndValue
- result = result * 37 + atav.hashCode();
- break;
-
- default:
- // We have more than one AttributeTypeAndValue
-
- for ( Iterator elems = atavs.iterator();elems.hasNext(); )
- {
- AttributeTypeAndValue ata = ( AttributeTypeAndValue ) elems.next();
- result = result * 37 + ata.hashCode();
- }
- }
-
- return result;
- }
+ /**
+ * 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 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 <string> form, a
+ * LDAP string representation asserted value can be obtained by replacing
+ * (left-to-right, non-recursively) each <pair> appearing in the <string> as
+ * follows: replace <ESC><ESC> with <ESC>; replace <ESC><special> with
+ * <special>; replace <ESC><hexpair> with the octet indicated by the
+ * <hexpair> If in <hexstring> form, a BER representation can be obtained
+ * from converting each <hexpair> of the <hexstring> to the octet indicated
+ * by the <hexpair>
+ *
+ * @param value
+ * The value to be unescaped
+ * @return Returns a string value as a String, and a binary value as a byte
+ * array.
+ * @throws IllegalArgumentException -
+ * When an Illegal value is provided.
+ */
+ public static Object unescapeValue( String value ) throws IllegalArgumentException
+ {
+ if ( StringTools.isEmpty( value ) )
+ {
+ return "";
+ }
+
+ char[] chars = value.toCharArray();
+
+ if ( chars[0] == '#' )
+ {
+ if ( chars.length == 1 )
+ {
+ // The value is only containing a #
+ return StringTools.EMPTY_BYTES;
+ }
+
+ if ( ( chars.length % 2 ) != 1 )
+ {
+ throw new IllegalArgumentException( "This value is not in hex form, we have an odd number of hex chars" );
+ }
+
+ // HexString form
+ byte[] hexValue = new byte[( chars.length - 1 ) / 2];
+ int pos = 0;
+
+ for ( int i = 1; i < chars.length; i += 2 )
+ {
+ if ( StringTools.isHex( chars, i ) && StringTools.isHex( chars, i + 1 ) )
+ {
+ hexValue[pos++] = ( byte ) ( ( StringTools.HEX_VALUE[chars[i]] << 4 ) + StringTools.HEX_VALUE[chars[i + 1]] );
+ }
+ else
+ {
+ throw new IllegalArgumentException( "This value is not in hex form" );
+ }
+ }
+
+ return hexValue;
+ }
+ else
+ {
+ boolean escaped = false;
+ boolean isHex = false;
+ byte pair = -1;
+ int pos = 0;
+
+ byte[] bytes = new byte[chars.length * 6];
+
+ for ( int i = 0; i < chars.length; i++ )
+ {
+ if ( escaped )
+ {
+ escaped = false;
+
+ switch ( chars[i] )
+ {
+ case '\\':
+ case '"':
+ case '+':
+ case ',':
+ case ';':
+ case '<':
+ case '>':
+ case '#':
+ case '=':
+ case ' ':
+ bytes[pos++] = ( byte ) chars[i];
+ break;
+
+ default:
+ if ( StringTools.isHex( chars, i ) )
+ {
+ isHex = true;
+ pair = ( ( byte ) ( StringTools.HEX_VALUE[chars[i]] << 4 ) );
+ }
+ }
+ }
+ else
+ {
+ if ( isHex )
+ {
+ if ( StringTools.isHex( chars, i ) )
+ {
+ pair += ( byte ) StringTools.HEX_VALUE[chars[i]];
+ bytes[pos++] = pair;
+ }
+ }
+ else
+ {
+ switch ( chars[i] )
+ {
+ case '\\':
+ escaped = true;
+ break;
+
+ // We must not have a special char
+ // Specials are : '"', '+', ',', ';', '<', '>', ' ',
+ // '#' and '='
+ case '"':
+ case '+':
+ case ',':
+ case ';':
+ case '<':
+ case '>':
+ case '#':
+ case '=':
+ case ' ':
+ throw new IllegalArgumentException( "Unescaped special characters are not allowed" );
+
+ default:
+ byte[] result = StringTools.charToBytes( chars[i] );
+
+ for ( int j = 0; j < result.length; j++ )
+ {
+ bytes[pos++] = result[j];
+ }
+ }
+ }
+ }
+ }
+
+ return StringTools.utf8ToString( bytes, pos );
+ }
+ }
+
+
+ /**
+ * Transform a value in a String, accordingly to RFC 2253
+ *
+ * @param attrValue
+ * The attribute value to be escaped
+ * @return The escaped string value.
+ */
+ public static String escapeValue( Object attrValue )
+ {
+ if ( StringTools.isEmpty( ( byte[] ) attrValue ) )
+ {
+ return "";
+ }
+
+ String value = StringTools.utf8ToString( ( byte[] ) attrValue );
+
+ char[] chars = value.toCharArray();
+ char[] newChars = new char[chars.length * 3];
+ int pos = 0;
+
+ for ( int i = 0; i < chars.length; i++ )
+ {
+ switch ( chars[i] )
+ {
+ case ' ':
+ case '"':
+ case '#':
+ case '+':
+ case ',':
+ case ';':
+ case '=':
+ case '<':
+ case '>':
+ case '\\':
+ newChars[pos++] = '\\';
+ newChars[pos++] = chars[i];
+ break;
+
+ case 0x7F:
+ newChars[pos++] = '\\';
+ newChars[pos++] = '7';
+ newChars[pos++] = 'F';
+ break;
+
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0A:
+ case 0x0B:
+ case 0x0C:
+ case 0x0D:
+ case 0x0E:
+ case 0x0F:
+ newChars[pos++] = '\\';
+ newChars[pos++] = '0';
+ newChars[pos++] = StringTools.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
+ break;
+
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ case 0x17:
+ case 0x18:
+ case 0x19:
+ case 0x1A:
+ case 0x1B:
+ case 0x1C:
+ case 0x1D:
+ case 0x1E:
+ case 0x1F:
+ newChars[pos++] = '\\';
+ newChars[pos++] = '1';
+ newChars[pos++] = StringTools.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
+ break;
+
+ default:
+ newChars[pos++] = chars[i];
+
+ }
+ }
+
+ return new String( newChars, 0, pos );
+ }
+
+ /**
+ * Gets the hashcode of this rdn.
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ int result = 17;
+
+ switch ( nbAtavs )
+ {
+ case 0:
+ // An empty RDN
+ break;
+
+ case 1:
+ // We have a single AttributeTypeAndValue
+ result = result * 37 + atav.hashCode();
+ break;
+
+ default:
+ // We have more than one AttributeTypeAndValue
+
+ for ( Iterator elems = atavs.iterator();elems.hasNext(); )
+ {
+ AttributeTypeAndValue ata = ( AttributeTypeAndValue ) elems.next();
+ result = result * 37 + ata.hashCode();
+ }
+ }
+
+ return result;
+ }
}