
com.dmurph.mvc.MVC Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-simple-mvc Show documentation
Show all versions of java-simple-mvc Show documentation
A simple mvc framework for java application and gui developers.
The newest version!
/**
* 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.List;
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()}
* . 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 null
, 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 mvcThreads = new ArrayList();
private static final HashMap> listeners = new HashMap>();
private static final Queue eventQueue = new LinkedList();
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 Iterator currEventList;
private MVCEvent currEvent;
private MVC(int argNum) {
super(mvcThreadGroup, "MVC Thread #" + argNum);
threadCount = argNum;
mvcThreads.add(this);
}
private MVC(int argNum, Iterator currEventList,
MVCEvent currEvent) {
this(argNum);
this.currEvent = currEvent;
this.currEventList = currEventList;
}
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");
}
}
List 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 ArrayList();
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;
}
List 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 getListeners(String argKey) {
if (argKey == null) {
throw new RuntimeException("Key cannot be null");
}
synchronized (listeners) {
if (listeners.containsKey(argKey)) {
return new LinkedList(listeners.get(argKey));
} else {
return new LinkedList();
}
}
}
/**
* 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)) {
List 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,
old.currEventList, old.currEvent);
old.currEvent = null;
old.currEventList = null;
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);
}
}
public static boolean isMainMVCThread() {
MVC thread = (MVC) Thread.currentThread();
return thread == mainThread;
}
@Override
public void run() {
running = true;
log.info("MVC thread #" + threadCount + " starting up");
while (running) {
IEventListener listener;
if (currEvent != null && currEventList != null
&& currEventList.hasNext() && currEvent.isPropagating()) {
synchronized (listeners) {
listener = currEventList.next();
}
tryPreMonitor(currEvent);
tryTrackEvent(currEvent);
try {
if (!listener.eventReceived(currEvent)) {
if (isMainMVCThread()) {
synchronized (listeners) {
currEventList.remove();
}
} else {
log.error("Cannot remove the listener " + listener
+ ", as we've been split off");
}
}
} catch (Exception e) {
synchronized (monitorLock) {
if (monitor != null) {
try {// why do I have to do this? monitors shouldn't
// throw
// exceptions
monitor.exceptionThrown(currEvent, 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);
}
}
}
tryPostMonitor(currEvent);
} else {
// grab next event
try {
synchronized (eventQueue) {
if (eventQueue.isEmpty()) {
eventQueue.wait();
}
if (!eventQueue.isEmpty()) {
currEvent = eventQueue.poll();
}
}
if (currEvent != null) {
synchronized (listeners) {
currEventList = listeners.get(currEvent.key)
.iterator();
}
}
} catch (Exception e) {
log.error("Caught exception in dispatch thread", e);
}
}
}
mvcThreads.remove(this);
}
private void tryTrackEvent(MVCEvent argEvent) {
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);
}
}
}
}
private void tryPreMonitor(MVCEvent argEvent) {
if (monitor != null) {
synchronized (monitorLock) {
try {
monitor.beforeDispatch(argEvent);
} catch (Exception e) {
log.error("Exception caught from monitor", e);
}
}
}
}
private void tryPostMonitor(MVCEvent argEvent) {
synchronized (monitorLock) {
if (monitor != null) {
try {
monitor.afterDispatch(argEvent);
} catch (Exception e) {
log.error("Exception caught from monitor", e);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy