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

  $Archive: /njcl_v2/src/com/novell/service/file/nw/ExtendedAttribute.java $
  $Revision: 6 $
  $Modtime: 12/17/99 10:41a $
 
  Copyright (c) 1998 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.service.file.nw;

import com.novell.service.rfc1960.SearchStringComponent;
import com.novell.service.rfc1960.Rfc1960Parser;

/** 
 * Provides constructors and methods for the support of an extended
 * attribute. An extended attribute is composed of a name and a
 * corresponding byte array of data.
 * 
 * <p>WARNING: This class results in a mutable object. For versitility 
 * code block synchronization has been implemented in the various jndi
 * search and modify methods of this classes implementation to provide
 * for thread safe operation.
 * </p>
 * <p>If you have a multi-threaded application and a code segment of that
 * application uses get methods of this class, and there is a potential 
 * of another thread randomly using corresponding set methods of this class,
 * you should enclose the calls to the get method of this class in a 
 * synchronized code block.
 * </p>
 * <p>If you have a multi-threaded application and a code segment of that
 * application uses set methods of this class, and there is a potential 
 * of another thread randomly using corresponding get methods of this class,
 * you should enclose the calls to the set methods of this class in a 
 * synchronized code block.
 * </p>
 *
 * @see EAEnumerator
 */
 
public class ExtendedAttribute implements Cloneable
{
/* **************************************************************************
   jndi naming interface defines
****************************************************************************/

   /**
    * Attribute ID of ExtendedAttribute
    *
    * <p>(ATTRIBUTE_ID = "Extended Attribute")
    * </p>
    */

   public static final String ATTRIBUTE_ID = "Extended Attribute";

   /**
    * Schema name of ExtendedAttribute
    *
    * <p>(SCHEMA_BINDING_NAME = ATTRIBUTE_ID + " Definition")
    * </p>
    */

   public static final String SCHEMA_BINDING_NAME = 
         ATTRIBUTE_ID + " Definition";

   /**
    * Schema syntax of ExtendedAttribute
    *
    * <p>(COMPARE_SYNTAX_STRING = 
    *       "(" +
    *       SearchStringComponent.EQUALS_STRING + "," +
    *       SearchStringComponent.PRESENT_STRING + "," +
    *       SearchStringComponent.SUBSTRING_STRING + 
    *       ")")
    * </p>
    */

   public static final String COMPARE_SYNTAX_STRING = 
         new String(
               "(" +
               SearchStringComponent.EQUALS_STRING + "," +
               SearchStringComponent.PRESENT_STRING + "," +
               SearchStringComponent.SUBSTRING_STRING + 
               ")");

/* **************************************************************************
   compareString field name defines
****************************************************************************/

   /**
    * Compare Fields
    *
    * <p>(NAME_FIELD = "Name")
    * </p>
    * @see #setCompareString
    * @see #equals
    */
   public static final String NAME_FIELD = new String("Name");

/* **************************************************************************
   ExtendedAttributes state data members
****************************************************************************/

   private String eaName;
   private byte[] eaData;

   private String compareString;
   private Rfc1960Parser ssParser = null;
   private boolean firstParser = true;

/* **************************************************************************
* constructors
****************************************************************************/

   /**
    * Constructs an ExtendedAttribute object with name and data set to 
    * NULL.
    * 
    * @see #setName
    * @see #setData
    */
    
   public ExtendedAttribute()
   {
      this(null,null);
   }

   /**
    * Constructs an ExtendedAttribute object with the name set to the 
    * passed in parameter.
    * 
    * @param name The extended attribute's name
    *
    * @see #setData
    */
    
   public ExtendedAttribute(String name)
   {
      this(name, null);
   }

   /**
    * Constructs an ExtendedAttribute object with the data set to the 
    * passed in parameter.
    * 
    * @param data The extended attribute's data.
    *
    * @see #setName
    */
    
   public ExtendedAttribute(byte[] data)
   {
      this(null, data);
   }

