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(); 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()); } }