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 2005/12/25 20:23:47 UTC
svn commit: r359002 -
/directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java
Author: elecharny
Date: Sun Dec 25 11:23:42 2005
New Revision: 359002
URL: http://svn.apache.org/viewcvs?rev=359002&view=rev
Log:
Totally reworked version of LdapRdn class, renamed Rdn.
It is compatible with the JDK 1.5 javax.naming.directory.Rdn
class.
Added:
directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java
- copied, changed from r357844, directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java
Copied: directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java (from r357844, directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java)
URL: http://svn.apache.org/viewcvs/directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java?p2=directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java&p1=directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java&r1=357844&r2=359002&rev=359002&view=diff
==============================================================================
--- directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/LdapRDN.java (original)
+++ directory/shared/ldap/branches/DN-refactoring/common/src/main/java/org/apache/ldap/common/name/Rdn.java Sun Dec 25 11:23:42 2005
@@ -17,17 +17,19 @@
package org.apache.ldap.common.name;
import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Set;
+import java.util.Map;
+import java.util.TreeSet;
import javax.naming.InvalidNameException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
import org.apache.asn1.codec.util.StringUtils;
import org.apache.commons.collections.MultiHashMap;
-import org.apache.ldap.common.LdapString;
/**
* This class store the name-component part or the following BNF grammar (as of RFC2253, par. 3,
@@ -54,6 +56,10 @@
* following RDN :<br>
* ou=value + cn=other value<br>
* <br>
+ * or
+ * <br>
+ * ou=value + ou=another value<br>
+ * <br>
* In this case, we have to store an 'ou' and a 'cn' in the RDN.<br>
* <br>
* The types are case insensitive. <br>
@@ -82,52 +88,64 @@
* because we have more than one spaces inside the value.<br>
* <br>
*
+ * The Rdn is composed of one or more AttributeTypeAndValue (atav)
+ * Those atavs are ordered in the alphabetical natural order :
+ * a < b < c ... < z
+ *
+ * As the type are not case sensitive, we can say that a = A
+ *
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*
*/
-public class LdapRDN extends LdapString implements Cloneable, Comparable
+public class Rdn implements Cloneable, Comparable
{
/** 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. The key is
- * the type, the value is a AttributeTypeAndValue.
+ * 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 MultiHashMap atavs;
+ private TreeSet atavs = null;
- /** Stores the lowest element of the RDN */
- private transient String lowest;
+ /**
+ * We also keep a set of types, in order to use manipulations. A type is
+ * connected with the atav it represents.
+ */
+ private Map atavTypes = new MultiHashMap();
/**
- * A simple AttributeTypeAndValue is used to store the LdapRDN for the simple
+ * 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;
+ private AttributeTypeAndValue atav = null;
- private transient int nbAtavs;
+ /** The number of atavs. We store this number here to avoid complex
+ * manipulation of atav and atavs */
+ private transient int nbAtavs = 0;
- /** Value returned by the compareTo method if values are not equals */
- public final static int NOT_EQUALS = -1;
-
- /** Value returned by the compareTo method if values are equals */
- public final static int EQUALS = 0;
+ /** 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 LdapRDN()
+ public Rdn()
{
- super();
-
// Don't waste space... This is not so often we have multiple
- // name-components in a RDN... So we won't initialize the Map.
- atavs = null;
- atav = null;
+ // name-components in a RDN... So we won't initialize the Map and the treeSet.
upName = "";
- nbAtavs = 0;
+ string = "";
}
/**
@@ -136,10 +154,8 @@
* @param rdn The String containing the RDN to parse
* @throws InvalidNameException If the RDN is invalid
*/
- public LdapRDN( String rdn ) throws InvalidNameException
+ public Rdn( String rdn ) throws InvalidNameException
{
- super();
-
if ( StringUtils.isNotEmpty( rdn ) )
{
try
@@ -152,9 +168,15 @@
throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
}
- // Parse the string and normalize the RDN
- RDNParser.parse( rdn, this );
- normalize();
+ // Parse the string. The Rdn will be updated.
+ RdnParser.parse( rdn, this );
+ normalizeString();
+ // The upName is set by the RdnParser
+ }
+ else
+ {
+ upName = "";
+ string = "";
}
}
@@ -167,15 +189,13 @@
* @throws InvalidNameException If the RDN is invalid
*/
- public LdapRDN( byte[] bytes ) throws InvalidNameException
+ public Rdn( byte[] bytes ) throws InvalidNameException
{
- super();
-
try
{
- RDNParser.parse( new String( bytes, "UTF-8" ), this );
- normalize();
- upName = StringUtils.utf8ToString( bytes );
+ RdnParser.parse( new String( bytes, "UTF-8" ), this );
+ normalizeString();
+ // The upName is set by the RdnParser
}
catch ( UnsupportedEncodingException uee )
{
@@ -184,21 +204,62 @@
}
/**
- * A constructor that constructs a RDN from a type and a value
+ * 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 LdapRDN( String type, String value ) throws InvalidNameException
+ public Rdn( String type, String value ) throws InvalidNameException
{
super();
-
- // Don't waste space... This is not so often we have multiple
- // name-components in a RDN... So we won't initialize the Map.
- atavs = null;
+
addAttributeTypeAndValue( type, value );
- normalize();
+
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 );
+ }
+ }
}
/**
@@ -207,7 +268,10 @@
* - types are trimmed and lowercased
* - values are trimmed and lowercased
*/
- private void normalize()
+ // 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 )
{
@@ -227,7 +291,7 @@
// We have more than one AttributeTypeAndValue
StringBuffer sb = new StringBuffer();
- Iterator elems = atavs.values().iterator();
+ Iterator elems = atavs.iterator();
boolean isFirst = true;
while ( elems.hasNext() )
@@ -250,15 +314,6 @@
string = sb.toString();
break;
}
-
- try
- {
- bytes = string.getBytes( "UTF-8" );
- }
- catch ( UnsupportedEncodingException uee )
- {
- // We can't reach this point.
- }
}
/**
@@ -268,138 +323,61 @@
* @param value The value of the added RDN
* @throws InvalidNameException If the RDN is invalid
*/
- public void addAttributeTypeAndValue( String type, String value) throws InvalidNameException
+ // 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 = StringUtils.lowerCase( StringUtils.trim( type ) );
String normalizedValue = StringUtils.trim( value );
- boolean isLowest = true;
-
switch ( nbAtavs )
{
case 0 :
// This is the first AttributeTypeAndValue. Just stores it.
atav = new AttributeTypeAndValue( normalizedType, normalizedValue );
- lowest = normalizedType;
nbAtavs = 1;
- break;
+ atavTypes.put( normalizedType, atav );
+ return;
case 1 :
// We already have an atav. We have to put it in the HashMap
// before adding a new one.
// First, create the HashMap,
- atavs = new MultiHashMap();
+ atavs = new TreeSet();
// and store the existing AttributeTypeAndValue into it.
- atavs.put( atav.getType(), atav );
+ atavs.add( atav );
+
atav = null;
// Now, fall down to the commmon case
// NO BREAK !!!
default :
- // Before adding the new AttributeTypeAndValue, we want to compare
- // it to the first element : we must the first element to be the
- // lowest of all the elements.
-
- if ( normalizedType.compareTo( lowest ) < 0 )
- {
- lowest = normalizedType;
- }
- else
- {
- isLowest = false;
- }
-
// add a new AttributeTypeAndValue
- atavs.put( normalizedType, new AttributeTypeAndValue( normalizedType, normalizedValue ) );
+ AttributeTypeAndValue newAtav = new AttributeTypeAndValue( normalizedType, normalizedValue );
+ atavs.add( newAtav );
+ atavTypes.put( normalizedType, newAtav );
nbAtavs++;
break;
}
-
- if ( StringUtils.isEmpty( string ) )
- {
- string = normalizedType + '=' + normalizedValue;
- }
- else
- {
- if ( isLowest )
- {
- string = normalizedType + '=' + normalizedValue + '+' + string;
- }
- else
- {
- string = string + '+' + normalizedType + '=' + normalizedValue;
- }
- }
}
/**
- * Remove a Name from a RDN
- *
- * @param type The nome to remove
- * @throws InvalidNameException If the name does not exists or if the RDN is empty
- */
- public void removeAttributeTypeAndValue( String type ) throws InvalidNameException
- {
- if ( StringUtils.isEmpty( type ) )
- {
- return;
- }
-
- // First, let's normalize the type
- String normalizedType = StringUtils.lowerCase( StringUtils.trim( type ) );
-
- switch ( nbAtavs )
- {
- case 0 :
- throw new InvalidNameException( "Cannot remove a AttributeTypeAndValue form an empty RDN" );
-
- case 1 :
- if ( normalizedType.equals( atav.getType() ) )
- {
- atav = null;
- string = "";
- nbAtavs--;
- }
- else
- {
- throw new InvalidNameException( "Name '" + normalizedType + "' is not valid for the RDN '" + this.toString() + "'");
- }
-
- break;
-
- default :
- if ( atavs.containsKey( normalizedType ) )
- {
- atavs.remove( normalizedType );
- normalize();
- nbAtavs --;
- }
- else
- {
- throw new InvalidNameException( "Name '" + normalizedType + "' is not valid for the RDN '" + this.toString() + "'");
- }
-
- break;
-
- }
- }
-
- /**
* Clear the RDN, removing all the AttributeTypeAndValues.
*/
public void clear()
{
atav = null;
atavs = null;
+ atavTypes.clear();
nbAtavs = 0;
string = "";
upName = "";
- bytes = EMPTY_BYTES;
}
/**
@@ -429,27 +407,41 @@
}
default :
- if ( atavs.containsKey( normalizedType ) )
+ if ( atavTypes.containsKey( normalizedType ) )
{
- List avas = (ArrayList)atavs.get( normalizedType );
- StringBuffer sb = new StringBuffer();
- boolean isFirst = true;
+ Object obj = atavTypes.get( normalizedType );
- for ( int i = 0; i < avas.size(); i++ )
+ if ( obj instanceof AttributeTypeAndValue )
{
- if ( isFirst )
- {
- isFirst = false;
- }
- else
+ 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++ )
{
- sb.append( ',' );
+ AttributeTypeAndValue elem = (AttributeTypeAndValue)((List)obj).get( i );
+
+ if ( isFirst )
+ {
+ isFirst = false;
+ }
+ else
+ {
+ sb.append( ',' );
+ }
+
+ sb.append( elem.getValue() );
}
- sb.append( ( (AttributeTypeAndValue)avas.get( i ) ).getValue() );
+ return sb.toString();
+ }
+ else
+ {
+ throw new InvalidNameException( "Bad object stored in the RDN" );
}
-
- return sb.toString();
}
else
{
@@ -487,11 +479,9 @@
}
default :
- if ( atavs.containsKey( normalizedType ) )
+ if ( atavTypes.containsKey( normalizedType ) )
{
- List values = (ArrayList)atavs.get( normalizedType );
-
- return (AttributeTypeAndValue)values.get( 0 );
+ return (AttributeTypeAndValue)atavTypes.get( normalizedType );
}
else
{
@@ -536,34 +526,48 @@
}
else
{
- return atavs.values().iterator();
+ return atavs.iterator();
}
}
/**
- * Clone the LdapRDN
+ * Clone the Rdn
*/
public Object clone()
{
try
{
- LdapRDN rdn = (LdapRDN)super.clone();
+ Rdn rdn = (Rdn)super.clone();
// The AttributeTypeAndValue is immutable. We won't clone it
- if ( atavs != null )
- {
- rdn.atavs = new MultiHashMap( nbAtavs );
-
- Iterator values = atavs.values().iterator();
-
- while ( values.hasNext() )
- {
- AttributeTypeAndValue ava = (AttributeTypeAndValue)values.next();
-
- rdn.atavs.put( ava.getType(), ava );
- }
- }
+ switch ( rdn.getNbAtavs() )
+ {
+ case 0 :
+ break;
+
+ case 1 :
+ rdn.atav = (AttributeTypeAndValue)this.atav.clone();
+ rdn.atavTypes = new MultiHashMap();
+ rdn.atavTypes.put( rdn.atav.getType(), rdn.atav );
+ break;
+
+ default :
+ // We must duplicate the treeSet and the hashMap
+ rdn.atavTypes = new MultiHashMap();
+ rdn.atavs = new TreeSet();
+
+ Iterator iter = this.atavs.iterator();
+
+ while ( iter.hasNext() )
+ {
+ AttributeTypeAndValue currentAtav = (AttributeTypeAndValue)iter.next();
+ rdn.atavs.add( currentAtav.clone() );
+ rdn.atavTypes.put( currentAtav.getType(), currentAtav );
+ }
+
+ break;
+ }
return rdn;
}
@@ -574,30 +578,35 @@
}
/**
- * Compares two RDN. They are equals if :
+ * Compares two RDNs. They are equals if :
* - their have the same number of NC (AttributeTypeAndValue)
- * - for each NC in object, their is one NC which is equal
- * - comparizon of type are done case insensitive
- * - each value is equel, case sensitive
+ * - 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
+ * @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 LdapRDN )
+ if ( object instanceof Rdn )
{
- LdapRDN rdn = (LdapRDN)object;
+ Rdn rdn = (Rdn)object;
if ( rdn == null )
{
- return 1;
+ 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;
}
@@ -612,58 +621,64 @@
default :
// We have more than one value. We will
// go through all of them.
- Iterator keys = ((MultiHashMap)atavs).keySet().iterator();
+ Iterator keys = atavs.iterator();
while ( keys.hasNext() )
{
- String type = (String)keys.next();
+ AttributeTypeAndValue current = (AttributeTypeAndValue)keys.next();
+ String type = current.getType();
- if ( ((MultiHashMap)rdn.atavs).containsKey( type ) )
+ if ( rdn.atavTypes.containsKey( type ) )
{
- List atavList = (List)((MultiHashMap)atavs).get( type );
- List atavList2 = (List)((MultiHashMap)rdn.atavs).get( type );
-
- // Ok, let's go for ugliness :
- // We are not supposed to have a lot of multi-valued RDN
- // with a type that contains multiple values. In fact,
- // I don't think that we will ever have this kind of RDN :
- // "ou=test+ou=test2".
- // And if we do, I won't put a cent on an application that
- // have this kind of RDNs, especially if the number of AVA
- // is higher than 2...
- Iterator atavIter1 = atavList.iterator();
- Set atavSet = new HashSet();
-
- // We build a set of value from the local RDN
- while ( atavIter1.hasNext() )
- {
- AttributeTypeAndValue atavValue = (AttributeTypeAndValue)atavIter1.next();
- atavSet.add( StringUtils.lowerCase( atavValue.getValue() ) );
- }
-
- Iterator atavIter2 = atavList2.iterator();
-
- // Now, we compare each NC from the second RDN to the NC of the first RDN
- while ( atavIter2.hasNext() )
- {
- AttributeTypeAndValue atavValue = (AttributeTypeAndValue)atavIter2.next();
-
- if ( atavSet.contains( StringUtils.lowerCase( atavValue.getValue() ) ) )
- {
- atavSet.remove( StringUtils.lowerCase( atavValue.getValue() ) );
- }
- else
- {
- return NOT_EQUALS;
- }
- }
-
- // Last, not least, the Set must be empty for
- // both RDN to be equals
- if ( atavSet.size() != 0 )
- {
- return NOT_EQUALS;
- }
+ 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;
}
}
@@ -672,7 +687,7 @@
}
else
{
- return NOT_EQUALS;
+ return object != null ? UNDEFINED : SUPERIOR;
}
}
@@ -714,17 +729,345 @@
*/
public AttributeTypeAndValue getAtav()
{
- if ( nbAtavs == 0 )
+ switch ( nbAtavs )
{
- return null;
+ case 0 :
+ return null;
+
+ case 1 :
+ return atav;
+
+ default :
+ return (AttributeTypeAndValue)atavs.first();
}
- else if ( nbAtavs == 1 )
+ }
+
+ /**
+ * 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 )
{
- return atav;
+ case 0 :
+ return null;
+
+ case 1 :
+ return atav.getType();
+
+ default :
+ return ((AttributeTypeAndValue)atavs.first()).getType();
}
- else
+ }
+
+ /**
+ * 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 )
{
- return (AttributeTypeAndValue)atavs.get( lowest );
+ case 0 :
+ return null;
+
+ case 1 :
+ return atav.getValue();
+
+ default :
+ return ((AttributeTypeAndValue)atavs.first()).getValue();
}
}
+
+ /**
+ * Compares the specified Object with this Rdn for equality. Returns true
+ * if the given object is also a Rdn and the two Rdns represent the same
+ * attribute type and value mappings. The order of components in multi-valued
+ * Rdns is not significant.
+ * @param rdn Rdn to be compared for equality with this Rdn
+ * @return true if the specified object is equal to this Rdn
+ */
+ public boolean equals( Object rdn )
+ {
+ if ( this == rdn )
+ {
+ return true;
+ }
+
+ if ( !(rdn instanceof Rdn ) )
+ {
+ return false;
+ }
+
+ return compareTo( (Rdn)rdn ) == EQUALS;
+ }
+
+ /**
+ * Returns the hash code of this RDN. Two RDNs that are equal (according
+ * to the equals method) will have the same hash code.
+ *
+ * @returnAn int representing the hash code of this Rdn
+ */
+ public int hashcode()
+ {
+ // We compute the hashcode using the string, which is a
+ // normalized form of a rdn. unescapeValue
+ return 37*17 + string.hashCode();
+ }
+
+ /**
+ * Get the number of Attribute type and value of this Rdn
+ * @return The number of ATAVs in this Rdn
+ */
+ public int size()
+ {
+ return nbAtavs;
+ }
+
+ /**
+ * Transform the Rdn into an javax.naming.directory.Attributes
+ *
+ * @return An attributes structure containing all the ATAVs
+ */
+ public Attributes toAttributes()
+ {
+ Attributes attributes = new BasicAttributes();
+
+ Iterator types = atavTypes.keySet().iterator();
+
+ while ( types.hasNext() )
+ {
+ String type = (String)types.next();
+ List values = (List)atavTypes.get( type );
+
+ Attribute attribute = new BasicAttribute( type, true );
+
+ Iterator iterValues = values.iterator();
+
+ while ( iterValues.hasNext() )
+ {
+ AttributeTypeAndValue value = (AttributeTypeAndValue)iterValues.next();
+
+ attribute.add( value.getValue() );
+ }
+
+ attributes.put( attribute );
+ }
+
+ return attributes;
+ }
+
+ /**
+ * Unescape the given string according to RFC 2253
+ * If in <string> form, a LDAP string representation asserted value can
+ * be obtained by replacing (left-to-right, non-recursively) each <pair>
+ * appearing in the <string> as follows:
+ * replace <ESC><ESC> with <ESC>;
+ * replace <ESC><special> with <special>;
+ * replace <ESC><hexpair> with the octet indicated by the <hexpair>
+ *
+ * If in <hexstring> form, a BER representation can be obtained from
+ * converting each <hexpair> of the <hexstring> to the octet indicated by
+ * the <hexpair>
+ *
+ * @param value The value to be unescaped
+ * @return Returns a string value as a String, and a binary value as a byte array.
+ * @throws IllegalArgumentException - When an Illegal value is provided.
+ */
+ public static Object unescapeValue( String value ) throws IllegalArgumentException
+ {
+ if ( StringUtils.isEmpty( value ) )
+ {
+ return "";
+ }
+
+ char[] chars = value.toCharArray();
+
+ if ( chars[0] == '#' )
+ {
+ if ( chars.length == 1 )
+ {
+ // The value is only containing a #
+ return StringUtils.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 ( StringUtils.isHex( chars, i ) && StringUtils.isHex( chars, i + 1 ) )
+ {
+ hexValue[ pos++ ] = (byte)((StringUtils.HEX_VALUE[ chars[i] ] << 4) + StringUtils.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 ( StringUtils.isHex( chars, i ) )
+ {
+ isHex = true;
+ pair = ((byte)(StringUtils.HEX_VALUE[ chars[i] ] << 4));
+ }
+ }
+ }
+ else
+ {
+ if ( isHex )
+ {
+ if ( StringUtils.isHex( chars, i ) )
+ {
+ pair += (byte)StringUtils.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 = StringUtils.charToBytes( chars[i] );
+
+ for ( int j = 0; j < result.length; j++ )
+ {
+ bytes[pos++] = result[j];
+ }
+ }
+ }
+ }
+ }
+
+ return StringUtils.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 (StringUtils.isEmpty( (byte[])attrValue) )
+ {
+ return "";
+ }
+
+ String value = StringUtils.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++] = StringUtils.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++] = StringUtils.dumpHex( (byte)(chars[i] & 0x0F) );
+ break;
+
+
+ default :
+ newChars[pos++] = chars[i];
+
+ }
+ }
+
+ return new String( newChars, 0, pos);
+ }
}