   /**
    * Constructs an ExtendedAttribute object with the name and data set 
    * to the passed in parameters.
    * 
    * @param name The extended attribute's name.
    * @param data The extended attribute's data.
    */
    
   public ExtendedAttribute(String name, byte[] data)
   {
      eaName = new String(name);
      if (data != null)
      {
         int size = data.length;
         eaData = new byte[size];
         for (int i=0; i < size; i++)
            eaData[i] = data[i];
      }else
         eaData = null;
   }

/* **************************************************************************
* public accessors
****************************************************************************/

   /**
    * Returns the name associated with this extended attribute.
    *
    * @return The current name assigned to this object.
    */
    
   public String getName()
   {
      return (eaName);
   }

   /**
    * Sets the name to be associated with this extended attribute.
    *
    * @param name The extended attribute's name.
    */
    
   public void setName(String name)
   {
      eaName = new String(name);
   }

   /**
    * Returns the Data associated with this extended attribute.
    *
    * @return The current object data in a byte array.
    */
    
   public byte[] getData()
   {
      return (eaData);
   }

   /**
    * Sets the Data to be associated with this extended attribute.
    * 
    * @param data The extended attributes data.
    */
    
   public void setData(byte[] data)
   {
      if (data != null)
      {
         int size = data.length;
         eaData = new byte[size];
         for (int i=0; i < size; i++)
            eaData[i] = data[i];
      }else
         eaData = null;
   }

/* **************************************************************************
 public compareString support methods
****************************************************************************/

   /**
    * Sets the compare string value used to allow individual compares on
    * the eaName field. The compare string uses the RFC1960 (LDAP)
    * search string format.
    *
    * <p>See the various SearchStringComponent compare methods referenced
    * below for detailed information on the operation types allowed for
    * String and integer data members.
    *
    * <p>These fields have taken the names of their corresponding methods
    * minus the get or set prefix. Given the following string:
    * <i>"(&(Name=*printer*)(ObjectId>=2)(Rights>=128))"</i>.
    * The equals method returns TRUE if the name contains the substring
    * "printer" and the objectId is not 2, and the rights are set to modify
    * (TA_MODIFY). If the approximate operator type is used on the Rights
    * field the various bits of the operand value are checked, and if any of
    * them are set, equals will return TRUE. For example: (Rights~=3) returns
    * TRUE if either the TA_READ or the TA_WRITE bits are set, regardless
    * of what other bits are set.
    * </p>
    * <p>If the string passed in is not a valid RFC1960 formatted string,
    * this method will throw an IllegalArgumentException. If the compareString 
    * value is NULL, RFC1960 formatted compares will be disabled.
    * </p>
    *
    * @param compareString The RFC1960 formatted search string. NULL 
    *                      disables this compare functionality.
    *
    * @exception IllegalArgumentException When the string passed in is not
    *            a valid RFC1960 formatted string.
    *
    * @see #equals
    * @see #NAME_FIELD
    */
    
   public void setCompareString(String compareString)
   {
      firstParser = true;
      if (compareString != null)
      {
         this.compareString = new String(compareString);
         ssParser = new Rfc1960Parser(compareString);
      }else
      {
         this.compareString = null;
         ssParser = null;
      }
   }

   /**
    * Returns the current value of compareString.
    *
    * @return The current value of compareString.
    *
    * @see Trustee#setCompareString
    */
    
   public String getCompareString()
   {
      return compareString;
   }


   /**
    * Returns the RFC1960 search string parser for the compare string.
    * 
    * <p>Returns a Rfc1960Parser object that was instantiated with the
    * compareString last set by the setCompareString() method.</p>
    *
    * @return The Rfc1960Parser object for the current compare String.
    *         NULL is returned if the compare string has not been set.
    */
    
   public Rfc1960Parser getSearchStringParser()
   {
      if (firstParser)
      {
         firstParser = false;
         return ssParser;
      }else
      {
         if (compareString == null)
            return null;
         return new Rfc1960Parser(compareString);
      }
   }

/* **************************************************************************
* overriden Object methods
****************************************************************************/

