de.uniluebeck.itm.spyglass.io.SpyglassPacketRecorder.java Source code

Java tutorial

Introduction

Here is the source code for de.uniluebeck.itm.spyglass.io.SpyglassPacketRecorder.java

Source

/*----------------------------------------------------------------------------------------
 * This file is part of the
 * WSN visualization framework SpyGlass. Copyright (C) 2004-2007 by the SwarmNet (www.swarmnet.de)
 * project SpyGlass is free software; you can redistribute it and/or modify it under the terms of
 * the BSD License. Refer to spyglass-licence.txt file in the root of the SpyGlass source tree for
 * further details.
 * ---------------------------------------------------------------------------------------
 */
package de.uniluebeck.itm.spyglass.io;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.log4j.Logger;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.simpleframework.xml.Element;

import de.uniluebeck.itm.spyglass.SpyglassEnvironment;
import de.uniluebeck.itm.spyglass.core.Spyglass;
import de.uniluebeck.itm.spyglass.gateway.FileReaderGateway;
import de.uniluebeck.itm.spyglass.gateway.Gateway;
import de.uniluebeck.itm.spyglass.packet.SpyglassPacket;
import de.uniluebeck.itm.spyglass.packet.SpyglassPacketException;
import de.uniluebeck.itm.spyglass.util.SpyglassLoggerFactory;
import de.uniluebeck.itm.spyglass.util.Tools;

// --------------------------------------------------------------------------------
/**
 * Instances of this class can record provided packages to files located in the local file system.
 * The recorded packages can be played back, too.<br>
 * Since it extends the {@link PacketReader} it can be used as ordinary {@link PacketReader} with
 * extended functionality within SpyGlass.
 * 
 * @author Sebastian Ebers
 * @see SpyglassPacket
 */
public class SpyglassPacketRecorder extends SpyGlassPacketQueue implements PacketRecorder {

    // ----------------------------------------------------------------
    /** Object to log status and error messages within the PacketRecorder */
    private static final Logger log = SpyglassLoggerFactory.getLogger(PacketRecorder.class);

    // --------------------------------------------------------------------------------
    /** Object to secure packet reading and manipulations at the input gateway */
    private final Object gatewayMutex = new Object();

    // ----------------------------------------------------------------
    /** Indicates whether the recorder should skip waiting for the arrival of a new packet */
    private AtomicBoolean skipWaiting = new AtomicBoolean(false);

    // ----------------------------------------------------------------
    /** Indicates whether the recorder is shut down or not */
    private volatile boolean recorderShutDown = false;

    // ----------------------------------------------------------------
    /** Indicates whether the incoming packages are currently recorded */
    private boolean record = false;

    // ----------------------------------------------------------------
    /** The path to the directory where the record files are located */
    private final String recordDirectory = new File(SpyglassEnvironment.getDefalutRecordDirectory())
            .getAbsoluteFile().toString();

    // ----------------------------------------------------------------
    /** Object used to coordinate the time between delivering two packets */
    private DelayModule delayModule = null;

    // ----------------------------------------------------------------
    /** Object encapsulating playback functionality */
    private PlaybackModule playbackModule = null;

    // ----------------------------------------------------------------
    /** Object encapsulating recording functionality */
    private RecordingModule recModule = null;

    // --------------------------------------------------------------------------------
    /**
     * Constructor
     */
    public SpyglassPacketRecorder() {
        setSourceType(SOURCE_TYPE.ISHELL);
        playbackModule = new PlaybackModule();
    }

    // --------------------------------------------------------------------------------
    @Override
    public void init(final Spyglass spyglass) {

        super.init(spyglass);
        delayModule = new DelayModule(gatewayMutex, spyglass.getConfigStore().getSpyglassConfig());
        playbackModule.init(spyglass);
        recModule = new RecordingModule();
    }

