/*
 * Dijjer - A Peer to Peer HTTP Cache
 * Copyright (C) 2004,2005 Change.Tv, Inc
 *
 * 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
 */
package freenet.io.comm;

import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;

import freenet.io.comm.MessageFilter.MATCHED;
import freenet.node.PeerNode;
import freenet.support.Executor;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Ticker;
import freenet.support.TimeUtil;
import freenet.support.Logger.LogLevel;

public class MessageCore {

	public static final String VERSION = "$Id: MessageCore.java,v 1.22 2005/08/25 17:28:19 amphibian Exp $";
	private static volatile boolean logMINOR;
	private static volatile boolean logDEBUG;

	static {
		Logger.registerLogThresholdCallback(new LogThresholdCallback(){
			@Override
			public void shouldUpdate(){
				logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
				logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
			}
		});
	}

	private Dispatcher _dispatcher;
	private Executor _executor;
	/** _filters serves as lock for both */
	private final LinkedList<MessageFilter> _filters = new LinkedList<MessageFilter>();
	private final LinkedList<Message> _unclaimed = new LinkedList<Message>();
	private static final int MAX_UNMATCHED_FIFO_SIZE = 50000;
	private static final long MAX_UNCLAIMED_FIFO_ITEM_LIFETIME = MINUTES.toMillis(10);  // maybe this should be per message type??
	// FIXME do we need MIN_FILTER_REMOVE_TIME? Can we make this more efficient?
	// FIXME may not work well for newly added filters with timeouts close to the minimum, or filters with timeouts close to the minimum in general.
	private static final long MAX_FILTER_REMOVE_TIME = SECONDS.toMillis(10);
	private static final long MIN_FILTER_REMOVE_TIME = SECONDS.toMillis(1);
	private long startedTime;
	
	public synchronized long getStartedTime() {
		return startedTime;
	}

	public MessageCore(Executor executor) {
		_executor = executor;
	}

	/**
	 * Decode a packet from data and a peer.
	 * Can be called by IncomingPacketFilter's.
     * @param data
     * @param offset
     * @param length
     * @param peer
     */
    public Message decodeSingleMessage(byte[] data, int offset, int length, PeerContext peer, int overhead) {
        try {
            return Message.decodeMessageFromPacket(data, offset, length, peer, overhead);
        } catch (Throwable t) {
            Logger.error(this, "Could not decode packet: "+t, t);
            return null;
        }
    }

    public void start(final Ticker ticker) {
    	synchronized(this) {
    		startedTime = System.currentTimeMillis();
    	}
    	ticker.queueTimedJob(new Runnable() {

			@Override
			public void run() {
				long now = System.currentTimeMillis();
				long nextRun = now + MAX_FILTER_REMOVE_TIME;
				try {
					nextRun = removeTimedOutFilters(nextRun);
				} catch (Throwable t) {
					Logger.error(this, "Failed to remove timed out filters: "+t, t);
				} finally {
					ticker.queueTimedJob(this, Math.max(MIN_FILTER_REMOVE_TIME, nextRun - System.currentTimeMillis()));
				}
			}
    		
    	}, MIN_FILTER_REMOVE_TIME);
    }
    
