test/java/nio/channels/TestUtil.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.

@@ -25,22 +25,21 @@
  *
  */
 
 import java.io.*;
 import java.net.*;
-import java.nio.*;
 import java.nio.channels.*;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
 import java.util.Random;
 
 
 public class TestUtil {
 
     // Test hosts used by the channels tests - change these when
     // executing in a different network.
-    public static final String HOST = "javaweb.sfbay.sun.com";
-    public static final String REFUSING_HOST = "jano1.sfbay.sun.com";
-    public static final String FAR_HOST = "irejano.ireland.sun.com";
     public static final String UNRESOLVABLE_HOST = "blah-blah.blah-blah.blah";
 
     private TestUtil() { }
 
     // Repeatedly try random ports until we bind to one.  You might be tempted

@@ -86,6 +85,817 @@
     }
 
     static boolean onWindows() {
         return osName.startsWith("Windows");
     }
+
+    /**
+     * An abstract server identifies a server which listens on a port on on a
+     * given machine.
+     */
+    static abstract class AbstractServer {
+
+        private AbstractServer() {
+        }
+
+        public abstract int getPort();
+
+        public abstract InetAddress getAddress();
+    }
+
+    /**
+     * A downgraded type of AbstractServer which will refuse connections. Note:
+     * use it once and throw it away - this implementation opens an anonymous
+     * socket and closes it, returning the address of the closed socket. If
+     * other servers are started afterwards, the address/port might get reused
+     * and become connectable again - so it's not a good idea to assume that
+     * connections using this address/port will always be refused. Connections
+     * will be refused as long as the address/port of the refusing server has
+     * not been reused.
+     */
+    static class RefusingServer extends AbstractServer {
+
+        final InetAddress address;
+        final int port;
+
+        private RefusingServer(InetAddress address, int port) {
+            this.address = address;
+            this.port = port;
+        }
+
+        @Override
+        public int getPort() {
+            return port;
+        }
+
+        @Override
+        public InetAddress getAddress() {
+            return address;
+        }
+
+        public static RefusingServer startNewServer() throws IOException {
+            ServerSocket socket = new ServerSocket(0, 100,
+                    InetAddress.getLocalHost());
+            RefusingServer server = new RefusingServer(socket.getInetAddress(),
+                    socket.getLocalPort());
+            socket.close();
+            return server;
+        }
+    }
+
+    /**
+     * An abstract class for implementing small TCP servers for the nio tests
+     * purposes. Disclaimer: This is a naive implementation that uses the old
+     * networking APIs (not those from {@code java.nio.*}) and shamelessly
+     * extends/creates Threads instead of using an executor service.
+     */
+    static abstract class AbstractTcpServer extends AbstractServer
+            implements Runnable, Closeable {
+
+        protected final long linger; // #of ms to wait before responding
+        private Thread acceptThread; // thread waiting for accept
+        // list of opened connections that should be closed on close.
+        private List<TcpConnectionThread> connections = new ArrayList<>();
+        private ServerSocket serverSocket; // the server socket
+        private boolean started = false; // whether the server is started
+        Throwable error = null;
+
+        /**
+         * Creates a new abstract TCP server.
+         *
+         * @param linger the amount of time the server should wait before
+         * responding to requests.
+         */
+        protected AbstractTcpServer(long linger) {
+            this.linger = linger;
+        }
+
+        /**
+         * The local port to which the server is bound.
+         *
+         * @return The local port to which the server is bound.
+         * @exception IllegalStateException is thrown if the server is not
+         * started.
+         */
+        @Override
+        public final synchronized int getPort() {
+            if (!started) {
+                throw new IllegalStateException("Not started");
+            }
+            return serverSocket.getLocalPort();
+        }
+
+        /**
+         * The local address to which the server is bound.
+         *
+         * @return The local address to which the server is bound.
+         * @exception IllegalStateException is thrown if the server is not
+         * started.
+         */
+        @Override
+        public final synchronized InetAddress getAddress() {
+            if (!started) {
+                throw new IllegalStateException("Not started");
+            }
+            return serverSocket.getInetAddress();
+        }
+
+        /**
+         * Tells whether the server is started.
+         *
+         * @return true if the server is started.
+         */
+        public final synchronized boolean isStarted() {
+            return started;
+        }
+
+        /**
+         * Creates a new server socket.
+         *
+         * @param port local port to bind to.
+         * @param backlog requested maximum length of the queue of incoming
+         * connections.
+         * @param address local address to bind to.
+         * @return a new bound server socket ready to accept connections.
+         * @throws IOException if the socket cannot be created or bound.
+         */
+        protected ServerSocket newServerSocket(int port, int backlog,
+                InetAddress address)
+                throws IOException {
+            return new ServerSocket(port, backlog, address);
+        }
+
+        /**
+         * Starts listening for connections.
+         *
+         * @throws IOException if the server socket cannot be created or bound.
+         */
+        public final synchronized void start() throws IOException {
+            if (started) {
+                return;
+            }
+            final ServerSocket socket =
+                    newServerSocket(0, 100, InetAddress.getLocalHost());
+            serverSocket = socket;
+            acceptThread = new Thread(this);
+            acceptThread.setDaemon(true);
+            acceptThread.start();
+            started = true;
+        }
+
+        /**
+         * Calls {@code Thread.sleep(linger);}
+         */
+        protected final void lingerIfRequired() {
+            if (linger > 0) {
+                try {
+                    Thread.sleep(linger);
+                } catch (InterruptedException x) {
+                    Thread.interrupted();
+                    final ServerSocket socket = serverSocket();
+                    if (socket != null && !socket.isClosed()) {
+                        System.err.println("Thread interrupted...");
+                    }
+                }
+            }
+        }
+
+        final synchronized ServerSocket serverSocket() {
+            return this.serverSocket;
+        }
+
+        /**
+         * The main accept loop.
+         */
+        @Override
+        public final void run() {
+            final ServerSocket sSocket = serverSocket();
+            try {
+                Socket s;
+                while (isStarted() && !Thread.interrupted()
+                        && (s = sSocket.accept()) != null) {
+                    lingerIfRequired();
+                    listen(s);
+                }
+            } catch (Exception x) {
+                error = x;
+            } finally {
+                synchronized (this) {
+                    if (!sSocket.isClosed()) {
+                        try {
+                            sSocket.close();
+                        } catch (IOException x) {
+                            System.err.println("Failed to close server socket");
+                        }
+                    }
+                    if (started && this.serverSocket == sSocket) {
+                        started = false;
+                        this.serverSocket = null;
+                        this.acceptThread = null;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Represents a connection accepted by the server.
+         */
+        protected abstract class TcpConnectionThread extends Thread {
+
+            protected final Socket socket;
+
+            protected TcpConnectionThread(Socket socket) {
+                this.socket = socket;
+                this.setDaemon(true);
+            }
+
+            public void close() throws IOException {
+                socket.close();
+                interrupt();
+            }
+        }
+
+        /**
+         * Creates a new TcpConnnectionThread to handle the connection through
+         * an accepted socket.
+         *
+         * @param s the socket returned by {@code serverSocket.accept()}.
+         * @return a new TcpConnnectionThread to handle the connection through
+         * an accepted socket.
+         */
+        protected abstract TcpConnectionThread createConnection(Socket s);
+
+        /**
+         * Creates and starts a new TcpConnectionThread to handle the accepted
+         * socket.
+         *
+         * @param s the socket returned by {@code serverSocket.accept()}.
+         */
+        private synchronized void listen(Socket s) {
+            TcpConnectionThread c = createConnection(s);
+            c.start();
+            addConnection(c);
+        }
+
+        /**
+         * Add the connection to the list of accepted connections.
+         *
+         * @param connection an accepted connection.
+         */
+        protected synchronized void addConnection(
+                TcpConnectionThread connection) {
+            connections.add(connection);
+        }
+
+        /**
+         * Remove the connection from the list of accepted connections.
+         *
+         * @param connection an accepted connection.
+         */
+        protected synchronized void removeConnection(
+                TcpConnectionThread connection) {
+            connections.remove(connection);
+        }
+
+        /**
+         * Close the server socket and all the connections present in the list
+         * of accepted connections.
+         *
+         * @throws IOException
+         */
+        @Override
+        public synchronized void close() throws IOException {
+            if (serverSocket != null && !serverSocket.isClosed()) {
+                serverSocket.close();
+            }
+            if (acceptThread != null) {
+                acceptThread.interrupt();
+            }
+            int failed = 0;
+            for (TcpConnectionThread c : connections) {
+                try {
+                    c.close();
+                } catch (IOException x) {
+                    // no matter - we're closing.
+                    failed++;
+                }
+            }
+            connections.clear();
+            if (failed > 0) {
+                throw new IOException("Failed to close some connections");
+            }
+        }
+    }
+
+    /**
+     * A small TCP Server that emulates the echo service for tests purposes. See
+     * http://en.wikipedia.org/wiki/Echo_Protocol This server uses an anonymous
+     * port - NOT the standard port 7. We don't guarantee that its behavior
+     * exactly matches the RFC - the only purpose of this server is to have
+     * something that responds to nio tests...
+     */
+    static final class EchoServer extends AbstractTcpServer {
+
+        public EchoServer() {
+            this(0L);
+        }
+
+        public EchoServer(long linger) {
+            super(linger);
+        }
+
+        @Override
+        protected TcpConnectionThread createConnection(Socket s) {
+            return new EchoConnection(s);
+        }
+
+        private final class EchoConnection extends TcpConnectionThread {
+
+            public EchoConnection(Socket socket) {
+                super(socket);
+            }
+
+            @Override
+            public void run() {
+                try {
+                    final InputStream is = socket.getInputStream();
+                    final OutputStream out = socket.getOutputStream();
+                    byte[] b = new byte[255];
+                    int n;
+                    while ((n = is.read(b)) > 0) {
+                        lingerIfRequired();
+                        out.write(b, 0, n);
+                    }
+                } catch (IOException io) {
+                    // fall through to finally
+                } finally {
+                    if (!socket.isClosed()) {
+                        try {
+                            socket.close();
+                        } catch (IOException x) {
+                            System.err.println(
+                                    "Failed to close echo connection socket");
+                        }
+                    }
+                    removeConnection(this);
+                }
+            }
+        }
+
+        public static EchoServer startNewServer() throws IOException {
+            return startNewServer(0);
+        }
+
+        public static EchoServer startNewServer(long linger) throws IOException {
+            final EchoServer echoServer = new EchoServer(linger);
+            echoServer.start();
+            return echoServer;
+        }
+    }
+
+    /**
+     * A small TCP server that emulates the Day & Time service for tests
+     * purposes. See http://en.wikipedia.org/wiki/Daytime_Protocol This server
+     * uses an anonymous port - NOT the standard port 13. We don't guarantee
+     * that its behavior exactly matches the RFC - the only purpose of this
+     * server is to have something that responds to nio tests...
+     */
+    static final class DayTimeServer extends AbstractTcpServer {
+
+        public DayTimeServer() {
+            this(0L);
+        }
+
+        public DayTimeServer(long linger) {
+            super(linger);
+        }
+
+        @Override
+        protected TcpConnectionThread createConnection(Socket s) {
+            return new DayTimeServerConnection(s);
+        }
+
+        @Override
+        protected void addConnection(TcpConnectionThread connection) {
+            // do nothing - the connection just write the date and terminates.
+        }
+
+        @Override
+        protected void removeConnection(TcpConnectionThread connection) {
+            // do nothing - we're not adding connections to the list...
+        }
+
+        private final class DayTimeServerConnection extends TcpConnectionThread {
+
+            public DayTimeServerConnection(Socket socket) {
+                super(socket);
+            }
+
+            @Override
+            public void run() {
+                try {
+                    final OutputStream out = socket.getOutputStream();
+                    lingerIfRequired();
+                    out.write(new Date(System.currentTimeMillis())
+                            .toString().getBytes("US-ASCII"));
+                    out.flush();
+                } catch (IOException io) {
+                    // fall through to finally
+                } finally {
+                    if (!socket.isClosed()) {
+                        try {
+                            socket.close();
+                        } catch (IOException x) {
+                            System.err.println(
+                                    "Failed to close echo connection socket");
+                        }
+                    }
+                }
+            }
+        }
+
+        public static DayTimeServer startNewServer()
+                throws IOException {
+            return startNewServer(0);
+        }
+
+        public static DayTimeServer startNewServer(long linger)
+                throws IOException {
+            final DayTimeServer daytimeServer = new DayTimeServer(linger);
+            daytimeServer.start();
+            return daytimeServer;
+        }
+    }
+
+    /**
+     * An abstract class for implementing small UDP Servers for the nio tests
+     * purposes. Disclaimer: This is a naive implementation that uses the old
+     * networking APIs (not those from {@code java.nio.*}) and shamelessly
+     * extends/creates Threads instead of using an executor service.
+     */
+    static abstract class AbstractUdpServer extends AbstractServer
+            implements Runnable, Closeable {
+
+        protected final long linger; // #of ms to wait before responding
+        private Thread acceptThread; // thread waiting for packets
+        private DatagramSocket serverSocket; // the server socket
+        private boolean started = false; // whether the server is started
+        Throwable error = null;
+
+        /**
+         * Creates a new abstract UDP server.
+         *
+         * @param linger the amount of time the server should wait before
+         * responding to requests.
+         */
+        protected AbstractUdpServer(long linger) {
+            this.linger = linger;
+        }
+
+        /**
+         * The local port to which the server is bound.
+         *
+         * @return The local port to which the server is bound.
+         * @exception IllegalStateException is thrown if the server is not
+         * started.
+         */
+        @Override
+        public final synchronized int getPort() {
+            if (!started) {
+                throw new IllegalStateException("Not started");
+            }
+            return serverSocket.getLocalPort();
+        }
+
+        /**
+         * The local address to which the server is bound.
+         *
+         * @return The local address to which the server is bound.
+         * @exception IllegalStateException is thrown if the server is not
+         * started.
+         */
+        @Override
+        public final synchronized InetAddress getAddress() {
+            if (!started) {
+                throw new IllegalStateException("Not started");
+            }
+            return serverSocket.getLocalAddress();
+        }
+
+        /**
+         * Tells whether the server is started.
+         *
+         * @return true if the server is started.
+         */
+        public final synchronized boolean isStarted() {
+            return started;
+        }
+
+        /**
+         * Creates a new datagram socket.
+         *
+         * @param port local port to bind to.
+         * @param address local address to bind to.
+         * @return a new bound server socket ready to listen for packets.
+         * @throws IOException if the socket cannot be created or bound.
+         */
+        protected DatagramSocket newDatagramSocket(int port,
+                InetAddress address)
+                throws IOException {
+            return new DatagramSocket(port, address);
+        }
+
+        /**
+         * Starts listening for connections.
+         *
+         * @throws IOException if the server socket cannot be created or bound.
+         */
+        public final synchronized void start() throws IOException {
+            if (started) {
+                return;
+            }
+            final DatagramSocket socket =
+                    newDatagramSocket(0, InetAddress.getLocalHost());
+            serverSocket = socket;
+            acceptThread = new Thread(this);
+            acceptThread.setDaemon(true);
+            acceptThread.start();
+            started = true;
+        }
+
+        /**
+         * Calls {@code Thread.sleep(linger);}
+         */
+        protected final void lingerIfRequired() {
+            if (linger > 0) {
+                try {
+                    Thread.sleep(linger);
+                } catch (InterruptedException x) {
+                    Thread.interrupted();
+                    final DatagramSocket socket = serverSocket();
+                    if (socket != null && !socket.isClosed()) {
+                        System.err.println("Thread interrupted...");
+                    }
+                }
+            }
+        }
+
+        final synchronized DatagramSocket serverSocket() {
+            return this.serverSocket;
+        }
+
+        final synchronized boolean send(DatagramSocket socket,
+                DatagramPacket response) throws IOException {
+            if (!socket.isClosed()) {
+                socket.send(response);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        /**
+         * The main receive loop.
+         */
+        @Override
+        public final void run() {
+            final DatagramSocket sSocket = serverSocket();
+            try {
+                final int size = Math.max(1024, sSocket.getReceiveBufferSize());
+                if (size > sSocket.getReceiveBufferSize()) {
+                    sSocket.setReceiveBufferSize(size);
+                }
+                while (isStarted() && !Thread.interrupted() && !sSocket.isClosed()) {
+                    final byte[] buf = new byte[size];
+                    final DatagramPacket packet =
+                            new DatagramPacket(buf, buf.length);
+                    lingerIfRequired();
+                    sSocket.receive(packet);
+                    //System.out.println("Received packet from: "
+                    //        + packet.getAddress()+":"+packet.getPort());
+                    handle(sSocket, packet);
+                }
+            } catch (Exception x) {
+                error = x;
+            } finally {
+                synchronized (this) {
+                    if (!sSocket.isClosed()) {
+                        sSocket.close();
+                    }
+                    if (started && this.serverSocket == sSocket) {
+                        started = false;
+                        this.serverSocket = null;
+                        this.acceptThread = null;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Represents an UDP request received by the server.
+         */
+        protected abstract class UdpRequestThread extends Thread {
+
+            protected final DatagramPacket request;
+            protected final DatagramSocket socket;
+
+            protected UdpRequestThread(DatagramSocket socket, DatagramPacket request) {
+                this.socket = socket;
+                this.request = request;
+                this.setDaemon(true);
+            }
+        }
+
+        /**
+         * Creates a new UdpRequestThread to handle a DatagramPacket received
+         * through a DatagramSocket.
+         *
+         * @param socket the socket through which the request was received.
+         * @param request the datagram packet received through the socket.
+         * @return a new UdpRequestThread to handle the request received through
+         * a DatagramSocket.
+         */
+        protected abstract UdpRequestThread createConnection(DatagramSocket socket,
+                DatagramPacket request);
+
+        /**
+         * Creates and starts a new UdpRequestThread to handle the received
+         * datagram packet.
+         *
+         * @param socket the socket through which the request was received.
+         * @param request the datagram packet received through the socket.
+         */
+        private synchronized void handle(DatagramSocket socket,
+                DatagramPacket request) {
+            UdpRequestThread c = createConnection(socket, request);
+            // c can be null if the request requires no response.
+            if (c != null) {
+                c.start();
+            }
+        }
+
+        /**
+         * Close the server socket.
+         *
+         * @throws IOException
+         */
+        @Override
+        public synchronized void close() throws IOException {
+            if (serverSocket != null && !serverSocket.isClosed()) {
+                serverSocket.close();
+            }
+            if (acceptThread != null) {
+                acceptThread.interrupt();
+            }
+        }
+    }
+
+    /**
+     * A small UDP Server that emulates the discard service for tests purposes.
+     * See http://en.wikipedia.org/wiki/Discard_Protocol This server uses an
+     * anonymous port - NOT the standard port 9. We don't guarantee that its
+     * behavior exactly matches the RFC - the only purpose of this server is to
+     * have something that responds to nio tests...
+     */
+    static final class UdpDiscardServer extends AbstractUdpServer {
+
+        public UdpDiscardServer() {
+            this(0L);
+        }
+
+        public UdpDiscardServer(long linger) {
+            super(linger);
+        }
+
+        @Override
+        protected UdpRequestThread createConnection(DatagramSocket socket,
+                DatagramPacket request) {
+            // no response required
+            return null;
+        }
+
+        public static UdpDiscardServer startNewServer() throws IOException {
+            return startNewServer(0);
+        }
+
+        public static UdpDiscardServer startNewServer(long linger) throws IOException {
+            final UdpDiscardServer discardServer = new UdpDiscardServer(linger);
+            discardServer.start();
+            return discardServer;
+        }
+    }
+
+    /**
+     * A small UDP Server that emulates the echo service for tests purposes. See
+     * http://en.wikipedia.org/wiki/Echo_Protocol This server uses an anonymous
+     * port - NOT the standard port 7. We don't guarantee that its behavior
+     * exactly matches the RFC - the only purpose of this server is to have
+     * something that responds to nio tests...
+     */
+    static final class UdpEchoServer extends AbstractUdpServer {
+
+        public UdpEchoServer() {
+            this(0L);
+        }
+
+        public UdpEchoServer(long linger) {
+            super(linger);
+        }
+
+        @Override
+        protected UdpEchoRequest createConnection(DatagramSocket socket,
+                DatagramPacket request) {
+            return new UdpEchoRequest(socket, request);
+        }
+
+        private final class UdpEchoRequest extends UdpRequestThread {
+
+            public UdpEchoRequest(DatagramSocket socket, DatagramPacket request) {
+                super(socket, request);
+            }
+
+            @Override
+            public void run() {
+                try {
+                    lingerIfRequired();
+                    final DatagramPacket response =
+                            new DatagramPacket(request.getData(),
+                                    request.getOffset(), request.getLength(),
+                                    request.getAddress(), request.getPort());
+                    send(socket, response);
+                } catch (IOException io) {
+                    System.err.println("Failed to send response: " + io);
+                    io.printStackTrace(System.err);
+                }
+            }
+        }
+
+        public static UdpEchoServer startNewServer() throws IOException {
+            return startNewServer(0);
+        }
+
+        public static UdpEchoServer startNewServer(long linger) throws IOException {
+            final UdpEchoServer echoServer = new UdpEchoServer(linger);
+            echoServer.start();
+            return echoServer;
+        }
+    }
+
+    /**
+     * A small UDP server that emulates the Day & Time service for tests
+     * purposes. See http://en.wikipedia.org/wiki/Daytime_Protocol This server
+     * uses an anonymous port - NOT the standard port 13. We don't guarantee
+     * that its behavior exactly matches the RFC - the only purpose of this
+     * server is to have something that responds to nio tests...
+     */
+    static final class UdpDayTimeServer extends AbstractUdpServer {
+
+        public UdpDayTimeServer() {
+            this(0L);
+        }
+
+        public UdpDayTimeServer(long linger) {
+            super(linger);
+        }
+
+        @Override
+        protected UdpDayTimeRequestThread createConnection(DatagramSocket socket,
+                DatagramPacket request) {
+            return new UdpDayTimeRequestThread(socket, request);
+        }
+
+        private final class UdpDayTimeRequestThread extends UdpRequestThread {
+
+            public UdpDayTimeRequestThread(DatagramSocket socket,
+                    DatagramPacket request) {
+                super(socket, request);
+            }
+
+            @Override
+            public void run() {
+                try {
+                    lingerIfRequired();
+                    final byte[] data = new Date(System.currentTimeMillis())
+                            .toString().getBytes("US-ASCII");
+                    final DatagramPacket response =
+                            new DatagramPacket(data, 0, data.length,
+                                    request.getAddress(), request.getPort());
+                    send(socket, response);
+                } catch (IOException io) {
+                    System.err.println("Failed to send response: " + io);
+                    io.printStackTrace(System.err);
+                }
+            }
+        }
+
+        public static UdpDayTimeServer startNewServer() throws IOException {
+            return startNewServer(0);
+        }
+
+        public static UdpDayTimeServer startNewServer(long linger)
+                throws IOException {
+            final UdpDayTimeServer echoServer = new UdpDayTimeServer(linger);
+            echoServer.start();
+            return echoServer;
+        }
+    }
 }