• their have the same number of NC (AttributeTypeAndValue) - *
• each ATAVs are equals - *
• comparison of type are done case insensitive - *
• each value is equal, case sensitive - *
• their have the same number of NC (AttributeTypeAndValue) + *
• each ATAVs are equals + *
• comparison of type are done case insensitive + *
• each value is equal, case sensitive + *
• Order of ATAV is not important If the RDNs are not equals, a positive number is + * returned if the first RDN is greater, negative otherwise + * + * @param object + * @return 0 if both rdn are equals. -1 if the current RDN is inferior, 1 if + * the current Rdn is superior, UNDEFINED otherwise. + */ + public int compareTo( Object object ) + { + if ( object == null ) + { + return SUPERIOR; + } + + if ( object instanceof Rdn ) + { + Rdn rdn = ( Rdn ) object; + + 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 EQUAL; + + case 1: + return atav.compareTo( rdn.atav ); + + default: + // We have more than one value. We will + // go through all of them. + + // the types are already normalized and sorted in the atavs TreeSet + // so we could compare the 1st with the 1st, then the 2nd with the 2nd, etc. + Iterator localIterator = atavs.iterator(); + Iterator paramIterator = rdn.atavs.iterator(); + + while ( localIterator.hasNext() || paramIterator.hasNext() ) + { + if ( !localIterator.hasNext() ) + { + return SUPERIOR; + } + if ( !paramIterator.hasNext() ) + { + return INFERIOR; + } + + AttributeTypeAndValue localAtav = localIterator.next(); + AttributeTypeAndValue paramAtav = paramIterator.next(); + int result = localAtav.compareTo( paramAtav ); + if ( result != EQUAL ) + { + return result; + } + } + + return EQUAL; + } + } + else + { + return UNDEFINED; + } + } + + + /** + * @return a String representation of the RDN + */ + public String toString() + { + return normName == null ? "" : normName; + } + + + /** + * @return the user provided name + */ + public String getUpName() + { + return upName; + } + + + /** + * @return The normalized name + */ + public String getNormName() + { + return normName == null ? "" : normName; + } + + + /** + * Set the User Provided Name + * @param upName the User Provided dame + */ + 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 ( ( TreeSet ) atavs ).first(); + } + } + + + /** + * Return the user provided type, or the first one of we have more than one (the lowest) + * + * @return The first user provided type of this RDN + */ + public String getUpType() + { + switch ( nbAtavs ) + { + case 0: + return null; + + case 1: + return atav.getUpType(); + + default: + return ( ( TreeSet ) atavs ).first().getUpType(); + } + } + + + /** + * Return the normalized type, or the first one of we have more than one (the lowest) + * + * @return The first normalized type of this RDN + */ + public String getNormType() + { + switch ( nbAtavs ) + { + case 0: + return null; + + case 1: + return atav.getNormType(); + + default: + return ( ( TreeSet ) atavs ).first().getNormType(); + } + } + + + /** + * 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.getNormValue().get(); + + default: + return ( ( TreeSet ) atavs ).first().getNormValue().get(); + } + } + + + /** + * Return the User Provided value + * + * @return The first User provided value of this RDN + */ + public String getUpValue() + { + switch ( nbAtavs ) + { + case 0: + return null; + + case 1: + return atav.getUpValue().getString(); + + default: + return ( ( TreeSet ) atavs ).first().getUpValue().getString(); + } + } - /** - * 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 ((TreeSet)atavs).first(); - } - } - - - /** - * Return the user provided type, or the first one of we have more than one (the lowest) - * - * @return The first user provided type of this RDN - */ - public String getUpType() - { - switch ( nbAtavs ) - { - case 0: - return null; - - case 1: - return atav.getUpType(); - - default: - return ((TreeSet)atavs).first().getUpType(); - } - } - - /** - * Return the normalized type, or the first one of we have more than one (the lowest) - * - * @return The first normalized type of this RDN - */ - public String getNormType() - { - switch ( nbAtavs ) - { - case 0: - return null; - - case 1: - return atav.getNormType(); - - default: - return ((TreeSet)atavs).first().getNormType(); - } - } - - /** - * 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.getNormValue().get(); - - default: - return ((TreeSet)atavs).first().getNormValue().get(); - } - } - - - /** - * Return the User Provided value - * - * @return The first User provided value of this RDN - */ - public String getUpValue() - { - switch ( nbAtavs ) - { - case 0: - return null; - - case 1: - return atav.getUpValue().getString(); - - default: - return ((TreeSet)atavs).first().getUpValue().getString(); - } - } - /** * Return the normalized value, or the first one of we have more than one (the lowest) * @@ -947,509 +952,511 @@ { case 0: return null; - + case 1: return atav.getNormValue().getString(); - + default: - return ((TreeSet)atavs).first().getNormalizedValue(); + return ( ( TreeSet ) atavs ).first().getNormalizedValue(); + } + } + + + /** + * 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 ) == EQUAL; + } + + + /** + * Get the number of Attribute type and value of this Rdn + * + * @return The number of ATAVs in this Rdn + */ + public int size() + { + return nbAtavs; + } + + + /** + * 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 ) + { + 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++] = StringTools.getHexValue( chars[i], 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.getHexValue( chars[i] ) << 4 ) ); + } + + break; + } + } + else + { + if ( isHex ) + { + if ( StringTools.isHex( chars, i ) ) + { + pair += StringTools.getHexValue( 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 '#': + if ( i != 0 ) + { + // '#' are allowed if not in first position + bytes[pos++] = '#'; + break; + } + case '=': + throw new IllegalArgumentException( "Unescaped special characters are not allowed" ); + + case ' ': + if ( ( i == 0 ) || ( i == chars.length - 1 ) ) + { + throw new IllegalArgumentException( "Unescaped special characters are not allowed" ); + } + else + { + bytes[pos++] = ' '; + break; + } + + default: + if ( ( chars[i] >= 0 ) && ( chars[i] < 128 ) ) + { + bytes[pos++] = ( byte ) chars[i]; + } + else + { + byte[] result = StringTools.charToBytes( chars[i] ); + System.arraycopy( result, 0, bytes, pos, result.length ); + pos += result.length; + } + + break; + } + } + } + } + + return StringTools.utf8ToString( bytes, pos ); + } + } + + + /** + * Transform a value in a String, accordingly to RFC 2253 + * + * @param value The attribute value to be escaped + * @return The escaped string value. + */ + public static String escapeValue( String value ) + { + if ( StringTools.isEmpty( value ) ) + { + return ""; + } + + 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 ' ': + if ( ( i > 0 ) && ( i < chars.length - 1 ) ) + { + newChars[pos++] = chars[i]; + } + else + { + newChars[pos++] = '\\'; + newChars[pos++] = chars[i]; + } + + break; + + case '#': + if ( i != 0 ) + { + newChars[pos++] = chars[i]; + } + else + { + newChars[pos++] = '\\'; + newChars[pos++] = chars[i]; + } + + break; + + 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]; + break; + + } + } + + return new String( newChars, 0, 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( byte[] attrValue ) + { + if ( StringTools.isEmpty( attrValue ) ) + { + return ""; + } + + String value = StringTools.utf8ToString( attrValue ); + + return escapeValue( value ); + } + + + /** + * Gets the hashcode of this rdn. + * + * @see java.lang.Object#hashCode() + * @return the instance's hash code + */ + public int hashCode() + { + int result = 37; + + switch ( nbAtavs ) + { + case 0: + // An empty RDN + break; + + case 1: + // We have a single AttributeTypeAndValue + result = result * 17 + atav.hashCode(); + break; + + default: + // We have more than one AttributeTypeAndValue + + for ( AttributeTypeAndValue ata : atavs ) + { + result = result * 17 + ata.hashCode(); + } + + break; + } + + return result; + } + + + /** + * @see Externalizable#readExternal(ObjectInput)

+ * + * A RDN is composed of on to many ATAVs (AttributeType And Value). + * We should write all those ATAVs sequencially, following the + * structure : + * + *

• nbAtavs
• The number of ATAVs to write. Can't be 0. + *
• upName
• The User provided RDN + *
• normName
• The normalized RDN. It can be empty if the normalized + * name equals the upName. + *
• atavs
• + *

+ * For each ATAV :

+ *

• start
• The position of this ATAV in the upName string + *
• length
• The ATAV user provided length + *
• Call the ATAV write method
• The ATAV itself + * + * @param out The stream into which the serialized RDN will be put + * @throws IOException If the stream can't be written + */ + public void writeExternal( ObjectOutput out ) throws IOException + { + out.writeInt( nbAtavs ); + out.writeUTF( upName ); + + if ( upName.equals( normName ) ) + { + out.writeUTF( "" ); + } + else + { + out.writeUTF( normName ); + } + + out.writeInt( start ); + out.writeInt( length ); + + switch ( nbAtavs ) + { + case 0: + break; + + case 1: + out.writeObject( atav ); + break; + + default: + for ( AttributeTypeAndValue value : atavs ) + { + out.writeObject( value ); + } + + break; + } + } + + + /** + * @see Externalizable#readExternal(ObjectInput) + * + * We read back the data to create a new RDB. The structure + * read is exposed in the {@link Rdn#writeExternal(ObjectOutput)} + * method

- * - * A RDN is composed of on to many ATAVs (AttributeType And Value). - * We should write all those ATAVs sequencially, following the - * structure : - * - *

• nbAtavs
• The number of ATAVs to write. Can't be 0. - *
• upName
• The User provided RDN - *
• normName
• The normalized RDN. It can be empty if the normalized - * name equals the upName. - *
• atavs
• - *

- * For each ATAV :

- *

• start
• The position of this ATAV in the upName string - *
• length
• The ATAV user provided length - *
• Call the ATAV write method
• The ATAV itself - * - * @param out The stream into which the serialized RDN will be put - * @throws IOException If the stream can't be written - */ - public void writeExternal( ObjectOutput out ) throws IOException - { - out.writeInt( nbAtavs ); - out.writeUTF( upName ); - - if ( upName.equals( normName ) ) - { - out.writeUTF( "" ); - } - else - { - out.writeUTF( normName ); - } - - out.writeInt( start ); - out.writeInt( length ); - - switch ( nbAtavs ) - { - case 0 : - break; - - case 1 : - out.writeObject( atav ); - break; - - default : - for ( AttributeTypeAndValue value:atavs ) - { - out.writeObject( value ); - } - - break; - } - } - - - /** - * @see Externalizable#readExternal(ObjectInput) - * - * We read back the data to create a new RDB. The structure - * read is exposed in the {@link Rdn#writeExternal(ObjectOutput)} - * method