    /**
     * Remove timed out filters.
     */
	long removeTimedOutFilters(long nextTimeout) {
		long tStart = System.currentTimeMillis() + 1;
		// Extra millisecond to give waitFor() a chance to remove the filter.
		// Avoids exhaustive and unsuccessful search in waitFor() removal of a timed out filter.
		if(logMINOR)
			Logger.minor(this, "Removing timed out filters");
		HashSet<MessageFilter> timedOutFilters = null;
		synchronized (_filters) {
			for (ListIterator<MessageFilter> i = _filters.listIterator(); i.hasNext();) {
				MessageFilter f = i.next();
				if (f.timedOut(tStart)) {
					if(logMINOR)
						Logger.minor(this, "Removing "+f);
					i.remove();
					if(timedOutFilters == null) 
						timedOutFilters = new HashSet<MessageFilter>();
					if(!timedOutFilters.add(f))
						Logger.error(this, "Filter "+f+" is in filter list twice!");
					if(logMINOR) {
						for (ListIterator<Message> it = _unclaimed.listIterator(); it.hasNext();) {
							Message m = it.next();
							MATCHED status = f.match(m, true, tStart);
							if (status == MATCHED.MATCHED) {
								// Don't match it, we timed out; two-level timeouts etc may want it for the next filter.
								Logger.error(this, "Timed out but should have matched in _unclaimed: "+m+" for "+f);
								break;
							}
						}
					}
				} else {
					if(f.hasCallback() && nextTimeout > f.getTimeout())
						nextTimeout = f.getTimeout();
				}
				// Do not break after finding a non-timed-out filter because some filters may 
				// be timed out because their client callbacks say they should be.
				// Also simplifies the logic significantly, we've had some major bugs here.
				
				// See also the end of waitFor() for another weird case.
			}
		}
		
		if(timedOutFilters != null) {
			for(MessageFilter f : timedOutFilters) {
				f.setMessage(null);
				f.onTimedOut(_executor);
			}
		}
		
		long tEnd = System.currentTimeMillis();
		if(tEnd - tStart > 50) {
			if(tEnd - tStart > 3000)
				Logger.error(this, "removeTimedOutFilters took "+(tEnd-tStart)+"ms");
			else
				if(logMINOR) Logger.minor(this, "removeTimedOutFilters took "+(tEnd-tStart)+"ms");
		}
		return nextTimeout;
	}

