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