    // --------------------------------------------------------------------------------
    @Override
    public void add(final SpyglassPacket packet) {
        if (!recorderShutDown) {
            // the packets could be pushed in the super classes queue but when the readFromFile time
            // lasts to long, an out of memory exception might occur
            if (!getSourceType().equals(SOURCE_TYPE.FILE)) {
                recModule.handlePacket(packet);
                super.add(packet);
            }
        } else {
            log.warn("The packet will not be pushed into the packet queue since this instance was shut down!");
        }
    }

    // --------------------------------------------------------------------------------
    @Override
    public SpyglassPacket getNextPacket(final boolean block) throws SpyglassPacketException, InterruptedException {

        SpyglassPacket packet = null;

        if (!recorderShutDown) {
            // this do while is needed, to guarantee that valid packets are returned
            do {

                if (block && getSourceType().equals(SOURCE_TYPE.NONE)) {
                    synchronized (gatewayMutex) {
                        gatewayMutex.wait();
                    }
                }

                // if a file was chosen as input source ...
                else if (getSourceType().equals(SOURCE_TYPE.FILE)) {
                    recModule.handlePacket(packet = playbackModule.getNextPacket(block));
                } else {
                    packet = super.getNextPacket(block);
                }
            } while ((packet == null) && !skipWaiting.getAndSet(false));
        } else {
            log.warn("No packet will be fetched from the queue since this instance was shut down!");
        }

        return packet;
    }

    // --------------------------------------------------------------------------------
    /**
     * Returns whether the currently provided files are read from a file or handed over from iShell
     * 
     * @return <code>true</code> if the currently provided files are read from a file or handed over
     *         from iShell
     */
    public boolean isReadFromFile() {
        return getSourceType().equals(PacketReader.SOURCE_TYPE.FILE);
    }

    // --------------------------------------------------------------------------------
    /**
     * Enables or disables the readFromFile mode.<br>
     * Note that the packets will fetched from the packet queue if they are not read from a file
     * 
     * @param enable
     *            <code>true</code> if the readFromFile mode is to be enabled, <code>false</code>
     *            otherwise
     * @return <code>true</code> if the readFromFile mode is enabled, <code>false</code> otherwise
     */
    public boolean setReadFromFile(final boolean enable) {
        this.setSourceType(enable ? SOURCE_TYPE.FILE : SOURCE_TYPE.ISHELL);
        return getSourceType().equals(SOURCE_TYPE.FILE);
    }

    // --------------------------------------------------------------------------------
    @Override
    public void setSourceType(final SOURCE_TYPE sourceType) {

        if (sourceType.equals(SOURCE_TYPE.FILE) && (getPlayBackFile() == null)) {
            setPlayBackFile(playbackModule.selectPlayBackFileByUser());
        }
        super.setSourceType(sourceType);
    }

    // --------------------------------------------------------------------------------
    /**
     * Returns whether the incoming packets are currently recorded
     * 
     * @return <code>true</code> if the incoming packets are currently recorded
     */
    public boolean isRecord() {
        return record;
    }

    // --------------------------------------------------------------------------
    /**
     * Sets the file to be used to play back the previously recorded packages.
     * 
     * @param path
     *            the path to the file
     * @return <code>true</code> if a readFromFile file was set successfully
     */
    public boolean setPlayBackFile(final String path) {
        return playbackModule.setPlayBackFile(path);
    }

    // --------------------------------------------------------------------------------
    @Override
    public File getPlayBackFile() {
        return playbackModule.getPlayBackFile();
    }

