
/* **************************************************************************

  $Archive: /njcl_v2/src/com/novell/utility/naming/directory/SingleValueAttribute.java $
  $Revision: 3 $
  $Modtime: 8/11/99 1:19p $
 
  Copyright (c) 1999 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.

****************************************************************************/

package com.novell.utility.naming.directory;

import java.lang.reflect.Array;
import java.util.Vector;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.NamingException;
import javax.naming.NamingEnumeration;
import javax.naming.OperationNotSupportedException;

/**
  * This class provides a sigle value implementation of the <tt>Attribute</tt> interface.
  *<p>
  * This implementation does not support schema.
  * The schema methods, <tt>getAttributeDefinition()</tt> and <tt>getAttributeSyntaxDefinition()</tt>,
  * simply throw <tt>OperationNotSupportedException</tt>.
  * Subclasses of <tt>SingleValueAttribute</tt> should override these methods if they
  * support schemas.
  *<p>
  * The <tt>SingleValueAttribute</tt> class by default uses <tt>Object.equals()</tt> to
  * determine equality of the attribute value when testing for equality
  * <em>except</em> when the value is an array.
  * For an array, each element of the array is checked using <tt>Object.equals()</tt>.
  * <p>
  * The <tt>SingleValueAttribute</tt> class by default returns the values passed to its
  * constructor and/or manipulated using the add/remove methods.
  * Subclasses of <tt>SingleValueAttribute</tt> can override <tt>get()</tt> and <tt>getAll()</tt>
  * to get the values dynamically from the directory (or implement
  * the <tt>Attribute</tt> interface directly instead of subclassing <tt>SingleValueAttribute</tt>).
  *<p>
  * Note that updates to <tt>SingleValueAttribute</tt> (such as adding or removing a value)
  * does not affect the corresponding representation of the attribute
  * in the directory.  Updates to the directory can only be effected
  * using operations in the <tt>DirContext</tt> interface.
  *<p>
  * Note that the add method deviates in behavior from the add method of the Attribute interface 
  * as it ovverrides the previous value.
  *<p>
  * A <tt>SingleValueAttribute</tt> instance is not synchronized against concurrent
  * multithreaded access. Multiple threads trying to access and modify a
  * <tt>SingleValueAttribute</tt> should lock the object.
  *
  */
public class SingleValueAttribute implements Attribute {
    /**
     * Holds the attribute's id. It is initialized by the public constructor and
     * cannot be null unless methods in Attribute that use attrID
     * have been overridden.
     * @serial
     */
    protected String attrID;

    /**
     * Holds the attribute's value. Initialized by public constructors.
     * Cannot be null unless methods in Attribute that use
     * values have been overridden.
     */
    protected Object value;

    /**
     * Whether the value has been set or not.
     */
    protected boolean hasValue;
    
    /**
      * Makes a copy of this attribute.
      * The copy contains the same attribute values as the original attribute:
      * the attribute value is not cloned.
      * Changes to the copy does not affect the original and vice versa.
      *
      * @return A non-null copy of this attribute.
      */
   public Object clone() 
   {
      if (hasValue)
         return new SingleValueAttribute(attrID, value);
      else
         return new SingleValueAttribute(attrID);
   }

    /**
      * Determines whether obj is equal to this attribute.
      * Two attributes are equal if their attribute-ids, syntaxes
      * and values are equal. The order that the values were added
      * are irrelevant. If obj is null or not an Attribute, false is returned.
      *<p>
      * By default <tt>Object.equals()</tt> is used when comparing the attribute
      * id and its values except when a value is an array. For an array,
      * each element of the array is checked using <tt>Object.equals()</tt>.
      * A subclass may override this to make
      * use of schema syntax information and matching rules,
      * which define what it means for two attributes to be equal.
      * How and whether a subclass makes
      * use of the schema information is determined by the subclass.
      * If a subclass overrides <tt>equals()</tt>, it should also override
      * <tt>hashCode()</tt>
      * such that two attributes that are equal have the same hash code.
      *
      * @param obj	The possibly null object to check.
      * @return true if obj is equal to this attribute; false otherwise.
      * @see #hashCode
      * @see #contains
      */
   public boolean equals(Object obj) 
   {
      if ((obj != null) && (obj instanceof Attribute)) 
      {
         Attribute target = (Attribute)obj;

         if (attrID.equals(target.getID()) &&
            size() == target.size()) 
         {
            try 
            {
               if (value != ((Attribute)obj).get())
                  return false;
            } 
            catch (NamingException e) 
            {
               return false;
            }
            return true;
         }
      }
      return false;
   }

