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

  $Archive: /njcl_v2rmi/src/com/novell/service/file/nw/naming/DirectoryDirContext.java $
  $Revision: 29 $
  $Modtime: 11/13/03 12:23p $
 
  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.io.FileNotFoundException;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import java.rmi.RemoteException;

import javax.naming.Binding;
import javax.naming.NamingEnumeration;
import javax.naming.Context;
import javax.naming.ContextNotEmptyException;
import javax.naming.InsufficientResourcesException;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NameClassPair;
import javax.naming.NamingException;
import javax.naming.NameNotFoundException;
import javax.naming.OperationNotSupportedException;
import javax.naming.directory.Attribute;
import javax.naming.directory.AttributeModificationException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.spi.ResolveResult;

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

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

import com.novell.service.file.nw.NetwareDirectory;
import com.novell.service.file.nw.DirectoryEntryInformation;
import com.novell.service.file.nw.DirectorySpaceInformation;
import com.novell.service.file.nw.NameSpace;
import com.novell.service.file.nw.DataAccessableParameters;
import com.novell.service.file.nw.NFileOutputStream;

import com.novell.service.file.nw.calls.DirectoryEntryInfoImpl;
import com.novell.service.file.nw.calls.DirectorySpaceInfoImpl;
import com.novell.service.file.nw.calls.NativeFileAccessor;

import com.novell.service.jncp.NSIException;
import com.novell.service.jncp.ServerErrors;

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

import com.novell.service.toolkit.jcl.NetwareOSName;

import com.novell.service.session.SessionException;
import com.novell.service.session.xplat.CallsService;

/** @internal
 * Represents a NetWare directory.  From a directory, you can: list all
 * bindings, bind Referenceable objects in, unbind bindings, look up
 * bound objects, open files, create files, create dirs, delete files
 * and dirs, and more.
 *
 * <p>Subordinate to a directory are any files which that directory contains.
 * The directory treats these files as Subcontexts.  'unbind()' will
 * not unbind these files, because they are not explicit bindings.
 * Files and Directories can be removed by calling 'destroySubcontext()'.
 *
 * <p>DirectoryDirContext implements the StreamOpener interface.  This
 * interface allows NWInputStream and NWOutputStream to open input and
 * output streams on subordinate files.  Because DirectoryDirContext is also
 * a DirEntryDirContext, it is able to open streams on extended attributes.
 * This is accomplished by passing in 0xFF for the dataStream parameter on
 * the 'openStream()' method.
 * </p>
 */
public class DirectoryDirContext extends DirEntryDirContext
                              implements NetwareDirectory
{
   protected static final int DIRMASK =
      DirectoryEntryInfoImpl.FA_READ_ONLY |
      DirectoryEntryInfoImpl.FA_HIDDEN |
      DirectoryEntryInfoImpl.FA_SYSTEM |
      DirectoryEntryInfoImpl.FA_DIRECTORY;

   protected static final int FILEMASK =
      DirectoryEntryInfoImpl.FA_READ_ONLY |
      DirectoryEntryInfoImpl.FA_HIDDEN |
      DirectoryEntryInfoImpl.FA_SYSTEM;

   protected static final int SEARCHRETURNMASK =
      DirectoryEntryInfoImpl.IM_ENTRY_NAME |
      DirectoryEntryInfoImpl.IM_ATTRIBUTES;

   // OC_MODE_OPEN in <nwnamspc.h>
   protected static final int OC_MODE_OPEN = 0x01;

   // OC_MODE_REPLACE in <nwnamspc.h>
   protected static final int OC_MODE_REPLACE = 0x02;

   // OC_MODE_REPLACE | OC_MODE_CREATE in <nwnamspc.h>
   protected static final int OC_MODE_CREATE = 0x0A;

   protected StaticAttributeValue[] attributeValues = null;

   private static final int DSI_SAV = 0;

   /**
    * Constructs a NetWare diretory.
    *
    * @param      dir            The parent directory.
    * @param      atomicName     The atomic name of this directory.
    * @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 DirectoryDirContext (
               DirectoryDirContext dir,
               String atomicName,
               FSEnvironment environment)
      throws NamingException
   {
      super(dir, atomicName, environment);

      attributeValues = new StaticAttributeValue[1];
      try
      {
         setupDirEntryStaticAttributeValues();
         setupDirectoryStaticAttributeValues();
      }catch (NSIException nsie)
      {
         NamingException ne = new NamingException();
         ne.setRootCause(nsie);
         throw ne;
      }
   }

/* *************************************************************************
* Directory and Volume common interface implementation
****************************************************************************/

   /**
    * Returns the DirectorySpaceInformation object associated with this file.
    *
    * @return                    The DirectorySpaceInformation object.
    */
   public DirectorySpaceInformation getDirectorySpaceInformation()
   {
      return (DirectorySpaceInformation)
         attributeValues[DSI_SAV].getStaticInterface();
   }

   /**
    * Modifies the Backend DirectorySpaceInformation object associated with 
    * this directory.
    *
    * @param      dsi            The DirectorySpaceInformation modify value.
    * @param      mod_op         The modification to preform if the
    *                            attribute is part of the set, valid values
    *                            are:
    *                            DirContext.REPLACE_ATTRIBUTE.
    */
   public void setDirectorySpaceInformation(
      DirectorySpaceInformation dsi,
      int mod_op)
   {
      setStaticInterface(
         attributeValues[DSI_SAV], 
         mod_op, 
         attributeValues[DSI_SAV].getID(),
         dsi);
   }

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

   /**
    * Opens a subordinate stream for use with NInputStream and NOutputStream.
    *
    * <p>This is the DataAccessable default interface method that opens a
    * subordinate stream.  It will default to a File stream (verses a
    * Extended Attribute stream).
    * <p/>
    *
    * @param name                The name of the stream to open.
    *
    * @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(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)
               openFlags =
                  DataAccessableParameters.READ |
                  DataAccessableParameters.WRITE;
            else
               throw new IOException(Integer.toString(type));

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

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

   public DataAccessor openStream(String name, int type, Object custom)
      throws IOException
   {
      DataAccessableParameters dap = (DataAccessableParameters) custom;
      name = dap.checkParameters(name, environment.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

      int[] fileHandle = new int[1];
      // see OC_MODE_OPEN, OC_MODE_REPLACE and OC_MODE_CREATE in <nwnamspc.h>
      int[] openModes;

      // if both READ and WRITE are set, open the file, but don't destroy
      //   the existing contents of the file (for random access)
      // if just READ is set, then just try to open the file, but not create
      // if just WRITE is set, then first try to open it and replace all
      //   existing file content, then try to create it, if the open failed

      if ((dap.getOpenFlags() & DataAccessableParameters.READ) != 0 &&
          (dap.getOpenFlags() & DataAccessableParameters.WRITE) != 0)
      {
         openModes = new int[2];

         openModes[0] = OC_MODE_OPEN;   // OC_MODE_OPEN in <nwnamspc.h>
         openModes[1] = OC_MODE_CREATE; // OC_MODE_REPLACE | OC_MODE_CREATE
      }
      else if ((dap.getOpenFlags() & DataAccessableParameters.WRITE) != 0)
      {
         openModes = new int[2];

         openModes[0] = OC_MODE_REPLACE; // OC_MODE_REPLACE in <nwnamspc.h>
         openModes[1] = OC_MODE_CREATE;  // OC_MODE_REPLACE | OC_MODE_CREATE
      }
      else
      {
         openModes = new int[1];

         openModes[0] = OC_MODE_OPEN;   // OC_MODE_OPEN in <nwnamspc.h>
      }

      int ccode = 0;

      // this for loop progresses through progressively severe open modes
      //   in order to guarantee the opening of a certain data stream
      // first comes just plain OPEN, then come more severe opens, like
      //   REPLACE or CREATE
      for (int i = 0; i < openModes.length; i++)
      {
         ccode = 0;
         try
         {
            callsService.openNSEntry (
               0,     // no dir handle
               NameSpace.nameToNumber(environment.getNameSpace()),
               dap.getDataSelector(),
               addPath (name),
               openModes[i],
               0x07, // all normal, r/o, hidden, sys. files
               0x00, // normal attributes
               dap.getOpenFlags(),
               fileHandle);

            // if the operation was unsuccessful with this open mode, and
            //   the failure of the operation was caused by something other
            //   than the failure to open the file, then exit now with error
            //   code
         } catch (NSIException nsie)
         {
            ccode = nsie.getCCode();
            if (ccode != ServerErrors.NWE_PATH_NOT_LOCATABLE)
               break;

            // ccode is now 0x89FF -- this means that the operation failed
            //   progress to the next open mode
            ccode = 0x89FF;
         }
         catch (SessionException e)
         {
            throw new NSIException(e.getMessage(), 0, e);
         }

         // if the operation was successful with this open mode then break
         //   the process of going through open modes
         if (ccode == 0)
            break;
      }

      // this is a special exception that signals to anyone opening a
      //    file stream, why it failed.
      if (ccode != 0)
         throw new FileNotFoundException (Integer.toString (ccode));

      // determine which accessor to return
      String osName = System.getProperty("os.name");

      // currently does not support streams on any platform but win95,nt,nw
      try
      {
      return (new NativeFileAccessor (
                                          fileHandle[0],
                                          dap.getOpenFlags(),
                                          environment.getSession ()));
      }
      catch (NamingException e)
      {
         throw (new IOException (e.getMessage ()));
      }
   }

   /**
    * Opens a stream for use with NInputStream and NOutputStream.
    *
    * <p>This is the DataAccessable default method that opens a stream.
    * This functionality is not valid for a Directory context and
    * will always throw an IOException.
    * <p/>
    *
    * @param type                Tells if the stream should be read/write or
    *                            random access.
    *
    * @exception  IOException    Always
    */

   public DataAccessor openStream(int type)
      throws IOException
   {
      throw new SubordinateAccessOnlyException(getName());
   }

   /**
    * Opens a stream for use with NInputStream and NOutputStream.
    *
    * <p>This is the DataAccessable custom data method that opens a Stream.
    * This functionality is not valid for a Directory context and will always
    * throw an IOException.
    * <p/>
    *
    * @param type                Tells if the stream should be read/write or
    *                            random access.
    *
    * @param custom              The DataAccessableParameters custom data
    *                            structure
    *
    * @exception  IOException    Always
    */

   public DataAccessor openStream(int type, Object custom)
      throws IOException
   {
      throw new SubordinateAccessOnlyException(getName());
   }

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

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

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

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

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

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

   /**
    * Resolves the given single-component name.
    *
    * <p>This method handles names of both explicitly bound objects and
    * native file system subcontexts.  The name is first checked in
    * the list of explicitly bound objects.  If it is there, the native
    * file system subcontexts are NOT checked.  If the name is not in
    * the list of explicitly bound objects, then the file system
    * subcontexts are checked.
    *
    * <p>Under pure JNDI operation, this is guaranteed to form no name
    * conflicts, however, there is the case where a user binds an object
    * under 'xxx' and then SEPARATE FROM JNDI creates a file or dir.
    * with the name 'xxx'.  This will result in a name conflict on a
    * list() operation, but whenever the name 'xxx' is looked up, then
    * the explictly bound object will be returned.
    *
    * <p>The bind() method checks to see if there is a file with the
    * specified name before binding the specified object.
    * </p>
    *
    * @param      name           The single-component name to be resolved.
    *                            If 'name' is empty, 'lookup()' returns
    *                            'this'.
    * @param      cont           The continuation information.
    * @return                    The object bound to this context under
    *                            'name'.
    * @exception NamingException When an error occurs resolving the name.
    */
   protected Object c_lookup(Name name, Continuation cont)
      throws NamingException
   {
      if (name.isEmpty())
      {
         cont.setSuccess();
         return getInstance();
      }

      try
      {
         Object obj = super.c_lookup(name, cont);

         return (obj);
      }
      catch (NameNotFoundException e)
      {
      }

      String entryName = name.get(0);

      if (name.size() > 0 && entryName.length() == 0)
      {
         NamingException ne = new NameNotFoundException();
         cont.setError(this, name);
         throw cont.fillInException(ne);
      }

      if (NameSpace.nameToNumber(environment.getNameSpace()) == NameSpace.DOS_INT)
         entryName = entryName.toUpperCase();

      try
      {
         boolean isFileFlag = isFile(entryName);

         Context ctx = null;

         if (isFileFlag)
         {
            FileDirContext fctx = (FileDirContext) 
               ContextFactoryImpl.getContextInstance(
                  this,
                  entryName,
                  environment,
                  ContextFactoryImpl.CREATE_FILE_CONTEXT);

            ctx = fctx;
         }
         else
         {
            DirectoryDirContext dctx = (DirectoryDirContext)
               ContextFactoryImpl.getContextInstance(
                  this,
                  entryName,
                  environment,
                  ContextFactoryImpl.CREATE_DIR_CONTEXT);

            ctx = dctx;
         }

         cont.setContinue(ctx, name.getPrefix(1), this, name.getSuffix(1));

         return (ctx);
      }
      catch(NSIException e)
      {
         NameNotFoundException ne = new NameNotFoundException();
         ne.setRootCause(e);
         cont.setError(this, name);
         throw cont.fillInException(ne);
      }
   }

   /**
    * Lists all binding names and their class types bound to this context.
    *
    * @param      name           The name which should be empty coming
    *                            from the toolkit.
    * @param      cont           The continuation information.
    * @return                    NameClassEnumeration with all NameClassPairs
    *                            in it.
    * @exception NamingException When an error occurs getting the named
    *                            References of all bound objects.
    */
   protected NamingEnumeration c_list(Name name, Continuation cont)
      throws NamingException
   {
      if (!name.isEmpty ())
      {
         resolveNext(name, cont);
         return null;
      }

      try
      {
         NamingEnumeration enum1 = super.c_list (name, cont);
         DirEntryNCEnumerator enum2 = 
            new DirEntryNCEnumerator(this, environment);

         cont.setSuccess ();

         return (new CompositeNamingEnumerator (enum1, enum2));
      }
      catch (NSIException e)
      {
         NamingException ne = new NamingException();
         ne.setRootCause(e);
         cont.setError(this, name);
         throw cont.fillInException(ne);
      }
   }

   /**
    * Lists all bindings bound to this context.
    *
    * @param      name           The name which should be empty coming
    *                            from the toolkit.
    * @param      cont           The continuation information.
    * @return                    BindingEnumeration with all Bindings
    *                            in it.
    * @exception NamingException When an error occurs getting the named
    *                            References of all bound objects.
    */
   protected NamingEnumeration c_listBindings(Name name, Continuation cont)
      throws NamingException
   {
      if (!name.isEmpty ())
      {
         resolveNext(name, cont);
         return null;
      }

      try
      {
         NamingEnumeration enum1 = super.c_listBindings (name, cont);
         DirEntryBindingEnumerator enum2 =
               new DirEntryBindingEnumerator (this, environment);

         cont.setSuccess();

         return (new CompositeNamingEnumerator (enum1, enum2));
      }
      catch (NSIException e)
      {
         NameNotFoundException ne = new NameNotFoundException();
         ne.setRootCause(e);
         cont.setError(this, name);
         throw cont.fillInException(ne);
      }
   }

   /**
    * Binds a Referencable object to this context.
    *
    * @param      name           The single-component name under which
    *                            to bind 'obj'.
    * @param      obj            The Referenceable object which should
    *                            be bound to this context.
    * @param      cont           The continuation information.
    * @exception NamingException When an error occurs binding 'obj' to this
    *                            context.
    * @exception NameAlreadyBoundException When an object is already bound
    *                            to this context under the specified name.
    */
   protected void c_bind (Name name, Object obj, Continuation cont)
      throws NamingException
   {
      if (name.isEmpty ())
      {
         cont.setError (this, name);
         throw cont.fillInException (new InvalidNameException ());
      }

      if (super.isReferenceBound (name, cont))
      {
         cont.setError (this, name);
         throw cont.fillInException (new NameAlreadyBoundException ());
      }

      try
      {
         // will throw NSIException if entry is non-existant
         isFile (name.toString ());

         cont.setError (this, name);
         throw cont.fillInException (new NameAlreadyBoundException ());
      }
      catch (NSIException e)
      {
         if (e.getCCode () != ServerErrors.NWE_PATH_NOT_LOCATABLE)
         {
            NamingException ne = new NamingException ();
            ne.setRootCause (e);
            cont.setError (this, name);
            throw cont.fillInException (ne);
         }
      }

      super.c_bind (name, obj, cont);
   }

   /**
    * Overwrites any existing binding by the binding specified.
    *
    * <p>This method on a directory behaves in an interesting fashion
    * to avoid name conflicts.  Here's the scenerio:  There is a file
    * or a directory named 'xxx' under this directory.  We try to bind
    * an object under 'xxx'.  rebind() should overwrite any existing
    * bindings, but what if the file (or directory) 'xxx' cannot be
    * deleted?  This method tries to delete it, but if the deletion
    * fails, it will result in a name conflict and an exception will be
    * thrown.
    * </p>
    *
    * @param      name           The single-component name under which
    *                            to bind 'obj'.
    * @param      obj            The Referenceable object which should
    *                            be bound to this context.
    * @param      cont           The continuation information.
    * @exception NamingException When an error occurs binding 'obj' to this
    *                            context.
    */
   protected void c_rebind(Name name, Object obj, Continuation cont)
      throws NamingException
   {
      if (name.isEmpty ())
      {
         cont.setError (this, name);
         throw cont.fillInException (new InvalidNameException ());
      }

      super.c_rebind (name, obj, cont);

      String entryName = name.toString ();

      if (NameSpace.nameToNumber(environment.getNameSpace()) == NameSpace.DOS_INT)
         entryName = entryName.toUpperCase();

      boolean isFileFlag = true; // compiler complained not initialized.

      try
      {
         // isFile() throws an NSIException if the entry is not present
         isFileFlag = isFile (entryName);
      }
      catch (NSIException e)
      {
         // ignore the NSIException, because if the entry is not there,
         //   we don't care in rebind()

         return;
      }

      try
      {
         hardDeleteEntry (entryName, isFileFlag);
      } catch (NSIException nsie)
      {
         NamingException ne = new NamingException (nsie.getMessage());
         ne.setRootCause (nsie);
         cont.setError (this, name);
         throw cont.fillInException (ne);
      }
   }

   /**
    * Unbinds an explicitly bound object from this context.
    *
    * <p>If the name passed in is actually bound to this context, then
    * the binding is removed, no questions asked.  If the binding is not
    * present, then we check to see if the name specifies a valid
    * file or directory.  If so, OperationNotSupportedException is thrown.
    * </p>
    *
    * @param      name           The name of the binding to remove.
    * @param      cont           The continuation information.
    * @exception NamingException When the binding cannot be removed.
    * @exception OperationNotSupportedException When trying to unbind
    *                            a binding that is a subcontext
    *                            (file or dir).
    */
   protected void c_unbind(Name name, Continuation cont)
      throws NamingException
   {
      if (name.isEmpty ())
      {
         cont.setError (this, name);
         throw cont.fillInException (new InvalidNameException ());
      }

      try
      {
         // always unbind if the dir. entry can unbind
         super.c_unbind (name, cont);
      }
      catch (NameNotFoundException e)  // no binding of that name
      {
         try
         {
            // isFile() throws an NSIException if dir. entry does not exist
            isFile (name.toString ());

            // if no exception is thrown, the entry exists, now
            //   we need to show that this operation: "unbinding a
            //   subcontext", is not supported.
            cont.setError(this, name);
            throw cont.fillInException(new OperationNotSupportedException());
         }
         catch (NSIException nsie)
         {
            NamingException ne = new NameNotFoundException();

            // we got an exception from isFile(), the entry does not exist
            //   so we throw the name-not-found exception
            cont.setError(this, name);
            ne.setRootCause (nsie);

            throw cont.fillInException(ne);
         }
      }
   }

   /**
    * Destroys a sub-context in the NetWare file system naming system.
    *
    * <p>This method only destroys subcontexts, that is, subordinate
    * bindings that are contexts in the file system naming system.
    * If you try to unbind any other binding, OperationNotSupportedException
    * is thrown.
    * </p>
    *
    * @param      name           The single-component name specifying
    *                            which subcontext to destroy.  Binding
    *                            names do not apply here, unless they
    *                            are actually file system objects with
    *                            a native binding to this context.
    * @param      cont           The continuation information.
    * @exception NamingException When the subcontext cannot be destroyed.
    * @exception OperationNotSupportedException When the name specifies
    *                            an explicit binding.
    */
   protected void c_destroySubcontext(Name name, Continuation cont)
      throws NamingException
   {
      if (name.isEmpty ())
      {
         cont.setError (this, name);
         throw cont.fillInException (new InvalidNameException ());
      }

      if (super.isReferenceBound (name, cont))
      {
         cont.setError (this, name);
         throw cont.fillInException (new OperationNotSupportedException ());
      }

      String entryName = name.toString();

      if (NameSpace.nameToNumber(environment.getNameSpace()) == NameSpace.DOS_INT)
         entryName = entryName.toUpperCase();

      try
      {
         int ccode;
         NamingException ne = null;
         boolean fileFlag = isFile(entryName);
         
         // NSIException will be thrown if not found

         // ensure that there are no bindings
         Context ctx = (Context) c_lookup(name, cont);

         try
         {
            Enumeration enum = ctx.list("");

            enum.nextElement();

            cont.setError(this, name);
            throw cont.fillInException(new ContextNotEmptyException());
         }
         catch (NoSuchElementException ignore)
         {
            // no bindings found
         }

         hardDeleteEntry(entryName, fileFlag);

         cont.setSuccess();
      }
      catch (NSIException e)
      {
         if (e.getCCode() != ServerErrors.NWE_PATH_NOT_LOCATABLE)
         {
            NamingException ne = new NamingException();
            ne.setRootCause(e);
            cont.setError(this, name);
            throw cont.fillInException(ne);
         }
      }
   }

   /**
    * Renames a binding in this context.
    *
    * @param      oldname        The single-component name specifying
    *                            which binding to rename.
    * @param      newname        The single-component name specifying the
    *                            new name of the binding.
    * @param      cont           The continuation information.
    * @exception NamingException When an error occurs renaming 'oldname' to
    *                            'newname'.
    * @exception NameNotFoundException When the binding specified by
    *                            'oldname' is not present.
    * @exception NameAlreadyBoundException When an object is already bound
    *                            to this context under the new name.
    */
   protected void c_rename(Name oldname, Name newname, Continuation cont)
      throws NamingException
   {
      if (oldname.isEmpty ())
      {
         cont.setError (this, oldname);
         throw cont.fillInException (new InvalidNameException ());
      }

      try
      {
         super.c_rename (oldname, newname, cont);

         return;
      }
      catch (NameNotFoundException e1)
      {
         // binding not found, we'll look for native subcontext
      }
      catch (OperationNotSupportedException e2)
      {
         // bindings turned off, we'll look for a dir or file
      }

      String oldNameStr = oldname.toString();
      String newNameStr = newname.toString();

      //The NSRename function funnels down to NWNSRename which will only work on atomic names in the same directory.
      //NSRename gets the path from the old name and then passes in the rest of the old name and the full new name.
      //If the names are multi component check if the paths are equal. If they are, strip the path off the new name
      int oldsize = oldname.size();
      int newsize = newname.size();
      if((oldsize != 1) || (newsize != 1)) //if the names are not a single component
      {
         boolean pathsEqual = false;
         if(oldsize == newsize)
         {
            Name oldpath = oldname.getPrefix(oldsize-1);
            Name newpath = newname.getPrefix(newsize-1);
            if(oldpath.compareTo(newpath) == 0)
            {
               pathsEqual = true;
               newNameStr = newname.get(newsize-1);
            }
         }
         if(!pathsEqual)
            throw new InvalidNameException("Paths not Equal");
      }
      if (NameSpace.nameToNumber(environment.getNameSpace()) == NameSpace.DOS_INT)
      {
         oldNameStr = oldNameStr.toUpperCase();
         newNameStr = newNameStr.toUpperCase();
      }

      try
      {
         // NSIException will be thrown if not found
         boolean fileFlag = isFile(oldNameStr);

         callsService.NSRename(
            0,       // no dir handle
            NameSpace.nameToNumber(environment.getNameSpace()),
            addPath(oldNameStr),
            fileFlag,
            newNameStr,
            true);   // rename in all name spaces

         cont.setSuccess ();
      }
      catch (Exception e)
      {
         NamingException ne = new NamingException(e.getMessage());
         ne.setRootCause(e);
         cont.setError(this, oldname);
         throw cont.fillInException(ne);
      }
   }

/* **************************************************************************
   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             List of attribute ID's to be included
    *                            in the returned Attributes instance
    * @param cont                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);
   }

   protected StaticAttributeValue[] getDirEntryAttributeValues()
   {
      return super.attributeValues;
   }


   /**
    * 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              The 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();
      while (enum.hasMoreElements())
      {
         Attribute attr = (Attribute)enum.next();

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

         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              The 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,
      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 DirContext c_createSubcontext(
      Name name,
      Attributes attrs,
      Continuation cont)
      throws NamingException
   {
      // there must be a name (it is assumed to be atomic from toolkit)
      if (name.isEmpty())
      {
         cont.setError(this, name);
         throw cont.fillInException(new InvalidNameException());
      }

      // the name to be bound must not currently exist

      Object obj = null;
      try
      {
         obj = c_lookup(name, cont);
      } catch (NameNotFoundException ne)
      {
         obj = null;
      }

      if (obj != null)
      {
         cont.setError(this, name);
         throw cont.fillInException(new NameAlreadyBoundException());
      }
      // there must be a DirectoryEntryInformation attribute
      Attribute attr = attrs.get(DirectoryEntryInfoImpl.ATTRIBUTE_ID);
      if (attr == null)
      {
         cont.setError(this, name);
         throw cont.fillInException(
                     new
                     InsufficientResourcesException(
                        DirectoryEntryInfoImpl.ATTRIBUTE_ID));
      }

      /*
         The DirectoryEntryInformation attribute was only to flag us to
         create a file or subdirectory.  This attribute value should not be
         handed to the modifyAttributes call at the end of this method, and
         so will be removed from the attribute set now.
      */

      attrs.remove(DirectoryEntryInfoImpl.ATTRIBUTE_ID);

      DirContext ctx = null;
      Enumeration enum = attr.getAll();
      DirectoryEntryInformation dei = (DirectoryEntryInformation)
                                          enum.nextElement();

      String entryName = name.toString();

      if (NameSpace.nameToNumber(environment.getNameSpace()) == NameSpace.DOS_INT)
          entryName = entryName.toUpperCase();

      if ((dei.getAttributes() & DirectoryEntryInformation.A_DIRECTORY) == 0)
      {
         // they want a file
         try
         {
            NFileOutputStream nwos = new NFileOutputStream(
                                             entryName,
                                             this,
                                             DataAccessableParameters.WRITE);
            nwos.close();
         } catch (IOException io)
         {
            NamingException ne = new NamingException();
            ne.setRootCause(io);
            cont.setError(this, name);
            throw cont.fillInException(ne);
         }
         ctx = new FileDirContext(
                     this,
                     name.toString(),
                     environment);
      }else
      {
         // they want a directory

         int[] fileHandle = new int[1];

         try
         {
            callsService.openCreateNSEntry(
                           0,       // no dir handle
                           addPath(entryName),
                           NameSpace.nameToNumber(environment.getNameSpace()),
                           0x08,    // create
                           0x0010,  // creating only a subdirectory
                           0x0010,  // creating a subdirectory
                           0x00FF,  // all netware access rights
                           fileHandle);  // not used when creating dirs

         }
         catch (Exception e)
         {
            NamingException ne = new NamingException(e.getMessage());
            ne.setRootCause(e);
            cont.setError(this, name);
            throw cont.fillInException(ne);
         }

         ctx = new DirectoryDirContext(
                        this,
                        name.toString(),
                        environment);
      }
      ctx.modifyAttributes("", ctx.REPLACE_ATTRIBUTE, attrs);
      cont.setSuccess();
      return ctx;
   }

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

   /**
    * Prepend the full directory path to an arbitrary directory entry name
    * and return the resultant full path.
    *
    * @param      entryName      The entry name to which to prepend path.
    * @return                    full native entry name.
    */
   protected String addPath(String entryName)
   {
      if (this instanceof VolumeDirContext)
         return (getFullNodeName() + ':' + entryName);
      else
         return (getFullNodeName() + '\\' + entryName);
   }

   protected void setupDirectoryStaticAttributeValues()
      throws NSIException
   {
      attributeValues[DSI_SAV] = new DirectorySpaceInfoImpl(
                                       environment);
   }

   /**
    * Return whether or not the entry is a file.
    *
    * @param      entryName      The entry relative to the current context.
    * @return                    true if 'entryName' specifies a file, false
    *                            if 'entryName' specifies a directory.
    * @exception  NSIException   When 'entryName' does not specify a valid
    *                            directory entry.
    */
   private boolean isFile(String entryName)
      throws NSIException
   {
      DirectoryEntryInfoImpl info =
            new DirectoryEntryInfoImpl(environment);

      try
      {
         callsService.getNSEntryInfo(
            0,          // no dir handle
            addPath(entryName),
            NameSpace.nameToNumber(environment.getNameSpace()),
            NameSpace.nameToNumber(environment.getNameSpace()),
            DIRMASK,       // all dirs
            SEARCHRETURNMASK,     // get just the name and attributes
            info);

      }
      catch (NSIException nsie)
      {
         int ccode = nsie.getCCode();

         // kludge for the root directory of a volume where you don't have
         //   rights, but you can see certain directories because you have
         //   rights down inside.  This assumes that such a situation would
         //   only yield a directory inside the root directory of a volume.
         if (ccode == ServerErrors.NWE_FILE_NO_SRCH_PRIV)
            return (false);

         if (ccode != ServerErrors.NWE_PATH_INVALID)
            throw nsie;

         if (ccode == ServerErrors.NWE_PATH_INVALID)
         {
            try
            {
               callsService.getNSEntryInfo(
                  0,          // no dir handle
                  addPath(entryName),
                  NameSpace.nameToNumber(environment.getNameSpace()),
                  NameSpace.nameToNumber(environment.getNameSpace()),
                  FILEMASK,       // all dirs
                  SEARCHRETURNMASK,   // get just the name and attributes
                  info);
            } catch (Exception e)
            {
               if (e instanceof NSIException)
                  throw (NSIException) e;
               throw new NSIException(e.getMessage(), 0, e);
            }
         }
      }
      catch(Exception e)
      {
         throw new NSIException(e.getMessage(), 0, e);
      }

      if ((info.getAttributes() & DirectoryEntryInfoImpl.FA_DIRECTORY) != 0)
         return (false);
      else
         return (true);
   }

   private void hardDeleteEntry (String entryName, boolean isFileFlag)
   {
      try
      {
         if (isFileFlag)
         {
            callsService.deleteNSEntry(
               0,       // no dir handle
               addPath (entryName),
               NameSpace.nameToNumber (environment.getNameSpace()),
               0x0006); // search all files
               return;
         }
         else
         {
            callsService.deleteNSEntry(
               0,       // no dir handle
               addPath(entryName),
               NameSpace.nameToNumber(environment.getNameSpace()),
               0x0016); // search all dirs
               return;
         }
      }
      catch (SessionException e)
      {
         throw new NSIException(e.getMessage(), 0, e);
      }
      catch (RemoteException e)
      {
         throw new NSIException(e.getMessage(), 0, e);
      }
   }

   protected StaticAttributeValue getSubsAttributeValue(String attrId)
   {
      StaticAttributeValue value;

      if ((value = getAttributeValue(attrId, attributeValues)) == null)
         value = getAttributeValue(attrId, super.attributeValues);
      return value;
   }
}

