/*
* Created by Andrey Cherkashin (acherkashin)
* http://acherkashin.me
*
* License
* Copyright (c) 2015 Andrey Cherkashin
* The project released under the MIT license: http://opensource.org/licenses/MIT
*/
package ragefist.core.network;
import com.juniform.IJUniformPacker;
import com.juniform.JUniform;
import com.juniform.JUniformObject;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import static java.nio.channels.SelectionKey.OP_WRITE;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author acherkashin
*/
public class Connection extends SocketClient
{
private static final Logger _log = Logger.getLogger(Connection.class.getName());
private static int _nextId = 0;
private boolean _isClosed = false;
private final int _id;
private final Server _server;
private final Socket _socket;
private final SelectionKey _selectionKey;
private final SocketChannel _socketChannel;
private final ReadableByteChannel _readableByteChannel;
private final WritableByteChannel _writableByteChannel;
private Object _packetBuffer;
private final Queue<byte[]> _sendQueue = new LinkedList<>();
private Object _attachment = null;
private IJUniformPacker _packer;
private final ISocketStrategy _socketStrategy;
// ---------------------------------------------------------------------- //
// BUILDER
// ---------------------------------------------------------------------- //
public final static class ConnectionBuilder
{
public Server server = null;
public Socket socket = null;
public SelectionKey selectionKey = null;
public IJUniformPacker packerPacker = null;
public ISocketStrategy socketStrategy = null;
// BUILD
public final Connection build() throws IllegalArgumentException {
if (server == null) {
throw new IllegalArgumentException("ConnectionBuilder.server parameter is null");
}
if (socket == null) {
throw new IllegalArgumentException("ConnectionBuilder.socket parameter is null");
}
if (selectionKey == null) {
throw new IllegalArgumentException("ConnectionBuilder.selectionKey parameter is null");
}
if (socketStrategy == null) {
throw new IllegalArgumentException("ConnectionBuilder.socketStrategy parameter is null");
}
return new Connection(this);
}
}
private Connection(ConnectionBuilder builder) {
_id = ++_nextId;
if (_nextId == Integer.MAX_VALUE) {
_nextId = 0;
}
_server = builder.server;
_socket = builder.socket;
_selectionKey = builder.selectionKey;
_socketChannel = (SocketChannel)_selectionKey.channel();
_readableByteChannel = _socket.getChannel();
_writableByteChannel = _socket.getChannel();
_packer = builder.packerPacker;
if (_packer == null) {
_packer = JUniform.getPackerInstance(JUniform.PACKER_TYPE_JSON);
}
_socketStrategy = builder.socketStrategy;
}
// ---------------------------------------------------------------------- //
// PUBLIC
// ---------------------------------------------------------------------- //
// GETTERS / SETTERS
public final int getId() {
return _id;
}
public final boolean isClosed() {
return _isClosed;
}
public final Queue<byte[]> getSendQueue() {
return _sendQueue;
}
public final Object getAttachment() {
return _attachment;
}
public void setAttachment(Object object) {
_attachment = object;
}
public IJUniformPacker getPacker() {
return _packer;
}
public void setPacker(IJUniformPacker packer) {
_packer = packer;
}
/**
* Sending bytes into a socket
* @param bytes
*/
@Override
public void sendPacket(byte[] bytes) {
_log.info("Sending a packet...");
try {
int written = 0;
synchronized(this) {
written = _socketStrategy.writePacket(this, bytes);
}
if (written == 0) {
synchronized(getSendQueue()) {
getSendQueue().add(bytes);
_selectionKey.interestOps(_selectionKey.interestOps() | OP_WRITE);
_selectionKey.selector().wakeup();
}
}
}
catch(ClosedChannelException ex) {
close();
}
catch(IOException ex) {
Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "Connection is closed due to unknown reason: " + ex.getMessage());
close();
}
}
/**
* Sending a packet into the socket
* @param packet
*/
@Override
public void sendPacket(JUniformObject packet) {
byte[] bytes = _packer.fromUniformObjectToByteArray(packet);
sendPacket(bytes);
}
/**
* Returns an array of new packets or NULL if there are no new packets
* @param readBuffer
* @return array | null
* @throws IOException
*/
@Override
public JUniformObject[] readPackets(ByteBuffer readBuffer) throws IOException {
byte[][] packets = _socketStrategy.readPackets(this, readBuffer);
if (packets.length == 0) {
return null;
}
JUniformObject[] objects = new JUniformObject[packets.length];
for(int i = 0; i < packets.length; i++) {
objects[i] = _packer.toUniformObjectFromBytes(packets[i]);
}
return objects;
}
@Override
public final void close() {
_isClosed = true;
try {
_socket.close();
} catch (IOException ex) {
Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "Failed to close socket", ex);
}
}
// package API
@Override
public int getPacketBufferSize() {
return _server.getPacketBufferSize();
}
@Override
public final int read(final ByteBuffer buffer) throws IOException {
return _readableByteChannel.read(buffer);
}
@Override
public final int write(final ByteBuffer buffer) throws IOException {
return _writableByteChannel.write(buffer);
}
}