    /**
      * Calculates the hash code of this attribute.
      *<p>
      * The hash code is computed by adding the hash code of
      * the attribute's id and that of all of its values except for
      * values that are arrays.
      * For an array, the hash code of each element of the array is summed.
      * If a subclass overrides <tt>hashCode()</tt>, it should override <tt>equals()</tt>
      * as well so that two attributes that are equal have the same hash code.
      *
      * @return an int representing the hash code of this attribute.
      * @see #equals
      */
   public int hashCode() 
   {
      int hash = attrID.hashCode();
      if (value != null) 
      {
         if (value.getClass().isArray()) 
         {
            Object it;
            int len = Array.getLength(value);
            for (int j = 0 ; j < len ; j++) 
            {
               it = Array.get(value, j);
               if (it != null) 
               {
                  hash += it.hashCode();
               }
            }
         } 
         else 
         {
            hash += value.hashCode();
         }
      }
      return hash;
   }

    /**
      * Generates the string representation of this attribute.
      * The string consists of the attribute's id and its values.
      * This string is meant for debuggin and not meant to be
      * interpreted programmatically.
      * @return The non-null string representation of this attribute.
      */
   public String toString() 
   {
      StringBuffer answer = new StringBuffer(attrID + ": ");
      if (!hasValue) 
      {
         answer.append("No value");
      } 
      else 
      {
         answer.append(value);
      }
      return answer.toString();
   }

    /**
      * Constructs a new instance of an attribute with no value.
      *
      * @param id The attribute's id. It cannot be null.
      */
   public SingleValueAttribute(String id) 
   {
      attrID = id;
      hasValue = false;
      value = null;
   }

    /**
      * Constructs a new instance of an attribute with a single value.
      *
      * @param id The attribute's id. It cannot be null.
      * @param value The attribute's value. If null, a null
      *        value is added to the attribute.
      */
   public SingleValueAttribute(String id, Object value) 
   {
      attrID = id;
      hasValue = true;
      this.value = value;
   }

    /**
      * Retrieves an enumeration of this attribute's values.
      * The behaviour of this enumeration is unspecified
      * if the this attribute's value is added, changed,
      * or removed.
      *<p>
      * By default, the value returned is that passed to the
      * constructor and/or manipulated using the add/replace/remove methods.
      * A subclass may override this to retrieve the values dynamically
      * from the directory.
      *
      * @return A non-null enumeration of this attribute's values.
      * Since this is a single valued attribute, this operation is not useful
      * and is only implemented in order to adhere to the Attribute interface.
      * Use get() to return the value insted.
      * If the attribute has no value, an empty enumeration
      * is returned.
      * @exception NamingException
      *		If a naming exception was encountered while retrieving
      *		the value.
      */
   public NamingEnumeration getAll() throws NamingException 
   {
      return new ValuesEnumImpl();
   }

    /**
      * Retrieves the attribute's value.
      *<p>
      *
      * @return A possibly null object representing the attribute's value.
      * @exception NamingException
      *		If a naming exception was encountered while retrieving
      *		the value.
      * @exception java.util.NoSuchElementException
      *		If this attribute has no value.
      */
   public Object get() throws NamingException 
   {
      if (!hasValue) 
      {
         throw new NoSuchElementException("Attribute " + getID() + " has no value");
      } 
      else 
      {
         return value;
      }
   }

    /**
      * Retrieves the number of values in this attribute.
      *
      * @return The nonnegative number of values in this attribute.
      */
   public int size() 
   {
      return hasValue ? 1 : 0;
   }

    /**
      * Retrieves the id of this attribute.
      *
      * @return The id of this attribute. It cannot be null.
      */
    public String getID() {
	return attrID;
    }