	/**
	 * Dispatch a message to a waiting filter, or feed it to the
	 * Dispatcher if none are found.
	 * @param m The Message to dispatch.
	 */
	public void checkFilters(Message m, PacketSocketHandler from) {
		final boolean logMINOR = MessageCore.logMINOR;
		final boolean logDEBUG = MessageCore.logDEBUG;
		long tStart = System.currentTimeMillis();
		if(logMINOR) Logger.minor(this, "checkFilters: "+m+" from "+m.getSource());
		if ((m.getSource()) instanceof PeerNode)
		{
			((PeerNode)m.getSource()).addToLocalNodeReceivedMessagesFromStatistic(m);
		}
		boolean matched = false;
		if (logMINOR && !(m.getSpec().equals(DMT.packetTransmit))) {
			Logger.minor(this, "" + (System.currentTimeMillis() % 60000) + ' ' + from + " <- "
					+ m.getSource() + " : " + m);
		}
		MessageFilter match = null;
		ArrayList<MessageFilter> timedOut = null;
		synchronized (_filters) {
			for (ListIterator<MessageFilter> i = _filters.listIterator(); i.hasNext();) {
				MessageFilter f = i.next();
				if (f.matched()) {
					Logger.error(this, "removed pre-matched message filter found in _filters: "+f);
					i.remove();
					continue;
				}
				MATCHED status = f.match(m, false, tStart);
				if(status == MATCHED.TIMED_OUT || status == MATCHED.TIMED_OUT_AND_MATCHED) {
					if(timedOut == null)
						timedOut = new ArrayList<MessageFilter>();
					timedOut.add(f);
					i.remove();
					continue;
				} else if(status == MATCHED.MATCHED) {
					matched = true;
					i.remove();
					match = f;
					// We must setMessage() inside the lock to ensure that waitFor() sees it even if it times out.
					f.setMessage(m);
					if(logMINOR) Logger.minor(this, "Matched (1): "+f);
					break; // Only one match permitted per message
				} else if(logDEBUG) Logger.minor(this, "Did not match "+f);
			}
		}
		if(timedOut != null) {
			for(MessageFilter f : timedOut) {
				if(logMINOR) Logger.minor(this, "Timed out "+f);
				f.setMessage(null);
				f.onTimedOut(_executor);
			}
		}
		if(match != null) {
			match.onMatched(_executor);
		}
		// Feed unmatched messages to the dispatcher
		if ((!matched) && (_dispatcher != null)) {
		    try {
		    	if(logMINOR) Logger.minor(this, "Feeding to dispatcher: "+m);
		        matched = _dispatcher.handleMessage(m);
		    } catch (Throwable t) {
		        Logger.error(this, "Dispatcher threw "+t, t);
		    }
		}
		if(timedOut != null) timedOut.clear();
		// Keep the last few _unclaimed messages around in case the intended receiver isn't receiving yet
		if (!matched) {
			if(logMINOR) Logger.minor(this, "Unclaimed: "+m);
		    /** Check filters and then add to _unmatched is ATOMIC
		     * It has to be atomic, because otherwise we can get a
		     * race condition that results in timeouts on MFs.
		     * 
		     * Specifically:
		     * - Thread A receives packet
		     * - Thread A checks filters. It doesn't match any.
		     * - Thread A feeds to Dispatcher.
		     * - Thread B creates filter.
		     * - Thread B checks _unmatched.
		     * - Thread B adds filter.
		     * - Thread B sleeps.
		     * - Thread A returns from Dispatcher. Which didn't match.
		     * - Thread A adds to _unmatched.
		     * 
		     * OOPS!
		     * The only way to fix this is to have checking the
		     * filters and unmatched be a single atomic operation.
		     * Another race is possible if we merely recheck the
		     * filters after we return from dispatcher, for example.
		     */
			synchronized (_filters) {
				if(logMINOR) Logger.minor(this, "Rechecking filters and adding message");
				for (ListIterator<MessageFilter> i = _filters.listIterator(); i.hasNext();) {
					MessageFilter f = i.next();
					MATCHED status = f.match(m, false, tStart);
					if(status == MATCHED.MATCHED) {
						matched = true;
						match = f;
						i.remove();
						if(logMINOR) Logger.minor(this, "Matched (2): "+f);
						match.setMessage(m);
						break; // Only one match permitted per message
					} else if(status == MATCHED.TIMED_OUT || status == MATCHED.TIMED_OUT_AND_MATCHED) {
						if(timedOut == null)
							timedOut = new ArrayList<MessageFilter>();
						timedOut.add(f);
						i.remove();
						continue;
					}
				}
				if(!matched) {
				    while (_unclaimed.size() > MAX_UNMATCHED_FIFO_SIZE) {
				        Message removed = _unclaimed.removeFirst();
				        long messageLifeTime = System.currentTimeMillis() - removed.localInstantiationTime;
				        if ((removed.getSource()) instanceof PeerNode) {
				            Logger.normal(this, "Dropping unclaimed from "+removed.getSource().getPeer()+", lived "+TimeUtil.formatTime(messageLifeTime, 2, true)+" (quantity)"+": "+removed);
				        } else {
				            Logger.normal(this, "Dropping unclaimed, lived "+TimeUtil.formatTime(messageLifeTime, 2, true)+" (quantity)"+": "+removed);
				        }
				    }
				    _unclaimed.addLast(m);
				    if(logMINOR) Logger.minor(this, "Done");
				}
			}
			if(match != null) {
				match.onMatched(_executor);
			}
			if(timedOut != null) {
				for(MessageFilter f : timedOut) {
					f.setMessage(null);
					f.onTimedOut(_executor);
				}
			}
		}
		long tEnd = System.currentTimeMillis();
		long dT = tEnd - tStart;
		if(dT > 50) {
			if(dT > 3000)
				Logger.error(this, "checkFilters took "+(dT)+"ms with unclaimedFIFOSize of "+_unclaimed.size()+" for matched: "+matched);
			else
				if(logMINOR) Logger.minor(this, "checkFilters took "+(dT)+"ms with unclaimedFIFOSize of "+_unclaimed.size()+" for matched: "+matched);
		}
	}
	
	/** IncomingPacketFilter should call this when a node is disconnected. */
	public void onDisconnect(PeerContext ctx) {
		ArrayList<MessageFilter> droppedFilters = null; // rare operation, we can waste objects for better locking
	    synchronized(_filters) {
			ListIterator<MessageFilter> i = _filters.listIterator();
			while (i.hasNext()) {
			    MessageFilter f = i.next();
			    if(f.matchesDroppedConnection(ctx)) {
			    	if(droppedFilters == null)
			    		droppedFilters = new ArrayList<MessageFilter>();
			    	droppedFilters.add(f);
			    	i.remove();
			    }
			}
	    }
	    if(droppedFilters != null) {
	    	for(MessageFilter mf : droppedFilters) {
		        mf.onDroppedConnection(ctx, _executor);
	    	}
	    }
	}
	