   /**
    * Compares the input object against this object. If the input object
    * is a byte array, the compare string is ignored and the eaData is
    * compared. If the input object is an ExtendedAttribute and has 
    * a RFC1960 compare string, this method will do the comparision based
    * on the commands in the compare string. If the operationType is 
    * illegal for the data type being compared, an IllegalArmumentException 
    * will be thrown.</p>
    *
    * @param obj The object to compare.
    * @return A boolean set to TRUE if equals, otherwise FALSE. 
    *
    * @exception IllegalArgumentException When the operationType is illegal
    *            for the data type being compared.
    *
    * @see #setCompareString
    * @see #NAME_FIELD
    */
    
   public boolean equals(Object obj)
   {
      if (obj instanceof byte[])
         return compareData((byte[])obj);

      if (!(obj instanceof ExtendedAttribute))
         return false;

      ExtendedAttribute ea = (ExtendedAttribute)obj;

      Rfc1960Parser ssp = ea.getSearchStringParser();
      if (ssp == null)
      {
         // do a simple compare

         if (obj == this)
            return (true);

         if (!ea.getName().equalsIgnoreCase(eaName))
            return (false);

         if (!SearchStringComponent.compareStringsEqual(
                                    compareString, 
                                    ea.getCompareString()))
            return false;

         return compareData(ea.getData());
      }

      while (ssp.hasMoreElements())
      {
         SearchStringComponent ssc = ssp.next();
         String name = ssc.getAttributeId();
         boolean compared = false;

         if (name.equals(NAME_FIELD))
         {
            compared =ssc.compareString(
                              ssc.getOperationType(), 
                              ssc.operandReplacement() ?
                                 ea.getName() :
                                 ssc.getOperand(),
                              getName(),
                              true);
         }else
            compared = false;
         ssp.setCompareResult(ssc, compared);
      }
      return ssp.compared();
   }

   /**
    * Converts the object to a String.
    * 
    * <p>A String is created that contains the name of the attribute,
    * the size of the data and a comma separated list of its first
    * 128 data elements in the following format: "eaName: s,
    * compareString: s,  eaData length: n, eaData: 1, 2, 3, ..."</p>
    *
    * <p>This method overrides the Object.toString method. 
    *
    * @return The object information converted to a String.
    */
    
   public String toString()
   {
      int size = eaData.length;
      StringBuffer buf = new StringBuffer(
                                 "eaName: " + eaName + 
                                 ", compareString: " + compareString + 
                                 ", eaData length: " + size + " eaData: ");

      if (size > 128)
         size = 128;

      for (int i=0; i < size; i++)
      {
         buf.append(eaData[i] + ", ");
      }
      if (eaData.length > 128)
         buf.append("...");
      return buf.toString();
   }

   /**
    * Instantiates a new object of this type with all new references,
    * but with the same values within those references.
    * 
    * @return A new Trustee object with the same values.
    */
    
   public Object clone()
   {
      ExtendedAttribute obj = null;
      try
      {
         obj = (ExtendedAttribute) super.clone();
      }
      catch (CloneNotSupportedException e)
      {
         // this shouldn't happen, since we are Cloneable
         throw (new InternalError());
      }

      obj.eaName = eaName;
      obj.eaData = new byte[eaData.length];
      for (int i=0; i < eaData.length; i++)
         obj.eaData[i] = eaData[i];
      obj.compareString = compareString;
      obj.ssParser = ssParser;
      obj.firstParser = firstParser;
      obj.setCompareString(compareString);
      return obj;
   }

/* **************************************************************************
* private methods
****************************************************************************/

   private boolean compareData(byte[] data)
   {
      if (data == eaData)
         return true;

      if (data.length != eaData.length)
         return (false);

      for (int i = 0; i < data.length; i++)
         if (data[i] != eaData[i])
            return (false);

      return true;
   }
}
