/* * Copyright 2010 Brian S O'Neill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cojen.dirmi.io; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.net.SocketException; import org.cojen.dirmi.RejectedException; /** * * @author Brian S O'Neill */ class NioSocketChannel implements SimpleSocket { final SocketChannelSelector mSelector; final SocketChannel mChannel; private final Input mIn; private final Output mOut; NioSocketChannel(SocketChannelSelector selector, SocketChannel channel) { try { channel.socket().setTcpNoDelay(true); } catch (SocketException e) { // Ignore. } mSelector = selector; mChannel = channel; mIn = new Input(); mOut = new Output(); } public void flush() throws IOException { mOut.flush(); } public void close() throws IOException { try { mChannel.close(); } finally { mIn.ready(); mOut.ready(); } } public Object getLocalAddress() { return mChannel.socket().getLocalSocketAddress(); } public Object getRemoteAddress() { return mChannel.socket().getRemoteSocketAddress(); } public InputStream getInputStream() { return mIn; } public OutputStream getOutputStream() { return mOut; } public void inputNotify(Channel.Listener listener) { mSelector.inputNotify(mChannel, listener); } public void outputNotify(final Channel.Listener listener) { mSelector.outputNotify(mChannel, listener); } // Class is intended to be wrapped to provide buffering and thread-safety. private class Input extends InputStream implements Channel.Listener { private ByteBuffer mBuffer; private byte[] mWrapped; private IOException mNotify; @Override public int read() throws IOException { // Reads from PacketInputStream are always buffered, and so this // code doesn't really need to be optimized. byte[] b = new byte[1]; return read(b, 0, 1) <= 0 ? -1 : (b[0] & 0xff); } @Override public int read(byte[] b, int off, int len) throws IOException { ByteBuffer buffer; if (mWrapped == b) { buffer = mBuffer; } else { mBuffer = buffer = ByteBuffer.wrap(b); mWrapped = b; } buffer.position(off).limit(off + len); SocketChannel channel = mChannel; int amt; while ((amt = channel.read(buffer)) == 0) { mSelector.inputNotify(channel, this); synchronized (this) { IOException ex; while ((ex = mNotify) == null) { try { wait(); } catch (InterruptedException e) { throw new InterruptedIOException(); } } mNotify = null; if (ex != Ready.THE) { ex.fillInStackTrace(); throw ex; } } } return amt; } @Override public void close() throws IOException { NioSocketChannel.this.close(); } @Override public synchronized void ready() { if (mNotify == null) { mNotify = Ready.THE; } notifyAll(); } @Override public void rejected(RejectedException cause) { try { mChannel.close(); } catch (IOException e) { } closed(cause); } @Override public synchronized void closed(IOException cause) { mNotify = cause; notifyAll(); } } // Class is intended to be wrapped to provide buffering and thread-safety. private class Output extends OutputStream implements Channel.Listener { private ByteBuffer mBuffer; private byte[] mWrapped; private IOException mNotify; @Override public void write(int b) throws IOException { // Writes from PacketOutputStream are usually buffered, and so this // code doesn't really need to be optimized. write(new byte[] {(byte) b}, 0, 1); } @Override public void write(byte[] b, int off, int len) throws IOException { ByteBuffer buffer; if (mWrapped == b) { buffer = mBuffer; } else { mBuffer = buffer = ByteBuffer.wrap(b); mWrapped = b; } buffer.position(off).limit(off + len); SocketChannel channel = mChannel; int amt; while ((amt = channel.write(buffer)) < len) { len -= amt; mSelector.outputNotify(channel, this); synchronized (this) { IOException ex; while ((ex = mNotify) == null) { try { wait(); } catch (InterruptedException e) { throw new InterruptedIOException(); } } mNotify = null; if (ex != Ready.THE) { ex.fillInStackTrace(); throw ex; } } } } @Override public void close() throws IOException { NioSocketChannel.this.close(); } @Override public synchronized void ready() { if (mNotify == null) { mNotify = Ready.THE; } notifyAll(); } @Override public void rejected(RejectedException cause) { try { mChannel.close(); } catch (IOException e) { } closed(cause); } @Override public synchronized void closed(IOException cause) { mNotify = cause; notifyAll(); } } private static class Ready extends IOException { static final Ready THE = new Ready(); private Ready() { } // Override to remove the stack trace. @Override public Throwable fillInStackTrace() { return this; } } }