Package de.dfki.km.text20.browserplugin.services.sessionrecorder.impl.xstream

Source Code of de.dfki.km.text20.browserplugin.services.sessionrecorder.impl.xstream.SessionReplayImpl

package de.dfki.km.text20.browserplugin.services.sessionrecorder.impl.xstream;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import net.xeoh.plugins.base.util.OptionUtils;

import com.thoughtworks.xstream.XStream;

import de.dfki.km.text20.browserplugin.services.sessionrecorder.ReplayListener;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.SessionReplay;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.events.AbstractSessionEvent;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.events.ImageEvent;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.events.PropertyEvent;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.events.ScreenSizeEvent;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.events.pseudo.PseudoImageEvent;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.impl.xstream.loader.AbstractLoader;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.impl.xstream.loader.ZIPLoader;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.options.ReplayOption;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.options.replay.OptionGetMetaInfo;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.options.replay.OptionLoadImages;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.options.replay.OptionRealtime;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.options.replay.OptionSlowMotion;
import de.dfki.km.text20.browserplugin.services.sessionrecorder.util.metadata.DisplacementRegion;

/**
* @author rb
*
*/
public class SessionReplayImpl implements SessionReplay {

    /** Logger */
    final Logger logger = Logger.getLogger(this.getClass().getName());

    /** Xstream (de)serializer */
    final XStream xstream = new XStream();

    /**
     * Makes other threads wait for this element to be finished.
     */
    final Lock finishedLock = new ReentrantLock();

    /** Input stream to read the content from */
    ObjectInputStream in;

    /** List of events to filter */
    final List<Class<? extends AbstractSessionEvent>> toFilter = new ArrayList<Class<? extends AbstractSessionEvent>>();

    /** List of properties stored in the replay */
    final Map<String, String> propertyMap = new HashMap<String, String>();

    /** The recorded screen size */
    Dimension screenSize;

    /** The file to replay. This can either be a zip file (with an internal .xstream) or an xstream directly. */
    private File file;

    /** The loader to access elements */
    AbstractLoader loader = null;

    /** Displacement regions to apply */
    private List<DisplacementRegion> fixationDisplacementRegions;

    /**
     * @param file
     */
    public SessionReplayImpl(final File file) {
        if (!file.exists())
            throw new IllegalArgumentException("file: " + file.getAbsolutePath() + " does not exist ");

        this.file = file;

        SessionStreamer.setAlias(this.xstream);

        // parse file to get properties and screen size
        replay(null, new OptionGetMetaInfo());

        waitForFinish();
    }

    /* (non-Javadoc)
     * @see de.dfki.km.text20.browserplugin.services.sessionrecorder.SessionReplay#getDisplacements()
     */
    @Override
    public List<DisplacementRegion> getDisplacements() {
        return new ArrayList<DisplacementRegion>();
    }

    /**
     * @return .
     */
    public synchronized List<DisplacementRegion> getFixationDisplacementRegions() {
        return this.fixationDisplacementRegions;
    }

    /* (non-Javadoc)
     * @see de.dfki.km.augmentedtext.browserplugin.services.sessionrecorder.SessionReplay#getProperties()
     */
    @Override
    public Map<String, String> getProperties(String... properties) {
        waitForFinish();
        return this.propertyMap;
    }

    /* (non-Javadoc)
     * @see de.dfki.km.augmentedtext.browserplugin.services.sessionrecorder.SessionReplay#getScreenSize()
     */
    @Override
    public Dimension getScreenSize() {
        waitForFinish();
        return this.screenSize;
    }