    // --------------------------------------------------------------------------------
    @Override
    public void reset() throws IOException {
        log.info("Reset requested");
        skipWaiting.set(true);

        super.reset();
        synchronized (gatewayMutex) {
            if (isReadFromFile() && !isRecord()) {
                MessageDialog.openInformation(null, "Reset Playbak",
                        "The playback will be started from the beginning of the file.");
            }

            else if (isReadFromFile() && isRecord()) {
                if (MessageDialog.openQuestion(null, "Reset Recorder",
                        "The playback will be started from the beginning of the file.\r\n"
                                + "Do you want to disable recording?")) {
                    setRecording(false);
                }
            }

            else if (isRecord()
                    && MessageDialog.openQuestion(null, "Reset Recorder", "Do you want to disable recording?")) {
                setRecording(false);
            }

            recModule.reset();

            delayModule.reset();

            gatewayMutex.notifyAll();
            playbackModule.reset();
        }

        log.info("Reset done");
    }

    // --------------------------------------------------------------------------------
    /**
     * Sets the file which is used for recording the input
     * 
     * @param path
     *            the path to the file which is used for recording the input
     */
    public void setRecordFile(final String path) {
        recModule.setRecordFile(path);
    }

    // --------------------------------------------------------------------------------
    @Override
    public File getRecordFile() {
        return recModule.getRecordFile();
    }

    // --------------------------------------------------------------------------------
    /**
     * Activates or deactivates the recording mode
     * 
     * @param enable
     *            if <code>true</code> the recording mode will be activated, if <code>false</code>
     *            the recording mode will be deactivated
     * @return <code>true</code> if the recording mode was activated, <code>false</code> otherwise
     * 
     */
    public boolean setRecording(final boolean enable) {
        return recModule.setRecording(enable);
    }

    // --------------------------------------------------------------------------------
    /**
     * Shuts the packet recorder down.<br>
     * All currently cached packets will be disposed and the thread consuming the packets will be
     * stopped. Additionally, all registered listeners will be removed.
     * 
     * @throws IOException
     *             thrown if the resetting of the input fails
     */
    @Override
    public void shutdown() throws IOException {
        super.shutdown();
        recorderShutDown = true;
        recModule.shutdown();
    }

    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    /**
     * Module used for recording {@link SpyglassPacket}s
     * 
     * @author Sebastian Ebers
     */
    private class RecordingModule {

        /**
         * Constructor
         */
        public RecordingModule() {
            // nothing to do
        }

        // ----------------------------------------------------------------
        /**
         * The queue where packets are dropped by the packet dispatcher and which is maintained
         * concurrently
         */
        private ConcurrentLinkedQueue<SpyglassPacket> recordingQueue = new ConcurrentLinkedQueue<SpyglassPacket>();

        // ----------------------------------------------------------------
        /** The thread used to consume packets from the packet queue */
        private Thread packetConsumerThread = null;

        // ----------------------------------------------------------------
        /** The path to the file the packages are recorder */
        private String recordFileString = null;

        // ----------------------------------------------------------------
        /**
         * The string to the file which was last selected by the user for recording (this will be
         * needed to check whether the user has to be asked to append content)
         */
        private String lastSelectedRecordFilePath = null;

        // --------------------------------------------------------------------------------
        /**
         * Returns the queue where the packages which are to be recorded are temporarily stored
         * 
         * @return the queue where the packages which are to be recorded are temporarily stored
         */
        private ConcurrentLinkedQueue<SpyglassPacket> getPacketQueue() {
            return recordingQueue;
        }

        // --------------------------------------------------------------------------------
        /**
         * Sets the file which is used for recording the input
         * 
         * @param path
         *            the path to the file which is used for recording the input
         * @return <code>true</code> if the file was set successfully
         */
        @SuppressWarnings("synthetic-access")
        public boolean setRecordFile(final String path) {

            if (path == null) {
                recordFileString = null;
            }

            else {

                if (isReadFromFile() && playbackModule.equalsPlaybackFilePath(path)) {

                    MessageDialog.openError(null, "The file is already in use",
                            "Sorry, the chosen file is already in use for playback. Please choose a different one ");
                    return false;
                }

                // if a file conflict was detected but the readFromFile mode is disabled, set
                // the playback file to null
                else if (!isReadFromFile() && playbackModule.equalsPlaybackFilePath(path)) {
                    playbackModule.setPlayBackFile(null);
                }

                // if the recording is currently in process, stop it, replace the output file and
                // restart the recording again
                if (isRecord()) {
                    setRecording(false);
                    recordFileString = path;
                    setRecording(true);
                } else {
                    // otherwise just set the output file
                    recordFileString = path;
                }
            }
            return true;
        }