/**
 * Enumerates directory entries as NameClassPairs.
 */
class DirEntryNCEnumerator implements NamingEnumeration
{
   public static final String fileClass =
         "com.novell.service.file.nw.naming.FileDirContext";

   public static final String dirClass =
         "com.novell.service.file.nw.naming.DirectoryDirContext";

   private String fullNodePattern;
   private int nameSpaceNumber;
   private NameClassPair save = null;
   private boolean finished = false;
   private int[] scanAttrs =
   {
      DirectoryDirContext.DIRMASK,
      DirectoryDirContext.FILEMASK
   };  // scan dirs, then all files
   // currAttr is used as an index for scanAttrs and is set to -1 initially
   //   so that the first time it's incremented, it will point to the first
   //   entry in scanAttrs
   private int currAttr = -1;

   private int[] volNumber = new int[1];
   private int[] dirNumber = new int[1];
   private int[] searchDirNumber = new int[1];
   private DirectoryEntryInfoImpl info;
   private CallsService callsService;

   public DirEntryNCEnumerator(
      DirectoryDirContext ctx, 
      FSEnvironment environment)
      throws NSIException
   {
      String filter = environment.getListFilter();

      this.nameSpaceNumber = NameSpace.nameToNumber(environment.getNameSpace());

      if (this.nameSpaceNumber == NameSpace.DOS_INT)
         filter = filter.toUpperCase();

      this.fullNodePattern = ctx.addPath(filter);
      this.info = new DirectoryEntryInfoImpl(environment);

      try
      {
         callsService = environment.getCallsService();
      } catch (NamingException e)
      {
         throw new NSIException("" + e, e);
      }

      // set currAttr and iter handle for beginning of search
      scanNextAttr();

      save = scanNextDirEntry();

      if (save == null)
         finished = true;
   }

