/*
 * 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 sockets which enables to add functionality in subclasses
 * on top of existing, connected 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 SocketWrapper extends Socket {

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

    /**
     * Creates new socket wrapper for a given socket. The delegate
     * must be connected and 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 SocketWrapper(Socket delegate) throws SocketException {
        super(new WrappingSocketImpl(delegate));
        this.delegate = delegate;
    }

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

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

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

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

    public boolean isInputShutdown() {
        return super.isInputShutdown() || delegate.isInputShutdown();
    }

    public boolean isOutputShutdown() {
        return super.isInputShutdown() || delegate.isOutputShutdown();
    }

    private static class WrappingSocketImpl extends SocketImpl {
        private final Socket delegate;
        WrappingSocketImpl(Socket delegate) throws SocketException {
            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");
            }
            if (!(delegate.isConnected())) {
                throw new SocketException("Delegate server socket is not connected");
            }
            this.delegate = delegate;
        }

        protected void create(boolean stream) {}

        protected void connect(String host, int port) {
            // delegate is always connected, thus this method is never called
            throw new UnsupportedOperationException();
        }

        protected void connect(InetAddress address, int port) {
            // delegate is always connected, thus this method is never called
            throw new UnsupportedOperationException();
        }

        protected void connect(SocketAddress address, int timeout) {
            // delegate is always connected, thus this method is never called
            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) {
            // this wrapper is never used by a ServerSocket
            throw new UnsupportedOperationException();
        }

        protected void accept(SocketImpl s) {
            // this wrapper is never used by a ServerSocket
            throw new UnsupportedOperationException();
        }

        protected InputStream getInputStream() throws IOException {
            return delegate.getInputStream();
        }

        protected OutputStream getOutputStream() throws IOException {
            return delegate.getOutputStream();
        }

        protected int available() throws IOException {
            return getInputStream().available();
        }

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

        protected void shutdownInput() throws IOException {
            delegate.shutdownInput();
        }

        protected void shutdownOutput() throws IOException {
            delegate.shutdownOutput();
        }

        protected FileDescriptor getFileDescriptor() {
            // this wrapper is never used by a ServerSocket
            throw new UnsupportedOperationException();
        }

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

        protected int getPort() {
            return delegate.getPort();
        }

        protected boolean supportsUrgentData() {
            return false; // must be overridden in sub-class
        }

        protected void sendUrgentData (int data) throws IOException {
            delegate.sendUrgentData(data);
        }

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

        public Object getOption(int optID) throws SocketException {
            switch (optID) {
                case SocketOptions.IP_TOS:
                    return new Integer(delegate.getTrafficClass());
                case SocketOptions.SO_BINDADDR:
                    return delegate.getLocalAddress();
                case SocketOptions.SO_KEEPALIVE:
                    return Boolean.valueOf(delegate.getKeepAlive());
                case SocketOptions.SO_LINGER:
                    return new Integer(delegate.getSoLinger());
                case SocketOptions.SO_OOBINLINE:
                    return Boolean.valueOf(delegate.getOOBInline());
                case SocketOptions.SO_RCVBUF:
                    return new Integer(delegate.getReceiveBufferSize());
                case SocketOptions.SO_REUSEADDR:
                    return Boolean.valueOf(delegate.getReuseAddress());
                case SocketOptions.SO_SNDBUF:
                    return new Integer(delegate.getSendBufferSize());
                case SocketOptions.SO_TIMEOUT:
                    return new Integer(delegate.getSoTimeout());
                case SocketOptions.TCP_NODELAY:
                    return Boolean.valueOf(delegate.getTcpNoDelay());
                case SocketOptions.SO_BROADCAST:
                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_KEEPALIVE:
                    delegate.setKeepAlive(((Boolean)value).booleanValue());
                    break;
                case SocketOptions.SO_LINGER:
                    if (value instanceof Boolean) {
                        delegate.setSoLinger(((Boolean)value).booleanValue(), 0);
                    }
                    else {
                        delegate.setSoLinger(true, ((Integer)value).intValue());
                    }
                    break;
                case SocketOptions.SO_OOBINLINE:
                    delegate.setOOBInline(((Boolean)value).booleanValue());
                    break;
                case SocketOptions.SO_RCVBUF:
                    delegate.setReceiveBufferSize(((Integer)value).intValue());
                    break;
                case SocketOptions.SO_REUSEADDR:
                    delegate.setReuseAddress(((Boolean)value).booleanValue());
                    break;
                case SocketOptions.SO_SNDBUF:
                    delegate.setSendBufferSize(((Integer)value).intValue());
                    break;
                case SocketOptions.SO_TIMEOUT:
                    delegate.setSoTimeout(((Integer)value).intValue());
                    break;
                case SocketOptions.TCP_NODELAY:
                    delegate.setTcpNoDelay(((Boolean)value).booleanValue());
                    break;
                case SocketOptions.SO_BROADCAST:
                default:
                    throw new IllegalArgumentException("Unsupported option type");
            }
        }
    }

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

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