OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / vm / reference / gnu / java / nio / VMChannel.java
index fdea8ff..1f69877 100644 (file)
@@ -39,13 +39,16 @@ exception statement from your version. */
 package gnu.java.nio;
 
 import gnu.classpath.Configuration;
-import gnu.java.net.PlainSocketImpl;
-import gnu.java.nio.PipeImpl.SinkChannelImpl;
-import gnu.java.nio.PipeImpl.SourceChannelImpl;
-import gnu.java.nio.channels.FileChannelImpl;
 
 import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
 import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
 
 /**
  * Native interface to support configuring of channel to run in a non-blocking
@@ -54,33 +57,43 @@ import java.nio.ByteBuffer;
  * @author Michael Barker <mike@middlesoft.co.uk>
  *
  */
-public class VMChannel
+public final class VMChannel
 {
-  private final int fd;
-  
-  private VMChannel(int fd)
-  {
-    this.fd = fd;
-  }
+  /**
+   * Our reference implementation uses an integer to store the native
+   * file descriptor. Implementations without such support 
+   */
+  private final State nfd;
   
-  public static VMChannel getVMChannel(PlainSocketImpl socket)
-  {
-    return new VMChannel(socket.getNativeFD());
-  }
+  private Kind kind;
   
-  public static VMChannel getVMChannel(SourceChannelImpl source)
+  public VMChannel()
   {
-    return new VMChannel(source.getNativeFD());
+    // XXX consider adding security check here, so only Classpath
+    // code may create instances.
+    this.nfd = new State();
+    kind = Kind.OTHER;
   }
   
-  public static VMChannel getVMChannel(SinkChannelImpl sink)
+  /**
+   * This constructor is used by the POSIX reference implementation;
+   * other virtual machines need not support it.
+   * 
+   * <strong>Important:</strong> do not call this in library code that is
+   * not specific to Classpath's reference implementation.
+   * 
+   * @param native_fd The native file descriptor integer.
+   * @throws IOException
+   */
+  VMChannel(final int native_fd) throws IOException
   {
-    return new VMChannel(sink.getNativeFD());
+    this();
+    this.nfd.setNativeFD(native_fd);
   }
   
-  public static VMChannel getVMChannel(FileChannelImpl file)
+  public State getState()
   {
-    return new VMChannel(file.getNativeFD());
+    return nfd;
   }
 
   static
@@ -93,81 +106,151 @@ public class VMChannel
     initIDs();
   }
   
+  public static VMChannel getStdin() throws IOException
+  {
+    return new VMChannel(stdin_fd());
+  }
+  
+  public static VMChannel getStdout() throws IOException
+  {
+    return new VMChannel(stdout_fd());
+  }
+  
+  public static VMChannel getStderr() throws IOException
+  {
+    return new VMChannel(stderr_fd());
+  }
+  
+  private static native int stdin_fd();
+  private static native int stdout_fd();
+  private static native int stderr_fd();
+  
   /**
    * Set the file descriptor to have the required blocking
    * setting.
    * 
-   * @param fd
-   * @param blocking
+   * @param blocking The blocking flag to set.
    */
-  public native void setBlocking(int fd, boolean blocking);
+  public void setBlocking(boolean blocking) throws IOException
+  {
+    setBlocking(nfd.getNativeFD(), blocking);
+  }
   
-  public void setBlocking(boolean blocking)
+  private static native void setBlocking(int fd, boolean blocking)
+    throws IOException;
+  
+  public int available() throws IOException
   {
-    setBlocking(fd, blocking);
+    return available(nfd.getNativeFD());
   }
   
+  private static native int available(int native_fd) throws IOException;
 
   /**
    * Reads a byte buffer directly using the supplied file descriptor.
-   * Assumes that the buffer is a DirectBuffer.
    * 
-   * @param fd Native file descriptor to read from.
    * @param dst Direct Byte Buffer to read to.
    * @return Number of bytes read.
    * @throws IOException If an error occurs or dst is not a direct buffers. 
    */
-  native int read(int fd, ByteBuffer dst)
-    throws IOException;
-  
   public int read(ByteBuffer dst)
     throws IOException
   {
-    return read(fd, dst);
+    return read(nfd.getNativeFD(), dst);
+  }
+  
+  private static native int read(int fd, ByteBuffer dst) throws IOException;
+  
+  /**
+   * Read a single byte.
+   *
+   * @return The byte read, or -1 on end of file.
+   * @throws IOException
+   */
+  public int read() throws IOException
+  {
+    return read(nfd.getNativeFD());
   }
   