   public boolean hasMoreElements()
   {
      return !finished;
   }

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

   public Object nextElement()
   {
      try
      {
         return (next());
      }
      catch(NamingException ne)
      {
         Throwable t = ne.getRootCause();

         if (t instanceof NSIException)
            throw (NSIException) t;
         else
            throw new RuntimeException(ne.getMessage());
      }
   }

   public Object next()
      throws NamingException
   {
      if (finished)
         throw new NoSuchElementException();

      NameClassPair buf = save;

      try
      {
         save = scanNextDirEntry();
      } catch (NSIException e)
      {
         throw new NoSuchElementException();
      }

      if (save == null)
         finished = true;

      return (buf);
   }

   private NameClassPair scanNextDirEntry()
   throws NSIException
   {
      try
      {
         callsService.scanNSEntryInfo(
            0,          // no dir handle
            nameSpaceNumber,
            scanAttrs[currAttr],       // all dirs
            volNumber,
            dirNumber,
            searchDirNumber,
            fullNodePattern,
            DirectoryDirContext.SEARCHRETURNMASK, // just name & attributes
            info);

      } catch (NSIException nsie)
      {
         int ccode = nsie.getCCode();
         if (ccode == ServerErrors.NWE_NO_FILES_FOUND_ERROR)
         {
            if (!scanNextAttr())
               return null;                 // end of search
            else
               return (scanNextDirEntry());  // go on to next attribute
         }
         throw nsie;
      }
      catch (Exception e)
      {
         throw new NSIException(e.getMessage(), 0, e);
      }

      // the bit 0x10 is set in the file attributes if the file is a dir
      //   otherwise, it is a file.

      int dirAttr = DirectoryEntryInfoImpl.FA_DIRECTORY;

      return (new NameClassPair(info.getEntryName(),
            (info.getAttributes() & dirAttr) != 0 ? dirClass : fileClass,
            true));
   }