    /**
      * Determines whether a value is in this attribute.
      * By default, <tt>Object.equals()</tt> is used when comparing <tt>attrVal</tt>
      * with this attribute's values except when <tt>attrVal</tt> is an array.
      * For an array, each element of the array is checked using <tt>Object.equals()</tt>.
      * A subclass may use schema information to determine equality.
      *
      * @param attrVal The possibly null value to check.
      * @return true if <tt>attrVal</tt> is one of this attribute's values; false otherwise.
      * @see #equals
      */
   public boolean contains(Object attrVal) 
   {
      if (hasValue)
      {
         if (attrVal == null)
         {
            if (value == null)
               return true;
            return false;
         }
         else
         {
            if (value == null)
               return false;
            return value.equals(attrVal);            
         }
      }
      return false;
   }

    /**
      * Replaces the value of this attribute. If <tt>attrVal</tt> is already in the
      * attribute, this method does nothing.
      *<p>
      * By default, <tt>Object.equals()</tt> is used when comparing <tt>attrVal</tt>
      * with this attribute's values except when <tt>attrVal</tt> is an array.
      * For an array, each element of the array is checked using <tt>Object.equals()</tt>.
      * A subclass may use schema information to determine equality.
      *
      * @param attrVal The new possibly null value to add.
      * @return true If this attribute did not already have <tt>attrVal</tt>.
      */
   public boolean add(Object attrVal) 
   {
      if (contains(attrVal)) 
      {
         return false;
      } 
      else 
      {
         value = attrVal;
         return true;
      }
   }

    /**
      * Removes a specified value from this attribute.
      * If <tt>attrval</tt> is not in this attribute, just ignore.
      *<p>
      * By default, <tt>Object.equals()</tt> is used when comparing <tt>attrVal</tt>
      * with this attribute's values except when <tt>attrVal</tt> is an array.
      * For an array, each element of the array is checked using <tt>Object.equals()</tt>.
      * A subclass may use schema information to determine equality.
      *
      * @param attrVal The possibly null value to remove from this attribute.
      * @return true if the value was removed; false otherwise.
      */
   public boolean remove(Object attrval) 
   {
      if (contains(attrval))
      {
         hasValue = false;
         value = null;
         return true;
      }
      return false;
   }

    /**
      * Removes all values from this attribute.
      *
      */
   public void clear() 
   {
      hasValue = false;
      value = null;
   }

    /**
      * Retrieves the syntax definition associated with this attribute.
      * An attribute's syntax definition specifies the format
      * of the attribute's value(s). Note that this is different from
      * the attribute value's representation as a Java object. Syntax
      * definition refers to the directory's notion of <em>syntax</em>.
      *<p>
      * For example, even though a value might be
      * a Java String object, its directory syntax might be "Printable String"
      * or "Telephone Number". Or a value might be a byte array, and its
      * directory syntax is "JPEG" or "Certificate".
      * For example, if this attribute's syntax is "JPEG",
      * this method would return the syntax definition for "JPEG".
      * <p>
      * The information that you can retrieve from a syntax definition
      * is directory-dependent.
      *
      * This method by default throws OperationNotSupportedException. A subclass
      * should override this method if it supports schema.
      * @return This attribute's syntax definition.
      * @exception OperationNotSupportedException If getting the schema
      * 	is not supported.
      * @exception NamingException If a naming exception occurs while getting
      *		the schema.
      */

    public DirContext getAttributeSyntaxDefinition() throws NamingException {
	    throw new OperationNotSupportedException("attribute syntax");
    }

    /**
      * Retrieves this attribute's schema definition.
      * An attribute's schema definition contains information
      * such as whether the attribute is multivalued or single-valued,
      * the matching rules to use when comparing this attribiute's values.
      *
      * The information that you can retrieve from an attribute definition
      * is directory-dependent.
      *
      * This method by default throws OperationNotSupportedException. A subclass
      * should override this method if it supports schema.
      * @return This attribute's schema definition.
      * @exception OperationNotSupportedException If getting the schema
      * 	is not supported.
      * @exception NamingException If a naming exception occurs while getting
      *		the schema.
      */
    public DirContext getAttributeDefinition() throws NamingException {
	throw new OperationNotSupportedException("attribute definition");
    }


    //----------- Methods to support ordered multivalued attributes


