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() );
+ }
+ }
+ }
+}