/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.net;

import java.io.*;
import java.net.*;
import java.nio.channels.*;

/**
 * Wrapper for server sockets which enables to add functionality in subclasses
 * on top of existing, bound server sockets. It is useful when direct
 * subclassing of delegate socket class is not possible, e.g. if the delegate
 * socket is created by a library. Possible usage example is socket factory
 * chaining. This class delegates all socket-related requests to the wrapped
 * delegate, as of JDK 1.4.
 *
 * @author Dawid Kurzyniec
 * @version 1.4
 */
public abstract class ServerSocketWrapper extends ServerSocket {

    /**
     * the wrapped delegate socket.
     */
    protected final ServerSocket delegate;

    /**
     * Creates new socket wrapper for a given server socket. The delegate
     * must be bound and it must not be closed.
     * @param delegate the delegate socket to wrap
     * @throws SocketException if the delegate socket is closed, not bound,
     *                         or not connected
     */
    protected ServerSocketWrapper(ServerSocket delegate) throws IOException {
        if (delegate == null) {
            throw new NullPointerException();
        }
        if (delegate.isClosed()) {
            throw new SocketException("Delegate server socket is closed");
        }
        if (!(delegate.isBound())) {
            throw new SocketException("Delegate server socket is not bound");
        }
        this.delegate = delegate;
    }

    /**
     * Returns true, indicating that the socket is bound.
     * @return true
     */
    public boolean isBound() {
        return true;
    }

    public boolean isClosed() {
        return delegate.isClosed();
    }

    // no need to overwrite bind() methods - the socket is always bound,
    // so they always throw SocketException

    public InetAddress getInetAddress() {
        return delegate.getInetAddress();
    }

    public int getLocalPort() {
        return delegate.getLocalPort();
    }

    public SocketAddress getLocalSocketAddress() {
        return delegate.getLocalSocketAddress();
    }

    public Socket accept() throws IOException {
        Socket socket = delegate.accept();
        return wrapAcceptedSocket(socket);
    }

    protected abstract Socket wrapAcceptedSocket(Socket socket) throws IOException;

    public void close() throws IOException {
        delegate.close();
    }

    public ServerSocketChannel getChannel() {
        return delegate.getChannel();
    }

    public void setSoTimeout(int timeout) throws SocketException {
        delegate.setSoTimeout(timeout);
    }

    public int getSoTimeout() throws IOException {
        return delegate.getSoTimeout();
    }

    public void setReuseAddress(boolean on) throws SocketException {
        delegate.setReuseAddress(on);
    }

    public boolean getReuseAddress() throws SocketException {
        return delegate.getReuseAddress();
    }

    public String toString() {
        return delegate.toString();
    }

    public void setReceiveBufferSize (int size) throws SocketException {
        delegate.setReceiveBufferSize(size);
    }

    public int getReceiveBufferSize() throws SocketException{
        return delegate.getReceiveBufferSize();
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof ServerSocketWrapper)) return false;
        ServerSocketWrapper that = (ServerSocketWrapper)obj;
        return this.delegate.equals(that.delegate);
    }

    public int hashCode() {
        return delegate.hashCode() ^ 0x01010101;
    }

//    private static class WrappingServerSocketImpl extends SocketImpl {
//        private final ServerSocket delegate;
//        WrappingServerSocketImpl(ServerSocket delegate) throws SocketException {
//            this.delegate = delegate;
//        }
//
//        protected void create(boolean stream) {}
//
//        protected void connect(String host, int port) {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected void connect(InetAddress address, int port) {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected void connect(SocketAddress address, int timeout) {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected void bind(InetAddress host, int port) {
//            // delegate is always bound, thus this method is never called
//            throw new UnsupportedOperationException();
//        }
//
//        protected void listen(int backlog) {
//            // delegate is always bound, thus this method is never called
//            throw new UnsupportedOperationException();
//        }
//
//        protected void accept(SocketImpl s) {
//            // this wrapper is only used by a WrappingServerSocket, which
//            // overrides method accept() so that implAccept(), and thus
//            // this method, is never called
//            throw new UnsupportedOperationException();
//        }
//
//        protected InputStream getInputStream() throws IOException {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected OutputStream getOutputStream() throws IOException {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected int available() throws IOException {
//            return getInputStream().available();
//        }
//
//        protected void close() throws IOException {
//            delegate.close();
//        }
//
//        protected void shutdownInput() throws IOException {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected void shutdownOutput() throws IOException {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected FileDescriptor getFileDescriptor() {
//            // this wrapper is never used by a ServerSocket
//            throw new UnsupportedOperationException();
//        }
//
//        protected InetAddress getInetAddress() {
//            return delegate.getInetAddress();
//        }
//
//        protected int getPort() {
//            // this method is used (only) by ServerSocket.toString, hence we
//            // can't mark it as unsupported
//            return getLocalPort();
//        }
//
//        protected boolean supportsUrgentData() {
//            return false; // must be overridden in sub-class
//        }
//
//        protected void sendUrgentData(int data) throws IOException {
//            // this wrapper is only used by ServerSockets
//            throw new UnsupportedOperationException();
//        }
//
//        protected int getLocalPort() {
//            return delegate.getLocalPort();
//        }
//
//        public Object getOption(int optID) throws SocketException {
//            switch (optID) {
//                case SocketOptions.SO_BINDADDR:
//                    return delegate.getInetAddress();
//                case SocketOptions.SO_RCVBUF:
//                    return new Integer(delegate.getReceiveBufferSize());
//                case SocketOptions.SO_REUSEADDR:
//                    return Boolean.valueOf(delegate.getReuseAddress());
//                case SocketOptions.SO_TIMEOUT:
//                    try {
//                        return new Integer(delegate.getSoTimeout());
//                    }
//                    catch (SocketException e) {
//                        throw e;
//                    }
//                    catch (IOException e) {
//                        SocketException se = new SocketException(e.getMessage());
//                        se.initCause(e);
//                        throw se;
//                    }
//                case SocketOptions.SO_BROADCAST:
//                case SocketOptions.SO_KEEPALIVE:
//                case SocketOptions.SO_LINGER:
//                case SocketOptions.SO_OOBINLINE:
//                case SocketOptions.TCP_NODELAY:
//                case SocketOptions.SO_SNDBUF:
//                default:
//                    throw new IllegalArgumentException(
//                        "Unsupported option type");
//            }
//        }
//
//        public void setOption(int optID, Object value) throws SocketException {
//            switch (optID) {
//                case SocketOptions.SO_BINDADDR:
//                    throw new IllegalArgumentException("Socket is bound");
//                case SocketOptions.SO_RCVBUF:
//                    delegate.setReceiveBufferSize( ( (Integer) value).intValue());
//                    break;
//                case SocketOptions.SO_REUSEADDR:
//                    delegate.setReuseAddress( ( (Boolean) value).booleanValue());
//                    break;
//                case SocketOptions.SO_TIMEOUT:
//                    delegate.setSoTimeout( ( (Integer) value).intValue());
//                    break;
//                case SocketOptions.SO_BROADCAST:
//                case SocketOptions.SO_KEEPALIVE:
//                case SocketOptions.SO_LINGER:
//                case SocketOptions.SO_OOBINLINE:
//                case SocketOptions.SO_SNDBUF:
//                case SocketOptions.TCP_NODELAY:
//                default:
//                    throw new IllegalArgumentException(
//                        "Unsupported option type");
//            }
//        }
//    }
}

