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

  $Archive: /njcl_v2/src/com/novell/service/file/nw/calls/NativeDeviceAccessor.java $
  $Revision: 14 $
  $Modtime: 6/15/01 2:58p $
 
  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.calls;


import java.io.IOException;

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

import com.novell.service.jncp.NSIException;

import com.novell.service.session.Session;
import com.novell.service.session.SessionException;
import com.novell.service.session.xplat.DeviceAccessService;


/** @internal
 * Allows native I/O access to a native open file handle.
 */
public class NativeDeviceAccessor implements DataAccessor
{
   /**
    * mode constants
    */
   private static final int SEEK_SET   = DeviceAccessService.SEEK_SET;
   private static final int SEEK_CUR   = DeviceAccessService.SEEK_CUR;
   private static final int SEEK_END   = DeviceAccessService.SEEK_END;

   private static final String serviceKey    = DeviceAccessService.KEY;


   protected boolean closed;
   // native device handle
   protected int handle;
   private boolean doNotFlush; //if opened in read_only mode - then don't call flush
   private DeviceAccessService service;


   /**
    * Constructs a native data accessor that can access a platform native
    * file handle.
    *
    * @param      handle  The platform native file handle.
    * @param      doNotFlush  If the handle was opened read_only, then flush
    *             is not valid to be called.
    */
   public NativeDeviceAccessor
   (
      int      handle,
      Session  session,
      boolean doNotFlush
   )
   throws IOException
   {
      try
      {
         this.closed = false;
         this.service = (DeviceAccessService) session.getService (serviceKey);
         this.handle = handle;
         this.doNotFlush = doNotFlush;
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* NativeDeviceAccessor */

   /**
    * Destroys resources prior to garbage-collection.
    *
    * <p>The native file handle is closed, so that we don't have a handle
    * leak.
    * </p>
    */
   synchronized protected void finalize ()
      throws IOException
   {
      if (!closed)
         close ();
   }

   /**
    * Returns bytes left from current position.
    *
    * @return                    Number of bytes available to read (without
    *                            blocking) from the current position in the
    *                            file.
    * @exception  IOException    When there is an error getting the current
    *                            file position, or when file is closed.
    */
   synchronized public int available ()
      throws IOException
   {
      if (closed)
         throw new IOException("NativeDeviceAccessor::available-closed");

      try
      {
         int length;
         int position;

         length = service.getLength (handle);
         position = service.getPosition (handle);
         return (length - position);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* available () */

   /**
    * Closes the file.
    *
    * <p>If there was data written to the stream that has not yet been
    * written to disk, then this data is flushed to disk before closing the
    * stream.
    * </p>
    *
    * @exception  IOException    When there is an error flushing data or
    *                            closing the native file handle.
    */
   synchronized public void close ()
      throws IOException
   {
      // if the file is already closed, don't do anything
      if (closed)
         return;

      flush ();

      try
      {
         service.close (handle);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }
      finally
      {
         closed = true;
      }
   }

   /**
    * Flushes all unwritten data to disk.
    *
    * @exception  IOException    When there is an error writing the data
    *                            to disk, or when the file is closed.
    */
   synchronized public void flush ()
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::flush-closed");
      if(doNotFlush)
         return;

      try
      {
         service.flush (handle);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* flush () */

   /**
    * Returns true if mark/reset methods are supported, false if they are
    * not.
    *
    * <p>If this method returns false, reset will throw an exception if
    * called.
    * </p>
    * 
    * @return                    true if mark/reset methods are supported,
    *                            false if they are not.
    */
   public boolean markSupported ()
   {
      return (false);
   }

   /**
    * Mark is currently not supported on file streams.
    *
    * @param      limit          The number of bytes that can be read before
    *                            the mark is invalidated.
    */
   public void mark (int limit)
   {
      // no code here...
   }

   /**
    * Reads and returns next byte on stream.
    *
    * @return                    The next byte on the stream.
    *                            If the end-of-file was reached, this
    *                            method returns -1.
    * @exception  IOException    When an error occurs reading the next
    *                            byte, or when the file is closed.
    */
   synchronized public int read ()
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::read-closed");

      try
      {
         byte[] buffer;

         buffer = service.read (handle, 1);

         return ((buffer.length == 0) ? -1 : (buffer[0] & 0xFF));
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* read () */

   /**
    * Reads bytes from stream and fills the specified byte arry with the
    * data read.
    *
    * @param      b              The array of bytes to store bytes in.
    * @return                    The number of bytes actually read -- if less
    *                            than 'b.length' then end-of-file
    *                            was reached.  This method returns -1 when
    *                            end-of-file was reached and no bytes were
    *                            read.
    * @exception  IOException    When an error occurs reading bytes from
    *                            the file, or when the file is closed.
    */
   synchronized public int read (byte[] b)
      throws IOException
   {
      return read (b, 0, b.length);
   }

   /**
    * Reads bytes from stream and writes bytes to an offset within the
    * specified byte array.
    *
    * @param      b              The array of bytes to store bytes in.
    * @param      off            The offset into 'b' at which to store the
    *                            bytes.
    * @param      len            The number of bytes to read.
    * @return                    The number of bytes actually read -- if less
    *                            than 'len', then end-of-file was reached.
    *                            This method returns -1 when end-of-file
    *                            was reached and no bytes were read.
    * @exception  IOException    When an error occurs reading 'len'
    *                            bytes from stream, or when the file is
    *                            closed.
    */
   synchronized public int read (
         byte[] b,
         int off,
         int length)
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::read-closed");

      if (length <= 0)
      {
         return (0);
      }

      try
      {
         byte[] buffer;

         buffer = service.read (handle, length);

         System.arraycopy (buffer, 0, b, off, buffer.length);

         return ((buffer.length == 0) ? -1 : buffer.length);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* read () */

   /**
    * Writes one byte to stream.
    *
    * @param      b              The byte to write.
    * @exception  IOException    when an error occurs writing the specified
    *                            byte, or when the file is closed.
    */
   synchronized public void write (int b)
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::write-closed");

      try
      {
         service.write (handle, new byte[] { (byte) b });
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* write () */

   /**
    * Writes an array of bytes to stream.
    *
    * @param      b              The array of bytes to write.  Exactly
    *                            'b.length' bytes will be written.
    * @exception  IOException    When an error occurs writing specified
    *                            bytes, or when the file is closed.
    */
   synchronized public void write (byte[] b)
      throws IOException
   {
      write (b, 0, b.length);
   }

   /**
    * Writes specified number of bytes to stream from an offset into the
    * specified array.
    *
    * @param      b              The array to write.
    * @param      off            The offset into the array from which to
    *                            start writing.
    * @param      len            The number of bytes to write.
    * @exception  IOException    When an error occurs writing specified
    *                            bytes, or when the file is closed.
    */
   synchronized public void write (
         byte[] b,
         int off,
         int length)
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::write-closed");

      try
      {
         byte[] buffer = new byte[length];

         System.arraycopy (b, off, buffer, 0, length);

         service.write (handle, buffer);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* write () */

   /**
    * Resets stream position to location saved by mark().
    *
    * @exception  IOException    If mark/reset methods are supported on the
    *                            file, or when an error occurs moving the
    *                            file pointer to the saved location, or when
    *                            the file is closed.  If mark/reset methods
    *                            are not supported by the file an
    *                            exception is always thrown.
    */
   synchronized public void reset ()
      throws IOException
   {
      // mark/reset not supported because mark would have to throw an
      //   exception because the simplest implementation calls 
      //   _getFilePos() and there is the risk of an exceptional case,
      //   and java.io.InputStream.mark () does not throw an exception.
      // An alternate implementation could just ignore any exceptional cases,
      //   but then you run the risk of "mark"ing and not knowing there was
      //   an error, and then "reset"ing and not going back to where you
      //   thought you would have.
      // An alternate implementation could store initialize a counter which
      //   is incremented/decremented at every read/write/seek and "reset"
      //   could reset the file position relative to the counter.
      //
      // JGS: I opted for the quick, accurate implementation by not
      //      supporting mark/reset.  This is not the only option.

//      if (closed)
//         throw new IOException ();

      throw new IOException ();
   }

   /**
    * Moves the file pointer forward a specified number of bytes.
    *
    * @param      n              The number of bytes to skip.
    * @return                    The number of bytes skipped.
    * @exception  IOException    When an error occurs getting or setting the
    *                            file pointer, or when the file is closed.
    */
   synchronized public long skip (
         long n)
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::skip-closed");

      int after;
      int before;

//
// a Java file stream allows you to skip to a negative position, so why
// don't we do the same...  26.Feb.97 jgs
//
//      if (n < 0)
//         throw new IllegalArgumentException ();
//

      try
      {
         before = service.getPosition (handle);

         service.setPosition (handle, SEEK_CUR, (int) n);

         after = service.getPosition (handle);

         return (after - before);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* skip () */

   /**
    * Returns total number of bytes in the stream.
    *
    * @return                    The total number of bytes contained in
    *                            the file.
    * @exception  IOException    When an error occurs getting the stream
    *                            length, or when the file is closed.
    */
   synchronized public long length ()
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::length-closed");

      int length;

      flush ();

      try
      {
         length = service.getLength (handle);
         return (length);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* length () */

   /**
    * Moves file pointer to an absolute position in stream.
    *
    * @param      pos            The absolute position to move to. This
    *                            parameter is cast to an <i>int</i>, which
    *                            means there is a ~2GB limit on file size.
    * @return                    The position moved to.
    * @exception  IOException    When an error occurs setting file pointer,
    *                            or when file is closed.
    */
   synchronized public long seek (
         long position)
      throws IOException
   {
      if (closed)
         throw new IOException ("NativeDeviceAccessor::seek-closed");

      try
      {
         service.setPosition (handle, SEEK_SET, (int) position);
         return (position);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* seek () */

   /**
    * Changes stream length.
    *
    * <p>If the stream is smaller than the desired length, the stream is
    * extended to the desired length, using a byte value 0.
    *
    * <p>If the stream is larger than the desired length, the stream is
    * truncated at the desired length.  If the current position before
    * truncating the file is non-existant after truncation, the current
    * position should be reset to a valid file position.
    * </p>
    *
    * @param      len            The desired absolute length of the stream.
    * @exception  IOException    When an error occurs setting the length of
    *                            the stream.
    */
   synchronized public void setLength (
         long length)
      throws IOException
   {
      if (closed)
         throw new IOException("NativeDeviceAccessor::setLength-closed");

      // on Win32 platforms, if the current position is at a byte that is
      //   truncated, that position will be maintained -- the next write
      //   would extend the stream to that point, and then write the byte.
      //   Current Win95 behaviour.  14.Oct.1998 jgs
      // on NLM platforms, if the current position is at a byte that is
      //   truncated, that position will be changed to the end of the
      //   stream -- the next write will simply write the byte onto the end
      //   of the stream.
      //   Current NetWare 5 behaviour.  14.Oct.1998 jgs

      try
      {
         service.setLength (handle, (int) length);
      }
      catch (SessionException e)
      {
         throw (new IOException (e.getMessage ()));
      }

   } /* setLength () */

} /* NativeDeviceAccessor */