+  private static native int read(int fd) throws IOException;
+  
   /**
    * Reads into byte buffers directly using the supplied file descriptor.
    * Assumes that the buffer list contains DirectBuffers.  Will perform a
    * scattering read.
    * 
-   * @param fd Native file descriptor to read from.
    * @param dsts An array direct byte buffers.
    * @param offset Index of the first buffer to read to.
    * @param length The number of buffers to read to.
    * @return Number of bytes read.
    * @throws IOException If an error occurs or the dsts are not direct buffers. 
    */
-  native long readScattering(int fd, ByteBuffer[] dsts, int offset, int length)
-    throws IOException;
-
   public long readScattering(ByteBuffer[] dsts, int offset, int length)
     throws IOException
   {
     if (offset + length > dsts.length)
       throw new IndexOutOfBoundsException("offset + length > dsts.length");
+
+    return readScattering(nfd.getNativeFD(), dsts, offset, length);
+  }
+  
+  private static native long readScattering(int fd, ByteBuffer[] dsts,
+                                            int offset, int length)
+    throws IOException;
+  
+  /**
+   * Receive a datagram on this channel, returning the host address
+   * that sent the datagram.
+   *
+   * @param dst Where to store the datagram.
+   * @return The host address that sent the datagram.
+   * @throws IOException
+   */
+  public SocketAddress receive(ByteBuffer dst) throws IOException
+  {
+    if (kind != Kind.SOCK_DGRAM)
+      throw new SocketException("not a datagram socket");
+    ByteBuffer hostPort = ByteBuffer.allocateDirect(18);
+    int hostlen = receive(nfd.getNativeFD(), dst, hostPort);
+    if (hostlen == 0)
+      return null;
+    if (hostlen == 4) // IPv4
+      {
+        byte[] addr = new byte[4];
+        hostPort.get(addr);
+        int port = hostPort.getShort() & 0xFFFF;
+        return new InetSocketAddress(Inet4Address.getByAddress(addr), port);
+      }
+    if (hostlen == 16) // IPv6
+      {
+        byte[] addr = new byte[16];
+        hostPort.get(addr);
+        int port = hostPort.getShort() & 0xFFFF;
+        return new InetSocketAddress(Inet6Address.getByAddress(addr), port);
+      }
     
-    return readScattering(fd, dsts, offset, length);
+    throw new SocketException("host address received with invalid length: "
+                              + hostlen);
   }
   
+  private static native int receive (int fd, ByteBuffer dst, ByteBuffer address)
+    throws IOException;
+
   /**
    * Writes from a direct byte bufer using the supplied file descriptor.
    * Assumes the buffer is a DirectBuffer.
    * 
-   * @param fd
-   * @param src
+   * @param src The source buffer.
    * @return Number of bytes written.
    * @throws IOException
    */
-  native int write(int fd, ByteBuffer src)
-    throws IOException;
-
-  public int write(ByteBuffer src)
-    throws IOException
+  public int write(ByteBuffer src) throws IOException
   {
-    return write(fd, src);
+    return write(nfd.getNativeFD(), src);
   }
   
+  private native int write(int fd, ByteBuffer src) throws IOException;
+
   /**
    * Writes from byte buffers directly using the supplied file descriptor.
    * Assumes the that buffer list constains DirectBuffers.  Will perform
@@ -180,18 +263,488 @@ public class VMChannel
    * @return Number of bytes written.
    * @throws IOException
    */