        // --------------------------------------------------------------------------------
        /**
         * Returns the file used to record the packages
         * 
         * @return the file used to record the packages
         */
        public File getRecordFile() {
            return recordFileString != null ? new File(recordFileString) : null;
        }

        // --------------------------------------------------------------------------------
        /**
         * Activates or deactivates the recording mode
         * 
         * @param enable
         *            if <code>true</code> the recording mode will be activated, if
         *            <code>false</code> the recording mode will be deactivated
         * @return <code>true</code> if the recording mode was activated, <code>false</code>
         *         otherwise
         * 
         */
        @SuppressWarnings("synthetic-access")
        public boolean setRecording(final boolean enable) {

            if (enable) {

                // if no record file was selected, let the user select one and the user denies to
                // select a file, the recording will be aborted

                if (getRecordFilePath() == null) {
                    log.info(
                            "No file selected to be used to record the packages.\r\n The recording will be aborted!");
                    record = false;
                }

                if (recordFileString != null) {

                    final File file = new File(recordFileString);

                    if (Tools.isWritable(file, true)) {

                        // Check if the file already exists and if it differs from the previous
                        // chosen one.
                        // If so, the user can decide to append the information, to overwrite the
                        // file or to abort the recording
                        final int result = checkAppend(file);

                        // the user decided to abort selecting a file and nothing is to be done
                        if (result == 2) {
                            // in case a recording process is already running it will not be aborted
                            return record;
                        }

                        startPacketConsumerThread((result == 0));
                        record = true;
                    }
                }

            }

            // if recording is to be disabled, stop the thread and clean up
            else {

                record = false;
                if ((packetConsumerThread != null) && !packetConsumerThread.isInterrupted()) {
                    packetConsumerThread.interrupt();
                }
                recordingQueue.clear();
                packetConsumerThread = null;
            }

            return record;
        }

        // --------------------------------------------------------------------------------
        /**
         * Checks if the selected recording file already exists. If so, a dialog window will show up
         * to ask the user whether the content is to be appended to the file or not. Alternatively,
         * the user can abort selecting the file at all.
         * 
         * @param file
         *            the file to be checked
         * @return <ul>
         *         <li><tt>0</tt> if the content is to be appended</li>
         *         <li><tt>1</tt> if the files content is to be replaced with the new one</li>
         *         <li><tt>2</tt> if the selection is to be aborted</li>
         *         </ul>
         */
        private int checkAppend(final File file) {
            int result = 0;

            // Check if the file already exists and if it differs from the previous chosen one.
            // If so, the user can decide to append the information, to overwrite the file or to
            // abort the recording
            if (file.exists() && (file.length() > 0)) {
                result = new MessageDialog(Display.getCurrent().getActiveShell(), "Append or Replace", null,
                        "The file already exists. Shall the new information be appended or shall the file be replaced?",
                        SWT.ICON_QUESTION, new String[] { "Append", "Replace", "Abort" }, 0).open();
                lastSelectedRecordFilePath = recordFileString;
            }
            return result;
        }

