Package com.dmurph.mvc

Source Code of com.dmurph.mvc.MVC

/**
* Copyright (c) 2010 Daniel Murphy
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Created at 2:19:39 AM, Mar 12, 2010
*/
package com.dmurph.mvc;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dmurph.mvc.monitor.EventMonitor;
import com.dmurph.mvc.monitor.LoggingMonitor;
import com.dmurph.mvc.monitor.WarningMonitor;
import com.dmurph.mvc.tracking.ICustomTracker;
import com.dmurph.mvc.tracking.ITrackable;
import com.dmurph.tracking.JGoogleAnalyticsTracker;

/**
* This stores all the listener information, dispatches events
* to the corresponding listeners. To dispatch events use {@link MVCEvent#dispatch()}
* .</br>
* </br>
* Also, look at {@link #splitOff()}. To set up Google analytics, call
* {@link #setTracker(JGoogleAnalyticsTracker)},
* or implement {@link ICustomTracker} in your events to be tracked, and then any event
* that implements {@link ITrackable} will be tracked. If
* {@link ITrackable#getTrackingCategory()} or {@link ITrackable#getTrackingAction()}
* returns <code>null</code>, then it will be ignored.
*
* @author Daniel Murphy
*/
public class MVC extends Thread {
 
  private static final Logger log = LoggerFactory.getLogger(MVC.class);
 
  private static final ThreadGroup mvcThreadGroup = new ThreadGroup("MVC Thread Group");
  private static final ArrayList<MVC> mvcThreads = new ArrayList<MVC>();
  private static final HashMap<String, LinkedList<IEventListener>> listeners = new HashMap<String, LinkedList<IEventListener>>();
  private static final Queue<MVCEvent> eventQueue = new LinkedList<MVCEvent>();
 
  private static final Object trackerLock = new Object();
  private volatile static JGoogleAnalyticsTracker tracker = null;
  private static final Object monitorLock = new Object();
  private volatile static IGlobalEventMonitor monitor = new LoggingMonitor();
  private static final Object mainThreadLock = new Object();
  private volatile static MVC mainThread;
  private volatile static String currKey = null;
 
  private volatile boolean running = false;
  private final int threadCount;
 
  private MVC(int argNum) {
    super(mvcThreadGroup, "MVC Thread #" + argNum);
    threadCount = argNum;
    mvcThreads.add(this);
  }
 
  public static void setTracker(JGoogleAnalyticsTracker argTracker) {
    synchronized (trackerLock) {
      tracker = argTracker;
    }
  }
 
  public static JGoogleAnalyticsTracker getTracker() {
    return tracker;
  }
 
  /**
   * Adds a listener for the given event key. If the listener is already listening
   * to that key, then nothing is done. On the rare occurrence that the key is being
   * dispatched at the same time by the mvc thread, this call will wait till all the
   * events
   * of that key are dispatched before adding and returning. If that happens and the
   * thead making this call is also the mvc thread,
   * (a listener for a key adds another listener for the same key), then a runtime
   * exception is thrown.
   *
   * @param argKey
   * @param argListener
   */
  public static void addEventListener(String argKey, IEventListener argListener) {
    if (argKey == null) {
      throw new RuntimeException("Key cannot be null");
    }
   
    synchronized (listeners) {
      synchronized (mainThreadLock) {
        if (argKey.equals(currKey) && Thread.currentThread() == mainThread) {
          throw new RuntimeException("Cannot add a listener to the same key that's being dispatched");
        }
      }
      LinkedList<IEventListener> fifo;
      if (listeners.containsKey(argKey)) {
        // return if we're already listening
        if (listeners.get(argKey).contains(argListener)) {
          log.debug("We already have that listener here", argListener);
          return;
        }
        fifo = listeners.get(argKey);
      }
      else {
        fifo = new LinkedList<IEventListener>();
        listeners.put(argKey, fifo);
      }
      fifo.add(argListener);
    }
  }
 
  /**
   * Checks to see if the listener is listening to the given key.
   *
   * @param argKey
   * @param argListener
   * @return
   */
  public static boolean isEventListener(String argKey, IEventListener argListener) {
    if (argKey == null) {
      throw new RuntimeException("Key cannot be null");
    }
   
    synchronized (listeners) {
      if (!listeners.containsKey(argKey)) {
        return false;
      }
     
      LinkedList<IEventListener> stack = listeners.get(argKey);
      return stack.contains(argListener);
    }
  }
 
