de.xirp.io.comm.data.Datapool.java Source code

Java tutorial

Introduction

Here is the source code for de.xirp.io.comm.data.Datapool.java

Source

/** 
 * ============================================================================
 * Xirp 2: eXtendable interface for robotic purposes.
 * ============================================================================
 * 
 * Copyright (C) 2005-2007, by Authors and Contributors listed in CREDITS.txt
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at:
 *
 *             http://www.opensource.org/licenses/cpl1.0.php
 *
 * ----------------------------
 * Datapool.java
 * ----------------------------
 *
 * Original Author:  Matthias Gernand [matthias.gernand AT gmx.de]
 *                 Rabea Gransberger [rgransberger AT web.de]
 * Contributor(s):   
 *
 * Changes
 * -------
 * 05.02.2007:      Created by Matthias Gernand.
 */
package de.xirp.io.comm.data;

import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javolution.util.FastMap;
import javolution.util.FastTable;

import org.apache.commons.lang.builder.ToStringBuilder;

import de.xirp.io.comm.CommunicationManager;
import de.xirp.io.comm.handler.IHandler;
import de.xirp.io.event.ConnectionEvent;
import de.xirp.io.event.ConnectionListener;
import de.xirp.io.logging.RobotLogger;
import de.xirp.profile.Robot;
import de.xirp.ui.Application;
import de.xirp.util.Constants;
import de.xirp.util.I18n;
import de.xirp.util.collections.ConcurrentMultiValueHashMap;

/**
 * The datapool is the main point for data which is received from the
 * robot or send to the robot.<br/><br/> The data is saved under a
 * given key. Incoming data of an infrared sensor may be saved under
 * the keys ir_front and ir_rear whereas incoming data of a laser
 * scanner may be saved as whole float array under the key
 * laser_front.<br/><br/>Plugins may add listeners for send and
 * received data to the datapool and/or just get the actual value.
 * 
 * @author Matthias Gernand
 * @author Rabea Gransberger
 */
public final class Datapool {

    /**
     * Log4j Logger for this class
     */
    private static RobotLogger logClass = RobotLogger.getLogger(Datapool.class);

    /**
     * Key for listeners registered for all data of the robot
     */
    private final static String ROBOT_KEY = "%ROBOTALL%"; //$NON-NLS-1$

    /**
     * The robot this datapool is for
     */
    private Robot robot;
    /**
     * The handler over which the robot sends data
     */
    private IHandler handler;

    /**
     * Listeners for incoming data.
     * 
     * @see #ROBOT_KEY is a special key for this
     */
    private ConcurrentMultiValueHashMap<String, DatapoolListener> recvListeners = new ConcurrentMultiValueHashMap<String, DatapoolListener>();
    /**
     * Listeners for outgoing data.
     * 
     * @see #ROBOT_KEY is a special key for this
     */
    private ConcurrentMultiValueHashMap<String, DatapoolListener> sendListeners = new ConcurrentMultiValueHashMap<String, DatapoolListener>();

    /**
     * Queue for sending data. Data is not sent directly, to prevent
     * failures and to decouple the sender from the sending
     */
    private final Queue<DatapoolMessage> sendQueue = new ConcurrentLinkedQueue<DatapoolMessage>();
    /**
     * Queue for received data. Data is not distributed directly to
     * the registered listeners, to decouple the sending robot from
     * the receivers
     */
    // private final Queue<DatapoolMessage> receiveQueue = new
    // ConcurrentLinkedQueue<DatapoolMessage>( );
    private final DatapoolQueue receiveQueue = new DatapoolQueue();
    /**
     * Current data for each key
     */
    private FastMap<String, Object> data = new FastMap<String, Object>().setShared(true);

    /**
     * Threadpool used for distributing received data
     */
    private ExecutorService threadPool = Executors.newCachedThreadPool();

    /**
     * Flag showing if the datapool is stopped. If this is the case
     * all running threads should also stop
     */
    private boolean stopped = false;

    /**
     * Constructs a new datapool for the given robot, initializes
     * threads and listeners.<br/><br/> The constructor should only
     * be called by the {@link DatapoolManager}
     * 
     * @param robot
     *            the robot to create the datapool for
     */
    protected Datapool(Robot robot) {
        this.robot = robot;
        startThreads();
        initListeners();
    }