	/** IncomingPacketFilter should call this when a node connects with a new boot ID */
	public void onRestart(PeerContext ctx) {
		ArrayList<MessageFilter> droppedFilters = null; // rare operation, we can waste objects for better locking
	    synchronized(_filters) {
			ListIterator<MessageFilter> i = _filters.listIterator();
			while (i.hasNext()) {
			    MessageFilter f = i.next();
			    if(f.matchesRestartedConnection(ctx)) {
			    	if(droppedFilters == null)
			    		droppedFilters = new ArrayList<MessageFilter>();
			    	droppedFilters.add(f);
			    	i.remove();
			    }
			}
	    }
	    if(droppedFilters != null) {
	    	for(MessageFilter mf : droppedFilters) {
		        mf.onRestartedConnection(ctx, _executor);
	    	}
	    }
	}

	public void addAsyncFilter(MessageFilter filter, AsyncMessageFilterCallback callback, ByteCounter ctr) throws DisconnectedException {
		filter.setAsyncCallback(callback, ctr);
		if(filter.matched()) {
			Logger.error(this, "addAsyncFilter() on a filter which is already matched: "+filter, new Exception("error"));
			filter.clearMatched();
		}
		filter.onStartWaiting(false);
		if(logMINOR) Logger.minor(this, "Adding async filter "+filter+" for "+callback);
		Message ret = null;
		if(filter.anyConnectionsDropped()) {
			throw new DisconnectedException();
			//or... filter.onDroppedConnection(filter.droppedConnection());
		}
		// Check to see whether the filter matches any of the recently _unclaimed messages
		// Drop any _unclaimed messages that the filter doesn't match that are also older than MAX_UNCLAIMED_FIFO_ITEM_LIFETIME
		long now = System.currentTimeMillis();
		long messageDropTime = now - MAX_UNCLAIMED_FIFO_ITEM_LIFETIME;
		long messageLifeTime = 0;
		long timeout = filter.getTimeout();
		synchronized (_filters) {
			//Once in the list, it is up to the callback system to trigger the disconnection, however, we may
			//have disconnected between check above and locking, so we *must* check again.
			if(filter.anyConnectionsDropped()) {
				throw new DisconnectedException();
				//or... filter.onDroppedConnection(filter.droppedConnection());
				//but we are holding the _filters lock!
			}
			if(logMINOR) Logger.minor(this, "Checking _unclaimed");
			for (ListIterator<Message> i = _unclaimed.listIterator(); i.hasNext();) {
				Message m = i.next();
				// These messages have already arrived, so we can match against them even if we are timed out.
				MATCHED status = filter.match(m, true, now);
				if (status == MATCHED.MATCHED) {
					i.remove();
					ret = m;
					if(logMINOR) Logger.minor(this, "Matching from _unclaimed");
					break;
				} else if (m.localInstantiationTime < messageDropTime) {
					i.remove();
					messageLifeTime = now - m.localInstantiationTime;
					if ((m.getSource()) instanceof PeerNode) {
						Logger.normal(this, "Dropping unclaimed from "+m.getSource().getPeer()+", lived "+TimeUtil.formatTime(messageLifeTime, 2, true)+" (age)"+": "+m);
					} else {
						Logger.normal(this, "Dropping unclaimed, lived "+TimeUtil.formatTime(messageLifeTime, 2, true)+" (age)"+": "+m);
					}
				}
			}
			if (ret == null && timeout >= System.currentTimeMillis()) {
				if(logMINOR) Logger.minor(this, "Not in _unclaimed");
			    // Insert filter into filter list in order of timeout
				ListIterator<MessageFilter> i = _filters.listIterator();
				while (true) {
					if (!i.hasNext()) {
						i.add(filter);
						if(logMINOR) Logger.minor(this, "Added at end");
						return;
					}
					MessageFilter mf = i.next();
					if (mf.getTimeout() > timeout) {
						i.previous();
						i.add(filter);
						if(logMINOR) Logger.minor(this, "Added in middle - mf timeout="+mf.getTimeout()+" - my timeout="+filter.getTimeout());
						return;
					}
				}
			}
		}
		if(ret != null) {
			filter.setMessage(ret);
			filter.onMatched(_executor);
			filter.clearMatched();
		} else {
			filter.onTimedOut(_executor);
		}
	}

