/* * 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.InputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * * * @author Brian S O'Neill */ abstract class SocketChannel implements Channel { public static SimpleSocket toSimpleSocket(final Socket socket) { return new SimpleSocket() { public void flush() throws IOException { socket.getOutputStream().flush(); } public void close() throws IOException { socket.close(); } public Object getLocalAddress() { return socket.getLocalSocketAddress(); } public Object getRemoteAddress() { return socket.getRemoteSocketAddress(); } public InputStream getInputStream() throws IOException { return socket.getInputStream(); } public OutputStream getOutputStream() throws IOException { return socket.getOutputStream(); } }; } private static final AtomicIntegerFieldUpdater<SocketChannel> closedUpdater = AtomicIntegerFieldUpdater.newUpdater(SocketChannel.class, "mClosed"); private final IOExecutor mExecutor; private final SimpleSocket mSocket; private final ChannelInputStream mIn; private final ChannelOutputStream mOut; // Access while synchronized on mSocket. private CloseableGroup<? super Channel>[] mGroups; private volatile int mClosed; SocketChannel(IOExecutor executor, SimpleSocket socket) throws IOException { mExecutor = executor; mSocket = socket; try { mIn = createInputStream(socket); mOut = createOutputStream(socket); } catch (IOException e) { try { socket.close(); } catch (IOException e2) { } throw e; } } /** * Copy constructor which accepts different streams. */ SocketChannel(SocketChannel channel, ChannelInputStream in, ChannelOutputStream out) { mExecutor = channel.mExecutor; mSocket = channel.mSocket; mIn = in; mOut = out; synchronized (mSocket) { if ((mGroups = channel.mGroups) != null) { for (CloseableGroup<? super Channel> group : mGroups) { group.remove(channel); } channel.mGroups = null; } } } public ChannelInputStream getInputStream() { return mIn; } public ChannelOutputStream getOutputStream() { return mOut; } public Object getLocalAddress() { return mSocket.getLocalAddress(); } public Object getRemoteAddress() { return mSocket.getRemoteAddress(); } public boolean isInputReady() throws IOException { return mIn.isReady(); } public boolean isOutputReady() throws IOException { return mOut.isReady(); } public int setInputBufferSize(int size) { return mIn.setBufferSize(size); } public int setOutputBufferSize(int size) { return mOut.setBufferSize(size); } public void inputNotify(Channel.Listener listener) { mIn.inputNotify(mExecutor, listener); } public void outputNotify(Channel.Listener listener) { mOut.outputNotify(mExecutor, listener); } public boolean inputResume() { return mIn.inputResume(); } public boolean isResumeSupported() { return mIn.isResumeSupported(); } public boolean outputSuspend() throws IOException { return mOut.outputSuspend(); } @Override public String toString() { return "Channel {localAddress=" + getLocalAddress() + ", remoteAddress=" + getRemoteAddress() + '}'; } public void flush() throws IOException { mOut.flush(); } public boolean usesSelectNotification() { return false; } public void register(CloseableGroup<? super Channel> group) { if (!group.add(this)) { return; } synchronized (mSocket) { if (mGroups == null) { mGroups = new CloseableGroup[] {group}; } else { CloseableGroup<? super Channel>[] groups = new CloseableGroup[mGroups.length + 1]; System.arraycopy(mGroups, 0, groups, 0, mGroups.length); groups[groups.length - 1] = group; mGroups = groups; } } } public boolean isClosed() { return mClosed != 0; } public void close() throws IOException { preClose(); try { // Ensure buffer is flushed before closing socket. mOut.outputClose(); } catch (IOException e) { try { mSocket.close(); } catch (IOException e2) { // Ignore. } throw e; } mSocket.close(); // Input must must always be explicitly closed to ensure that // subsequent reads throw ClosedException. Do so after socket close in // case buffered implementation blocks concurrent close while reading. mIn.inputClose(); } public void disconnect() { preClose(); mOut.outputDisconnect(); mIn.inputDisconnect(); try { mSocket.close(); } catch (IOException e) { // Ignore. } } private void preClose() { mClosed = 1; synchronized (mSocket) { if (mGroups != null) { for (CloseableGroup<? super Channel> group : mGroups) { group.remove(this); } mGroups = null; } } } protected IOExecutor executor() { return mExecutor; } protected SimpleSocket socket() { return mSocket; } /** * @return true if just marked closed */ protected boolean markClosed() { return closedUpdater.compareAndSet(this, 0, 1); } /** * Called by constructor. */ abstract ChannelInputStream createInputStream(SimpleSocket socket) throws IOException; /** * Called by constructor. */ abstract ChannelOutputStream createOutputStream(SimpleSocket socket) throws IOException; }