You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2005/10/09 15:45:57 UTC
svn commit: r307427 -
/directory/shared/ldap/trunk/apache2-provider/src/java/main/org/apache/asn1new/ldap/codec/primitives/LdapRDN.java
Author: elecharny
Date: Sun Oct 9 06:45:52 2005
New Revision: 307427
URL: http://svn.apache.org/viewcvs?rev=307427&view=rev
Log:
Added this class to handle RDNs which are parts of a DN.
It is also possible to use this class directly (for instance, to store LdapRelativeDN)
Added:
directory/shared/ldap/trunk/apache2-provider/src/java/main/org/apache/asn1new/ldap/codec/primitives/LdapRDN.java
Added: directory/shared/ldap/trunk/apache2-provider/src/java/main/org/apache/asn1new/ldap/codec/primitives/LdapRDN.java
URL: http://svn.apache.org/viewcvs/directory/shared/ldap/trunk/apache2-provider/src/java/main/org/apache/asn1new/ldap/codec/primitives/LdapRDN.java?rev=307427&view=auto
==============================================================================
--- directory/shared/ldap/trunk/apache2-provider/src/java/main/org/apache/asn1new/ldap/codec/primitives/LdapRDN.java (added)
+++ directory/shared/ldap/trunk/apache2-provider/src/java/main/org/apache/asn1new/ldap/codec/primitives/LdapRDN.java Sun Oct 9 06:45:52 2005
@@ -0,0 +1,613 @@
+/*
+ * 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.asn1new.ldap.codec.primitives;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+
+import org.apache.asn1new.util.StringUtils;
+import org.apache.commons.collections.MultiHashMap;
+
+/**
+ * This class store the name-component part or the following BNF grammar (as of RFC2253, par. 3,
+ * and RFC1779, fig. 1) : <br>
+ * - <name-component> ::= <attributeType> <spaces> '=' <spaces> <attributeValue> <attributeTypeAndValues> <br>
+ * - <attributeTypeAndValues> ::= <spaces> '+' <spaces> <attributeType> <spaces> '=' <spaces> <attributeValue> <attributeTypeAndValues> | e <br>
+ * - <attributeType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9] <digits> <oids> | [0-9] <digits> <oids> <br>
+ * - <keychars> ::= [a-zA-Z] <keychars> | [0-9] <keychars> | '-' <keychars> | e <br>
+ * - <oidPrefix> ::= 'OID.' | 'oid.' | e <br>
+ * - <oids> ::= '.' [0-9] <digits> <oids> | e <br>
+ * - <attributeValue> ::= <pairs-or-strings> | '#' <hexstring> |'"' <quotechar-or-pairs> '"' <br>
+ * - <pairs-or-strings> ::= '\' <pairchar> <pairs-or-strings> | <stringchar> <pairs-or-strings> | e <br>
+ * - <quotechar-or-pairs> ::= <quotechar> <quotechar-or-pairs> | '\' <pairchar> <quotechar-or-pairs> | e <br>
+ * - <pairchar> ::= ',' | '=' | '+' | '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F] <br>
+ * - <hexstring> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> <br>
+ * - <hexpairs> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> | e <br>
+ * - <digits> ::= [0-9] <digits> | e <br>
+ * - <stringchar> ::= [0x00-0xFF] - [,=+<>#;\"\n\r] <br>
+ * - <quotechar> ::= [0x00-0xFF] - [\"] <br>
+ * - <separator> ::= ',' | ';' <br>
+ * - <spaces> ::= ' ' <spaces> | e <br>
+ *<br>
+ * A RDN is a part of a DN. It can be composed of many types, as in the RDN
+ * following RDN :<br>
+ * ou=value + cn=other value<br>
+ * <br>
+ * In this case, we have to store an 'ou' and a 'cn' in the RDN.<br>
+ * <br>
+ * The types are case insensitive. <br>
+ * Spaces before and after types and values are not stored.<br>
+ * Spaces before and after '+' are not stored.<br>
+ * <br>
+ * Thus, we can consider that the following RDNs are equals :<br>
+ * <br>
+ * 'ou=test 1'<br>
+ * ' ou=test 1'<br>
+ * 'ou =test 1'<br>
+ * 'ou= test 1'<br>
+ * 'ou=test 1 '<br>
+ * ' ou = test 1 '<br>
+ * <br>
+ * So are the following :<br>
+ * <br>
+ * 'ou=test 1+cn=test 2'<br>
+ * 'ou = test 1 + cn = test 2'<br>
+ * ' ou =test 1+ cn =test 2 ' <br>
+ * 'cn = test 2 +ou = test 1'<br>
+ * <br>
+ * but the following are not equal :<br>
+ * 'ou=test 1' <br>
+ * 'ou=test 1'<br>
+ * because we have more than one spaces inside the value.<br>
+ * <br>
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ *
+ */
+public class LdapRDN extends LdapString implements Cloneable, Comparable
+{
+ /**
+ * Stores all couple type = value. We may have more than one type,
+ * if the '+' character appears in the AttributeTypeAndValue. The key is
+ * the type, the value is a AttributeTypeAndValue.
+ */
+ private MultiHashMap atavs;
+
+ /**
+ * A simple AttributeTypeAndValue is used to store the LdapRDN for the simple
+ * case where we only have a single type=value. This will be 99.99%
+ * the case. This avoids the creation of a HashMap.
+ */
+ private AttributeTypeAndValue atav;
+
+ private transient int nbAtavs;
+
+ /** 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;
+
+ /**
+ * A empty constructor.
+ */
+ public LdapRDN()
+ {
+ super();
+
+ // Don't waste space... This is not so often we have multiple
+ // name-components in a RDN... So we won't initialize the Map.
+ atavs = null;
+ atav = null;
+ nbAtavs = 0;
+ }
+
+ /**
+ * A constructor that parse a String RDN
+ *
+ * @param rdn The String containing the RDN to parse
+ * @throws InvalidNameException If the RDN is invalid
+ */
+ public LdapRDN( String rdn ) throws InvalidNameException
+ {
+ super();
+
+ if ( StringUtils.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 and normalize the RDN
+ RDNParser.parse( rdn, this );
+ normalize();
+ }
+ }
+
+ /**
+ * 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 LdapRDN( byte[] bytes ) throws InvalidNameException
+ {
+ super();
+
+ try
+ {
+ RDNParser.parse( new String( bytes, "UTF-8" ), this );
+ normalize();
+ }
+ 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
+ * @param type The type of the RDN
+ * @param value The value of the RDN
+ * @throws InvalidNameException If the RDN is invalid
+ */
+ public LdapRDN( String type, String value ) throws InvalidNameException
+ {
+ super();
+
+ // Don't waste space... This is not so often we have multiple
+ // name-components in a RDN... So we won't initialize the Map.
+ atavs = null;
+ atav = new AttributeTypeAndValue( type, value );
+ nbAtavs = 1;
+ normalize();
+ }
+
+ /**
+ * Transform the external representation of the current RDN
+ * to an internal normalized form where :
+ * - types are trimmed and lowercased
+ * - values are trimmed and lowercased
+ */
+ private void normalize()
+ {
+ 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 = StringUtils.lowerCase( StringUtils.trim( atav.getType() ) ) +
+ '=' + StringUtils.trim( atav.getValue() );
+ break;
+
+ default :
+ // We have more than one AttributeTypeAndValue
+ StringBuffer sb = new StringBuffer();
+
+ Iterator elems = atavs.values().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;
+ }
+
+ try
+ {
+ bytes = string.getBytes( "UTF-8" );
+ }
+ catch ( UnsupportedEncodingException uee )
+ {
+ // We can't reach this point.
+ }
+ }
+
+ /**
+ * 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
+ */
+ public void addAttributeTypeAndValue( String type, String value) throws InvalidNameException
+ {
+ // First, let's normalize the type
+ String normalizedType = StringUtils.lowerCase( StringUtils.trim( type ) );
+ String normalizedValue = StringUtils.trim( value );
+
+ switch ( nbAtavs )
+ {
+ case 0 :
+ // This is the first AttributeTypeAndValue. Just stores it.
+ atav = new AttributeTypeAndValue( normalizedType, normalizedValue );
+ nbAtavs = 1;
+ break;
+
+ case 1 :
+ // We already have an atav. We have to put it in the HashMap
+ // before adding a new one.
+ // First, create the HashMap,
+ atavs = new MultiHashMap();
+
+ // and store the existing AttributeTypeAndValue into it.
+ atavs.put( atav.getType(), atav );
+ atav = null;
+
+ // add a new AttributeTypeAndValue
+ atavs.put( normalizedType, new AttributeTypeAndValue( normalizedType, normalizedValue ) );
+
+ nbAtavs = 2;
+ break;
+
+ default :
+ // add a new AttributeTypeAndValue
+ atavs.put( normalizedType, new AttributeTypeAndValue( normalizedType, normalizedValue ) );
+
+ nbAtavs++;
+ break;
+
+ }
+
+ if ( StringUtils.isEmpty( string ) )
+ {
+ string = normalizedType + '=' + normalizedValue;
+ }
+ else
+ {
+ string = string + '+' + normalizedType + '=' + normalizedValue;
+ }
+ }
+
+ /**
+ * Remove a Name from a RDN
+ *
+ * @param type The nome to remove
+ * @throws InvalidNameException If the name does not exists or if the RDN is empty
+ */
+ public void removeAttributeTypeAndValue( String type ) throws InvalidNameException
+ {
+ if ( StringUtils.isEmpty( type ) )
+ {
+ return;
+ }
+
+ // First, let's normalize the type
+ String normalizedType = StringUtils.lowerCase( StringUtils.trim( type ) );
+
+ switch ( nbAtavs )
+ {
+ case 0 :
+ throw new InvalidNameException( "Cannot remove a AttributeTypeAndValue form an empty RDN" );
+
+ case 1 :
+ if ( normalizedType.equals( atav.getType() ) )
+ {
+ atav = null;
+ string = "";
+ nbAtavs--;
+ }
+ else
+ {
+ throw new InvalidNameException( "Name '" + normalizedType + "' is not valid for the RDN '" + this.toString() + "'");
+ }
+
+ break;
+
+ default :
+ if ( atavs.containsKey( normalizedType ) )
+ {
+ atavs.remove( normalizedType );
+ normalize();
+ nbAtavs --;
+ }
+ else
+ {
+ throw new InvalidNameException( "Name '" + normalizedType + "' is not valid for the RDN '" + this.toString() + "'");
+ }
+
+ break;
+
+ }
+ }
+
+ /**
+ * Clear the RDN, removing all the AttributeTypeAndValues.
+ */
+ public void clear()
+ {
+ atav = null;
+ atavs = null;
+ nbAtavs = 0;
+ string = "";
+ bytes = EMPTY_BYTES;
+ }
+
+ /**
+ * 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 = StringUtils.lowerCase( StringUtils.trim( type ) );
+
+ switch ( nbAtavs )
+ {
+ case 0:
+ return "";
+
+ case 1:
+ if ( StringUtils.equals( atav.getType(), normalizedType ) )
+ {
+ return atav.getValue();
+ }
+ else
+ {
+ return "";
+ }
+
+ default :
+ if ( atavs.containsKey( normalizedType ) )
+ {
+ List avas = (ArrayList)atavs.get( normalizedType );
+ StringBuffer sb = new StringBuffer();
+ boolean isFirst = true;
+
+ for ( int i = 0; i < avas.size(); i++ )
+ {
+ if ( isFirst )
+ {
+ isFirst = false;
+ }
+ else
+ {
+ sb.append( ',' );
+ }
+
+ sb.append( ( (AttributeTypeAndValue)avas.get( i ) ).getValue() );
+ }
+
+ return sb.toString();
+ }
+ 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 = StringUtils.lowerCase( StringUtils.trim( type ) );
+
+ switch ( nbAtavs )
+ {
+ case 0:
+ return null;
+
+ case 1:
+ if ( atav.getType().equals( normalizedType ) )
+ {
+ return atav;
+ }
+ else
+ {
+ return null;
+ }
+
+ default :
+ if ( atavs.containsKey( normalizedType ) )
+ {
+ List values = (ArrayList)atavs.get( normalizedType );
+
+ return (AttributeTypeAndValue)values.get( 0 );
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Clone the LdapRDN
+ */
+ public Object clone()
+ {
+ try
+ {
+ LdapRDN rdn = (LdapRDN)super.clone();
+
+ // The AttributeTypeAndValue is immutable. We won't clone it
+
+ if ( atavs != null )
+ {
+ rdn.atavs = new MultiHashMap( nbAtavs );
+
+ Iterator values = atavs.values().iterator();
+
+ while ( values.hasNext() )
+ {
+ AttributeTypeAndValue ava = (AttributeTypeAndValue)values.next();
+
+ rdn.atavs.put( ava.getType(), ava );
+ }
+ }
+
+ return rdn;
+ }
+ catch ( CloneNotSupportedException cnse )
+ {
+ throw new Error( "Assertion failure" );
+ }
+ }
+
+ /**
+ * Compares two RDN. They are equals if :
+ * - their have the same number of NC (AttributeTypeAndValue)
+ * - for each NC in object, their is one NC which is equal
+ * - comparizon are done case insensitive
+ * @param object
+ * @return
+ */
+ public int compareTo( Object object )
+ {
+ if ( object instanceof LdapRDN )
+ {
+ LdapRDN rdn = (LdapRDN)object;
+
+ if ( ( rdn == null ) || ( rdn.nbAtavs != nbAtavs ) )
+ {
+ return NOT_EQUALS;
+ }
+
+ 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 = ((MultiHashMap)atavs).keySet().iterator();
+
+ while ( keys.hasNext() )
+ {
+ String type = (String)keys.next();
+
+ if ( ((MultiHashMap)rdn.atavs).containsKey( type ) )
+ {
+ List atavList = (List)((MultiHashMap)atavs).get( type );
+ List atavList2 = (List)((MultiHashMap)rdn.atavs).get( type );
+
+ // Ok, let's go for ugliness :
+ // We are not supposed to have a lot of multi-valued RDN
+ // with a type that contains multiple values. In fact,
+ // I don't think that we will ever have this kind of RDN :
+ // "ou=test+ou=test2".
+ // And if we do, I won't put a cent on an application that
+ // have this kind of RDNs, especially if the number of AVA
+ // is higher than 2...
+ Iterator atavIter1 = atavList.iterator();
+ Set atavSet = new HashSet();
+
+ while ( atavIter1.hasNext() )
+ {
+ AttributeTypeAndValue atavValue = (AttributeTypeAndValue)atavIter1.next();
+ atavSet.add( StringUtils.lowerCase( atavValue.getValue() ) );
+ }
+
+ Iterator atavIter2 = atavList2.iterator();
+
+ while ( atavIter2.hasNext() )
+ {
+ AttributeTypeAndValue atavValue = (AttributeTypeAndValue)atavIter2.next();
+
+ if ( atavSet.contains( StringUtils.lowerCase( atavValue.getValue() ) ) )
+ {
+ atavSet.remove( StringUtils.lowerCase( atavValue.getValue() ) );
+ }
+ else
+ {
+ return NOT_EQUALS;
+ }
+ }
+
+ // Last, not least, the Set must be empty for
+ // both RDN to be equals
+ if ( atavSet.size() != 0 )
+ {
+ return NOT_EQUALS;
+ }
+ }
+ }
+
+ return EQUALS;
+ }
+ }
+ else
+ {
+ return NOT_EQUALS;
+ }
+ }
+
+ /**
+ * Returns a String representation of the RDN
+ */
+ public String toString()
+ {
+ return string;
+ }
+
+ /**
+ * @return Returns the nbAtavs.
+ */
+ public int getNbAtavs()
+ {
+ return nbAtavs;
+ }
+}