        // --------------------------------------------------------------------------------
        /**
         * Checks if the selected recording file already exists and if it is was already selected.
         * If it was not selected but it already exists, a dialog window will show up to ask the
         * user whether the content is to be appended to the file or not. Alternatively, the user
         * can abort selecting the file at all.
         * 
         * @param file
         *            the file to be checked
         * @return <ul>
         *         <li><tt>0</tt> if the content is to be appended</li>
         *         <li><tt>1</tt> if the files content is to be replaced with the new one</li>
         *         <li><tt>2</tt> if the selection is to be aborted</li>
         *         </ul>
         */
        protected int checkAppendToPrevFile(final File file) {
            int result = 0;

            // Check if the file already exists and if it differs from the previous chosen one.
            // If so, the user can decide to append the information, to overwrite the file or to
            // abort the recording
            if (file.exists() && (file.length() > 0) && ((lastSelectedRecordFilePath == null)
                    || !recordFileString.equals(lastSelectedRecordFilePath))) {
                result = new MessageDialog(Display.getCurrent().getActiveShell(), "Append or Replace", null,
                        "The file already exists. Shall the new information be appended or shall the file be replaced?",
                        SWT.ICON_QUESTION, new String[] { "Append", "Replace", "Abort" }, 0).open();
                lastSelectedRecordFilePath = recordFileString;
            }
            return result;
        }

        // --------------------------------------------------------------------------------
        /**
         * Starts the thread which consumes the packets which have been pushed into the recording
         * queue previously
         */
        private void startPacketConsumerThread(final boolean append) {

            packetConsumerThread = new Thread() {
                @SuppressWarnings("synthetic-access")
                @Override
                public void run() {
                    try {

                        FileOutputStream recordFileWriter = new FileOutputStream(getRecordFileString(), append);
                        final ConcurrentLinkedQueue<SpyglassPacket> queue = getPacketQueue();
                        SpyglassPacket packet = null;
                        while (!isInterrupted()) {
                            byte[] data = null;
                            synchronized (queue) {

                                if (queue.isEmpty()) {
                                    try {
                                        queue.wait();
                                    } catch (final InterruptedException e) {
                                        log.debug(
                                                "The packet recorder's packet consumer thread was interrupted while waiting for a notification of the arrival of a new packet");
                                        interrupt();
                                    }
                                }
                                packet = queue.poll();

                            }
                            if (packet != null) {
                                data = packet.serialize();
                                recordFileWriter.write(data.length);
                                recordFileWriter.write(data);
                                recordFileWriter.flush();
                            }
                        }
                        queue.clear();
                        recordFileWriter.close();
                        recordFileWriter = null;
                    } catch (final IOException e) {
                        log.error("Error while recording a packet", e);
                    }
                }
            };
            packetConsumerThread.start();
        }

        // --------------------------------------------------------------------------------
        /**
         * Returns the path to the file which is used for recording the input
         * 
         * @return the path to the file which is used for recording the input
         */
        private String getRecordFileString() {
            return recordFileString;
        }

        // --------------------------------------------------------------------------
        /**
         * Returns the file where the recoded data has to be saved.<br>
         * If no file was specified, yet, a dialog will be opened to let the user choose one.
         */
        private String getRecordFilePath() {
            if (recordFileString == null) {
                setRecodingFileByUser();
            }
            return recordFileString;
        }

        // --------------------------------------------------------------------------------
        /**
         * Opens a message dialog for the user to select and set the recording file
         * 
         * @return the path to the file selected by the user
         */
        @SuppressWarnings("synthetic-access")
        private String setRecodingFileByUser() {
            final FileDialog fd = new FileDialog(Display.getCurrent().getActiveShell(), SWT.SAVE);
            fd.setFilterExtensions(new String[] { "*.rec" });
            fd.setFilterPath(recordDirectory);
            String path = null;
            boolean conflictingFileSelected = false;
            do {
                path = fd.open();
                conflictingFileSelected = false;
                if (path != null) {
                    if (!path.endsWith(".rec")) {
                        path += ".rec";
                    }

                    conflictingFileSelected = !setRecordFile(path);

                }
            } while (conflictingFileSelected);
            return path;
        }

