/*
* Created on Oct 17, 2004
* Created by Alon Rohter
* Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package com.aelitis.azureus.core.networkmanager.impl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import org.gudy.azureus2.core3.util.*;
import com.aelitis.azureus.core.networkmanager.IncomingMessageQueue;
import com.aelitis.azureus.core.networkmanager.NetworkConnection;
import com.aelitis.azureus.core.peermanager.messaging.*;
/**
* Inbound peer message queue.
*/
public class IncomingMessageQueueImpl implements IncomingMessageQueue{
private volatile ArrayList<MessageQueueListener> listeners = new ArrayList<MessageQueueListener>(); //copy-on-write
private final AEMonitor listeners_mon = new AEMonitor( "IncomingMessageQueue:listeners" );
private MessageStreamDecoder stream_decoder;
private final NetworkConnection connection;
/**
* Create a new incoming message queue.
* @param stream_decoder default message stream decoder
* @param connection owner to read from
*/
public IncomingMessageQueueImpl( MessageStreamDecoder stream_decoder, NetworkConnection connection ) {
if (stream_decoder == null) {
throw new NullPointerException("stream_decoder is null");
}
this.connection = connection;
this.stream_decoder = stream_decoder;
}
/**
* Set the message stream decoder that will be used to decode incoming messages.
* @param new_stream_decoder to use
*/
public void setDecoder( MessageStreamDecoder new_stream_decoder ) {
ByteBuffer already_read = stream_decoder.destroy();
connection.getTransport().setAlreadyRead( already_read );
stream_decoder = new_stream_decoder;
stream_decoder.resumeDecoding();
}
public MessageStreamDecoder
getDecoder()
{
return( stream_decoder );
}
/**
* Get the percentage of the current message that has already been received.
* @return percentage complete (0-99), or -1 if no message is currently being received
*/
public int getPercentDoneOfCurrentMessage() {
return stream_decoder.getPercentDoneOfCurrentMessage();
}
/**
* Receive (read) message(s) data from the underlying transport.
* @param max_bytes to read
* @return number of bytes received
* @throws IOException on receive error
*/
public int receiveFromTransport( int max_bytes ) throws IOException {
if( max_bytes < 1 ) {
Debug.out( "max_bytes < 1: " +max_bytes );
return 0;
}
if( listeners.isEmpty() ) {
Debug.out( "no queue listeners registered!" );
throw new IOException( "no queue listeners registered!" );
}
int bytes_read;
try{
//perform decode op
bytes_read = stream_decoder.performStreamDecode( connection.getTransport(), max_bytes );
}catch( RuntimeException e ){
Debug.out( "Stream decode for " + connection.getString() + " failed: " + Debug.getNestedExceptionMessageAndStack(e));
throw( e );
}
//check if anything was decoded and notify listeners if so
Message[] messages = stream_decoder.removeDecodedMessages();
if( messages != null ) {
for( int i=0; i < messages.length; i++ ) {
Message msg = messages[ i ];
if( msg == null ) {
System.out.println( "received msg == null [messages.length=" +messages.length+ ", #" +i+ "]: " +connection.getTransport().getDescription() );
continue;
}
ArrayList listeners_ref = listeners; //copy-on-write
boolean handled = false;
for( int x=0; x < listeners_ref.size(); x++ ) {
MessageQueueListener mql = (MessageQueueListener)listeners_ref.get( x );
handled = handled || mql.messageReceived( msg );
}
if( !handled ) {
if( listeners_ref.size() > 0 ) {
System.out.println( "no registered listeners [out of " +listeners_ref.size()+ "] handled decoded message [" +msg.getDescription()+ "]" );
}
DirectByteBuffer[] buffs = msg.getData();
for( int x=0; x < buffs.length; x++ ) {
buffs[ x ].returnToPool();
}
}
}
}
int protocol_read = stream_decoder.getProtocolBytesDecoded();
if( protocol_read > 0 ) {
ArrayList listeners_ref = listeners; //copy-on-write
for( int i=0; i < listeners_ref.size(); i++ ) {
MessageQueueListener mql = (MessageQueueListener)listeners_ref.get( i );
mql.protocolBytesReceived( protocol_read );
}
}
int data_read = stream_decoder.getDataBytesDecoded();
if( data_read > 0 ) {
ArrayList listeners_ref = listeners; //copy-on-write
for( int i=0; i < listeners_ref.size(); i++ ) {
MessageQueueListener mql = (MessageQueueListener)listeners_ref.get( i );
mql.dataBytesReceived( data_read );
}
}
return bytes_read;
}
/**
* Notifty the queue (and its listeners) of a message received externally on the queue's behalf.
* @param message received externally
*/
public void notifyOfExternallyReceivedMessage( Message message ) {
ArrayList listeners_ref = listeners; //copy-on-write
boolean handled = false;
DirectByteBuffer[] dbbs = message.getData();
int size = 0;
for( int i=0; i < dbbs.length; i++ ) {
size += dbbs[i].remaining( DirectByteBuffer.SS_NET );
}
for( int x=0; x < listeners_ref.size(); x++ ) {
MessageQueueListener mql = (MessageQueueListener)listeners_ref.get( x );
handled = handled || mql.messageReceived( message );
if( message.getType() == Message.TYPE_DATA_PAYLOAD ) {
mql.dataBytesReceived( size );
}
else {
mql.protocolBytesReceived( size );
}
}
if( !handled ) {
if( listeners_ref.size() > 0 ) {
System.out.println( "no registered listeners [out of " +listeners_ref.size()+ "] handled decoded message [" +message.getDescription()+ "]" );
}
DirectByteBuffer[] buffs = message.getData();
for( int x=0; x < buffs.length; x++ ) {
buffs[ x ].returnToPool();
}
}
}
/**
* Manually resume processing (reading) incoming messages.
* NOTE: Allows us to resume docoding externally, in case it was auto-paused internally.
*/
public void resumeQueueProcessing() {
stream_decoder.resumeDecoding();
}
/**
* Add a listener to be notified of queue events.
* @param listener
*/
public void registerQueueListener( MessageQueueListener listener ) {
try{ listeners_mon.enter();
//copy-on-write
ArrayList<MessageQueueListener> new_list = new ArrayList<MessageQueueListener>( listeners.size() + 1 );
if ( listener.isPriority()){
boolean added = false;
for (int i=0;i<listeners.size();i++){
MessageQueueListener existing = listeners.get(i);
if ( added || existing.isPriority()){
}else{
new_list.add( listener );
added = true;
}
new_list.add( existing );
}
if ( !added ){
new_list.add( listener );
}
}else{
new_list.addAll( listeners );
new_list.add( listener );
}
listeners = new_list;
}
finally{ listeners_mon.exit(); }
}
/**
* Cancel queue event notification listener.
* @param listener
*/
public void cancelQueueListener( MessageQueueListener listener ) {
try{ listeners_mon.enter();
//copy-on-write
ArrayList new_list = new ArrayList( listeners );
new_list.remove( listener );
listeners = new_list;
}
finally{ listeners_mon.exit(); }
}
/**
* Destroy this queue.
*/
public void destroy() {
stream_decoder.destroy();
}
}