	/**
	 * Wait for a filter to trigger, or timeout. Blocks until either the trigger is activated, or it times
	 * out, or the peer is disconnected.
	 * @param filter The filter to wait for. This filter must not have a callback.
	 * @param ctr Byte counter to add bytes from the message to.
	 *
	 * @return Either a message, or null if the filter timed out.
	 *
	 * @throws DisconnectedException If the single peer being waited for disconnects.
	 * @throws IllegalArgumentException If {@code filter} has a callback
	 */
	public Message waitFor(MessageFilter filter, ByteCounter ctr) throws DisconnectedException {
		if(logDEBUG) Logger.debug(this, "Waiting for "+filter);

		if(filter.hasCallback()) {
			throw new IllegalArgumentException("waitFor called with a filter that has a callback");
		}

		long startTime = System.currentTimeMillis();
		if(filter.matched()) {
			Logger.error(this, "waitFor() on a filter which is already matched: "+filter, new Exception("error"));
			filter.clearMatched();
		}
		filter.onStartWaiting(true);
		Message ret = null;
		if(filter.anyConnectionsDropped()) {
			filter.onDroppedConnection(filter.droppedConnection(), _executor);
			throw new DisconnectedException();
		}
		// Check to see whether the filter matches any of the recently _unclaimed messages
		// Drop any _unclaimed messages that the filter doesn't match that are also older than MAX_UNCLAIMED_FIFO_ITEM_LIFETIME
		long now = System.currentTimeMillis();
		long messageDropTime = now - MAX_UNCLAIMED_FIFO_ITEM_LIFETIME;
		long messageLifeTime = 0;
		synchronized (_filters) {
			if(logMINOR) Logger.minor(this, "Checking _unclaimed");
			for (ListIterator<Message> i = _unclaimed.listIterator(); i.hasNext();) {
				Message m = i.next();
				MATCHED status = filter.match(m, true, startTime);
				if(status == MATCHED.MATCHED) {
					i.remove();
					ret = m;
					if(logMINOR) Logger.minor(this, "Matching from _unclaimed");
					break;
				} else if (m.localInstantiationTime < messageDropTime) {
					i.remove();
					messageLifeTime = now - m.localInstantiationTime;
					if ((m.getSource()) instanceof PeerNode) {
						Logger.normal(this, "Dropping unclaimed from "+m.getSource().getPeer()+", lived "+TimeUtil.formatTime(messageLifeTime, 2, true)+" (age)"+": "+m);
					} else {
						Logger.normal(this, "Dropping unclaimed, lived "+TimeUtil.formatTime(messageLifeTime, 2, true)+" (age)"+": "+m);
					}
				}
			}
			if (ret == null) {
				if(logMINOR) Logger.minor(this, "Not in _unclaimed");
			    // Insert filter into filter list in order of timeout
				ListIterator<MessageFilter> i = _filters.listIterator();
				while (true) {
					if (!i.hasNext()) {
						i.add(filter);
						if(logMINOR) Logger.minor(this, "Added at end "+filter);
						break;
					}
					MessageFilter mf = i.next();
					if (mf.getTimeout() > filter.getTimeout()) {
						i.previous();
						i.add(filter);
						if(logMINOR) Logger.minor(this, "Added in middle - mf timeout="+mf.getTimeout()+" - my timeout="+filter.getTimeout()+" filter "+filter);
						break;
					}
				}
			}
		}
		long tEnd = System.currentTimeMillis();
		if(tEnd - now > 50) {
			if(tEnd - now > 3000)
				Logger.error(this, "waitFor _unclaimed iteration took "+(tEnd-now)+"ms with unclaimedFIFOSize of "+_unclaimed.size()+" for ret of "+ret);
			else
				if(logMINOR) Logger.minor(this, "waitFor _unclaimed iteration took "+(tEnd-now)+"ms with unclaimedFIFOSize of "+_unclaimed.size()+" for ret of "+ret);
		}
		// Unlock to wait on filter
		// Waiting on the filter won't release the outer lock
		// So we have to release it here
		if(ret == null) {	
			if(logMINOR) Logger.minor(this, "Waiting...");
			synchronized (filter) {
				try {
					// Precaution against filter getting matched between being added to _filters and
					// here - bug discovered by Mason
					// Check reallyTimedOut() too a) for paranoia, b) for filters with a callback (we could conceivably waitFor() them).
				    while(!(filter.matched() || (filter.droppedConnection() != null) || (filter.reallyTimedOut(now = System.currentTimeMillis())))) {
						long wait = filter.getTimeout()-now;
						if(wait <= 0)
							break;
						filter.wait(wait);
					}
				    if(filter.droppedConnection() != null)
				        throw new DisconnectedException();
				} catch (InterruptedException e) {
				}
				ret = filter.getMessage();
			}
			if(logMINOR) Logger.minor(this, "Returning "+ret+" from "+filter);
		}
		
		// More tricky locking ...
		
		synchronized(_filters) {
			// Some nasty race conditions can happen here.
			// E.g. the filter can be matched and yet we timeout at the same time.
			// Hence we need to be absolutely sure that when we remove it it hasn't been matched.
			// Note also that the locking does work here - the filter lock is taken last, and
			// _filters protects both the unwanted messages (above), the filter list, and 
			// is taken when a match is found too.
			if(ret == null) {
				// Check again.
				if(filter.matched()) {
					ret = filter.getMessage();
				}
			}
			filter.clearMatched();
			// We must remove it from _filters before we return, or when it is re-added,
			// it will be in the list twice, and potentially many more times than twice!
			// Fortunately, it will be close to the beginning of the filters list, having
			// just timed out. That is assuming it hasn't already been removed; in that
			// case, this will be slower.
			_filters.remove(filter);
			// A filter being waitFor()'ed cannot have any callbacks, so we don't need to call onMatched().
		}
		
		// Probably get rid...
//		if (Dijjer.getDijjer().getDumpMessageWaitTimes() != null) {
//			Dijjer.getDijjer().getDumpMessageWaitTimes().println(filter.toString() + "\t" + filter.getInitialTimeout() + "\t"
//					+ (System.currentTimeMillis() - startTime));
//			Dijjer.getDijjer().getDumpMessageWaitTimes().flush();
//		}
		long endTime = System.currentTimeMillis();
		if(logDEBUG) Logger.debug(this, "Returning in "+(endTime-startTime)+"ms");
		if((ctr != null) && (ret != null))
			ctr.receivedBytes(ret._receivedByteCount);
		return ret;
	}