   /* This method advances to the next attribute for which we want to scan.
      Typically, we do two scans -- one for dirs and one for files */
   private boolean scanNextAttr()
   {
      if (currAttr + 1 >= scanAttrs.length)
         return (false);

      // set up iter handle to start a new search
      searchDirNumber[0] = -1;

      // advance to new search attribute
      currAttr++;

      return (true);
   }

   public void close() throws NamingException
   {
      //no action necessary
   }
}


/**
 * Enumerates directory entries as Bindings.
 */
class DirEntryBindingEnumerator implements NamingEnumeration
{
   private NamingEnumeration enum;
   private DirectoryDirContext dir;

   public DirEntryBindingEnumerator(
      DirectoryDirContext dir,
      FSEnvironment environment)
      throws NSIException
   {
      this.enum = new DirEntryNCEnumerator(dir, environment);
      this.dir = dir;
   }

   public boolean hasMoreElements()
   {
      return enum.hasMoreElements();
   }

   public boolean hasMore()
      throws NamingException
   {
      return enum.hasMoreElements();
   }

   public Object nextElement()
   {
      try
      {
         return (next());
      }
      catch(NamingException ne)
      {
         Throwable t = ne.getRootCause();

         if (t instanceof NSIException)
            throw (NSIException) t;
         else
            throw new RuntimeException(ne.getMessage());
      }
   }

   public Object next()
      throws NamingException
   {
      if (!hasMore())
         throw new NoSuchElementException();

      NameClassPair ncPair = (NameClassPair)enum.next();
      String name = ncPair.getName();
      String escapedName = name;
      if(name.charAt(0) == '\'') //need to escape a leading quote
         escapedName = "\\" + name;

      return (new Binding(name, dir.lookup(escapedName), true));
   }

   public void close() throws NamingException
   {
      //no action necessary
   }
}
