Return-Path: Delivered-To: apmail-directory-commits-archive@www.apache.org Received: (qmail 53445 invoked from network); 9 Jul 2006 15:27:50 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 9 Jul 2006 15:27:50 -0000 Received: (qmail 73429 invoked by uid 500); 9 Jul 2006 15:27:50 -0000 Delivered-To: apmail-directory-commits-archive@directory.apache.org Received: (qmail 73380 invoked by uid 500); 9 Jul 2006 15:27:50 -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 73369 invoked by uid 99); 9 Jul 2006 15:27:50 -0000 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 (hermes.apache.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, 09 Jul 2006 08:27:45 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id 8A7FD1A981C; Sun, 9 Jul 2006 08:26:48 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r420301 [1/2] - in /directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name: LdapDN.java LdapDnParser.java Rdn.java RdnParser.java Date: Sun, 09 Jul 2006 15:26:47 -0000 To: commits@directory.apache.org From: elecharny@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060709152648.8A7FD1A981C@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Author: elecharny Date: Sun Jul 9 08:26:47 2006 New Revision: 420301 URL: http://svn.apache.org/viewvc?rev=420301&view=rev Log: Restored the missing files... Added: directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java (with props) directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/RdnParser.java (with props) Added: directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java URL: http://svn.apache.org/viewvc/directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java?rev=420301&view=auto ============================================================================== --- directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java (added) +++ directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDN.java Sun Jul 9 08:26:47 2006 @@ -0,0 +1,1426 @@ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.directory.shared.ldap.name; + + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.NamingException; + +import org.apache.directory.shared.ldap.schema.OidNormalizer; +import org.apache.directory.shared.ldap.util.StringTools; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The LdapDN class contains a DN (Distinguished Name). + * + * Its specification can be found in RFC 2253, + * "UTF-8 String Representation of Distinguished Names". + * + * We will store two representation of a DN : + * - a user Provider represeentation, which is the parsed String given by a user + * - an internal representation. + * + * A DN is formed of RDNs, in a specific order : + * RDN[n], RDN[n-1], ... RDN[1], RDN[0] + * + * It represents a tree, in which the root is the last RDN (RDN[0]) and the leaf + * is the first RDN (RDN[n]). + * + * @author Apache Directory Project + */ +public class LdapDN /* extends LdapString */implements Name +{ + /** The LoggerFactory used by this class */ + private static Logger log = LoggerFactory.getLogger( LdapDN.class ); + + /** + * Declares the Serial Version Uid. + * + * @see Always + * Declare Serial Version Uid + */ + private static final long serialVersionUID = 1L; + + /** 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; + + // ~ Static fields/initializers + // ----------------------------------------------------------------- + /** The RDNs that are elements of the DN */ + private List rdns = new ArrayList( 5 ); + + /** The user provided name */ + private String upName; + + /** The normalized name */ + private String normName; + + /** The bytes representation of the normName */ + private byte[] bytes; + + /** A null LdapDN */ + public static final LdapDN EMPTY_LDAPDN = new LdapDN(); + + // ~ Methods + // ------------------------------------------------------------------------------------ + + /** + * Construct an empty LdapDN object + */ + public LdapDN() + { + super(); + upName = ""; + normName = ""; + } + + + /** + * Transduces, or copies a Name to an LdapDN. + * + * @param name composed of String name components. + */ + public LdapDN( Name name ) throws InvalidNameException + { + if ( ( name != null ) && ( name.size() != 0 ) ) + { + for ( int ii = 0; ii < name.size(); ii++ ) + { + String nameComponent = ( String ) name.get( ii ); + add( nameComponent ); + } + } + } + + + /** + * Creates an ldap name using a list of NameComponents. Each NameComponent + * is a String + * + * @param list of String name components. + */ + LdapDN( List list ) throws InvalidNameException + { + if ( ( list != null ) && ( list.size() != 0 ) ) + { + Iterator nameComponents = list.iterator(); + + while ( nameComponents.hasNext() ) + { + String nameComponent = ( String ) nameComponents.next(); + add( 0, nameComponent ); + } + } + } + + + /** + * Creates an ldap name using a list of name components. + * + * @param nameComponents + * List of String name components. + */ + LdapDN( Iterator nameComponents ) throws InvalidNameException + { + if ( nameComponents != null ) + { + while ( nameComponents.hasNext() ) + { + String nameComponent = ( String ) nameComponents.next(); + add( 0, nameComponent ); + } + } + } + + + /** + * Parse a String and checks that it is a valid DN
+ *

+ * <distinguishedName> ::= <name> | e
+ * <name> ::= <name-component> <name-components>
+ * <name-components> ::= <spaces> <separator> + * <spaces> <name-component> <name-components> | e
+ *

+ * + * @param bytes + * The byte buffer that contains the DN + * @exception A + * InvalidNameException is thrown if the buffer does not + * contains a valid DN. + */ + public LdapDN( String upName ) throws InvalidNameException + { + if ( upName != null ) + { + LdapDnParser.parseInternal( upName, rdns ); + } + + // Stores the representations of a DN : internal (as a string and as a + // byte[]) and external. + normalizeInternal(); + this.upName = upName; + } + + + /** + * Parse a buffer and checks that it is a valid DN
+ *

+ * <distinguishedName> ::= <name> | e
+ * <name> ::= <name-component> <name-components>
+ * <name-components> ::= <spaces> <separator> + * <spaces> <name-component> <name-components> | e
+ *

+ * + * @param bytes + * The byte buffer that contains the DN + * @exception A + * InvalidNameException is thrown if the buffer does not + * contains a valid DN. + */ + public LdapDN(byte[] bytes) throws InvalidNameException + { + try + { + upName = new String( bytes, "UTF-8" ); + LdapDnParser.parseInternal( upName, rdns ); + this.normName = toNormName(); + } + catch ( UnsupportedEncodingException uee ) + { + log.error( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() ); + throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : " + + uee.getMessage() ); + } + } + + + /** + * Normalize the DN by triming useless spaces and lowercasing names. + * + * @return a normalized form of the DN + */ + private void normalizeInternal() + { + normName = toNormName(); + } + + + /** + * Build the normalized DN as a String, + * + * @return A String representing the normalized DN + */ + public String toNormName() + { + if ( ( rdns == null ) || ( rdns.size() == 0 ) ) + { + bytes = null; + return ""; + } + else + { + StringBuffer sb = new StringBuffer(); + boolean isFirst = true; + + for ( int i = 0; i < rdns.size(); i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ',' ); + } + + sb.append( ( ( Rdn ) rdns.get( i ) ) ); + } + + String newNormName = sb.toString(); + + if ( normName != newNormName ) + { + bytes = StringTools.getBytesUtf8( newNormName ); + normName = newNormName; + } + + return normName; + } + } + + + /** + * Return the normalized DN as a String. It returns the same value as the + * getNormName method + * + * @return A String representing the normalized DN + */ + public String toString() + { + return normName == null ? "" : normName; + } + + + /** + * Return the User Provided DN as a String, + * + * @return A String representing the User Provided DN + */ + private String toUpName() + { + if ( ( rdns == null ) || ( rdns.size() == 0 ) ) + { + upName = ""; + } + else + { + StringBuffer sb = new StringBuffer(); + boolean isFirst = true; + + for ( int i = 0; i < rdns.size(); i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ',' ); + } + + sb.append( ( ( Rdn ) rdns.get( i ) ).getUpName() ); + } + + upName = sb.toString(); + } + + return upName; + } + + + /** + * Return the User Provided prefix representation of the DN starting at the + * posn position. + * + * If posn = 0, return an empty string. + * + * for DN : sn=smith, dc=apache, dc=org + * getUpname(0) -> "" + * getUpName(1) -> "dc=org" + * getUpname(3) -> "sn=smith, dc=apache, dc=org" + * getUpName(4) -> ArrayOutOfBoundException + * + * Warning ! The returned String is not exactly the + * user provided DN, as spaces before and after each RDNs have been trimmed. + * + * @param posn + * The starting position + * @return The truncated DN + */ + private String getUpNamePrefix( int posn ) + { + if ( posn == 0 ) + { + return ""; + } + + if ( posn > rdns.size() ) + { + String message = "Impossible to get the position " + posn + ", the DN only has " + rdns.size() + " RDNs"; + log.error( message ); + throw new ArrayIndexOutOfBoundsException( message ); + } + + int start = rdns.size() - posn; + StringBuffer sb = new StringBuffer(); + boolean isFirst = true; + + for ( int i = start; i < rdns.size(); i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ',' ); + } + + sb.append( ( ( Rdn ) rdns.get( i ) ).getUpName() ); + } + + return sb.toString(); + } + + + /** + * Return the User Provided suffix representation of the DN starting at the + * posn position. + * If posn = 0, return an empty string. + * + * for DN : sn=smith, dc=apache, dc=org + * getUpname(0) -> "sn=smith, dc=apache, dc=org" + * getUpName(1) -> "sn=smith, dc=apache" + * getUpname(3) -> "sn=smith" + * getUpName(4) -> "" + * + * Warning ! The returned String is not exactly the user + * provided DN, as spaces before and after each RDNs have been trimmed. + * + * @param posn The starting position + * @return The truncated DN + */ + private String getUpNameSuffix( int posn ) + { + if ( posn > rdns.size() ) + { + return ""; + } + + int end = rdns.size() - posn; + StringBuffer sb = new StringBuffer(); + boolean isFirst = true; + + for ( int i = 0; i < end; i++ ) + { + if ( isFirst ) + { + isFirst = false; + } + else + { + sb.append( ',' ); + } + + sb.append( ( ( Rdn ) rdns.get( i ) ).getUpName() ); + } + + return sb.toString(); + } + + + /** + * Gets the hashcode of this name. + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + int result = 17; + + if ( ( rdns != null ) || ( rdns.size() == 0 ) ) + { + for ( Iterator rdnsIter = rdns.iterator(); rdnsIter.hasNext(); ) + { + result = result * 37 + rdnsIter.next().hashCode(); + } + } + + return result; + } + + + /** + * Get the initial DN (without normalization) + * + * @return The DN as a String + */ + public String getUpName() + { + return ( upName == null ? "" : upName ); + } + + + /** + * Get the initial DN (without normalization) + * + * @return The DN as a String + */ + public String getNormName() + { + return ( normName == null ? "" : normName ); + } + + /** + * Get the number of NameComponent conatained in this LdapDN + * + * @return The number of NameComponent conatained in this LdapDN + */ + public int size() + { + return rdns.size(); + } + + + /** + * Get the number of bytes necessary to store this DN + * + * @return A integer, which is the size of the UTF-8 byte array + */ + public static int getNbBytes( Name dn ) + { + LdapDN ldapDn = ( LdapDN ) dn; + return ldapDn.bytes == null ? 0 : ldapDn.bytes.length; + } + + + /** + * Get an UTF-8 representation of the normalized form of the DN + * + * @return A byte[] representation of the DN + */ + public static byte[] getBytes( LdapDN dn ) + { + return dn == null ? null : dn.bytes; + } + + + /** + * Determines whether this name starts with a specified prefix. A name + * name is a prefix if it is equal to + * getPrefix(name.size()). + * + * Be aware that for a specific DN like : + * cn=xxx, ou=yyy + * the startsWith method will return true with ou=yyy, and + * false with cn=xxx + * + * @param name + * the name to check + * @return true if name is a prefix of this name, false otherwise + */ + public boolean startsWith( Name name ) + { + if ( name instanceof LdapDN ) + { + LdapDN nameDN = ( LdapDN ) name; + + if ( nameDN.size() == 0 ) + { + return true; + } + + if ( nameDN.size() > size() ) + { + // The name is longer than the current LdapDN. + return false; + } + + // Ok, iterate through all the RDN of the name, + // starting a the end of the current list. + + for ( int i = nameDN.size() - 1; i >= 0; i-- ) + { + Rdn nameRdn = ( Rdn ) ( nameDN.rdns.get( nameDN.rdns.size() - i - 1 ) ); + Rdn ldapRdn = ( Rdn ) rdns.get( rdns.size() - i - 1 ); + + if ( nameRdn.compareTo( ldapRdn ) != 0 ) + { + return false; + } + } + + return true; + } + else if ( name instanceof Name ) + { + if ( name.size() == 0 ) + { + return true; + } + + if ( name.size() > size() ) + { + // The name is longer than the current LdapDN. + return false; + } + + // Ok, iterate through all the RDN of the name, + // starting a the end of the current list. + + for ( int i = name.size() - 1; i >= 0; i-- ) + { + Rdn ldapRdn = ( Rdn ) rdns.get( rdns.size() - i - 1 ); + Rdn nameRdn = null; + try + { + nameRdn = new Rdn( ( String ) name.get( name.size() - i - 1 ) ); + } + catch ( InvalidNameException e ) + { + e.printStackTrace(); + log.error( "Failed to parse RDN for name " + name.toString(), e ); + } + + if ( nameRdn.compareTo( ldapRdn ) != 0 ) + { + return false; + } + } + + return true; + } + else + { + // We don't accept a Name which is not a LdapName + return name == null; + } + } + + + /** + * Determines whether this name ends with a specified suffix. A name + * name is a suffix if it is equal to + * getSuffix(size()-name.size()). + * + * Be aware that for a specific + * DN like : cn=xxx, ou=yyy the endsWith method will return true with + * cn=xxx, and false with ou=yyy + * + * @param name + * the name to check + * @return true if name is a suffix of this name, false otherwise + */ + public boolean endsWith( Name name ) + { + if ( name instanceof LdapDN ) + { + LdapDN nameDN = ( LdapDN ) name; + + if ( nameDN.size() == 0 ) + { + return true; + } + + if ( nameDN.size() > size() ) + { + // The name is longer than the current LdapDN. + return false; + } + + // Ok, iterate through all the RDN of the name + for ( int i = 0; i < nameDN.size(); i++ ) + { + Rdn nameRdn = ( Rdn ) ( nameDN.rdns.get( i ) ); + Rdn ldapRdn = ( Rdn ) rdns.get( i ); + + if ( nameRdn.compareTo( ldapRdn ) != 0 ) + { + return false; + } + } + + return true; + } + else + { + // We don't accept a Name which is not a LdapName + return name == null; + } + } + + + /** + * Determines whether this name is empty. An empty name is one with zero + * components. + * + * @return true if this name is empty, false otherwise + */ + public boolean isEmpty() + { + return ( rdns.size() == 0 ); + } + + + /** + * Retrieves a component of this name. + * + * @param posn + * the 0-based index of the component to retrieve. Must be in the + * range [0,size()). + * @return the component at index posn + * @throws ArrayIndexOutOfBoundsException + * if posn is outside the specified range + */ + public String get( int posn ) + { + if ( rdns.size() == 0 ) + { + return ""; + } + else + { + Rdn rdn = ( Rdn ) rdns.get( rdns.size() - posn - 1 ); + + return rdn.toString(); + } + } + + + /** + * Retrieves a component of this name. + * + * @param posn + * the 0-based index of the component to retrieve. Must be in the + * range [0,size()). + * @return the component at index posn + * @throws ArrayIndexOutOfBoundsException + * if posn is outside the specified range + */ + public Rdn getRdn( int posn ) + { + if ( rdns.size() == 0 ) + { + return null; + } + else + { + Rdn rdn = ( Rdn ) rdns.get( rdns.size() - posn - 1 ); + + return rdn; + } + } + + /** + * Retrieves the last component of this name. + * + * @return the last component of this DN + */ + public Rdn getRdn() + { + if ( rdns.size() == 0 ) + { + return null; + } + else + { + Rdn rdn = ( Rdn ) rdns.get( 0 ); + + return rdn; + } + } + + + /** + * Retrieves all the components of this name. + * + * @return All the components + */ + public List getRdns() + { + List newRdns = new ArrayList(); + + // We will clone the list, to avoid user modifications + for ( int i = 0; i < rdns.size(); i++ ) + { + newRdns.add( i, ( ( Rdn ) rdns.get( i ) ).clone() ); + } + + return newRdns; + } + + + /** + * 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 as string + */ + public Enumeration getAll() + { + /* + * Note that by accessing the name component using the get() method on + * the name rather than get() on the list we are reading components from + * right to left with increasing index values. LdapName.get() does the + * index translation on m_list for us. + */ + return new Enumeration() + { + private int pos; + + + public boolean hasMoreElements() + { + return pos < rdns.size(); + } + + + public Object nextElement() + { + if ( pos >= rdns.size() ) + { + log.error( "Exceeded number of elements in the current object" ); + throw new NoSuchElementException(); + } + + Object obj = rdns.get( rdns.size() - pos - 1 ); + pos++; + return obj.toString(); + } + }; + } + + + /** + * 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, as Rdn + */ + public Enumeration getAllRdn() + { + /* + * Note that by accessing the name component using the get() method on + * the name rather than get() on the list we are reading components from + * right to left with increasing index values. LdapName.get() does the + * index translation on m_list for us. + */ + return new Enumeration() + { + private int pos; + + + public boolean hasMoreElements() + { + return pos < rdns.size(); + } + + + public Object nextElement() + { + if ( pos >= rdns.size() ) + { + log.error( "Exceeded number of elements in the current object" ); + throw new NoSuchElementException(); + } + + Object obj = rdns.get( rdns.size() - pos - 1 ); + pos++; + return obj; + } + }; + } + + + /** + * Creates a name whose components consist of a prefix of the components of + * this name. Subsequent changes to this name will not affect the name that + * is returned and vice versa. + * + * @param posn + * the 0-based index of the component at which to stop. Must be + * in the range [0,size()]. + * @return a name consisting of the components at indexes in the range + * [0,posn]. + * @throws ArrayIndexOutOfBoundsException + * if posn is outside the specified range + */ + public Name getPrefix( int posn ) + { + if ( rdns.size() == 0 ) + { + return EMPTY_LDAPDN; + } + + if ( ( posn < 0 ) || ( posn > rdns.size() ) ) + { + String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]"; + log.error( message ); + throw new ArrayIndexOutOfBoundsException( message ); + } + + LdapDN newLdapDN = new LdapDN(); + + for ( int i = rdns.size() - posn; i < rdns.size(); i++ ) + { + // Don't forget to clone the rdns ! + newLdapDN.rdns.add( ( ( Rdn ) rdns.get( i ) ).clone() ); + } + + newLdapDN.normName = newLdapDN.toNormName(); + newLdapDN.upName = getUpNamePrefix( posn ); + + return newLdapDN; + } + + + /** + * Creates a name whose components consist of a suffix of the components in + * this name. Subsequent changes to this name do not affect the name that is + * returned and vice versa. + * + * @param posn + * the 0-based index of the component at which to start. Must be + * in the range [0,size()]. + * @return a name consisting of the components at indexes in the range + * [posn,size()]. If posn is equal to size(), an empty name is + * returned. + * @throws ArrayIndexOutOfBoundsException + * if posn is outside the specified range + */ + public Name getSuffix( int posn ) + { + if ( rdns.size() == 0 ) + { + return EMPTY_LDAPDN; + } + + if ( ( posn < 0 ) || ( posn > rdns.size() ) ) + { + String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]"; + log.error( message ); + throw new ArrayIndexOutOfBoundsException( message ); + } + + LdapDN newLdapDN = new LdapDN(); + + for ( int i = 0; i < size() - posn; i++ ) + { + // Don't forget to clone the rdns ! + newLdapDN.rdns.add( ( ( Rdn ) rdns.get( i ) ).clone() ); + } + + newLdapDN.normName = newLdapDN.toNormName(); + newLdapDN.upName = getUpNameSuffix( posn ); + + return newLdapDN; + } + + + /** + * Adds the components of a name -- in order -- to the end of this name. + * + * @param suffix + * the components to add + * @return the updated name (not a new one) + * @throws InvalidNameException + * if suffix is not a valid name, or if the addition + * of the components would violate the syntax rules of this name + */ + public Name addAll( Name suffix ) throws InvalidNameException + { + addAll( rdns.size(), suffix ); + normalizeInternal(); + toUpName(); + + return this; + } + + + /** + * Adds the components of a name -- in order -- at a specified position + * within this name. Components of this name at or after the index of the + * first new component are shifted up (away from 0) to accommodate the new + * components. + * + * @param name + * the components to add + * @param posn + * the index in this name at which to add the new components. + * Must be in the range [0,size()]. + * @return the updated name (not a new one) + * @throws ArrayIndexOutOfBoundsException + * if posn is outside the specified range + * @throws InvalidNameException + * if n is not a valid name, or if the addition of + * the components would violate the syntax rules of this name + */ + public Name addAll( int posn, Name name ) throws InvalidNameException + { + if ( name instanceof LdapDN ) + { + if ( ( name == null ) || ( name.size() == 0 ) ) + { + return this; + } + + // Concatenate the rdns + rdns.addAll( size() - posn, ( ( LdapDN ) name ).rdns ); + + // Regenerate the normalized name and the original string + normalizeInternal(); + toUpName(); + + return this; + } + else + { + log.error( "Not a valid LdapDN suffix : " + name ); + throw new InvalidNameException( "The suffix is not a LdapDN" ); + } + } + + + /** + * Adds a single component to the end of this name. + * + * @param comp + * the component to add + * @return the updated name (not a new one) + * @throws InvalidNameException + * if adding comp would violate the syntax rules of + * this name + */ + public Name add( String comp ) throws InvalidNameException + { + // We have to parse the nameComponent which is given as an argument + Rdn newRdn = new Rdn( comp ); + + rdns.add( 0, newRdn ); + normalizeInternal(); + toUpName(); + + return this; + } + + /** + * Adds a single component to the end of this name. + * + * @param comp + * the component to add + * @return the updated name (not a new one) + * @throws InvalidNameException + * if adding comp would violate the syntax rules of + * this name + */ + public Name add( Rdn newRdn ) throws InvalidNameException + { + rdns.add( rdns.size() - 1, newRdn ); + normalizeInternal(); + toUpName(); + + return this; + } + + /** + * Adds a single component at a specified position within this name. + * Components of this name at or after the index of the new component are + * shifted up by one (away from index 0) to accommodate the new component. + * + * @param comp + * the component to add + * @param posn + * the index at which to add the new component. Must be in the + * range [0,size()]. + * @return the updated name (not a new one) + * @throws ArrayIndexOutOfBoundsException + * if posn is outside the specified range + * @throws InvalidNameException + * if adding comp would violate the syntax rules of + * this name + */ + public Name add( int posn, String comp ) throws InvalidNameException + { + if ( ( posn < 0 ) || ( posn > size() ) ) + { + String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]"; + log.error( message ); + throw new ArrayIndexOutOfBoundsException( message ); + } + + // We have to parse the nameComponent which is given as an argument + Rdn newRdn = new Rdn( comp ); + + int realPos = size() - posn; + rdns.add( realPos, newRdn ); + + normalizeInternal(); + toUpName(); + + return this; + } + + + /** + * Removes a component from this name. The component of this name at the + * specified position is removed. Components with indexes greater than this + * position are shifted down (toward index 0) by one. + * + * @param posn + * the index of the component to remove. Must be in the range + * [0,size()). + * @return the component removed (a String) + * @throws ArrayIndexOutOfBoundsException + * if posn is outside the specified range + * @throws InvalidNameException + * if deleting the component would violate the syntax rules of + * the name + */ + public Object remove( int posn ) throws InvalidNameException + { + if ( rdns.size() == 0 ) + { + return EMPTY_LDAPDN; + } + + if ( ( posn < 0 ) || ( posn >= rdns.size() ) ) + { + String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]"; + log.error( message ); + throw new ArrayIndexOutOfBoundsException( message ); + } + + int realPos = size() - posn - 1; + Rdn rdn = ( Rdn ) rdns.remove( realPos ); + + normalizeInternal(); + toUpName(); + + return rdn; + } + + + /** + * Generates a new copy of this name. Subsequent changes to the components + * of this name will not affect the new copy, and vice versa. + * + * @return a copy of this name + * @see Object#clone() + */ + public Object clone() + { + try + { + LdapDN dn = ( LdapDN ) super.clone(); + dn.rdns = new ArrayList(); + + for ( int i = 0; i < rdns.size(); i++ ) + { + dn.rdns.add( i, ( ( Rdn ) rdns.get( i ) ).clone() ); + } + + return dn; + } + catch ( CloneNotSupportedException cnse ) + { + log.error( "The clone operation has failed" ); + throw new Error( "Assertion failure : cannot clone the object" ); + } + } + + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals( Object obj ) + { + if ( obj instanceof String ) + { + return normName.equals( obj ); + } + else if ( obj instanceof LdapDN ) + { + LdapDN name = ( LdapDN ) obj; + + if ( name.size() != this.size() ) + { + return false; + } + + for ( int i = 0; i < size(); i++ ) + { + if ( ( ( Rdn ) name.rdns.get( i ) ).compareTo( rdns.get( i ) ) != 0 ) + { + return false; + } + } + + // All components matched so we return true + return true; + } + else + { + return false; + } + } + + + /** + * Compares this name with another name for order. Returns a negative + * integer, zero, or a positive integer as this name is less than, equal to, + * or greater than the given name. + *

+ * As with Object.equals(), the notion of ordering for names + * depends on the class that implements this interface. For example, the + * ordering may be based on lexicographical ordering of the name components. + * Specific attributes of the name, such as how it treats case, may affect + * the ordering. In general, two names of different classes may not be + * compared. + * + * @param obj + * the non-null object to compare against. + * @return a negative integer, zero, or a positive integer as this name is + * less than, equal to, or greater than the given name + * @throws ClassCastException + * if obj is not a Name of a type that may be + * compared with this name + * @see Comparable#compareTo(Object) + */ + public int compareTo( Object obj ) + { + if ( obj instanceof LdapDN ) + { + LdapDN ldapDN = ( LdapDN ) obj; + + if ( ldapDN.size() != size() ) + { + return size() - ldapDN.size(); + } + + for ( int i = rdns.size(); i > 0; i-- ) + { + Rdn rdn1 = ( Rdn ) rdns.get( i - 1 ); + Rdn rdn2 = ( Rdn ) ldapDN.rdns.get( i - 1 ); + int res = rdn1.compareTo( rdn2 ); + + if ( res != 0 ) + { + return res; + } + } + + return EQUALS; + } + else + { + return 1; + } + } + + + private static AttributeTypeAndValue atavOidToName( AttributeTypeAndValue atav, Map oidsMap ) + throws InvalidNameException, NamingException + { + String type = StringTools.trim( atav.getType() ); + + if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) ) + { + type = type.substring( 4 ); + } + + if ( StringTools.isNotEmpty( StringTools.lowerCase( type ) ) ) + { + if ( oidsMap == null ) + { + return atav; + } + else + { + OidNormalizer oidNormalizer = ( OidNormalizer ) oidsMap.get( type ); + + if ( oidNormalizer != null ) + { + return new AttributeTypeAndValue( oidNormalizer.getAttributeTypeOid(), ( String ) oidNormalizer.getNormalizer() + .normalize( atav.getValue() ) ); + + } + else + { + // We don't have a normalizer for this OID : just do nothing. + return atav; + } + } + } + else + { + // The type is empty : this is not possible... + log.error( "Empty type not allowed in a DN" ); + throw new InvalidNameException( "Empty type not allowed in a DN" ); + } + + } + + + /** + * Transform a RDN by changing the value to its OID counterpart and + * normalizing the value accordingly to its type. + * + * @param rdn + * The RDN to modify + * @param oids + * The map of all existing oids and normalizer + * @throws InvalidNameException + * If + * @throws NamingException + */ + private static void rdnOidToName( Rdn rdn, Map oidsMap ) throws InvalidNameException, NamingException + { + if ( rdn.getNbAtavs() > 1 ) + { + // We have more than one ATAV for this RDN. We will loop on all + // ATAVs + Rdn rdnCopy = ( Rdn ) rdn.clone(); + rdn.clear(); + + Iterator atavs = rdnCopy.iterator(); + + while ( atavs.hasNext() ) + { + Object val = atavs.next(); + AttributeTypeAndValue newAtav = atavOidToName( ( AttributeTypeAndValue ) val, oidsMap ); + rdn.addAttributeTypeAndValue( newAtav.getType(), newAtav.getValue() ); + } + + } + else + { + String type = StringTools.trim( rdn.getType() ); + + if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) ) + { + type = type.substring( 4 ); + } + + if ( StringTools.isNotEmpty( StringTools.lowerCase( type ) ) ) + { + if ( oidsMap == null ) + { + return; + } + else + { + OidNormalizer oidNormalizer = ( OidNormalizer ) oidsMap.get( type ); + + if ( oidNormalizer != null ) + { + Rdn rdnCopy = ( Rdn ) rdn.clone(); + rdn.clear(); + + rdn.addAttributeTypeAndValue( oidNormalizer.getAttributeTypeOid(), ( String ) oidNormalizer.getNormalizer() + .normalize( rdnCopy.getValue() ) ); + + } + else + { + // We don't have a normalizer for this OID : just do + // nothing. + return; + } + } + } + else + { + // The type is empty : this is not possible... + log.error( "We should not have an empty DN" ); + throw new InvalidNameException( "Empty type not allowed in a DN" ); + } + } + } + + + /** + * Change the internal DN, using the first alias instead of oids or other + * aliases. As we still have the UP name of each RDN, we will be able to + * provide both representation of the DN. example : dn: 2.5.4.3=People, + * dc=example, domainComponent=com will be transformed to : cn=People, + * dc=example, dc=com because 2.5.4.3 is the OID for cn and dc is the first + * alias of the couple of aliases (dc, domaincomponent). This is really + * important do have such a representation, as 'cn' and 'commonname' share + * the same OID. + * + * @param dn + * The DN to transform + * @param oids + * The mapping between names and oids. + * @return A normalized form of the DN + * @throws InvalidNameException + * If the DN is invalid + */ + public static LdapDN normalize( LdapDN dn, Map oidsMap ) throws InvalidNameException, NamingException + { + if ( ( dn == null ) || ( dn.size() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) ) + { + return dn; + } + + LdapDN newDn = ( LdapDN ) dn.clone(); + + Enumeration rdns = newDn.getAllRdn(); + + // Loop on all RDNs + while ( rdns.hasMoreElements() ) + { + Rdn rdn = ( Rdn ) rdns.nextElement(); + String upName = rdn.getUpName(); + rdnOidToName( rdn, oidsMap ); + rdn.normalizeString(); + rdn.setUpName( upName ); + } + + newDn.normalizeInternal(); + + return newDn; + } + + /** + * Change the internal DN, using the first alias instead of oids or other + * aliases. As we still have the UP name of each RDN, we will be able to + * provide both representation of the DN. example : dn: 2.5.4.3=People, + * dc=example, domainComponent=com will be transformed to : cn=People, + * dc=example, dc=com because 2.5.4.3 is the OID for cn and dc is the first + * alias of the couple of aliases (dc, domaincomponent). This is really + * important do have such a representation, as 'cn' and 'commonname' share + * the same OID. + * + * @param dn + * The DN to transform + * @param oids + * The mapping between names and oids. + * @return A normalized form of the DN + * @throws InvalidNameException + * If the DN is invalid + */ + public void normalize( Map oidsMap ) throws InvalidNameException, NamingException + { + if ( ( oidsMap == null ) || ( oidsMap.size() == 0 ) ) + { + return; + } + + Enumeration rdns = getAllRdn(); + + // Loop on all RDNs + while ( rdns.hasMoreElements() ) + { + Rdn rdn = ( Rdn ) rdns.nextElement(); + String upName = rdn.getUpName(); + rdnOidToName( rdn, oidsMap ); + rdn.normalizeString(); + rdn.setUpName( upName ); + } + + normalizeInternal(); + } +} Added: directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java URL: http://svn.apache.org/viewvc/directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java?rev=420301&view=auto ============================================================================== --- directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java (added) +++ directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/LdapDnParser.java Sun Jul 9 08:26:47 2006 @@ -0,0 +1,163 @@ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.directory.shared.ldap.name; + + +import java.util.List; + +import javax.naming.InvalidNameException; +import javax.naming.Name; + +import org.apache.directory.shared.ldap.util.DNUtils; +import org.apache.directory.shared.ldap.util.Position; +import org.apache.directory.shared.ldap.util.StringTools; + +import javax.naming.NameParser; + + +/** + * This class parses a DN. The DN MUST respect this BNF grammar (as of RFC2253, + * par. 3, and RFC1779, fig. 1)
+ *

- <distinguishedName> ::= <name> | e
- <name> ::= + * <name-component> <name-components>
- <name-components> + * ::= <spaces> <separator> <spaces> <name-component> + * <name-components> | e
- <name-component> ::= + * <attributeType> <spaces> '=' <spaces> + * <attributeValue> <attributeTypeAndValues>
- + * <attributeTypeAndValues> ::= <spaces> '+' <spaces> + * <attributeType> <spaces> '=' <spaces> + * <attributeValue> <attributeTypeAndValues> | e
- + * <attributeType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9] + * <digits> <oids> | [0-9] <digits> <oids>
- + * <keychars> ::= [a-zA-Z] <keychars> | [0-9] <keychars> | '-' + * <keychars> | e
- <oidPrefix> ::= 'OID.' | 'oid.' | e
- + * <oids> ::= '.' [0-9] <digits> <oids> | e
- + * <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
- <pairchar> ::= ',' | '=' | '+' | + * '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F]
- + * <hexstring> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs>
- + * <hexpairs> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> | e
- + * <digits> ::= [0-9] <digits> | e
- <stringchar> ::= + * [0x00-0xFF] - [,=+<>#;\"\n\r]
- <quotechar> ::= [0x00-0xFF] - + * [\"]
- <separator> ::= ',' | ';'
- <spaces> ::= ' ' + * <spaces> | e
+ *

+ * + * @author Apache Directory Project + */ +public class LdapDnParser implements NameParser +{ + private static LdapDnParser instance = new LdapDnParser(); + + + /** + * A private constructor. It's useless, as this object is totally stateless, + * but we need to expose a NameParser. + */ + private LdapDnParser() + { + } + + + /** + * Get a reference to the NameParser. Needed to be compliant with the JNDI + * API + * + * @return An instance of the NameParser + */ + public static NameParser getNameParser() + { + return instance; + } + + + /** + * Parse a DN + * + * @param dn + * The DN to be parsed + * @param rdns + * The list that will contain the RDNs + * @throws InvalidNameException + * If the DN is invalid + */ + public static void parseInternal( String dn, List rdns ) throws InvalidNameException + { + if ( dn.length() == 0 ) + { + // We have an empty DN, just get out of the function. + return; + } + + Position pos = new Position(); + pos.start = 0; + Rdn rdn = new Rdn(); + + // ::= + // ::= + // | e + if ( RdnParser.parse( dn, pos, rdn ) != DNUtils.PARSING_ERROR ) + { + // Now, parse the following nameComponents + do + { + rdns.add( rdn ); + rdn = new Rdn(); + + if ( ( StringTools.isCharASCII( dn, pos.start, ',' ) == false ) + && ( StringTools.isCharASCII( dn, pos.start, ';' ) == false ) ) + { + + if ( pos.start != dn.length() ) + { + throw new InvalidNameException( "Bad DN : " + dn ); + } + else + { + break; + } + } + + pos.start++; + } + while ( RdnParser.parse( dn, pos, rdn ) != DNUtils.PARSING_ERROR ); + } + else + { + throw new InvalidNameException( "Bad DN : " + dn ); + } + } + + + /** + * Parse a String and return a LdapDN if the String is a valid DN + * + * @param dn + * The DN to parse + * @return A LdapDN + * @throws InvalidNameException + * If the String is not a valid DN + */ + public Name parse( String dn ) throws InvalidNameException + { + return new LdapDN( dn ); + } +} Added: directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java URL: http://svn.apache.org/viewvc/directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java?rev=420301&view=auto ============================================================================== --- directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java (added) +++ directory/branches/shared/optimization/ldap/src/main/java/org/apache/directory/shared/ldap/name/Rdn.java Sun Jul 9 08:26:47 2006 @@ -0,0 +1,1216 @@ +/* + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.directory.shared.ldap.name; + + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import javax.naming.InvalidNameException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; + +import org.apache.commons.collections.MultiHashMap; +import org.apache.directory.shared.ldap.util.StringTools; + + +/** + * This class store the name-component part or the following BNF grammar (as of + * RFC2253, par. 3, and RFC1779, fig. 1) :
- <name-component> ::= + * <attributeType> <spaces> '=' <spaces> + * <attributeValue> <attributeTypeAndValues>
- + * <attributeTypeAndValues> ::= <spaces> '+' <spaces> + * <attributeType> <spaces> '=' <spaces> + * <attributeValue> <attributeTypeAndValues> | e
- + * <attributeType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9] + * <digits> <oids> | [0-9] <digits> <oids>
- + * <keychars> ::= [a-zA-Z] <keychars> | [0-9] <keychars> | '-' + * <keychars> | e
- <oidPrefix> ::= 'OID.' | 'oid.' | e
- + * <oids> ::= '.' [0-9] <digits> <oids> | e
- + * <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
- <pairchar> ::= ',' | '=' | '+' | + * '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F]
- + * <hexstring> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs>
- + * <hexpairs> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> | e
- + * <digits> ::= [0-9] <digits> | e
- <stringchar> ::= + * [0x00-0xFF] - [,=+<>#;\"\n\r]
- <quotechar> ::= [0x00-0xFF] - + * [\"]
- <separator> ::= ',' | ';'
- <spaces> ::= ' ' + * <spaces> | e
+ *
+ * A RDN is a part of a DN. It can be composed of many types, as in the RDN + * following RDN :
+ * ou=value + cn=other value
+ *
+ * or
+ * ou=value + ou=another value
+ *
+ * In this case, we have to store an 'ou' and a 'cn' in the RDN.
+ *
+ * The types are case insensitive.
+ * Spaces before and after types and values are not stored.
+ * Spaces before and after '+' are not stored.
+ *
+ * Thus, we can consider that the following RDNs are equals :
+ *
+ * 'ou=test 1'
' ou=test 1'
+ * 'ou =test 1'
+ * 'ou= test 1'
+ * 'ou=test 1 '
' ou = test 1 '
+ *
+ * So are the following :
+ *
+ * 'ou=test 1+cn=test 2'
+ * 'ou = test 1 + cn = test 2'
' ou =test 1+ cn =test 2 '
+ * 'cn = test 2 +ou = test 1'
+ *
+ * but the following are not equal :
+ * 'ou=test 1'
+ * 'ou=test 1'
+ * because we have more than one spaces inside the value.
+ *
+ * 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 BasicAttributes(); + 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; + } +}