	/**
	 * Send a Message to a PeerContext.
	 * @throws NotConnectedException If we are not currently connected to the node.
	 */
	public void send(PeerContext destination, Message m, ByteCounter ctr) throws NotConnectedException {
	    if(m.getSpec().isInternalOnly()) {
	        Logger.error(this, "Trying to send internal-only message "+m+" of spec "+m.getSpec(), new Exception("debug"));
	        return;
	    }
		destination.sendAsync(m, null, ctr);
	}

	public void setDispatcher(Dispatcher d) {
		_dispatcher = d;
	}

	/**
	 * @return the number of received messages that are currently unclaimed
	 */
	public int getUnclaimedFIFOSize() {
		synchronized (_filters){
			return _unclaimed.size();
		}
	}
	
	public Map<String, Integer> getUnclaimedFIFOMessageCounts() {
		Map<String, Integer> messageCounts = new HashMap<String, Integer>();
		synchronized(_filters) {
			for (ListIterator<Message> i = _unclaimed.listIterator(); i.hasNext();) {
				Message m = i.next();
				String messageName = m.getSpec().getName();
				Integer messageCount = messageCounts.get(messageName);
				if (messageCount == null) {
					messageCounts.put(messageName, 1);
				} else {
					messageCount = messageCount + 1;
					messageCounts.put(messageName, messageCount );
				}
			}
		}
		return messageCounts;
	}

	public Executor getExecutor() {
		return _executor;
	}
}