-  native long writeGathering(int fd, ByteBuffer[] srcs, int offset, int length)
-    throws IOException;
-  
   public long writeGathering(ByteBuffer[] srcs, int offset, int length)
     throws IOException
   {
     if (offset + length > srcs.length)
       throw new IndexOutOfBoundsException("offset + length > srcs.length");
     
-    return writeGathering(fd, srcs, offset, length);
+    // A gathering write is limited to 16 buffers; when writing, ensure
+    // that we have at least one buffer with something in it in the 16
+    // buffer window starting at offset.
+    while (!srcs[offset].hasRemaining() && offset < srcs.length)
+      offset++;
+    
+    // There are no buffers with anything to write.
+    if (offset == srcs.length)
+      return 0;
+
+    // If we advanced `offset' so far that we don't have `length'
+    // buffers left, reset length to only the remaining buffers.
+    if (length > srcs.length - offset)
+      length = srcs.length - offset;
+
+    return writeGathering(nfd.getNativeFD(), srcs, offset, length);
+  }
+  
+  private native long writeGathering(int fd, ByteBuffer[] srcs,
+                                     int offset, int length)
+    throws IOException;
+  
+  /**
+   * Send a datagram to the given address.
+   *
+   * @param src The source buffer.
+   * @param dst The destination address.
+   * @return The number of bytes written.
+   * @throws IOException
+   */
+  public int send(ByteBuffer src, InetSocketAddress dst)
+    throws IOException
+  {
+    InetAddress addr = dst.getAddress();
+    if (addr == null)
+      throw new NullPointerException();
+    if (addr instanceof Inet4Address)
+      return send(nfd.getNativeFD(), src, addr.getAddress(), dst.getPort());
+    else if (addr instanceof Inet6Address)
+      return send6(nfd.getNativeFD(), src, addr.getAddress(), dst.getPort());
+    else
+      throw new SocketException("unrecognized inet address type");
+  }
+  
+  // Send to an IPv4 address.
+  private static native int send(int fd, ByteBuffer src, byte[] addr, int port)
+    throws IOException;
+  
+  // Send to an IPv6 address.
+  private static native int send6(int fd, ByteBuffer src, byte[] addr, int port)
+    throws IOException;
+  
+  /**
+   * Write a single byte.
+   *
+   * @param b The byte to write.
+   * @throws IOException
+   */
+  public void write(int b) throws IOException
+  {
+    write(nfd.getNativeFD(), b);
   }
   
+  private static native void write(int fd, int b) throws IOException;
+  
   private native static void initIDs();
 