  /**
   * Gets a copy of the listeners for the given event key.
   *
   * @param argKey
   * @return
   */
  public static LinkedList<IEventListener> getListeners(String argKey) {
    if (argKey == null) {
      throw new RuntimeException("Key cannot be null");
    }
   
    synchronized (listeners) {
      if (listeners.containsKey(argKey)) {
        return new LinkedList<IEventListener>(listeners.get(argKey));
      }
      else {
        return new LinkedList<IEventListener>();
      }
    }
  }
 
  /**
   * removes a listener from the given key.
   *
   * @param argKey
   * @param argListener
   * @return true if the listener was removed, and false if it wasn't there to
   *         begin with
   */
  public static boolean removeEventListener(String argKey, IEventListener argListener) {
    if (argKey == null) {
      throw new RuntimeException("Key cannot be null");
    }
   
    synchronized (listeners) {
      synchronized (mainThreadLock) {
        if (argKey.equals(currKey) && Thread.currentThread() == mainThread) {
          throw new RuntimeException(
              "Cannot remove a listener to the same key that's being dispatched.  Return false instead.");
        }
      }
     
      if (listeners.containsKey(argKey)) {
        LinkedList<IEventListener> stack = listeners.get(argKey);
        return stack.remove(argListener);
      }
      else {
        return false;
      }
    }
  }
 
  /**
   * Adds an event to the dispatch queue for the MVC thread.
   * Used by {@link MVCEvent#dispatch()}.
   *
   * @param argEvent
   */
  protected static void dispatchEvent(MVCEvent argEvent) {
    boolean hasListeners;
    synchronized (listeners) {
      hasListeners = listeners.containsKey(argEvent.key);
    }
   
    if (hasListeners) {
      synchronized (eventQueue) {
        eventQueue.add(argEvent);
        eventQueue.notify();
      }
     
      if (!isDispatchThreadRunning()) {
        startDispatchThread();
      }
    }
    else {
      synchronized (monitorLock) {
        if (monitor != null) {
          try {
            monitor.noListeners(argEvent);
          } catch (Exception e) {
            log.error("Exception caught from monitor", e);
          }
        }
      }
    }
  }
 
  /**
   * Split off the current MVC thread, all queued events and future
   * event dispatches are handled by a new MVC thread, while this one
   * runs to completion. If the thread calling this is not the current
   * core MVC thread, then an exception is thrown
   *
   * @throws IllegalThreadException
   *             if the thread calling this is not an MVC thread
   * @throws IncorrectThreadException
   *             if the MVC thread calling this is not the main thread, e.g.
   *             it has already split off.
   */
  public static void splitOff() throws IllegalThreadException, IncorrectThreadException {
    if (Thread.currentThread() instanceof MVC) {
      MVC thread = (MVC) Thread.currentThread();
      synchronized (mainThreadLock) {
        if (thread == mainThread) {
          log.debug("Splitting off...");
         
          MVC old = mainThread;
          old.running = false;
          mainThread = new MVC(old.threadCount + 1);
          log.debug("Starting next MVC thread");
          mainThread.start();
        }
        else {
          log.error("Can't split off when this isn't the main thread");
          throw new IncorrectThreadException();
        }
      }
    }
    else {
      log.error("Can't split off, we're not in the MVC thread.");
      throw new IllegalThreadException();
    }
  }
 