    /**
     * @param inStream
     * @return .
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object loadFromStream(final ObjectInputStream inStream) throws IOException,
                                                                  ClassNotFoundException {
        return (inStream.readObject());
    }

    /* (non-Javadoc)
     * @see de.dfki.km.augmentedtext.browserplugin.services.sessionrecorder.SessionReplay#replay(de.dfki.km.augmentedtext.browserplugin.services.sessionrecorder.ReplayListener, de.dfki.km.augmentedtext.browserplugin.services.sessionrecorder.options.ReplayOption[])
     */
    @Override
    public synchronized void replay(final ReplayListener listener, final ReplayOption... options) {

        // Create a barrier that allows us to wait for synchronous replay
        final CyclicBarrier barrier = new CyclicBarrier(2);

        try {
            InputStream input = null;

            // First check if we have a .zip ...
            if (this.file.getAbsolutePath().endsWith(".xstream")) {
                input = new FileInputStream(this.file);
            }

            // ... and check if we have .xstream file
            if (this.file.getAbsolutePath().endsWith(".zip")) {
                this.loader = new ZIPLoader(this.file);
                input = this.loader.getSessionInputStream();
            }

            // FIXED: #26
            this.in = this.xstream.createObjectInputStream(new BufferedReader(new InputStreamReader(input, "UTF-8")));

        } catch (final FileNotFoundException e) {
            e.printStackTrace();
        } catch (final IOException e) {
            e.printStackTrace();
        }

        // Sanity check
        if (this.in == null) {
            this.logger.warning("Unable to load replay for file " + this.file);
            return;
        }

        // Options
        final AtomicBoolean gettingMetaInfo = new AtomicBoolean(false);
        final AtomicBoolean realtimeReplay = new AtomicBoolean(false);
        final AtomicBoolean loadImages = new AtomicBoolean(false);

        // Some variables
        final AtomicLong slowdownFactor = new AtomicLong(1);
        final AtomicLong currentEvenTime = new AtomicLong();
        final AtomicLong firstEventTime = new AtomicLong();
        final AtomicLong realtimeDuration = new AtomicLong();

        // Process options
        final OptionUtils<ReplayOption> ou = new OptionUtils<ReplayOption>(options);
        if (ou.contains(OptionGetMetaInfo.class)) gettingMetaInfo.set(true);
        if (ou.contains(OptionRealtime.class)) realtimeReplay.set(true);
        if (ou.contains(OptionLoadImages.class)) loadImages.set(true);
        if (ou.contains(OptionSlowMotion.class)) {
            realtimeReplay.set(true);
            slowdownFactor.set(ou.get(OptionSlowMotion.class).getFactor());
        }

        // Create the actual replay thread
        final Thread t = new Thread(new Runnable() {

            private boolean hasMore = true;

            @Override
            public void run() {
                try {
                    // Lock until we finished
                    SessionReplayImpl.this.finishedLock.lock();

                    // Synchronize with exit of the function
                    try {
                        barrier.await();
                    } catch (final InterruptedException e) {
                        e.printStackTrace();
                    } catch (final BrokenBarrierException e) {
                        e.printStackTrace();
                    }

                    AbstractSessionEvent previousEvent = null;

                    // As long as we have more events
                    while (this.hasMore) {
                        AbstractSessionEvent event = null;

                        try {
                            // Load the next event
                            event = (AbstractSessionEvent) loadFromStream(SessionReplayImpl.this.in);

                            // In case we have no previous event, save the first event time
                            if (previousEvent == null) {
                                firstEventTime.set(event.originalEventTime);
                                previousEvent = event;
                            }

                            // Store current event time
                            currentEvenTime.set(event.originalEventTime);

                            // Dont process if filtered
                            if (SessionReplayImpl.this.toFilter.contains(event.getClass())) {
                                continue;
                            }

                            // Check if we only get meta events ...
                            if (gettingMetaInfo.get()) {
                                if (event instanceof ScreenSizeEvent)
                                    SessionReplayImpl.this.screenSize = ((ScreenSizeEvent) event).screenSize;

                                if (event instanceof PropertyEvent) {
                                    SessionReplayImpl.this.propertyMap.put(((PropertyEvent) event).key, ((PropertyEvent) event).value);
                                }

                                continue;
                            }

                            // Can be switched off, to make replay as fast as possible.
                            if (realtimeReplay.get()) {
                                long delta = event.originalEventTime - previousEvent.originalEventTime;

                                // TODO: When does this happen?
                                if (delta < 0) {
                                    SessionReplayImpl.this.logger.fine("Event times are mixed up " + event.originalEventTime + " < " + previousEvent.originalEventTime);
                                    delta = 0;
                                }

                                // And now wait for the given time
                                try {
                                    Thread.sleep(delta * slowdownFactor.get());
                                } catch (final InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }

                            // Check what kind of event it is and if we have some special rules
                            if (event instanceof ImageEvent && loadImages.get()) {
                                final ImageEvent e = (ImageEvent) event;
                                final InputStream is = SessionReplayImpl.this.loader.getFile(e.associatedFilename);
                                final BufferedImage read = ImageIO.read(is);

                                event = new PseudoImageEvent(e, read);
                            }

                            // Now we are permitted to fire the event.
                            try {
                                listener.nextEvent(event);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }

                            previousEvent = event;
                        } catch (EOFException e) {
                            e.printStackTrace();
                            this.hasMore = false;
                            if (gettingMetaInfo.get()) {
                                realtimeDuration.set(currentEvenTime.get() - firstEventTime.get());
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                } finally {
                    SessionReplayImpl.this.finishedLock.unlock();

                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        });

        t.setDaemon(true);
        t.start();

        // Synchronize with starting of thread.
        try {
            barrier.await();
        } catch (final InterruptedException e) {
            e.printStackTrace();
        } catch (final BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param fixationDisplacementRegions
     */
    public synchronized void setFixationDisplacementRegions(final List<DisplacementRegion> fixationDisplacementRegions) {
        this.fixationDisplacementRegions = fixationDisplacementRegions;
    }

    /**
     * Waits until the thread has finished.
     */
    public void waitForFinish() {
        // If the lock is unlocked, do nothing
        if (this.finishedLock.tryLock()) return;

        // If it's locked, wait.
        this.finishedLock.lock();
        this.finishedLock.unlock();

        return;
    }

    /**
     * Events of that class wont be passed.
     *
     * @param filter
     */
    @SuppressWarnings("unused")
    private void addFilter(final Class<? extends AbstractSessionEvent> filter) {
        this.toFilter.add(filter);
    }
}
TOP

Related Classes of de.dfki.km.text20.browserplugin.services.sessionrecorder.impl.xstream.SessionReplayImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.