+  // Network (socket) specific methods.
+  
+  /**
+   * Create a new socket. This method will initialize the native file
+   * descriptor state of this instance.
+   *
+   * @param stream Whether or not to create a streaming socket, or a datagram
+   *  socket.
+   * @throws IOException If creating a new socket fails, or if this
+   *  channel already has its native descriptor initialized.
+   */
+  public void initSocket(boolean stream) throws IOException
+  {
+    if (nfd.isValid())
+      throw new IOException("native FD already initialized");
+    if (stream)
+      kind = Kind.SOCK_STREAM;
+    else
+      kind = Kind.SOCK_DGRAM;
+    nfd.setNativeFD(socket(stream));
+  }
+  
+  /**
+   * Create a new socket, returning the native file descriptor.
+   *
+   * @param stream Set to true for streaming sockets; false for datagrams.
+   * @return The native file descriptor.
+   * @throws IOException If creating the socket fails.
+   */
+  private static native int socket(boolean stream) throws IOException;
+
+  /**
+   * Connect the underlying socket file descriptor to the remote host.
+   *
+   * @param saddr The address to connect to.
+   * @param timeout The connect timeout to use for blocking connects.
+   * @return True if the connection succeeded; false if the file descriptor
+   *  is in non-blocking mode and the connection did not immediately
+   *  succeed.
+   * @throws IOException If an error occurs while connecting.
+   */
+  public boolean connect(InetSocketAddress saddr, int timeout)
+    throws SocketException
+  {
+    int fd;
+
+    InetAddress addr = saddr.getAddress();
+    // Translates an IOException into a SocketException to conform
+    // to the throws clause.
+    try
+      {
+        fd = nfd.getNativeFD();
+      }
+    catch (IOException ioe)
+      {
+        throw new SocketException(ioe.getMessage());
+      }
+
+    if (addr instanceof Inet4Address)
+      return connect(fd, addr.getAddress(), saddr.getPort(),
+                     timeout);
+    if (addr instanceof Inet6Address)
+      return connect6(fd, addr.getAddress(), saddr.getPort(),
+                      timeout);
+    throw new SocketException("unsupported internet address");
+  }
+  
+  private static native boolean connect(int fd, byte[] addr, int port, int timeout)
+    throws SocketException;
+  
+  private static native boolean connect6(int fd, byte[] addr, int port, int timeout)
+    throws SocketException;
+  
+  /**
+   * Disconnect this channel, if it is a datagram socket. Disconnecting
+   * a datagram channel will disassociate it from any address, so the
+   * socket will remain open, but can send and receive datagrams from
+   * any address.
+   *
+   * @throws IOException If disconnecting this channel fails, or if this
+   *  channel is not a datagram channel.
+   */
+  public void disconnect() throws IOException
+  {
+    if (kind != Kind.SOCK_DGRAM)
+      throw new IOException("can only disconnect datagram channels");
+    disconnect(nfd.getNativeFD());
+  }
+  
+  private static native void disconnect(int fd) throws IOException;
+  
+  public InetSocketAddress getLocalAddress() throws IOException
+  {
+    if (!nfd.isValid())
+      return null;
+    ByteBuffer name = ByteBuffer.allocateDirect(18);
+    int namelen = getsockname(nfd.getNativeFD(), name);
+    if (namelen == 0) // not bound
+      return null; // XXX return some wildcard?
+    if (namelen == 4)
+      {
+        byte[] addr = new byte[4];
+        name.get(addr);
+        int port = name.getShort() & 0xFFFF;
+        return new InetSocketAddress(Inet4Address.getByAddress(addr), port);
+      }
+    if (namelen == 16)
+      {
+        byte[] addr = new byte[16];
+        name.get(addr);
+        int port = name.getShort() & 0xFFFF;
+        return new InetSocketAddress(Inet6Address.getByAddress(addr), port);
+      }
+    throw new SocketException("invalid address length");
+  }
+  
+  private static native int getsockname(int fd, ByteBuffer name)
+    throws IOException;
+
+  /**
+   * Returns the socket address of the remote peer this channel is connected
+   * to, or null if this channel is not yet connected.
+   *
+   * @return The peer address.
+   * @throws IOException
+   */
+  public InetSocketAddress getPeerAddress() throws IOException
+  {
+    if (!nfd.isValid())
+      return null;
+    ByteBuffer name = ByteBuffer.allocateDirect(18);
+    int namelen = getpeername (nfd.getNativeFD(), name);
+    if (namelen == 0) // not connected yet
+      return null;
+    if (namelen == 4) // IPv4
+      {
+        byte[] addr = new byte[4];
+        name.get(addr);
+        int port = name.getShort() & 0xFFFF;
+        return new InetSocketAddress(Inet4Address.getByAddress(addr), port);
+      }
+    else if (namelen == 16) // IPv6
+      {
+        byte[] addr = new byte[16];
+        name.get(addr);
+        int port = name.getShort() & 0xFFFF;
+        return new InetSocketAddress(Inet6Address.getByAddress(addr), port);
+      }
+    throw new SocketException("invalid address length");
+  }
+  
+  /*
+   * The format here is the peer address, followed by the port number.
+   * The returned value is the length of the peer address; thus, there
+   * will be LEN + 2 valid bytes put into NAME.
+   */
+  private static native int getpeername(int fd, ByteBuffer name)
+    throws IOException;
+  
+  /**
+   * Accept an incoming connection, returning a new VMChannel, or null
+   * if the channel is nonblocking and no connection is pending.
+   *
+   * @return The accepted connection, or null.
+   * @throws IOException If an IO error occurs.
+   */
+  public VMChannel accept() throws IOException
+  {
+    int new_fd = accept(nfd.getNativeFD());
+    if (new_fd == -1) // non-blocking accept had no pending connection
+      return null;
+    return new VMChannel(new_fd);
+  }
+  
+  private static native int accept(int native_fd) throws IOException;
+
+  // File-specific methods.
+  
+  /**
+   * Open a file at PATH, initializing the native state to operate on
+   * that open file.
+   * 
+   * @param path The absolute file path.
+   * @throws IOException If the file cannot be opened, or if this 
+   *  channel was previously initialized.
+   */
+  public void openFile(String path, int mode) throws IOException
+  {
+    if (nfd.isValid() || nfd.isClosed())
+      throw new IOException("can't reinitialize this channel");
+    int fd = open(path, mode);
+    nfd.setNativeFD(fd);
+    kind = Kind.FILE;
+  }
+  
+  private static native int open(String path, int mode) throws IOException;
+  
+  public long position() throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    return position(nfd.getNativeFD());
+  }
+  
+  private static native long position(int fd) throws IOException;
+  
+  public void seek(long pos) throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    seek(nfd.getNativeFD(), pos);
+  }
+  
+  private static native void seek(int fd, long pos) throws IOException;
+
+  public void truncate(long length) throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    truncate(nfd.getNativeFD(), length);
+  }
+  
+  private static native void truncate(int fd, long len) throws IOException;
+  
+  public boolean lock(long pos, long len, boolean shared, boolean wait)
+    throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    return lock(nfd.getNativeFD(), pos, len, shared, wait);
+  }
+  
+  private static native boolean lock(int fd, long pos, long len,
+                                     boolean shared, boolean wait)
+    throws IOException;
+  
+  public void unlock(long pos, long len) throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    unlock(nfd.getNativeFD(), pos, len);
+  }
+  
+  private static native void unlock(int fd, long pos, long len) throws IOException;
+  
+  public long size() throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    return size(nfd.getNativeFD());
+  }
+  
+  private static native long size(int fd) throws IOException;
+
+  public MappedByteBuffer map(char mode, long position, int size)
+    throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    return map(nfd.getNativeFD(), mode, position, size);
+  }
+  
+  private static native MappedByteBuffer map(int fd, char mode,
+                                             long position, int size)
+    throws IOException;
+  
+  public boolean flush(boolean metadata) throws IOException
+  {
+    if (kind != Kind.FILE)
+      throw new IOException("not a file");
+    return flush(nfd.getNativeFD(), metadata);
+  }
+  
+  private static native boolean flush(int fd, boolean metadata) throws IOException;
+  
+  // Close.
+  
+  /**
+   * Close this socket. The socket is also automatically closed when this
+   * object is finalized.
+   *
+   * @throws IOException If closing the socket fails, or if this object has
+   *  no open socket.
+   */
+  public void close() throws IOException
+  {
+    nfd.close();
+  }
+  
+  static native void close(int native_fd) throws IOException;
+  
+  /**
+   * <p>Provides a simple mean for the JNI code to find out whether the
+   * current thread was interrupted by a call to Thread.interrupt().</p>
+   * 
+   * @return
+   */
+  static boolean isThreadInterrupted()
+  {
+    return Thread.currentThread().isInterrupted();
+  }
+
+  // Inner classes.
+  
+  /**
+   * A wrapper for a native file descriptor integer. This tracks the state
+   * of an open file descriptor, and ensures that 
+   * 
+   * This class need not be fully supported by virtual machines; if a
+   * virtual machine does not use integer file descriptors, or does and
+   * wishes to hide that, then the methods of this class may be stubbed out.
+   * 
+   * System-specific classes that depend on access to native file descriptor
+   * integers SHOULD declare this fact.
+   */
+  public final class State
+  {
+    private int native_fd;
+    private boolean valid;
+    private boolean closed;
+    
+    State()
+    {
+      native_fd = -1;
+      valid = false;
+      closed = false;
+    }
+    
+    public boolean isValid()
+    {
+      return valid;
+    }
+    
+    public boolean isClosed()
+    {
+      return closed;
+    }
+    
+    public int getNativeFD() throws IOException
+    {
+      if (!valid)
+        throw new IOException("invalid file descriptor");
+      return native_fd;
+    }
+    
+    void setNativeFD(final int native_fd) throws IOException
+    {
+      if (valid)
+        throw new IOException("file descriptor already initialized");
+      this.native_fd = native_fd;
+      valid = true;
+    }
+    
+    public void close() throws IOException
+    {
+      if (!valid)
+        throw new IOException("invalid file descriptor");
+      try
+        {
+          VMChannel.close(native_fd);
+        }
+      finally
+        {
+          valid = false;
+          closed = true;
+        }
+    }
+    
+    public String toString()
+    {
+      if (closed)
+        return "<<closed>>";
+      if (!valid)
+        return "<<invalid>>";
+      return String.valueOf(native_fd);
+    }
+    
+    protected void finalize() throws Throwable
+    {
+      try
+        {
+          if (valid)
+            close();
+        }
+      finally
+        {
+          super.finalize();
+        }
+    }
+  }
+  
+  /**
+   * An enumeration of possible kinds of channel.
+   */
+  static class Kind // XXX enum
+  {
+    /** A streaming (TCP) socket. */
+    static final Kind SOCK_STREAM = new Kind();
+    
+    /** A datagram (UDP) socket. */
+    static final Kind SOCK_DGRAM = new Kind();
+    
+    /** A file. */
+    static final Kind FILE = new Kind();
+    
+    /** Something else; not a socket or file. */
+    static final Kind OTHER = new Kind();
+    
+    private Kind() { }
+  }
 }