Modified: directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/entry/client/DefaultClientAttribute.java URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/entry/client/DefaultClientAttribute.java?rev=801332&r1=801331&r2=801332&view=diff ============================================================================== --- directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/entry/client/DefaultClientAttribute.java (original) +++ directory/shared/trunk/ldap/src/main/java/org/apache/directory/shared/ldap/entry/client/DefaultClientAttribute.java Wed Aug 5 17:40:50 2009 @@ -1,1470 +1,1470 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.entry.client; - - -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.naming.NamingException; -import javax.naming.directory.InvalidAttributeValueException; - -import org.apache.directory.shared.ldap.entry.EntryAttribute; -import org.apache.directory.shared.ldap.entry.Value; -import org.apache.directory.shared.ldap.schema.SyntaxChecker; -import org.apache.directory.shared.ldap.util.StringTools; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -/** - * A client side entry attribute. The client is not aware of the schema, - * so we can't tell if the stored value will be String or Binary. We will - * default to Binary.

- * To define the kind of data stored, the client must set the isHR flag. - * - * @author Apache Directory Project - * @version $Rev$, $Date$ - */ -public class DefaultClientAttribute implements ClientAttribute -{ - /** logger for reporting errors that might not be handled properly upstream */ - private static final Logger LOG = LoggerFactory.getLogger( DefaultClientAttribute.class ); - - - /** The set of contained values */ - protected Set> values = new LinkedHashSet>(); - - /** The User provided ID */ - protected String upId; - - /** The normalized ID */ - protected String id; - - /** Tells if the attribute is Human Readable or not. When not set, - * this flag is null. */ - protected Boolean isHR; - - - // maybe have some additional convenience constructors which take - // an initial value as a string or a byte[] - /** - * Create a new instance of a EntryAttribute, without ID nor value. - */ - public DefaultClientAttribute() - { - } - - - /** - * Create a new instance of a EntryAttribute, without value. - */ - public DefaultClientAttribute( String upId ) - { - setUpId( upId ); - } - - - /** - * If the value does not correspond to the same attributeType, then it's - * wrapped value is copied into a new ClientValue which uses the specified - * attributeType. - * - * Otherwise, the value is stored, but as a reference. It's not a copy. - * - * @param upId - * @param attributeType the attribute type according to the schema - * @param vals an initial set of values for this attribute - */ - public DefaultClientAttribute( String upId, Value... vals ) - { - // The value can be null, this is a valid value. - if ( vals[0] == null ) - { - add( new ClientStringValue() ); - } - else - { - for ( Value val:vals ) - { - if ( ( val instanceof ClientStringValue ) || ( val.isBinary() ) ) - { - add( val ); - } - else - { - String message = "Unknown value type: " + val.getClass().getName(); - LOG.error( message ); - throw new IllegalStateException( message ); - } - } - } - - setUpId( upId ); - } - - - /** - * Create a new instance of a EntryAttribute. - */ - public DefaultClientAttribute( String upId, String... vals ) - { - add( vals ); - setUpId( upId ); - } - - - /** - * Create a new instance of a EntryAttribute, with some byte[] values. - */ - public DefaultClientAttribute( String upId, byte[]... vals ) - { - add( vals ); - setUpId( upId ); - } - - - /** - *

- * Get the byte[] value, if and only if the value is known to be Binary, - * otherwise a InvalidAttributeValueException will be thrown - *

- *

- * Note that this method returns the first value only. - *

- * - * @return The value as a byte[] - * @throws InvalidAttributeValueException If the value is a String - */ - public byte[] getBytes() throws InvalidAttributeValueException - { - Value value = get(); - - if ( value.isBinary() ) - { - return value.getBytes(); - } - else - { - String message = "The value is expected to be a byte[]"; - LOG.error( message ); - throw new InvalidAttributeValueException( message ); - } - } - - - /** - *

- * Get the String value, if and only if the value is known to be a String, - * otherwise a InvalidAttributeValueException will be thrown - *

- *

- * Note that this method returns the first value only. - *

- * - * @return The value as a String - * @throws InvalidAttributeValueException If the value is a byte[] - */ - public String getString() throws InvalidAttributeValueException - { - Value value = get(); - - if ( value instanceof ClientStringValue ) - { - return value.getString(); - } - else - { - String message = "The value is expected to be a String"; - LOG.error( message ); - throw new InvalidAttributeValueException( message ); - } - } - - - /** - * Get's the attribute identifier. Its value is the same than the - * user provided ID. - * - * @return the attribute's identifier - */ - public String getId() - { - return id; - } - - - /** - *

- * Set the attribute to Human Readable or to Binary. - *

- * @param isHR true for a Human Readable attribute, - * false for a Binary attribute. - */ - public void setHR( boolean isHR ) - { - this.isHR = isHR; - //TODO : deal with the values, we may have to convert them. - } - - - /** - * Set the normalized ID. The ID will be lowercased, and spaces - * will be trimmed. - * - * @param id The attribute ID - * @throws IllegalArgumentException If the ID is empty or null or - * resolve to an empty value after being trimmed - */ - public void setId( String id ) - { - this.id = StringTools.trim( StringTools.lowerCaseAscii( id ) ); - - if ( this.id.length() == 0 ) - { - this.id = null; - throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" + - " value when trimmed" ); - } - } - - - /** - * Get's the user provided identifier for this entry. This is the value - * that will be used as the identifier for the attribute within the - * entry. If this is a commonName attribute for example and the user - * provides "COMMONname" instead when adding the entry then this is - * the format the user will have that entry returned by the directory - * server. To do so we store this value as it was given and track it - * in the attribute using this property. - * - * @return the user provided identifier for this attribute - */ - public String getUpId() - { - return upId; - } - - - /** - * Set the user provided ID. It will also set the ID, normalizing - * the upId (removing spaces before and after, and lowercasing it) - * - * @param upId The attribute ID - * @throws IllegalArgumentException If the ID is empty or null or - * resolve to an empty value after being trimmed - */ - public void setUpId( String upId ) - { - this.upId = StringTools.trim( upId ); - - if ( this.upId.length() == 0 ) - { - this.upId = null; - throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" + - " value when trimmed" ); - } - - this.id = StringTools.lowerCaseAscii( this.upId ); - } - - - /** - *

- * Tells if the attribute is Human Readable. - *

- *

This flag is set by the caller, or implicitly when adding String - * values into an attribute which is not yet declared as Binary. - *

- * @return - */ - public boolean isHR() - { - return isHR != null ? isHR : false; - } - - - /** - * Checks to see if this attribute is valid along with the values it contains. - * - * @return true if the attribute and it's values are valid, false otherwise - * @throws NamingException if there is a failure to check syntaxes of values - */ - public boolean isValid() throws NamingException - { - for ( Value value:values ) - { - if ( !value.isValid() ) - { - return false; - } - } - - return true; - } - - - /** - * Checks to see if this attribute is valid along with the values it contains. - * - * @return true if the attribute and it's values are valid, false otherwise - * @throws NamingException if there is a failure to check syntaxes of values - */ - public boolean isValid( SyntaxChecker checker ) throws NamingException - { - for ( Value value : values ) - { - if ( !value.isValid( checker ) ) - { - return false; - } - } - - return true; - } - - - /** - * Adds some values to this attribute. If the new values are already present in - * the attribute values, the method has no effect. - *

- * The new values are added at the end of list of values. - *

- *

- * This method returns the number of values that were added. - *

- *

- * If the value's type is different from the attribute's type, - * a conversion is done. For instance, if we try to set some - * StringValue into a Binary attribute, we just store the UTF-8 - * byte array encoding for this StringValue. - *

- *

- * If we try to store some BinaryValue in a HR attribute, we try to - * convert those BinaryValue assuming they represent an UTF-8 encoded - * String. Of course, if it's not the case, the stored value will - * be incorrect. - *

- *

- * It's the responsibility of the caller to check if the stored - * values are consistent with the attribute's type. - *

- *

- * The caller can set the HR flag in order to enforce a type for - * the current attribute, otherwise this type will be set while - * adding the first value, using the value's type to set the flag. - *

- *

- * Note : If the entry contains no value, and the unique added value - * is a null length value, then this value will be considered as - * a binary value. - *

- * @param val some new values to be added which may be null - * @return the number of added values, or 0 if none has been added - */ - public int add( Value... vals ) - { - int nbAdded = 0; - ClientBinaryValue nullBinaryValue = null; - ClientStringValue nullStringValue = null; - boolean nullValueAdded = false; - - for ( Value val:vals ) - { - if ( val == null ) - { - // We have a null value. If the HR flag is not set, we will consider - // that the attribute is not HR. We may change this later - if ( isHR == null ) - { - // This is the first value. Add both types, as we - // don't know yet the attribute type's, but we may - // know later if we add some new value. - // We have to do that because we are using a Set, - // and we can't remove the first element of the Set. - nullBinaryValue = new ClientBinaryValue( null ); - nullStringValue = new ClientStringValue( null ); - - values.add( nullBinaryValue ); - values.add( nullStringValue ); - nullValueAdded = true; - nbAdded++; - } - else if ( !isHR ) - { - // The attribute type is binary. - nullBinaryValue = new ClientBinaryValue( null ); - - // Don't add a value if it already exists. - if ( !values.contains( nullBinaryValue ) ) - { - values.add( nullBinaryValue ); - nbAdded++; - } - - } - else - { - // The attribute is HR - nullStringValue = new ClientStringValue( null ); - - // Don't add a value if it already exists. - if ( !values.contains( nullStringValue ) ) - { - values.add( nullStringValue ); - } - } - } - else - { - // Let's check the value type. - if ( val instanceof ClientStringValue ) - { - // We have a String value - if ( isHR == null ) - { - // The attribute type will be set to HR - isHR = true; - values.add( val ); - nbAdded++; - } - else if ( !isHR ) - { - // The attributeType is binary, convert the - // value to a BinaryValue - ClientBinaryValue cbv = new ClientBinaryValue(); - cbv.set( val.getBytes() ); - - if ( !contains( cbv ) ) - { - values.add( cbv ); - nbAdded++; - } - } - else - { - // The attributeType is HR, simply add the value - if ( !contains( val ) ) - { - values.add( val ); - nbAdded++; - } - } - } - else - { - // We have a Binary value - if ( isHR == null ) - { - // The attribute type will be set to binary - isHR = false; - values.add( val ); - nbAdded++; - } - else if ( !isHR ) - { - // The attributeType is not HR, simply add the value if it does not already exist - if ( !contains( val ) ) - { - values.add( val ); - nbAdded++; - } - } - else - { - // The attribute Type is HR, convert the - // value to a StringValue - ClientStringValue csv = new ClientStringValue(); - csv.set( val.getString() ); - - if ( !contains( csv ) ) - { - values.add( csv ); - nbAdded++; - } - } - } - } - } - - // Last, not least, if a nullValue has been added, and if other - // values are all String, we have to keep the correct nullValue, - // and to remove the other - if ( nullValueAdded ) - { - if ( isHR ) - { - // Remove the Binary value - values.remove( nullBinaryValue ); - } - else - { - // Remove the String value - values.remove( nullStringValue ); - } - } - - return nbAdded; - } - - - /** - * @see EntryAttribute#add(String...) - */ - public int add( String... vals ) - { - int nbAdded = 0; - - // First, if the isHR flag is not set, we assume that the - // attribute is HR, because we are asked to add some strings. - if ( isHR == null ) - { - isHR = true; - } - - // Check the attribute type. - if ( isHR ) - { - for ( String val:vals ) - { - // Call the add(Value) method, if not already present - if ( !contains( val ) ) - { - if ( add( new ClientStringValue( val ) ) == 1 ) - { - nbAdded++; - } - } - } - } - else - { - // The attribute is binary. Transform the String to byte[] - for ( String val:vals ) - { - byte[] valBytes = null; - - if ( val != null ) - { - valBytes = StringTools.getBytesUtf8( val ); - } - - // Now call the add(Value) method - if ( add( new ClientBinaryValue( valBytes ) ) == 1 ) - { - nbAdded++; - } - } - } - - return nbAdded; - } - - - /** - * Adds some values to this attribute. If the new values are already present in - * the attribute values, the method has no effect. - *

- * The new values are added at the end of list of values. - *

- *

- * This method returns the number of values that were added. - *

- * If the value's type is different from the attribute's type, - * a conversion is done. For instance, if we try to set some String - * into a Binary attribute, we just store the UTF-8 byte array - * encoding for this String. - * If we try to store some byte[] in a HR attribute, we try to - * convert those byte[] assuming they represent an UTF-8 encoded - * String. Of course, if it's not the case, the stored value will - * be incorrect. - *
- * It's the responsibility of the caller to check if the stored - * values are consistent with the attribute's type. - *
- * The caller can set the HR flag in order to enforce a type for - * the current attribute, otherwise this type will be set while - * adding the first value, using the value's type to set the flag. - * - * @param val some new values to be added which may be null - * @return the number of added values, or 0 if none has been added - */ - public int add( byte[]... vals ) - { - int nbAdded = 0; - - // First, if the isHR flag is not set, we assume that the - // attribute is not HR, because we are asked to add some byte[]. - if ( isHR == null ) - { - isHR = false; - } - - // Check the attribute type. - if ( isHR ) - { - // The attribute is HR. Transform the byte[] to String - for ( byte[] val:vals ) - { - String valString = null; - - if ( val != null ) - { - valString = StringTools.utf8ToString( val ); - } - - // Now call the add(Value) method, if not already present - if ( !contains( val ) ) - { - if ( add( new ClientStringValue( valString ) ) == 1 ) - { - nbAdded++; - } - } - } - } - else - { - for ( byte[] val:vals ) - { - if ( add( new ClientBinaryValue( val ) ) == 1 ) - { - nbAdded++; - } - } - } - - return nbAdded; - } - - - /** - * Remove all the values from this attribute. - */ - public void clear() - { - values.clear(); - } - - - /** - *

- * Indicates whether the specified values are some of the attribute's values. - *

- *

- * If the Attribute is HR, the binary values will be converted to String before - * being checked. - *

- * - * @param vals the values - * @return true if this attribute contains all the values, otherwise false - */ - public boolean contains( Value... vals ) - { - if ( isHR == null ) - { - // If this flag is null, then there is no values. - return false; - } - - if ( isHR ) - { - // Iterate through all the values, convert the Binary values - // to String values, and quit id any of the values is not - // contained in the object - for ( Value val:vals ) - { - if ( val instanceof ClientStringValue ) - { - if ( !values.contains( val ) ) - { - return false; - } - } - else - { - byte[] binaryVal = val.getBytes(); - - // We have to convert the binary value to a String - if ( ! values.contains( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) ) ) - { - return false; - } - } - } - } - else - { - // Iterate through all the values, convert the String values - // to binary values, and quit id any of the values is not - // contained in the object - for ( Value val:vals ) - { - if ( val.isBinary() ) - { - if ( !values.contains( val ) ) - { - return false; - } - } - else - { - String stringVal = val.getString(); - - // We have to convert the binary value to a String - if ( ! values.contains( new ClientBinaryValue( StringTools.getBytesUtf8( stringVal ) ) ) ) - { - return false; - } - } - } - } - - return true; - } - - - /** - *

- * Indicates whether the specified values are some of the attribute's values. - *

- *

- * If the Attribute is not HR, the values will be converted to byte[] - *

- * - * @param vals the values - * @return true if this attribute contains all the values, otherwise false - */ - public boolean contains( String... vals ) - { - if ( isHR == null ) - { - // If this flag is null, then there is no values. - return false; - } - - if ( isHR ) - { - // Iterate through all the values, and quit if we - // don't find one in the values - for ( String val:vals ) - { - if ( !contains( new ClientStringValue( val ) ) ) - { - return false; - } - } - } - else - { - // As the attribute type is binary, we have to convert - // the values before checking for them in the values - // Iterate through all the values, and quit if we - // don't find one in the values - for ( String val:vals ) - { - byte[] binaryVal = StringTools.getBytesUtf8( val ); - - if ( !contains( new ClientBinaryValue( binaryVal ) ) ) - { - return false; - } - } - } - - return true; - } - - - /** - *

- * Indicates whether the specified values are some of the attribute's values. - *

- *

- * If the Attribute is HR, the values will be converted to String - *

- * - * @param vals the values - * @return true if this attribute contains all the values, otherwise false - */ - public boolean contains( byte[]... vals ) - { - if ( isHR == null ) - { - // If this flag is null, then there is no values. - return false; - } - - if ( !isHR ) - { - // Iterate through all the values, and quit if we - // don't find one in the values - for ( byte[] val:vals ) - { - if ( !contains( new ClientBinaryValue( val ) ) ) - { - return false; - } - } - } - else - { - // As the attribute type is String, we have to convert - // the values before checking for them in the values - // Iterate through all the values, and quit if we - // don't find one in the values - for ( byte[] val:vals ) - { - String stringVal = StringTools.utf8ToString( val ); - - if ( !contains( new ClientStringValue( stringVal ) ) ) - { - return false; - } - } - } - - return true; - } - - - /** - * @see EntryAttribute#contains(Object...) - */ - public boolean contains( Object... vals ) - { - boolean isHR = true; - boolean seen = false; - - // Iterate through all the values, and quit if we - // don't find one in the values - for ( Object val:vals ) - { - if ( ( val instanceof String ) ) - { - if ( !seen ) - { - isHR = true; - seen = true; - } - - if ( isHR ) - { - if ( !contains( (String)val ) ) - { - return false; - } - } - else - { - return false; - } - } - else - { - if ( !seen ) - { - isHR = false; - seen = true; - } - - if ( !isHR ) - { - if ( !contains( (byte[])val ) ) - { - return false; - } - } - else - { - return false; - } - } - } - - return true; - } - - - /** - *

- * Get the first value of this attribute. If there is none, - * null is returned. - *

- *

- * Note : even if we are storing values into a Set, one can assume - * the values are ordered following the insertion order. - *

- *

- * This method is meant to be used if the attribute hold only one value. - *

- * - * @return The first value for this attribute. - */ - public Value get() - { - if ( values.isEmpty() ) - { - return null; - } - - return values.iterator().next(); - } - - - /** - *

- * Get the nth value of this attribute. If there is none, - * null is returned. - *

- *

- * Note : even if we are storing values into a Set, one can assume - * the values are ordered following the insertion order. - *

- *

- * - * @param i the index of the value to get - * @return The nth value for this attribute. - */ - public Value get( int i ) - { - if ( values.size() < i ) - { - return null; - } - else - { - int n = 0; - - for ( Value value:values ) - { - if ( n == i ) - { - return value; - } - - n++; - } - } - - // fallback to - return null; - } - - - /** - * Returns an iterator over all the attribute's values. - *

- * The effect on the returned enumeration of adding or removing values of - * the attribute is not specified. - *

- *

- * This method will throw any NamingException that occurs. - *

- * - * @return an enumeration of all values of the attribute - */ - public Iterator> getAll() - { - return iterator(); - } - - - /** - * Retrieves the number of values in this attribute. - * - * @return the number of values in this attribute, including any values - * wrapping a null value if there is one - */ - public int size() - { - return values.size(); - } - - - /** - *

- * Removes all the values that are equal to the given values. - *

- *

- * Returns true if all the values are removed. - *

- *

- * If the attribute type is HR and some value which are not String, we - * will convert the values first (same thing for a non-HR attribute). - *

- * - * @param vals the values to be removed - * @return true if all the values are removed, otherwise false - */ - public boolean remove( Value... vals ) - { - if ( ( isHR == null ) || ( values.size() == 0 ) ) - { - // Trying to remove a value from an empty list will fail - return false; - } - - boolean removed = true; - - if ( isHR ) - { - for ( Value val:vals ) - { - if ( val instanceof ClientStringValue ) - { - removed &= values.remove( val ); - } - else - { - // Convert the binary value to a string value - byte[] binaryVal = val.getBytes(); - removed &= values.remove( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) ); - } - } - } - else - { - for ( Value val:vals ) - { - removed &= values.remove( val ); - } - } - - return removed; - } - - - /** - *

- * Removes all the values that are equal to the given values. - *

- *

- * Returns true if all the values are removed. - *

- *

- * If the attribute type is HR, then the values will be first converted - * to String - *

- * - * @param vals the values to be removed - * @return true if all the values are removed, otherwise false - */ - public boolean remove( byte[]... vals ) - { - if ( ( isHR == null ) || ( values.size() == 0 ) ) - { - // Trying to remove a value from an empty list will fail - return false; - } - - boolean removed = true; - - if ( !isHR ) - { - // The attribute type is not HR, we can directly process the values - for ( byte[] val:vals ) - { - ClientBinaryValue value = new ClientBinaryValue( val ); - removed &= values.remove( value ); - } - } - else - { - // The attribute type is String, we have to convert the values - // to String before removing them - for ( byte[] val:vals ) - { - ClientStringValue value = new ClientStringValue( StringTools.utf8ToString( val ) ); - removed &= values.remove( value ); - } - } - - return removed; - } - - - /** - * Removes all the values that are equal to the given values. - *

- * Returns true if all the values are removed. - *

- *

- * If the attribute type is not HR, then the values will be first converted - * to byte[] - *

- * - * @param vals the values to be removed - * @return true if all the values are removed, otherwise false - */ - public boolean remove( String... vals ) - { - if ( ( isHR == null ) || ( values.size() == 0 ) ) - { - // Trying to remove a value from an empty list will fail - return false; - } - - boolean removed = true; - - if ( isHR ) - { - // The attribute type is HR, we can directly process the values - for ( String val:vals ) - { - ClientStringValue value = new ClientStringValue( val ); - removed &= values.remove( value ); - } - } - else - { - // The attribute type is binary, we have to convert the values - // to byte[] before removing them - for ( String val:vals ) - { - ClientBinaryValue value = new ClientBinaryValue( StringTools.getBytesUtf8( val ) ); - removed &= values.remove( value ); - } - } - - return removed; - } - - - /** - * An iterator on top of the stored values. - * - * @return an iterator over the stored values. - */ - public Iterator> iterator() - { - return values.iterator(); - } - - - /** - * Puts some values to this attribute. - *

- * The new values will replace the previous values. - *

- *

- * This method returns the number of values that were put. - *

- * - * @param val some values to be put which may be null - * @return the number of added values, or 0 if none has been added - */ - public int put( String... vals ) - { - values.clear(); - return add( vals ); - } - - - /** - * Puts some values to this attribute. - *

- * The new values will replace the previous values. - *

- *

- * This method returns the number of values that were put. - *

- * - * @param val some values to be put which may be null - * @return the number of added values, or 0 if none has been added - */ - public int put( byte[]... vals ) - { - values.clear(); - return add( vals ); - } - - - /** - * Puts some values to this attribute. - *

- * The new values are replace the previous values. - *

- *

- * This method returns the number of values that were put. - *

- * - * @param val some values to be put which may be null - * @return the number of added values, or 0 if none has been added - */ - public int put( Value... vals ) - { - values.clear(); - return add( vals ); - } - - - /** - *

- * Puts a list of values into this attribute. - *

- *

- * The new values will replace the previous values. - *

- *

- * This method returns the number of values that were put. - *

- * - * @param vals the values to be put - * @return the number of added values, or 0 if none has been added - */ - public int put( List> vals ) - { - values.clear(); - - // Transform the List to an array - Value[] valArray = new Value[vals.size()]; - return add( vals.toArray( valArray ) ); - } - - //------------------------------------------------------------------------- - // Overloaded Object classes - //------------------------------------------------------------------------- - /** - * The hashCode is based on the id, the isHR flag and - * on the internal values. - * - * @see Object#hashCode() - * @return the instance's hashcode - */ - public int hashCode() - { - int h = 37; - - if ( isHR != null ) - { - h = h*17 + isHR.hashCode(); - } - - if ( id != null ) - { - h = h*17 + id.hashCode(); - } - - for ( Value value:values ) - { - h = h*17 + value.hashCode(); - } - - return h; - } - - - /** - * @see Object#equals(Object) - */ - public boolean equals( Object obj ) - { - if ( obj == this ) - { - return true; - } - - if ( ! (obj instanceof EntryAttribute ) ) - { - return false; - } - - EntryAttribute other = (EntryAttribute)obj; - - if ( id == null ) - { - if ( other.getId() != null ) - { - return false; - } - } - else - { - if ( other.getId() == null ) - { - return false; - } - else - { - if ( !id.equals( other.getId() ) ) - { - return false; - } - } - } - - if ( isHR() != other.isHR() ) - { - return false; - } - - if ( values.size() != other.size() ) - { - return false; - } - - for ( Value val:values ) - { - if ( ! other.contains( val ) ) - { - return false; - } - } - - return true; - } - - - /** - * @see Cloneable#clone() - */ - public EntryAttribute clone() - { - try - { - DefaultClientAttribute attribute = (DefaultClientAttribute)super.clone(); - - attribute.values = new LinkedHashSet>( values.size() ); - - for ( Value value:values ) - { - attribute.values.add( value.clone() ); - } - - return attribute; - } - catch ( CloneNotSupportedException cnse ) - { - return null; - } - } - - - /** - * @see Object#toString() - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - - if ( ( values != null ) && ( values.size() != 0 ) ) - { - for ( Value value:values ) - { - sb.append( " " ).append( upId ).append( ": " ); - - if ( value.isNull() ) - { - sb.append( "''" ); - } - else - { - sb.append( value ); - } - - sb.append( '\n' ); - } - } - else - { - sb.append( " " ).append( upId ).append( ": (null)\n" ); - } - - return sb.toString(); - } - - - /** - * @see Externalizable#writeExternal(ObjectOutput) - *

- * - * This is the place where we serialize attributes, and all theirs - * elements. - * - * The inner structure is : - * - */ - public void writeExternal( ObjectOutput out ) throws IOException - { - // Write the UPId (the id will be deduced from the upID) - out.writeUTF( upId ); - - // Write the HR flag, if not null - if ( isHR != null ) - { - out.writeBoolean( true ); - out.writeBoolean( isHR ); - } - else - { - out.writeBoolean( false ); - } - - // Write the number of values - out.writeInt( size() ); - - if ( size() > 0 ) - { - // Write each value - for ( Value value:values ) - { - // Write the value - out.writeObject( value ); - } - } - - out.flush(); - } - - - /** - * @see Externalizable#readExternal(ObjectInput) - */ - public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException - { - // Read the ID and the UPId - upId = in.readUTF(); - - // Compute the id - setUpId( upId ); - - // Read the HR flag, if not null - if ( in.readBoolean() ) - { - isHR = in.readBoolean(); - } - - // Read the number of values - int nbValues = in.readInt(); - - if ( nbValues > 0 ) - { - for ( int i = 0; i < nbValues; i++ ) - { - values.add( (Value)in.readObject() ); - } - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.entry.client; + + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.naming.NamingException; +import javax.naming.directory.InvalidAttributeValueException; + +import org.apache.directory.shared.ldap.entry.EntryAttribute; +import org.apache.directory.shared.ldap.entry.Value; +import org.apache.directory.shared.ldap.schema.SyntaxChecker; +import org.apache.directory.shared.ldap.util.StringTools; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * A client side entry attribute. The client is not aware of the schema, + * so we can't tell if the stored value will be String or Binary. We will + * default to Binary.

+ * To define the kind of data stored, the client must set the isHR flag. + * + * @author Apache Directory Project + * @version $Rev$, $Date$ + */ +public class DefaultClientAttribute implements ClientAttribute +{ + /** logger for reporting errors that might not be handled properly upstream */ + private static final Logger LOG = LoggerFactory.getLogger( DefaultClientAttribute.class ); + + + /** The set of contained values */ + protected Set> values = new LinkedHashSet>(); + + /** The User provided ID */ + protected String upId; + + /** The normalized ID */ + protected String id; + + /** Tells if the attribute is Human Readable or not. When not set, + * this flag is null. */ + protected Boolean isHR; + + + // maybe have some additional convenience constructors which take + // an initial value as a string or a byte[] + /** + * Create a new instance of a EntryAttribute, without ID nor value. + */ + public DefaultClientAttribute() + { + } + + + /** + * Create a new instance of a EntryAttribute, without value. + */ + public DefaultClientAttribute( String upId ) + { + setUpId( upId ); + } + + + /** + * If the value does not correspond to the same attributeType, then it's + * wrapped value is copied into a new ClientValue which uses the specified + * attributeType. + * + * Otherwise, the value is stored, but as a reference. It's not a copy. + * + * @param upId + * @param attributeType the attribute type according to the schema + * @param vals an initial set of values for this attribute + */ + public DefaultClientAttribute( String upId, Value... vals ) + { + // The value can be null, this is a valid value. + if ( vals[0] == null ) + { + add( new ClientStringValue() ); + } + else + { + for ( Value val:vals ) + { + if ( ( val instanceof ClientStringValue ) || ( val.isBinary() ) ) + { + add( val ); + } + else + { + String message = "Unknown value type: " + val.getClass().getName(); + LOG.error( message ); + throw new IllegalStateException( message ); + } + } + } + + setUpId( upId ); + } + + + /** + * Create a new instance of a EntryAttribute. + */ + public DefaultClientAttribute( String upId, String... vals ) + { + add( vals ); + setUpId( upId ); + } + + + /** + * Create a new instance of a EntryAttribute, with some byte[] values. + */ + public DefaultClientAttribute( String upId, byte[]... vals ) + { + add( vals ); + setUpId( upId ); + } + + + /** + *

+ * Get the byte[] value, if and only if the value is known to be Binary, + * otherwise a InvalidAttributeValueException will be thrown + *

+ *

+ * Note that this method returns the first value only. + *

+ * + * @return The value as a byte[] + * @throws InvalidAttributeValueException If the value is a String + */ + public byte[] getBytes() throws InvalidAttributeValueException + { + Value value = get(); + + if ( value.isBinary() ) + { + return value.getBytes(); + } + else + { + String message = "The value is expected to be a byte[]"; + LOG.error( message ); + throw new InvalidAttributeValueException( message ); + } + } + + + /** + *

+ * Get the String value, if and only if the value is known to be a String, + * otherwise a InvalidAttributeValueException will be thrown + *

+ *

+ * Note that this method returns the first value only. + *

+ * + * @return The value as a String + * @throws InvalidAttributeValueException If the value is a byte[] + */ + public String getString() throws InvalidAttributeValueException + { + Value value = get(); + + if ( value instanceof ClientStringValue ) + { + return value.getString(); + } + else + { + String message = "The value is expected to be a String"; + LOG.error( message ); + throw new InvalidAttributeValueException( message ); + } + } + + + /** + * Get's the attribute identifier. Its value is the same than the + * user provided ID. + * + * @return the attribute's identifier + */ + public String getId() + { + return id; + } + + + /** + *

+ * Set the attribute to Human Readable or to Binary. + *

+ * @param isHR true for a Human Readable attribute, + * false for a Binary attribute. + */ + public void setHR( boolean isHR ) + { + this.isHR = isHR; + //TODO : deal with the values, we may have to convert them. + } + + + /** + * Set the normalized ID. The ID will be lowercased, and spaces + * will be trimmed. + * + * @param id The attribute ID + * @throws IllegalArgumentException If the ID is empty or null or + * resolve to an empty value after being trimmed + */ + public void setId( String id ) + { + this.id = StringTools.trim( StringTools.lowerCaseAscii( id ) ); + + if ( this.id.length() == 0 ) + { + this.id = null; + throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" + + " value when trimmed" ); + } + } + + + /** + * Get's the user provided identifier for this entry. This is the value + * that will be used as the identifier for the attribute within the + * entry. If this is a commonName attribute for example and the user + * provides "COMMONname" instead when adding the entry then this is + * the format the user will have that entry returned by the directory + * server. To do so we store this value as it was given and track it + * in the attribute using this property. + * + * @return the user provided identifier for this attribute + */ + public String getUpId() + { + return upId; + } + + + /** + * Set the user provided ID. It will also set the ID, normalizing + * the upId (removing spaces before and after, and lowercasing it) + * + * @param upId The attribute ID + * @throws IllegalArgumentException If the ID is empty or null or + * resolve to an empty value after being trimmed + */ + public void setUpId( String upId ) + { + this.upId = StringTools.trim( upId ); + + if ( this.upId.length() == 0 ) + { + this.upId = null; + throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" + + " value when trimmed" ); + } + + this.id = StringTools.lowerCaseAscii( this.upId ); + } + + + /** + *

+ * Tells if the attribute is Human Readable. + *

+ *

This flag is set by the caller, or implicitly when adding String + * values into an attribute which is not yet declared as Binary. + *

+ * @return + */ + public boolean isHR() + { + return isHR != null ? isHR : false; + } + + + /** + * Checks to see if this attribute is valid along with the values it contains. + * + * @return true if the attribute and it's values are valid, false otherwise + * @throws NamingException if there is a failure to check syntaxes of values + */ + public boolean isValid() throws NamingException + { + for ( Value value:values ) + { + if ( !value.isValid() ) + { + return false; + } + } + + return true; + } + + + /** + * Checks to see if this attribute is valid along with the values it contains. + * + * @return true if the attribute and it's values are valid, false otherwise + * @throws NamingException if there is a failure to check syntaxes of values + */ + public boolean isValid( SyntaxChecker checker ) throws NamingException + { + for ( Value value : values ) + { + if ( !value.isValid( checker ) ) + { + return false; + } + } + + return true; + } + + + /** + * Adds some values to this attribute. If the new values are already present in + * the attribute values, the method has no effect. + *

+ * The new values are added at the end of list of values. + *

+ *

+ * This method returns the number of values that were added. + *

+ *

+ * If the value's type is different from the attribute's type, + * a conversion is done. For instance, if we try to set some + * StringValue into a Binary attribute, we just store the UTF-8 + * byte array encoding for this StringValue. + *

+ *

+ * If we try to store some BinaryValue in a HR attribute, we try to + * convert those BinaryValue assuming they represent an UTF-8 encoded + * String. Of course, if it's not the case, the stored value will + * be incorrect. + *

+ *

+ * It's the responsibility of the caller to check if the stored + * values are consistent with the attribute's type. + *

+ *

+ * The caller can set the HR flag in order to enforce a type for + * the current attribute, otherwise this type will be set while + * adding the first value, using the value's type to set the flag. + *

+ *

+ * Note : If the entry contains no value, and the unique added value + * is a null length value, then this value will be considered as + * a binary value. + *

+ * @param val some new values to be added which may be null + * @return the number of added values, or 0 if none has been added + */ + public int add( Value... vals ) + { + int nbAdded = 0; + ClientBinaryValue nullBinaryValue = null; + ClientStringValue nullStringValue = null; + boolean nullValueAdded = false; + + for ( Value val:vals ) + { + if ( val == null ) + { + // We have a null value. If the HR flag is not set, we will consider + // that the attribute is not HR. We may change this later + if ( isHR == null ) + { + // This is the first value. Add both types, as we + // don't know yet the attribute type's, but we may + // know later if we add some new value. + // We have to do that because we are using a Set, + // and we can't remove the first element of the Set. + nullBinaryValue = new ClientBinaryValue( null ); + nullStringValue = new ClientStringValue( null ); + + values.add( nullBinaryValue ); + values.add( nullStringValue ); + nullValueAdded = true; + nbAdded++; + } + else if ( !isHR ) + { + // The attribute type is binary. + nullBinaryValue = new ClientBinaryValue( null ); + + // Don't add a value if it already exists. + if ( !values.contains( nullBinaryValue ) ) + { + values.add( nullBinaryValue ); + nbAdded++; + } + + } + else + { + // The attribute is HR + nullStringValue = new ClientStringValue( null ); + + // Don't add a value if it already exists. + if ( !values.contains( nullStringValue ) ) + { + values.add( nullStringValue ); + } + } + } + else + { + // Let's check the value type. + if ( val instanceof ClientStringValue ) + { + // We have a String value + if ( isHR == null ) + { + // The attribute type will be set to HR + isHR = true; + values.add( val ); + nbAdded++; + } + else if ( !isHR ) + { + // The attributeType is binary, convert the + // value to a BinaryValue + ClientBinaryValue cbv = new ClientBinaryValue(); + cbv.set( val.getBytes() ); + + if ( !contains( cbv ) ) + { + values.add( cbv ); + nbAdded++; + } + } + else + { + // The attributeType is HR, simply add the value + if ( !contains( val ) ) + { + values.add( val ); + nbAdded++; + } + } + } + else + { + // We have a Binary value + if ( isHR == null ) + { + // The attribute type will be set to binary + isHR = false; + values.add( val ); + nbAdded++; + } + else if ( !isHR ) + { + // The attributeType is not HR, simply add the value if it does not already exist + if ( !contains( val ) ) + { + values.add( val ); + nbAdded++; + } + } + else + { + // The attribute Type is HR, convert the + // value to a StringValue + ClientStringValue csv = new ClientStringValue(); + csv.set( val.getString() ); + + if ( !contains( csv ) ) + { + values.add( csv ); + nbAdded++; + } + } + } + } + } + + // Last, not least, if a nullValue has been added, and if other + // values are all String, we have to keep the correct nullValue, + // and to remove the other + if ( nullValueAdded ) + { + if ( isHR ) + { + // Remove the Binary value + values.remove( nullBinaryValue ); + } + else + { + // Remove the String value + values.remove( nullStringValue ); + } + } + + return nbAdded; + } + + + /** + * @see EntryAttribute#add(String...) + */ + public int add( String... vals ) + { + int nbAdded = 0; + + // First, if the isHR flag is not set, we assume that the + // attribute is HR, because we are asked to add some strings. + if ( isHR == null ) + { + isHR = true; + } + + // Check the attribute type. + if ( isHR ) + { + for ( String val:vals ) + { + // Call the add(Value) method, if not already present + if ( !contains( val ) ) + { + if ( add( new ClientStringValue( val ) ) == 1 ) + { + nbAdded++; + } + } + } + } + else + { + // The attribute is binary. Transform the String to byte[] + for ( String val:vals ) + { + byte[] valBytes = null; + + if ( val != null ) + { + valBytes = StringTools.getBytesUtf8( val ); + } + + // Now call the add(Value) method + if ( add( new ClientBinaryValue( valBytes ) ) == 1 ) + { + nbAdded++; + } + } + } + + return nbAdded; + } + + + /** + * Adds some values to this attribute. If the new values are already present in + * the attribute values, the method has no effect. + *

+ * The new values are added at the end of list of values. + *

+ *

+ * This method returns the number of values that were added. + *

+ * If the value's type is different from the attribute's type, + * a conversion is done. For instance, if we try to set some String + * into a Binary attribute, we just store the UTF-8 byte array + * encoding for this String. + * If we try to store some byte[] in a HR attribute, we try to + * convert those byte[] assuming they represent an UTF-8 encoded + * String. Of course, if it's not the case, the stored value will + * be incorrect. + *
+ * It's the responsibility of the caller to check if the stored + * values are consistent with the attribute's type. + *
+ * The caller can set the HR flag in order to enforce a type for + * the current attribute, otherwise this type will be set while + * adding the first value, using the value's type to set the flag. + * + * @param val some new values to be added which may be null + * @return the number of added values, or 0 if none has been added + */ + public int add( byte[]... vals ) + { + int nbAdded = 0; + + // First, if the isHR flag is not set, we assume that the + // attribute is not HR, because we are asked to add some byte[]. + if ( isHR == null ) + { + isHR = false; + } + + // Check the attribute type. + if ( isHR ) + { + // The attribute is HR. Transform the byte[] to String + for ( byte[] val:vals ) + { + String valString = null; + + if ( val != null ) + { + valString = StringTools.utf8ToString( val ); + } + + // Now call the add(Value) method, if not already present + if ( !contains( val ) ) + { + if ( add( new ClientStringValue( valString ) ) == 1 ) + { + nbAdded++; + } + } + } + } + else + { + for ( byte[] val:vals ) + { + if ( add( new ClientBinaryValue( val ) ) == 1 ) + { + nbAdded++; + } + } + } + + return nbAdded; + } + + + /** + * Remove all the values from this attribute. + */ + public void clear() + { + values.clear(); + } + + + /** + *

+ * Indicates whether the specified values are some of the attribute's values. + *

+ *

+ * If the Attribute is HR, the binary values will be converted to String before + * being checked. + *

+ * + * @param vals the values + * @return true if this attribute contains all the values, otherwise false + */ + public boolean contains( Value... vals ) + { + if ( isHR == null ) + { + // If this flag is null, then there is no values. + return false; + } + + if ( isHR ) + { + // Iterate through all the values, convert the Binary values + // to String values, and quit id any of the values is not + // contained in the object + for ( Value val:vals ) + { + if ( val instanceof ClientStringValue ) + { + if ( !values.contains( val ) ) + { + return false; + } + } + else + { + byte[] binaryVal = val.getBytes(); + + // We have to convert the binary value to a String + if ( ! values.contains( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) ) ) + { + return false; + } + } + } + } + else + { + // Iterate through all the values, convert the String values + // to binary values, and quit id any of the values is not + // contained in the object + for ( Value val:vals ) + { + if ( val.isBinary() ) + { + if ( !values.contains( val ) ) + { + return false; + } + } + else + { + String stringVal = val.getString(); + + // We have to convert the binary value to a String + if ( ! values.contains( new ClientBinaryValue( StringTools.getBytesUtf8( stringVal ) ) ) ) + { + return false; + } + } + } + } + + return true; + } + + + /** + *

+ * Indicates whether the specified values are some of the attribute's values. + *

+ *

+ * If the Attribute is not HR, the values will be converted to byte[] + *

+ * + * @param vals the values + * @return true if this attribute contains all the values, otherwise false + */ + public boolean contains( String... vals ) + { + if ( isHR == null ) + { + // If this flag is null, then there is no values. + return false; + } + + if ( isHR ) + { + // Iterate through all the values, and quit if we + // don't find one in the values + for ( String val:vals ) + { + if ( !contains( new ClientStringValue( val ) ) ) + { + return false; + } + } + } + else + { + // As the attribute type is binary, we have to convert + // the values before checking for them in the values + // Iterate through all the values, and quit if we + // don't find one in the values + for ( String val:vals ) + { + byte[] binaryVal = StringTools.getBytesUtf8( val ); + + if ( !contains( new ClientBinaryValue( binaryVal ) ) ) + { + return false; + } + } + } + + return true; + } + + + /** + *

+ * Indicates whether the specified values are some of the attribute's values. + *

+ *

+ * If the Attribute is HR, the values will be converted to String + *

+ * + * @param vals the values + * @return true if this attribute contains all the values, otherwise false + */ + public boolean contains( byte[]... vals ) + { + if ( isHR == null ) + { + // If this flag is null, then there is no values. + return false; + } + + if ( !isHR ) + { + // Iterate through all the values, and quit if we + // don't find one in the values + for ( byte[] val:vals ) + { + if ( !contains( new ClientBinaryValue( val ) ) ) + { + return false; + } + } + } + else + { + // As the attribute type is String, we have to convert + // the values before checking for them in the values + // Iterate through all the values, and quit if we + // don't find one in the values + for ( byte[] val:vals ) + { + String stringVal = StringTools.utf8ToString( val ); + + if ( !contains( new ClientStringValue( stringVal ) ) ) + { + return false; + } + } + } + + return true; + } + + + /** + * @see EntryAttribute#contains(Object...) + */ + public boolean contains( Object... vals ) + { + boolean isHR = true; + boolean seen = false; + + // Iterate through all the values, and quit if we + // don't find one in the values + for ( Object val:vals ) + { + if ( ( val instanceof String ) ) + { + if ( !seen ) + { + isHR = true; + seen = true; + } + + if ( isHR ) + { + if ( !contains( (String)val ) ) + { + return false; + } + } + else + { + return false; + } + } + else + { + if ( !seen ) + { + isHR = false; + seen = true; + } + + if ( !isHR ) + { + if ( !contains( (byte[])val ) ) + { + return false; + } + } + else + { + return false; + } + } + } + + return true; + } + + + /** + *

+ * Get the first value of this attribute. If there is none, + * null is returned. + *

+ *

+ * Note : even if we are storing values into a Set, one can assume + * the values are ordered following the insertion order. + *

+ *

+ * This method is meant to be used if the attribute hold only one value. + *

+ * + * @return The first value for this attribute. + */ + public Value get() + { + if ( values.isEmpty() ) + { + return null; + } + + return values.iterator().next(); + } + + + /** + *

+ * Get the nth value of this attribute. If there is none, + * null is returned. + *

+ *

+ * Note : even if we are storing values into a Set, one can assume + * the values are ordered following the insertion order. + *

+ *

+ * + * @param i the index of the value to get + * @return The nth value for this attribute. + */ + public Value get( int i ) + { + if ( values.size() < i ) + { + return null; + } + else + { + int n = 0; + + for ( Value value:values ) + { + if ( n == i ) + { + return value; + } + + n++; + } + } + + // fallback to + return null; + } + + + /** + * Returns an iterator over all the attribute's values. + *

+ * The effect on the returned enumeration of adding or removing values of + * the attribute is not specified. + *

+ *

+ * This method will throw any NamingException that occurs. + *

+ * + * @return an enumeration of all values of the attribute + */ + public Iterator> getAll() + { + return iterator(); + } + + + /** + * Retrieves the number of values in this attribute. + * + * @return the number of values in this attribute, including any values + * wrapping a null value if there is one + */ + public int size() + { + return values.size(); + } + + + /** + *

+ * Removes all the values that are equal to the given values. + *

+ *

+ * Returns true if all the values are removed. + *

+ *

+ * If the attribute type is HR and some value which are not String, we + * will convert the values first (same thing for a non-HR attribute). + *

+ * + * @param vals the values to be removed + * @return true if all the values are removed, otherwise false + */ + public boolean remove( Value... vals ) + { + if ( ( isHR == null ) || ( values.size() == 0 ) ) + { + // Trying to remove a value from an empty list will fail + return false; + } + + boolean removed = true; + + if ( isHR ) + { + for ( Value val:vals ) + { + if ( val instanceof ClientStringValue ) + { + removed &= values.remove( val ); + } + else + { + // Convert the binary value to a string value + byte[] binaryVal = val.getBytes(); + removed &= values.remove( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) ); + } + } + } + else + { + for ( Value val:vals ) + { + removed &= values.remove( val ); + } + } + + return removed; + } + + + /** + *

+ * Removes all the values that are equal to the given values. + *

+ *

+ * Returns true if all the values are removed. + *

+ *

+ * If the attribute type is HR, then the values will be first converted + * to String + *

+ * + * @param vals the values to be removed + * @return true if all the values are removed, otherwise false + */ + public boolean remove( byte[]... vals ) + { + if ( ( isHR == null ) || ( values.size() == 0 ) ) + { + // Trying to remove a value from an empty list will fail + return false; + } + + boolean removed = true; + + if ( !isHR ) + { + // The attribute type is not HR, we can directly process the values + for ( byte[] val:vals ) + { + ClientBinaryValue value = new ClientBinaryValue( val ); + removed &= values.remove( value ); + } + } + else + { + // The attribute type is String, we have to convert the values + // to String before removing them + for ( byte[] val:vals ) + { + ClientStringValue value = new ClientStringValue( StringTools.utf8ToString( val ) ); + removed &= values.remove( value ); + } + } + + return removed; + } + + + /** + * Removes all the values that are equal to the given values. + *

+ * Returns true if all the values are removed. + *

+ *

+ * If the attribute type is not HR, then the values will be first converted + * to byte[] + *

+ * + * @param vals the values to be removed + * @return true if all the values are removed, otherwise false + */ + public boolean remove( String... vals ) + { + if ( ( isHR == null ) || ( values.size() == 0 ) ) + { + // Trying to remove a value from an empty list will fail + return false; + } + + boolean removed = true; + + if ( isHR ) + { + // The attribute type is HR, we can directly process the values + for ( String val:vals ) + { + ClientStringValue value = new ClientStringValue( val ); + removed &= values.remove( value ); + } + } + else + { + // The attribute type is binary, we have to convert the values + // to byte[] before removing them + for ( String val:vals ) + { + ClientBinaryValue value = new ClientBinaryValue( StringTools.getBytesUtf8( val ) ); + removed &= values.remove( value ); + } + } + + return removed; + } + + + /** + * An iterator on top of the stored values. + * + * @return an iterator over the stored values. + */ + public Iterator> iterator() + { + return values.iterator(); + } + + + /** + * Puts some values to this attribute. + *

+ * The new values will replace the previous values. + *

+ *

+ * This method returns the number of values that were put. + *

+ * + * @param val some values to be put which may be null + * @return the number of added values, or 0 if none has been added + */ + public int put( String... vals ) + { + values.clear(); + return add( vals ); + } + + + /** + * Puts some values to this attribute. + *

+ * The new values will replace the previous values. + *

+ *

+ * This method returns the number of values that were put. + *

+ * + * @param val some values to be put which may be null + * @return the number of added values, or 0 if none has been added + */ + public int put( byte[]... vals ) + { + values.clear(); + return add( vals ); + } + + + /** + * Puts some values to this attribute. + *

+ * The new values are replace the previous values. + *

+ *

+ * This method returns the number of values that were put. + *

+ * + * @param val some values to be put which may be null + * @return the number of added values, or 0 if none has been added + */ + public int put( Value... vals ) + { + values.clear(); + return add( vals ); + } + + + /** + *

+ * Puts a list of values into this attribute. + *

+ *

+ * The new values will replace the previous values. + *

+ *

+ * This method returns the number of values that were put. + *

+ * + * @param vals the values to be put + * @return the number of added values, or 0 if none has been added + */ + public int put( List> vals ) + { + values.clear(); + + // Transform the List to an array + Value[] valArray = new Value[vals.size()]; + return add( vals.toArray( valArray ) ); + } + + //------------------------------------------------------------------------- + // Overloaded Object classes + //------------------------------------------------------------------------- + /** + * The hashCode is based on the id, the isHR flag and + * on the internal values. + * + * @see Object#hashCode() + * @return the instance's hashcode + */ + public int hashCode() + { + int h = 37; + + if ( isHR != null ) + { + h = h*17 + isHR.hashCode(); + } + + if ( id != null ) + { + h = h*17 + id.hashCode(); + } + + for ( Value value:values ) + { + h = h*17 + value.hashCode(); + } + + return h; + } + + + /** + * @see Object#equals(Object) + */ + public boolean equals( Object obj ) + { + if ( obj == this ) + { + return true; + } + + if ( ! (obj instanceof EntryAttribute ) ) + { + return false; + } + + EntryAttribute other = (EntryAttribute)obj; + + if ( id == null ) + { + if ( other.getId() != null ) + { + return false; + } + } + else + { + if ( other.getId() == null ) + { + return false; + } + else + { + if ( !id.equals( other.getId() ) ) + { + return false; + } + } + } + + if ( isHR() != other.isHR() ) + { + return false; + } + + if ( values.size() != other.size() ) + { + return false; + } + + for ( Value val:values ) + { + if ( ! other.contains( val ) ) + { + return false; + } + } + + return true; + } + + + /** + * @see Cloneable#clone() + */ + public EntryAttribute clone() + { + try + { + DefaultClientAttribute attribute = (DefaultClientAttribute)super.clone(); + + attribute.values = new LinkedHashSet>( values.size() ); + + for ( Value value:values ) + { + attribute.values.add( value.clone() ); + } + + return attribute; + } + catch ( CloneNotSupportedException cnse ) + { + return null; + } + } + + + /** + * @see Object#toString() + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if ( ( values != null ) && ( values.size() != 0 ) ) + { + for ( Value value:values ) + { + sb.append( " " ).append( upId ).append( ": " ); + + if ( value.isNull() ) + { + sb.append( "''" ); + } + else + { + sb.append( value ); + } + + sb.append( '\n' ); + } + } + else + { + sb.append( " " ).append( upId ).append( ": (null)\n" ); + } + + return sb.toString(); + } + + + /** + * @see Externalizable#writeExternal(ObjectOutput) + *

+ * + * This is the place where we serialize attributes, and all theirs + * elements. + * + * The inner structure is : + * + */ + public void writeExternal( ObjectOutput out ) throws IOException + { + // Write the UPId (the id will be deduced from the upID) + out.writeUTF( upId ); + + // Write the HR flag, if not null + if ( isHR != null ) + { + out.writeBoolean( true ); + out.writeBoolean( isHR ); + } + else + { + out.writeBoolean( false ); + } + + // Write the number of values + out.writeInt( size() ); + + if ( size() > 0 ) + { + // Write each value + for ( Value value:values ) + { + // Write the value + out.writeObject( value ); + } + } + + out.flush(); + } + + + /** + * @see Externalizable#readExternal(ObjectInput) + */ + public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException + { + // Read the ID and the UPId + upId = in.readUTF(); + + // Compute the id + setUpId( upId ); + + // Read the HR flag, if not null + if ( in.readBoolean() ) + { + isHR = in.readBoolean(); + } + + // Read the number of values + int nbValues = in.readInt(); + + if ( nbValues > 0 ) + { + for ( int i = 0; i < nbValues; i++ ) + { + values.add( (Value)in.readObject() ); + } + } + } +}