  /**
   * Wait for all remaining events to dispatch
   *
   * @param timeoutMillis
   *            The maximum number of milliseconds to wait.
   */
  public static void completeRemainingEvents(long timeoutMillis) {
   
    boolean fifoEmpty = false;
   
    long absTimeout = System.currentTimeMillis() + timeoutMillis;
    while (System.currentTimeMillis() < absTimeout) {
      synchronized (eventQueue) {
        fifoEmpty = (eventQueue.size() == 0);
      }
     
      if (fifoEmpty) {
        break;
      }
     
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        break;
      }
    }
  }
 
  /**
   * Stops the dispatch thread, dispatching any remaining events
   * before cleanly returning. Thread automatically gets started
   * when new events are dispatched
   */
  public static void stopDispatchThread(long argTimeoutMillis) {
    synchronized (mainThreadLock) {
      mainThread.running = false;
      synchronized (eventQueue) {
        eventQueue.notify();
      }
      if ((mainThread != null) && (argTimeoutMillis > 0)) {
        try {
          mainThread.join(argTimeoutMillis);
        } catch (InterruptedException e) {}
        mainThread = null;
      }
    }
   
  }
 
  public static boolean isDispatchThreadRunning() {
    synchronized (mainThreadLock) {
      return mainThread != null && (mainThread.running || mainThread.getState() == State.RUNNABLE);
    }
  }
 
  /**
   * Manually starts the dispatch thread.
   */
  public static void startDispatchThread() {
    synchronized (mainThreadLock) {
      if (mainThread == null) {
        mainThread = new MVC(0);
      }
      if (!mainThread.running) {
        if (mainThread.getState() == State.NEW) {
          mainThread.start();
        }
      }
    }
  }
 
  /**
   * Sets the global event monitor, which is called before and after each event is
   * dispatched.
   *
   * @param argMonitor
   * @see IGlobalEventMonitor
   */
  public static void setGlobalEventMonitor(IGlobalEventMonitor argMonitor) {
    synchronized (monitorLock) {
      monitor = argMonitor;
    }
  }
 
  /**
   * Gets the global event monitor. Default is {@link WarningMonitor}.
   *
   * @return
   * @see IGlobalEventMonitor
   */
  public static IGlobalEventMonitor getGlobalEventMonitor() {
    synchronized (monitorLock) {
      return monitor;
    }
  }
 
  private volatile static EventMonitor guiMonitor = null;
 
  /**
   * Convenience method to construct and show an {@link EventMonitor}. To
   * have more control on how the {@link EventMonitor} is configured,
   * you can just create it yourself and use
   * {@link #setGlobalEventMonitor(IGlobalEventMonitor)} to have it be the global event
   * monitor.
   *
   * @return the {@link EventMonitor}.
   */
  public static EventMonitor showEventMonitor() {
    if (guiMonitor == null) {
      synchronized (monitorLock) {
        guiMonitor = new EventMonitor(monitor);
        setGlobalEventMonitor(guiMonitor);
      }
    }
    guiMonitor.setVisible(true);
    return guiMonitor;
  }
 
  /**
   * Hides the event monitor, if you had used {@link #showEventMonitor()}.
   */
  public static void hideEventMonitor() {
    if (guiMonitor != null) {
      guiMonitor.setVisible(false);
    }
  }
 
  @Override
  public void run() {
    running = true;
    log.info("MVC thread #" + threadCount + " starting up");
    while (running) {
      try {
        MVCEvent event = null;
        synchronized (eventQueue) {
          if (eventQueue.isEmpty()) {
            eventQueue.wait();
          }
         
          if (!eventQueue.isEmpty()) {
            event = eventQueue.poll();
          }
        }
       
        if (event != null) {
          internalDispatchEvent(event);
        }
      } catch (Exception e) {
        log.error("Caught exception in dispatch thread", e);
      }
    }
    mvcThreads.remove(this);
  }
 
  private void internalDispatchEvent(MVCEvent argEvent) {
   
    if (monitor != null) {
      synchronized (monitorLock) {
        try {
          monitor.beforeDispatch(argEvent);
        } catch (Exception e) {
          e.printStackTrace();
          log.error("Exception caught from monitor", e);
        }
      }
    }
   
    if (argEvent instanceof ITrackable) {
      ITrackable event = (ITrackable) argEvent;
      if (event.getTrackingCategory() != null && event.getTrackingAction() != null) {
        if (event instanceof ICustomTracker) {
          ((ICustomTracker) event).getCustomTracker().trackEvent(event.getTrackingCategory(),
              event.getTrackingAction(), event.getTrackingLabel(), event.getTrackingValue());
        }
        else if (tracker != null) {
          synchronized (trackerLock) {
            tracker.trackEvent(event.getTrackingCategory(), event.getTrackingAction(),
                event.getTrackingLabel(), event.getTrackingValue());
          }
        }
        else {
          log.warn("Event could not be tracked, as the tracker is null", event);
        }
      }
    }
   
    currKey = argEvent.key;
    LinkedList<IEventListener> stack;
    synchronized (listeners) {
      stack = listeners.get(argEvent.key);
     
      Iterator<IEventListener> it = stack.iterator();
      while (it.hasNext() && argEvent.isPropagating()) {
        try {
          if (!it.next().eventReceived(argEvent)) {
            it.remove();
          }
        } catch (Exception e) {
          synchronized (monitorLock) {
            if (monitor != null) {
              try {// why do I have to do this? monitors shouldn't throw
                  // exceptions
                monitor.exceptionThrown(argEvent, e);
              } catch (Exception e2) {
                log.error("Exception caught from event dispatch", e);
                log.error("Exception caught from monitor", e2);
               
              }
            }
            else {
              log.error("Exception caught from event dispatch", e);
            }
          }
        }
      }
      currKey = null;
    }
   
    synchronized (monitorLock) {
      if (monitor != null) {
        try {
          monitor.afterDispatch(argEvent);
        } catch (Exception e) {
          log.error("Exception caught from monitor", e);
        }
      }
    }
  }
}
TOP

Related Classes of com.dmurph.mvc.MVC

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.