        // --------------------------------------------------------------------------------
        /**
         * Records a provided packet in recording mode, discards them in normal mode
         * 
         * @param packet
         *            the packet to be recorded
         */
        @SuppressWarnings("synthetic-access")
        void handlePacket(final SpyglassPacket packet) {

            if (packet != null) {

                if (!record) {
                    return;
                }
                synchronized (recordingQueue) {
                    recordingQueue.offer(packet);
                    recordingQueue.notify();
                }
            }
        }

        // --------------------------------------------------------------------------------
        /**
         * Returns whether a provided path equals the one of the recording file
         * 
         * @param path
         *            a path e.g. to the playback file
         * @return <code>true</code> if the provided path equals the one of the recording file
         */
        boolean equalsRecordingFilePath(final String path) {
            if ((path != null) && (recordFileString != null)) {
                return (new File(path).equals(new File(recordFileString)));
            }
            return false;
        }

        // --------------------------------------------------------------------------------
        /**
         * Resets the recording module
         */
        public void reset() {
            if (!isRecord()) {
                recordFileString = null;
            }
            lastSelectedRecordFilePath = null;
            getPacketQueue().clear();
        }

        // --------------------------------------------------------------------------------
        /**
         * Shuts the recording module down
         * 
         * @throws IOException
         */
        public void shutdown() throws IOException {
            if ((packetConsumerThread != null) && !packetConsumerThread.isInterrupted()) {
                packetConsumerThread.interrupt();
            }
        }

    }

    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    /**
     * Module encapsulating play back functionality
     * 
     * @author Sebastian Ebers
     */
    private class PlaybackModule {

        @SuppressWarnings("synthetic-access")
        @Element(name = "packetReader", required = false)
        private ComplexPacketReader complexPacketReader = new ComplexPacketReader(gatewayMutex);

        // --------------------------------------------------------------------------------
        /**
         * Constructor
         */
        public PlaybackModule() {
            // nothing to do
        }

        // --------------------------------------------------------------------------------
        /**
         * Initiates the module
         * 
         * @param spyglass
         *            the spyglass instance
         */
        public void init(final Spyglass spyglass) {
            complexPacketReader.init(spyglass);
            complexPacketReader.setDelayMillies(getDelayMillies());

            addPropertyChangeListener(new PropertyChangeListener() {
                // --------------------------------------------------------------------------------
                @SuppressWarnings("synthetic-access")
                @Override
                public void propertyChange(final PropertyChangeEvent evt) {
                    if (evt.getPropertyName().equals("delayMillies")) {
                        complexPacketReader.setDelayMillies((Integer) evt.getNewValue());
                    }
                }
            });

        }

        // --------------------------------------------------------------------------------
        /**
         * Opens a message dialog for the user to select the readFromFile file
         * 
         * @return the path to the file selected by the user
         */
        @SuppressWarnings("synthetic-access")
        public String selectPlayBackFileByUser() {
            final FileDialog fd = new FileDialog(Display.getCurrent().getActiveShell(), SWT.OPEN);
            fd.setFilterExtensions(new String[] { "*.rec" });
            fd.setFilterPath(recordDirectory);
            boolean isConflictingFileSelected = false;
            String path;
            do {
                isConflictingFileSelected = false;
                path = fd.open();
                isConflictingFileSelected = recModule.equalsRecordingFilePath(path);
                if (isRecord() && isConflictingFileSelected) {
                    MessageDialog.openError(null, "The file is already in use",
                            "Sorry, the chosen file is already in use for recording. Please choose a diferent one.");

                }
                // if a file conflict was detected but the recording mode is disabled, set the
                // recording file to null
                else if (isConflictingFileSelected && !isRecord()) {
                    setRecordFile(null);
                    isConflictingFileSelected = false;
                }
            } while (isConflictingFileSelected);
            return path;
        }

