DS-Lab / src / main / java / dslab / rmi / channel / SocketChannel.java
SocketChannel.java
Raw
package dslab.rmi.channel;

import dslab.routing.Domain;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.logging.Logger;

import static java.lang.String.join;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;

/**
 * Wraps a stream socket. Takes care of error handling and resource management.
 */
public class SocketChannel implements Channel {

    private static final Logger LOG = Logger.getLogger(SocketChannel.class.getSimpleName());
    protected final BufferedReader in;
    private final PrintWriter out;
    protected final Socket socket;

    public SocketChannel(Domain domain) throws IOException{
        this(new Socket(domain.getAddress(), domain.getPort()));
    }

    /**
     * @param socket that text lines are consumed from
     */
    public SocketChannel(Socket socket) throws IOException {
        if (socket.isClosed()) {
            throw new IllegalArgumentException("The socket " + socket + " passed in is already closed");
        }
        this.socket = socket;

        try {
            this.out = new PrintWriter(socket.getOutputStream());
            this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        } catch (SocketException e) {
            this.close();
            throw new IOException("SocketException while initializing conversation with client " +
                    socket.hashCode() + "." + " The client probably closed the connection.", e);
        } catch (IOException e) {
            this.close();
            throw new IOException("IOException while initializing conversation with client " +
                    socket.hashCode() + ". ", e);
        }
    }

    /**
     * Reads at least one line, but more if more are ready. Use for multi-line messages.
     */
    public String readLinesWhileReady() throws ClosedChannelException {

        var buffer = new ArrayList<String>();

        try {
            do buffer.add(readLine());
            while (in.ready());
        } catch (IOException e) {
            LOG.log(SEVERE, "IOException during conversation with client " + getId() + ". ", e);
            close();
            throw new ClosedChannelException();
        }

        var result = join("\n",buffer);
        return result;
    }


    public String readLine() throws ClosedChannelException {

        if (!isOpen())
            throw new ClosedChannelException();

        String line = null;

        try {
            while (line == null)
                line = in.readLine();

        } catch (SocketException e) {
            LOG.fine("SocketException during conversation with client " + getId() + "." +
                    " The client probably closed the connection, or the server is shutting down.");
            this.close();
            throw new ClosedChannelException();
        } catch (IOException e) {
            LOG.log(WARNING, "IOException during conversation with client " + getId() + ". ", e);
            close();
            throw new ClosedChannelException();
        } catch (NullPointerException e) {
            LOG.fine("Read null from the socket during conversation with client " + socket.hashCode() + "." +
                    " The client sent an end-of-input character.");
            close();
            throw new ClosedChannelException();
        }

        return line;
    }

    public void write(String string) throws ClosedChannelException {
        requireNonNull(string);
        out.println(string);
        out.flush();
    }

    @Override
    public boolean isOpen() {
        return !socket.isClosed();
    }

    @Override
    public void close() {
        if (socket.isClosed())
            return;

        try {
            out.close(); //necessary bc this forces flush of wrappers
            socket.close();
            LOG.info("Channel to client " + getId() + " was closed.");
        } catch (IOException e) {
            LOG.log(SEVERE, "Failed to close conversation resources", e);
        }
    }

    @Override
    public String toString() {
        return socket.toString();
    }

    public String getId() {
        return String.valueOf(socket.hashCode());
    }
}