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

  $Archive: /njcl_v2/src/com/novell/service/file/nw/naming/FileDirContext.java $
  $Revision: 9 $
  $Modtime: 1/28/00 12:45p $
 
  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.naming;

import java.io.IOException;
import java.util.Hashtable;

import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.NamingEnumeration;

import javax.naming.directory.Attribute;
import javax.naming.directory.AttributeModificationException;
import javax.naming.directory.Attributes;
import javax.naming.directory.ModificationItem;

import com.sun.jndi.toolkit.ctx.Continuation;

import com.novell.utility.naming.directory.StaticAttributeValue;
import com.novell.utility.naming.directory.NAttributes;

import com.novell.service.file.nw.NetwareFile;
import com.novell.service.file.nw.DataAccessableParameters;

import com.novell.service.jncp.NSIException;

import com.novell.java.io.DataAccessable;
import com.novell.java.io.RandomAccessNotSupportedException;
import com.novell.java.io.spi.DataAccessor;

/** @internal
 * Implements the File interface from the com.novell.service.file package, which
 * provides handy access to the input stream and output stream of the file.
 * Otherwise, from a Context standpoint, FileDirContext is the same as
 * DirEntryDirContext.  FileDirContext also introduces special attributes.
 */
public class FileDirContext extends DirEntryDirContext
                         implements NetwareFile
{
   private DataAccessable streamOpener;
   private StaticAttributeValue[] attributeValues = null;

   /**
    * Constructs a FileDirContext
    *
    * @param dir                 The parent directory.
    * @param atomicName          The atomic name of the file.
    * @param environment         The File system environment object
    * @exception NamingException When an error occurs getting the server
    *                            name from the connection or when any
    *                            other error occurs.
    */
   public FileDirContext(
               DirectoryDirContext dir,
               String atomicName,
               FSEnvironment environment)
      throws NamingException
   {
      super(dir, atomicName, environment);

      this.streamOpener = dir;

      try
      {
         setupDirEntryStaticAttributeValues();
//      attributeValues = new StaticAttributeValue[2];
//      attributeValues[0] = new SparseFileImpl(this, callsService);
//      attributeValues[1] = new CompressedFileSizeImpl(this, callsService);
      }catch (NSIException nsie)
      {
         NamingException ne = new NamingException();
         ne.setRootCause(nsie);
         throw ne;
      }
   }

/* **************************************************************************
   DataAccessable interface implementation
****************************************************************************/

   /**
    * Opens a stream for use with NInputStream and NOutputStream.
    *
    * <p>This is the DataAccessable default method that opens a stream.
    * <p/>
    *
    * @param type                Tells if the stream should be read/write or 
    *                            random access.
    *
    * @return                    A valid, open stream for the parameters
    *                            described above.
    *
    * @exception  IOException    When an error occurred opening the stream.
    */

   public DataAccessor openStream(int type)
      throws IOException
   {
      int openFlags = 0;
      if (type == DataAccessor.READ)
         openFlags = DataAccessableParameters.READ;
      else
         if (type == DataAccessor.WRITE)
            openFlags = DataAccessableParameters.WRITE;
         else
            if (type == DataAccessor.RANDOM)
               openFlags = DataAccessableParameters.READ | DataAccessableParameters.WRITE;
            else
               throw new IOException(Integer.toString(type));

      return openStream(
               type, 
               new DataAccessableParameters(
                     openFlags, 
                     DataAccessableParameters.DEFAULT_DATASELECTOR));
   }

   /**
    * Opens a stream for use with NInputStream and NOutputStream.
    *
    * <p>This is the DataAccessable custom data method that opens a Stream. 
    * <p/>
    *
    * @param custom              The DataAccessableParameters custom data 
    *                            structure
    *
    * @return                    A valid, open open stream for the parameters
    *                            described above.
    *
    * @exception  IOException    When an error occurred opening the stream.
    */

   public DataAccessor openStream(int type, Object custom)
      throws IOException
   {
      return streamOpener.openStream(getName(), type, custom);
   }

   /**
    * Opens an EAAccessor for use with NInputStream and NOutputStream.
    *
    * <p>This is the DataAccessable default openStream interface method, that
    * opens a subordinate stream.  The only type of subordinate stream
    * that a file can open is an Extended Attribute, so this method will 
    * default to trying to open one.
    * <p/>
    *
    * @param name                The name of the extended attribute to open.
    *
    * @param type                Tells if the stream should be read/write or 
    *                            random access.
    *
    * @return                    A valid, open EAAccessor instance for the
    *                            parameters described above.
    *
    * @exception  IOException    When an error occurred opening the
    *                            extended attribute.
    */

   public DataAccessor openStream(String name, int type)
      throws IOException
   {
      int openFlags = 0;
      if (type == DataAccessor.READ)
         openFlags = DataAccessableParameters.READ;
      else
         if (type == DataAccessor.WRITE)
            openFlags = DataAccessableParameters.WRITE;
         else
            if (type == DataAccessor.RANDOM)
               throw new RandomAccessNotSupportedException();
            else
               throw new IOException(Integer.toString(type));

      return openStream(
               name, 
               type,
               new DataAccessableParameters(
                        openFlags, 
                        DataAccessableParameters.EA_STREAM_SELECTOR));
   }

   /**
    * Opens an EAAccessor for use with NInputStream and NOutputStream.
    *
    * <p>This is the DataAccessable custom data interface method, that
    * opens a subordinate stream.  The only type of subordinate stream
    * that a file can open is an Extended Attribute, so this method will 
    * throw an exception if a Extended Attribute can not be opened.
    * <p/>
    *
    * @param name                The name of the extended attribute to open.
    *
    * @param custom              The DataAccessableParameters custom data 
    *                            structure
    *
    * @return                    A valid, open EAAccessor instance for the
    *                            parameters described above.
    *
    * @exception  IOException    When an error occurred opening the
    *                            extended attribute.
    */

   public DataAccessor openStream(String name, int type, Object custom)
      throws IOException
   {
      DataAccessableParameters dap = (DataAccessableParameters) custom;
      name = dap.checkParameters(name, getNameSpace());

      // see if they want to open an Extended attribute
      DataAccessor accessor = super.openStream (name, type, custom);

      if (accessor != null)
         return (accessor);   // it is an extended attribute

      throw new IOException ();
   }

   public boolean supportsInputStream()
   {
      return (true);
   }

   public boolean supportsOutputStream()
   {
      return (true);
   }

   public boolean supportsRandomAccess()
   {
      return (true);
   }

   public boolean supportsSubordinateInputStream()
   {
      return (true);
   }

   public boolean supportsSubordinateOutputStream()
   {
      return (true);
   }

   public boolean supportsSubordinateRandomAccess()
   {
      return false;
   }

/* **************************************************************************
   DSContext methods that are overridden from DirEntryDirContext
****************************************************************************/

   /**
    * Get attributes.
    *
    * <p>Retrieves all the attributes associated with named object.
    * </p>
    *
    * @param name                The string name of the object for which 
    *                            to retrieve the attributes.  If name is the 
    *                            empty string, retrieves the attributes of 
    *                            this context object.
    * @param attrIds             The list of attribute ID's to be included
    *                            in the returned AttributeSet. 
    * @param cont                The continuation object to be maintained
    *                            for downstream federation.
    * @return                    The attributes associated with 'name' and 
    *                            requested by the itemized attrIds list.
    * @exception NamingException When an error occurs getting the
    *                            attributes.
    */
   protected Attributes c_getAttributes(
                           Name name, 
                           String[] attrIds,
                           Continuation cont)
      throws NamingException
   {
      if (!name.isEmpty())
      {
         resolveNext(name, cont);
         return null;
      }

      Attributes as = new NAttributes(true);
//         addAttributes(as, attrIds, attributeValues);       // do mine
      addAttributes(as, attrIds, super.attributeValues); // do DirEntry
      cont.setSuccess();
      return (as);
   }

   /**
    * Modify attributes.
    *
    * <p>Modifies the attributes associated with named object.
    * </p>
    *
    * @param name                The string name of the object for which 
    *                            to modify the attributes.  If name is the 
    *                            empty string, modifies the attributes of 
    *                            this context object.
    * @param mod_op              Type of modification to do to with the
    *                            attrs given.  Possible values are:
    *                            DSContext.ADD_ATTRIBUTE,
    *                            DSContext.REPLACE_ATTRIBUTE, and
    *                            DSContext.DELETE_ATTRIBUTE
    *                            in the returned AttributeSet. 
    * @param attrs               The AttributeSet to be applied, in the 
    *                            manner specified in mod_op.
    * @param cont                The continuation object to be maintained
    *                            for downstream federation.
    * @exception AttributeModificationException When it is invalid to
    *                            perform this operation on this context.
    * @exception NamingException When an error occurs modifying the
    *                            attributes.
    */
   protected void c_modifyAttributes(
                     Name name, 
                     int mod_op,
                     Attributes attrs,
                     Continuation cont) 
      throws NamingException
   {
      if (!name.isEmpty())
      {
         resolveNext(name, cont);
         return;
      }

      NamingEnumeration enum = attrs.getAll();

      if (enum == null)    // if the set is empty, do nothing!
         return;        

      while (enum.hasMoreElements())
      {
         Attribute attr = (Attribute)enum.next();
//            if (!modifyAttributes(
//                     attr, mod_op, attributeValues, name, cont))
//            {
            if (!modifyAttributes(
                     attr, mod_op, 
                     super.attributeValues, name, cont))
            {
               cont.setError(this, name);
               throw cont.fillInException(
                              new 
                              AttributeModificationException(
                                 attr.getID()));
            }
//            }
      }
      cont.setSuccess();
   }

   /**
    * Modify attributes.
    *
    * <p>Modifies the attributes associated with named object.
    * </p>
    *
    * @param name                The string name of the object for which 
    *                            to modify the attributes.  If name is the 
    *                            empty string, modifies the attributes of 
    *                            this context object.
    * @param mod_op              Type of modification to do to with the
    *                            attrs given.  Possible values are:
    *                            DSContext.ADD_ATTRIBUTE
    *                            DSContext.REPLACE_ATTRIBUTE
    *                            DSContext.DELETE_ATTRIBUTE
    *                            in the returned AttributeSet. 
    * @param attrs               The AttributeSet to be applied, in the 
    *                            manner specified in mod_op.
    * @param cont                The continuation object to be maintained
    *                            for downstream federation.
    * @exception AttributeModificationException When it is invalid to
    *                            perform this operation on this context.
    * @exception NamingException When an error occurs modifying the
    *                            attributes.
    */
   protected void c_modifyAttributes(
                     Name name, 
                     ModificationItem[] mods,
                     Continuation cont) 
       throws NamingException
   {
      if (!name.isEmpty())
      {
         resolveNext(name, cont);
         return;
      }

      if (mods == null)
         return;  // if they give me nothing, do nothing

      ModificationItem mi = null;
      int index = 0;
      int max = mods.length;
      try
      {
         for (; index < max; index++)
         {
            mi = mods[index];
            Attribute attr = mi.getAttribute();
//            if (!modifyAttributes(
//                     attr, mi.getModificationOp(), 
//                     attributeValues, name, cont))
//            {
               if (!modifyAttributes(
                        attr, mi.getModificationOp(), 
                        super.attributeValues, name, cont))
               {
                  cont.setError(this, name);
                  throw cont.fillInException(
                                 new 
                                 AttributeModificationException(
                                    attr.getID()));
               }
//            }
         }
      } catch (AttributeModificationException e)
      {
         // Add enumeration in e to remainder of mods.
         AttributeModificationException ame =
               new AttributeModificationException();

         ModificationItem[] me = new ModificationItem[mods.length - index];

         int i = index;
         index = 0;
         for (; i < max; i++)
         {
            me[index++] = mods[i];
         }
         ame.setUnexecutedModifications(me);
         cont.setError(this, name);
         throw cont.fillInException(ame);
      }
      cont.setSuccess();
   }

   protected StaticAttributeValue getSubsAttributeValue(String attrId)
   {
      StaticAttributeValue value;
      
      if ((value = getAttributeValue(attrId, attributeValues)) == null)
         value = getAttributeValue(attrId, super.attributeValues);
      return value;
   }
}
