com.robonobo.eon.DEONConnection.java Source code

Java tutorial

Introduction

Here is the source code for com.robonobo.eon.DEONConnection.java

Source

package com.robonobo.eon;

/*
 * Eye-Of-Needle
 * Copyright (C) 2008 Will Morton (macavity@well.com) & Ray Hilton (ray@wirestorm.net)
    
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
    
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
    
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.robonobo.common.async.PushDataProvider;
import com.robonobo.common.async.PushDataReceiver;
import com.robonobo.common.concurrent.CatchingRunnable;
import com.robonobo.common.exceptions.SeekInnerCalmException;

public class DEONConnection extends EONConnection implements PushDataProvider {
    public static final int DEONConnectionState_Open = 1;
    public static final int DEONConnectionState_Closed = 2;
    private static final Log log = LogFactory.getLog(DEONConnection.class);
    EonSocketAddress localEP, remoteEP;
    private List<ByteBuffer> incomingDataBufs = new ArrayList<ByteBuffer>();
    private List<EonSocketAddress> incomingDataAddrs = new ArrayList<EonSocketAddress>();
    private LinkedList<DEONPacket> outgoingPkts = new LinkedList<DEONPacket>();
    int state = DEONConnectionState_Closed;
    PushDataReceiver dataReceiver;
    ReentrantLock receiveLock = new ReentrantLock();
    Condition canReceive;
    ReentrantLock sendLock = new ReentrantLock();
    boolean dataReceiverRunning = false;
    boolean waitingForVisitor = false;

    // Don't allow this class to be instantiated directly - use
    // EONManager.GetDEONConnection()
    protected DEONConnection(EONManager mgr) {
        super(mgr);
        canReceive = receiveLock.newCondition();
    }

    public void bind() throws EONException, IllegalArgumentException {
        connect(-1, null);
    }

    public void bind(int localEONPort) throws EONException, IllegalArgumentException {
        connect(localEONPort, null);
    }

    public void connect(EonSocketAddress remoteEndPoint) throws EONException, IllegalArgumentException {
        connect(-1, remoteEndPoint);
    }

    public void connect(int localEONPort, EonSocketAddress remoteEndPoint)
            throws EONException, IllegalArgumentException {
        if (state == DEONConnectionState_Open) {
            throw new EONException("Connection is already open");
        }
        if (localEONPort != -1 && (localEONPort < 1 || localEONPort > 65535)) {
            throw new IllegalArgumentException("Port must be between 1 and 65535");
        }
        localEP = new EonSocketAddress(mgr.getLocalSocketAddress(), 1);
        // First, request our port
        if (localEONPort == -1) {
            // Unspecified port
            localEP.setEonPort(mgr.getPort(this));
            if (localEP.getEonPort() == -1) {
                throw new IllegalArgumentException("No EON ports available");
            }
        } else {
            localEP.setEonPort(localEONPort);
            mgr.requestPort(localEP.getEonPort(), this);
        }
        remoteEP = remoteEndPoint;
        state = DEONConnectionState_Open;
    }

    public void send(byte[] buffer) throws EONException {
        send(buffer, 0, buffer.length);
    }

    public void send(byte[] buffer, int startIndex, int numBytes) throws EONException {
        if (remoteEP == null) {
            throw new EONException("Remote EON Endpoint has not been specified");
        }
        sendTo(remoteEP, buffer, startIndex, numBytes);
    }

    public void sendTo(EonSocketAddress remoteEndPoint, byte[] buffer) throws EONException {
        sendTo(remoteEndPoint, buffer, 0, buffer.length);
    }

    public void sendTo(EonSocketAddress remoteEndPoint, byte[] buffer, int startIndex, int numBytes)
            throws EONException {
        if (startIndex + numBytes > buffer.length)
            throw new SeekInnerCalmException("Supplied indices do not fit in supplied buffer");
        if (state == DEONConnectionState_Closed)
            throw new EONException("Connection is closed");
        byte[] payloadArr = new byte[numBytes];
        System.arraycopy(buffer, startIndex, payloadArr, 0, numBytes);
        ByteBuffer payload = ByteBuffer.wrap(payloadArr);
        DEONPacket thisPacket = new DEONPacket(null, remoteEndPoint, payload);
        thisPacket.setSourceSocketAddress(localEP);
        sendLock.lock();
        try {
            outgoingPkts.add(thisPacket);
            haveDataToSend();
        } finally {
            sendLock.unlock();
        }
        // TODO - some way of specifying that some deonconnections ignore our throttling?
    }

    protected void haveDataToSend() {
        if (waitingForVisitor)
            return;
        waitingForVisitor = true;
        mgr.haveDataToSend(this);
    }

    @Override
    boolean acceptVisitor(PktSendVisitor vis) throws EONException {
        sendLock.lock();
        try {
            waitingForVisitor = false;
            while (outgoingPkts.size() > 0) {
                if (vis.bytesAvailable() < outgoingPkts.getFirst().getPayloadSize()) {
                    waitingForVisitor = true;
                    return true;
                }
                vis.sendPkt(outgoingPkts.removeFirst());
            }
            return false;
        } finally {
            sendLock.unlock();
        }
    }

    public void close() {
        if (state == DEONConnectionState_Closed)
            log.warn("Connection is closed");
        try {
            mgr.returnPort(localEP.getEonPort(), this);
        } catch (Exception e) {
            // dont care, we are closing, but log anyway
            log.warn("Exception caught on close()", e);
        }
        state = DEONConnectionState_Closed;
        fireOnClose();
        receiveLock.lock();
        try {
            canReceive.signalAll();
        } finally {
            receiveLock.unlock();
        }
    }

    public void abort() {
        close();
    }

    public EonSocketAddress getLocalSocketAddress() {
        return localEP;
    }

    // This might be null
    public EonSocketAddress getRemoteSocketAddress() {
        return remoteEP;
    }

    public int getState() {
        return state;
    }

    @Override
    public float getGamma() {
        // DEON conns always have gamma 1
        return 1f;
    }

    /**
     * @return 2 element array - bytebuffer at 0, eonsocketaddress at 1
     */
    public Object[] read() throws EONException, InterruptedException {
        receiveLock.lock();
        try {
            while (incomingDataBufs.size() == 0 && state == DEONConnectionState_Open) {
                canReceive.await();
            }
            if (state == DEONConnectionState_Closed)
                throw new InterruptedException();
            ByteBuffer buf = (ByteBuffer) incomingDataBufs.get(0);
            incomingDataBufs.remove(0);
            EonSocketAddress addr = (EonSocketAddress) incomingDataAddrs.get(0);
            Object[] result = new Object[2];
            result[0] = buf;
            result[1] = addr;
            return result;
        } finally {
            receiveLock.unlock();
        }
    }

    void receivePacket(EONPacket eonPacket) {
        DEONPacket packet = (DEONPacket) eonPacket;
        ByteBuffer buf = ByteBuffer.allocate(packet.getPayload().limit());
        buf.put(packet.getPayload());
        buf.flip();
        EonSocketAddress addr = packet.getSourceSocketAddress();
        receiveLock.lock();
        try {
            incomingDataBufs.add(buf);
            incomingDataAddrs.add(addr);
            if (dataReceiver == null)
                canReceive.signal();
            else {
                // Async read
                if (dataReceiverRunning) // This will be picked up by the already-running receiver
                    return;
                else
                    fireAsyncReceiver();
            }
        } finally {
            receiveLock.unlock();
        }
    }

    /** Must only be called when holding receiveLock*/
    private void fireAsyncReceiver() {
        dataReceiverRunning = true;
        // Fire off our async receiver
        mgr.getExecutor().execute(new CatchingRunnable() {
            public void doRun() throws Exception {
                while (true) {
                    // Grab a copy of our datareceiver in case it's set to null
                    PushDataReceiver dataRec = null;
                    ByteBuffer buf = null;
                    EonSocketAddress sockAddr = null;
                    receiveLock.lock();
                    try {
                        dataRec = dataReceiver;
                        if (dataRec == null || incomingDataBufs.size() == 0) {
                            dataReceiverRunning = false;
                            return;
                        }
                        buf = incomingDataBufs.remove(0);
                        sockAddr = incomingDataAddrs.remove(0);
                    } finally {
                        receiveLock.unlock();
                    }
                    dataRec.receiveData(buf, sockAddr);
                }
            }
        });
    }

    public void setDataReceiver(PushDataReceiver dataReceiver) {
        receiveLock.lock();
        try {
            this.dataReceiver = dataReceiver;
            // If we've got incoming data blocks queued up, handle them now
            if (dataReceiver != null && incomingDataBufs.size() > 0 && !dataReceiverRunning)
                fireAsyncReceiver();
        } finally {
            receiveLock.unlock();
        }
    }
}