        // --------------------------------------------------------------------------
        /**
         * Returns the file to be used to read the previously recorded packages
         * 
         * @return the file to be used to read the previously recorded packages
         */
        public File getPlayBackFile() {
            if (complexPacketReader.getGateway() instanceof FileReaderGateway) {
                return ((FileReaderGateway) complexPacketReader.getGateway()).getFile();
            }
            return null;
        }

        // --------------------------------------------------------------------------
        /**
         * Sets the file to be used to play back the previously recorded packages.
         * 
         * @param path
         *            the path to the file
         * @return <code>true</code> if a readFromFile file was set successfully
         */
        @SuppressWarnings("synthetic-access")
        public boolean setPlayBackFile(final String path) {

            if (path != null) {

                boolean isConflictingFileSelected = recModule.equalsRecordingFilePath(path);
                if (isRecord() && isConflictingFileSelected) {
                    Display.getDefault().syncExec(new Runnable() {
                        @Override
                        public void run() {
                            MessageDialog.openError(null, "The file is already in use",
                                    "Sorry, the chosen file is already in use for recording.\r\nPlayback will not be started.");
                        }
                    });

                }
                // if a file conflict was detected but the recording mode is disabled, set the
                // recording file to null
                else if (isConflictingFileSelected && !isRecord()) {
                    setRecordFile(null);
                    isConflictingFileSelected = false;
                }

                if (!isConflictingFileSelected) {

                    // get a mutex lock which will prevent the input stream to be accessed
                    // externally
                    synchronized (gatewayMutex) {
                        // check whether the current Gateway is capable of processing a file
                        Gateway gw = complexPacketReader.getGateway();
                        if ((gw == null) || (!(gw instanceof FileReaderGateway))) {

                            // if not, create a usable gateway and
                            final FileReaderGateway frgw = new FileReaderGateway();
                            frgw.setFile(new File(path));
                            complexPacketReader.setGateway(frgw);
                            gw = frgw;
                        }

                        ((FileReaderGateway) gw).setFile(new File(path));

                        setReadFromFile(complexPacketReader.getGateway().getInputStream() != null);
                    }
                }
            } else {
                ((FileReaderGateway) complexPacketReader.getGateway()).setFile(null);
                if (getSourceType().equals(SOURCE_TYPE.FILE)) {
                    setReadFromFile(false);
                }
            }
            return isReadFromFile();
        }

        // --------------------------------------------------------------------------
        /**
         * Returns a new packet
         * 
         * @param block
         *            indicates whether the method has to block or return <code>null</code> if no
         *            packet is available
         * @exception SpyglassPacketException
         *                if the packet to return is invalid
         * @exception InterruptedException
         *                if the method was interrupted while waiting on a packet.
         * @return a new SpyGlass packet or <code>null</code> if no more packets are available
         * 
         */
        @SuppressWarnings("synthetic-access")
        public SpyglassPacket getNextPacket(final boolean block)
                throws SpyglassPacketException, InterruptedException {
            synchronized (gatewayMutex) {

                final SpyglassPacket packet = complexPacketReader.getNextPacket(false);
                if (packet == null) {
                    setSourceType(SOURCE_TYPE.NONE);
                    if (block) {
                        gatewayMutex.wait();
                    }
                }
                return packet;
            }
        }

        // --------------------------------------------------------------------------------
        /**
         * Returns whether a provided path equals the one of the playback file
         * 
         * @param path
         *            a path e.g. to the recording file
         * @return <code>true</code> if the provided path equals the one of the playback file
         */
        public boolean equalsPlaybackFilePath(final String path) {
            File in = null;
            if ((complexPacketReader.getGateway() instanceof FileReaderGateway) && (path != null)) {
                in = ((FileReaderGateway) complexPacketReader.getGateway()).getFile();
                return ((in != null) && in.equals(new File(path)));
            }
            return false;
        }

        // --------------------------------------------------------------------------------
        /**
         * Resets the playback module
         * 
         * @throws IOException
         */
        public void reset() throws IOException {
            complexPacketReader.reset();
        }

    }

}