    /**
     * Initializes all robot dependant listeners for this datapool
     * which might give important event.<br/><br/> - On disconnect
     * all queues and data for this robot is cleared
     */
    private void initListeners() {
        CommunicationManager.addConnectionListener(new ConnectionListener() {

            @Override
            public void connectionEstablished(@SuppressWarnings("unused") ConnectionEvent event) {
                // not used
            }

            @Override
            public void disconnected(ConnectionEvent event) {
                if (event.getRobotName().equals(robot.getName())) {
                    sendQueue.clear();
                    receiveQueue.clear();
                    data.clear();
                }
            }

        });
    }

    /**
     * Checks if the threads should keep running
     * 
     * @return <code>true</code> if the datapool is not stopped and
     *         the main shell is not disposed
     */
    private boolean keepThreadRunning() {
        return !stopped && !Application.getApplication().getShell().isDisposed();
    }

    /**
     * Starts all threads of the datapool. Currently these are the
     * send and receive thread which work on the send and receive
     * queues and will stop when the datapool itself is stopped
     */
    private void startThreads() {
        Thread sendThread = new Thread("DatapoolSend_" + robot.getName()) { //$NON-NLS-1$

            @Override
            public void run() {
                while (keepThreadRunning()) {
                    // get the next message to send
                    DatapoolMessage sendMsg = sendQueue.poll();
                    if (sendMsg != null && handler != null) {
                        // Send over Robot and fire the event
                        handler.sendToRobot(sendMsg);
                        fireSendEvent(new DatapoolEvent(this, robot, sendMsg.getTimestamp(), sendMsg.getKey(),
                                sendMsg.getValue()));
                    }
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        // do nothing
                    }
                }
            }
        };
        sendThread.start();
        Thread receiveThread = new Thread("DatapoolReceive_" + robot.getName()) { //$NON-NLS-1$

            @Override
            public void run() {
                while (keepThreadRunning()) {
                    // Get the next receive message
                    DatapoolMessage receiveMsg = receiveQueue.poll();
                    if (receiveMsg != null) {
                        // get the old data for this key and
                        // check if the current data is really new
                        Object aux = data.put(receiveMsg.getKey(), receiveMsg.getValue());

                        boolean changed = aux == null || !aux.equals(receiveMsg.getValue());

                        // fire the event to the listeners
                        fireReceiveEvent(new DatapoolEvent(this, robot, receiveMsg.getTimestamp(),
                                receiveMsg.getKey(), receiveMsg.getValue()), changed);

                    }
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        // do nothing
                    }
                }
            }
        };
        receiveThread.start();

    }

    /**
     * Adds the given message to a queue of messages which are send to
     * a robot within a thread.
     * 
     * @param message
     *            the message to send
     * @return <code>false</code> if there was no more space in the
     *         send queue or there was no handler available
     */
    public boolean sendToRobot(DatapoolMessage message) {
        boolean ableToSend = false;
        if (handler != null) {
            ableToSend = sendQueue.offer(message);
            if (!ableToSend) {
                logClass.warn(robot.getName(), I18n.getString("Datapool.log.failedToSend") //$NON-NLS-1$
                        + ToStringBuilder.reflectionToString(message)
                        + I18n.getString("Datapool.log.reasonCouldNotOffer") //$NON-NLS-1$
                        + Constants.LINE_SEPARATOR);
            }
        } else {
            logClass.warn(robot.getName(),
                    I18n.getString("Datapool.log.failedToSend") + ToStringBuilder.reflectionToString(message) //$NON-NLS-1$
                            + I18n.getString("Datapool.log.reasonNoHandler") + Constants.LINE_SEPARATOR); //$NON-NLS-1$
        }
        return ableToSend;

    }

    /**
     * Adds the given message to a queue of messages which were
     * received from the robot and should be distributed to the
     * registered listeners
     * 
     * @param message
     *            the message which was received
     * @return <code>false</code> if the message or it's value was
     *         <code>null</code><br>
     *         <code>true</code> if the message was successfully
     *         added to the queue and will be distributed to the
     *         listeners
     */
    public boolean receiveToDatapool(DatapoolMessage message) {
        if (message == null || message.getValue() == null) {
            logClass.warn(robot.getName(), I18n.getString("Datapool.log.failedToReceiveMessageNull") //$NON-NLS-1$
                    + Constants.LINE_SEPARATOR);
            return false;
        }

        boolean ableToReceive = receiveQueue.offer(message);
        if (!ableToReceive) {
            logClass.warn(robot.getName(), I18n.getString("Datapool.log.failedToReceive") //$NON-NLS-1$
                    + message + I18n.getString("Datapool.log.reasonCouldNotOffer") //$NON-NLS-1$
                    + Constants.LINE_SEPARATOR);
        }
        return ableToReceive;

    }

    /**
     * Sets the handler which is used by this datapool for sending
     * messages to the robot</br> This method is called automatically
     * when connecting to the robot, and must not be called explictly
     * 
     * @param handler
     *            the handler to set
     */
    public void setHandler(IHandler handler) {
        this.handler = handler;
    }

    /**
     * Get's the handler of the datapools robot
     * 
     * @return the handler for the robot or <code>null</code> if the
     *         handler was not set yet, f.e. if no connection to the
     *         robot was ever established
     */
    public IHandler getHandler() {
        return handler;
    }

    /**
     * Distributes the given event to all listeners which are
     * registered to the events key or to the robot in an own thread
     * 
     * @param event
     *            the event to distribute
     */
    private void fireSendEvent(final DatapoolEvent event) {
        threadPool.execute(new Runnable() {

            public void run() {
                for (DatapoolListener listener : sendListeners.get(event.getKey())) {
                    listener.valueChanged(event);
                }
                for (DatapoolListener listener : sendListeners.get(ROBOT_KEY)) {
                    listener.valueChanged(event);
                }
            }

        });

    }

    /**
     * Distributes the given event to all listeners which are
     * registered to the events key or to the robot in an own thread
     * 
     * @param event
     *            the event to distribute
     * @param changed
     *            listeners which like to be notified only when data
     *            has changed are skipped is this flag is
     *            <code>false</code>
     */
    private void fireReceiveEvent(final DatapoolEvent event, final boolean changed) {
        threadPool.execute(new Runnable() {

            public void run() {
                for (DatapoolListener listener : recvListeners.get(event.getKey())) {
                    if (listener.notifyOnlyWhenChanged()) {
                        if (changed) {
                            listener.valueChanged(event);
                        }
                    } else {
                        listener.valueChanged(event);
                    }
                }
                for (DatapoolListener listener : recvListeners.get(ROBOT_KEY)) {
                    if (listener.notifyOnlyWhenChanged()) {
                        if (changed) {
                            listener.valueChanged(event);
                        }
                    } else {
                        listener.valueChanged(event);
                    }
                }
            }
        });
    }

    /**
     * Gets the received value for the given key from the datapool
     * 
     * @param key
     *            the key to get the value for
     * @return the value or <code>null</code> if no value for the
     *         given key exists
     */
    public Object getValue(String key) {
        return data.get(key);
    }

    /**
     * Adds the given listeners to the list of listeners which are
     * notified if data for the given key is received from the robot
     * 
     * @param key
     *            the key to register on
     * @param listener
     *            the listener to register
     * @see #addRobotReceiveListener(DatapoolListener)
     */
    public void addDatapoolReceiveListener(String key, DatapoolListener listener) {
        if (key != null && listener != null) {
            recvListeners.put(key, listener);
        }
    }

    /**
     * Adds the given listeners to the list of listeners which are
     * notified if data is received from the robot
     * 
     * @param listener
     *            the listener to register
     * @see #addRobotReceiveListener(DatapoolListener)
     */
    public void addRobotReceiveListener(DatapoolListener listener) {
        addDatapoolReceiveListener(ROBOT_KEY, listener);
    }

    /**
     * Removes the given listeners from the list of listeners which
     * are notified if data for the given key is received from the
     * robot
     * 
     * @param key
     *            the key to deregister from
     * @param listener
     *            the listener to deregister
     * @return <code>true</code> if the listener was removed or the
     *         key or listener given were <code>null</code>
     * @see #removeRobotReceiveListener(DatapoolListener)
     */
    public boolean removeDatapoolReceiveListener(String key, DatapoolListener listener) {
        if (key != null && listener != null) {
            return recvListeners.remove(key, listener);
        }
        return true;
    }

    /**
     * Removes the given listeners from the list of listeners which
     * are notified if data is received from the robot
     * 
     * @param listener
     *            the listener to deregister
     * @return <code>true</code> if the listener was removed or the
     *         key or listener given were <code>null</code>
     * @see #removeRobotReceiveListener(DatapoolListener)
     */
    public boolean removeRobotReceiveListener(DatapoolListener listener) {
        return removeDatapoolReceiveListener(ROBOT_KEY, listener);
    }

    /**
     * Removes every occurrence of the given listeners from the list
     * of listeners which are notified if data is received from the
     * robot
     * 
     * @param listener
     *            the listener to deregister
     * @see #removeRobotReceiveListener(DatapoolListener)
     */
    public void removeDatapoolReceiveListener(DatapoolListener listener) {
        List<String> keys = new ArrayList<String>();

        for (Entry<String, FastTable<DatapoolListener>> e : recvListeners.entrySet()) {
            for (DatapoolListener dl : e.getValue()) {
                if (dl.equals(listener)) {
                    keys.add(e.getKey());
                }
            }
        }

        for (String key : keys) {
            removeDatapoolReceiveListener(key, listener);
        }
    }

    /**
     * Adds the given listeners to the list of listeners which are
     * notified if data for the given key is send to the robot
     * 
     * @param key
     *            the key to register on
     * @param listener
     *            the listener to register
     * @see #addRobotSendListener(DatapoolListener)
     */
    public void addDatapoolSendListener(String key, DatapoolListener listener) {
        if (key != null && listener != null) {
            sendListeners.put(key, listener);
        }
    }

    /**
     * Adds the given listeners to the list of listeners which are
     * notified if data is send to the robot
     * 
     * @param listener
     *            the listener to register
     * @see #addRobotSendListener(DatapoolListener)
     */
    public void addRobotSendListener(DatapoolListener listener) {
        addDatapoolSendListener(ROBOT_KEY, listener);
    }

    /**
     * Removes the given listeners from the list of listeners which
     * are notified if data for the given key is received from the
     * robot
     * 
     * @param key
     *            the key to deregister from
     * @param listener
     *            the listener to deregister
     * @return <code>true</code> if the listener was removed or the
     *         key or listener given were <code>null</code>
     * @see #removeRobotReceiveListener(DatapoolListener)
     */
    public boolean removeDatapoolSendListener(String key, DatapoolListener listener) {
        if (key != null && listener != null) {
            return sendListeners.remove(key, listener);
        }
        return true;
    }

    /**
     * Removes the given listeners from the list of listeners which
     * are notified if data is received from the robot
     * 
     * @param listener
     *            the listener to deregister
     * @return <code>true</code> if the listener was removed or the
     *         key or listener given were <code>null</code>
     * @see #removeRobotReceiveListener(DatapoolListener)
     */
    public boolean removeRobotSendListener(DatapoolListener listener) {
        return removeDatapoolSendListener(ROBOT_KEY, listener);
    }

    /**
     * Removes every occurrence of the given listeners from the list
     * of listeners which are notified if data is received from the
     * robot
     * 
     * @param listener
     *            the listener to deregister
     * @see #removeRobotReceiveListener(DatapoolListener)
     */
    public void removeDatapoolSendListener(DatapoolListener listener) {
        List<String> keys = new ArrayList<String>();

        for (Entry<String, FastTable<DatapoolListener>> e : sendListeners.entrySet()) {
            for (DatapoolListener dl : e.getValue()) {
                if (dl.equals(listener)) {
                    keys.add(e.getKey());
                }
            }
        }

        for (String key : keys) {
            removeDatapoolSendListener(key, listener);
        }
    }

    /**
     * Stops this datapool: Shuts down all running threads of this
     * datapool.<br/><br/> This method is called when stopping the
     * datapool manager. This datapool may not be started again after
     * calling this method.
     */
    protected void stop() {
        this.threadPool.shutdown();
        stopped = true;
    }
}