    /**
      * Determines whether this attribute's values are ordered.
      * If an attribute's values are ordered, duplicate values are allowed.
      * If an attribute's values are unordered, they are presented
      * in any order and there are no duplicate values.
      * @return true if this attribute's values are ordered; false otherwise.
      * @see #get(int)
      * @see #remove(int)
      * @see #add(int, java.lang.Object)
      * @see #set(int, java.lang.Object)
      */
   public boolean isOrdered()
   {
      return (false);
   }

    /**
     * Retrieves the attribute value from the ordered list of attribute values.
     * This method returns the value at the <tt>ix</tt> index of the list of
     * attribute values.
     * If the attribute values are unordered,
     * this method returns the value that happens to be at that index.
     * @param ix The index of the value in the ordered list of attribute values.
     * 0 <= <tt>ix</tt> < <tt>size()</tt>.
     * @return The possibly null attribute value at index <tt>ix</tt>; 
     *   null if the attribute value is null.
     * @exception NamingException If a naming exception was encountered while
     * retrieving the value.
     * @exception IndexOutOfBoundsException If <tt>ix</tt> is outside the specified range.
     */
   public Object get (
         int ix)
      throws NamingException
   {
      throw (new IndexOutOfBoundsException ());
   }

    /**
     * Removes an attribute value from the ordered list of attribute values.
     * This method removes the value at the <tt>ix</tt> index of the list of
     * attribute values. 
     * If the attribute values are unordered,
     * this method removes the value that happens to be at that index.
     * Values located at indices greater than <tt>ix</tt> are shifted up towards
     * the front of the list (and their indices decremented by one).
     *
     * @param ix The index of the value to remove.
     * 0 <= <tt>ix</tt> < <tt>size()</tt>.
     * @return The possibly null attribute value at index <tt>ix</tt> that was removed; 
     *   null if the attribute value is null.
     * @exception IndexOutOfBoundsException If <tt>ix</tt> is outside the specified range.
     * @since JNDI 1.2 / Java 2 Platform, Standard Edition, v 1.3
     */
   public Object remove (
         int ix)
   {
      throw (new IndexOutOfBoundsException ());
   }

    /**
     * Adds an attribute value to the ordered list of attribute values.
     * This method adds <tt>attrVal</tt> to the list of attribute values at
     * index <tt>ix</tt>.
     * Values located at indices at or greater than <tt>ix</tt> are 
     * shifted down towards the end of the list (and their indices incremented 
     * by one).
     *
     * @param ix The index in the ordered list of attribute values to add the new value.
     * 0 <= <tt>ix</tt> <= <tt>size()</tt>.
     * @param attrVal The possibly null attribute value to add; if null, null is
     * the value added.
     * @exception IndexOutOfBoundsException If <tt>ix</tt> is outside the specified range.
     * @since JNDI 1.2 / Java 2 Platform, Standard Edition, v 1.3
     */
   public void add (
         int ix,
         Object attrVal)
   {
      throw (new IndexOutOfBoundsException ());
   }


    /**
     * Sets an attribute value in the ordered list of attribute values.
     * This method sets the value at the <tt>ix</tt> index of the list of
     * attribute values to be <tt>attrVal</tt>. The old value is removed.
     * If the attribute values are unordered,
     * this method sets the value that happens to be at that index.
     *
     * @param ix The index of the value in the ordered list of attribute values.
     * 0 <= <tt>ix</tt> < <tt>size()</tt>.
     * @param attrVal The possibly null attribute value to use. 
     * If null, 'null' replaces the old value.
     * @return The possibly null attribute value at index ix that was replaced. 
     *   Null if the attribute value was null.
     * @exception IndexOutOfBoundsException If <tt>ix</tt> is outside the specified range.
     * @since JNDI 1.2 / Java 2 Platform, Standard Edition, v 1.3
     */
   public Object set (
         int ix,
         Object attrVal)
   {
      throw (new IndexOutOfBoundsException ());
   }


   class ValuesEnumImpl implements NamingEnumeration 
   {
      boolean hasMore = true;
      
      public boolean hasMoreElements() 
      {
         return hasMore;
      }

      public Object nextElement() 
      {
         hasMore = false;
         return(value);
      }

      public Object next() throws NamingException 
      {
         hasMore = false;
         return(value);
      }

      public boolean hasMore() throws NamingException 
      {
         return hasMore;
      }

      public void close ()
         throws NamingException
      {
      }

   }

}


