/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.swt.event.recorder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import com.windowtester.internal.debug.TraceHandler;
import com.windowtester.internal.debug.Tracer;
import com.windowtester.recorder.IEventFilter;
import com.windowtester.recorder.IEventRecorder;
import com.windowtester.recorder.event.ISemanticEventListener;
import com.windowtester.recorder.event.IUISemanticEvent;
import com.windowtester.recorder.event.meta.RecorderErrorEvent;
import com.windowtester.recorder.event.meta.RecorderTraceEvent;
import com.windowtester.recorder.event.user.SemanticFocusEvent;
import com.windowtester.runtime.swt.internal.debug.LogHandler;
import com.windowtester.swt.event.model.IEventRecorderCallBack;
import com.windowtester.swt.event.model.SWTSemanticEventInterpreter;
import com.windowtester.swt.event.model.SWTSemanticEventParser;
public abstract class BaseEventRecorder implements IEventRecorder {
/** The trace option for use in filtering debugging output*/
protected static final String TRACE_OPTION = "com.windowtester.swt/BaseEventRecorder_DEBUG";
/** A back-pointer to the root display */
private Display _display;
/** A list of semantic event listeners */
private List /*<ISemanticEventListener>*/ _listeners;
/** A list of primitive event filters */
private List /*<ISemanticEventListener>*/ _filters;
/** An interpreter (and stateful parser) for parsing semantic events */
private SWTSemanticEventInterpreter _interpreter = new SWTSemanticEventInterpreter();
private SWTSemanticEventParser _parser = new SWTSemanticEventParser(_interpreter);
/** A flag to indicate record state */
protected boolean _isRecording;
/** A flag to indicate pause state */
protected boolean _isPaused;
/**
* Create an instance.
*/
public BaseEventRecorder() {
//register the interpreter to receive recorder events
addListener(_interpreter);
//add a callback for dynamic updates to filters on the event stream
_interpreter.addEventRecorderCallBack(new EventRecorderCallBack());
}
/**
* Create an instance.
* @param display
*/
public BaseEventRecorder(Display display) {
this();
_display = display;
//remove ourselves on disposal && signal the dispose Semantic Event
_display.addListener(SWT.Dispose, new Listener() {
public void handleEvent(Event event) {
removeFilters();
/**
* To ensure that any buffered events are sent (notably here a CLOSE)
* we need to manually flush
*/
Tracer.trace(IEventRecorderPluginTraceOptions.RECORDER_EVENTS, "Calling recorder terminate on display disposal");
terminate();
//handleDispose(); <--- handled by listening to debug events instead
//System.out.println("dispose!");
}
});
//start menu watcher
// MenuWatcher.getInstance(display).startWatching();
}
/**
* Filter out/in events of interest here
* @return whether this event is significant
*/
protected boolean isSignificant(Event e) {
//iterate through all of the registered filters and check for exclusion
List filters = getEventFilters();
for (Iterator iter = filters.iterator(); iter.hasNext();) {
IEventFilter element = (IEventFilter) iter.next();
if (!element.include(e)) {
return false;
}
}
return true;
}
/** The recording event listener */
private Listener _recorder = new Listener(){
public void handleEvent(org.eclipse.swt.widgets.Event e){
if (isSignificant(e))
logEvent(e);
}
};
/**
* Report the given event to the registered listeners
*/
protected void logEvent(Event event) {
IUISemanticEvent semanticEvent = getSemanticEvent(event);
//some events don't have associated semantics; ignore these
if (semanticEvent != null)
record(semanticEvent);
}
/**
* Report the given typed event to the registered listeners
*/
protected void logEvent(TypedEvent event) {
/*
* Typed events interrupt our cached stream of untyped events.
* To ensure none get lost, we need to emty the buffer first.
*/
flushEventBuffer();
IUISemanticEvent semanticEvent = getSemanticEvent(event);
//some events don't have associated semantics; ignore these
if (semanticEvent != null)
record(semanticEvent);
}
/* (non-Javadoc)
* @see com.windowtester.event.model.IEventRecorder#record(com.windowtester.swt.event.model.IUISemanticEvent)
*/
public void record(IUISemanticEvent semanticEvent) {
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notify(semanticEvent);
}
/* (non-Javadoc)
* @see com.windowtester.event.model.IEventRecorder#reportError(com.windowtester.swt.event.model.RecorderErrorEvent)
*/
public void reportError(RecorderErrorEvent event) {
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyError(event);
}
/**
* @return the associated semantic event (or <code>null</code>)
* @see com.windowtester.swt.event.model.SWTSemanticEventInterpreter#interpret(Event)
*/
private IUISemanticEvent getSemanticEvent(Event event) {
IUISemanticEvent semanticEvent = null;
/*
* For safety, catch any exceptions generated by parsing and move on
*/
try {
semanticEvent = _parser.parse(event);
} catch(Throwable t) {
TraceHandler.trace(IEventRecorderPluginTraceOptions.RECORDER_EVENTS, "error caught in event parsing, event ignored (see log for details)");
LogHandler.log(t);
}
return semanticEvent;
}
/**
* @return the associated semantic event (or <code>null</code>)
*/
private IUISemanticEvent getSemanticEvent(TypedEvent event) {
IUISemanticEvent semanticEvent = null;
/*
* For safety, catch any exceptions generated by parsing and move on
*/
try {
semanticEvent = _parser.parse(event);
} catch(Throwable t) {
TraceHandler.trace(IEventRecorderPluginTraceOptions.RECORDER_EVENTS, "error caught in event parsing, event ignored (see log for details)");
LogHandler.log(t);
}
return semanticEvent;
}
/**
* Notify all listeners of disposal event.
*/
// private void handleDispose() {
// for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
// ((ISemanticEventListener)iter.next()).notifyDispose();
// }
/**
* @return the display being watched
*/
public Display getDisplay() {
return _display;
}
/**
* Add event filters
*/
private void addFilters() {
if (!_isRecording)
return;
int[] eventTypes = getEventTypes();
for (int i = 0; i < eventTypes.length; i++) {
_display.addFilter(eventTypes[i], _recorder);
}
}
/**
* @return the event types of interest
*/
private int[] getEventTypes() {
return com.windowtester.swt.event.model.EventModelConstants.EVENT_TYPES;
}
/**
* Remove event filters
*/
private void removeFilters() {
if (!_isRecording)
return;
int[] eventTypes = getEventTypes();
for (int i = 0; i < eventTypes.length; i++) {
_display.removeFilter(eventTypes[i], _recorder);
}
}
/* (non-Javadoc)
* @see com.windowtester.swt.event.recorder.IEventRecorder#start()
*/
public void start() {
_isRecording = true;
_isPaused = false;
addFilters();
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyStart();
}
/**
* @see com.windowtester.recorder.IEventRecorder#pause()
*/
public void pause() {
removeFilters();
_isRecording = false;
_isPaused = true;
flushEventBuffer();
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyPause();
}
/* (non-Javadoc)
* @see com.windowtester.swt.event.recorder.IEventRecorder#stop()
*/
public void stop() {
removeFilters();
_isRecording = false;
//first, flush buffer to ensure there are no undispatched events
flushEventBuffer();
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyStop();
}
/* (non-Javadoc)
* @see com.windowtester.event.model.IEventRecorder#restart()
*/
public void restart() {
//first, flush buffer to ensure there are no undispatched events
flushEventBuffer();
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyRestart();
}
/* (non-Javadoc)
* @see com.windowtester.event.model.IEventRecorder#write()
*/
public void write() {
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyWrite();
}
/**
* @see com.windowtester.recorder.IEventRecorder#addHook(java.lang.String)
*/
public void addHook(String hookName) {
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyAssertionHookAdded(hookName);
}
/**
* @see com.windowtester.recorder.IEventRecorder#terminate()
*/
public void terminate() {
//first, flush buffer to ensure there are no undispatched events
flushEventBuffer();
//notify dispose
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyDispose();
}
/* (non-Javadoc)
* @see com.windowtester.recorder.IEventRecorder#toggleSpyMode()
*/
public void toggleSpyMode() {
//first, flush buffer to ensure there are no undispatched events
flushEventBuffer();
//notify dispose
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifySpyModeToggle();
}
//surfaced to improve recorder interactivity
public void flushEventBuffer() {
IUISemanticEvent bufferedEvent = _parser.flush();
//notice that focus events are ignored... this is a kludge (they porpbably shouldn't be sent at all on close)
if (bufferedEvent != null && !(bufferedEvent instanceof SemanticFocusEvent))
record(bufferedEvent);
}
/* (non-Javadoc)
* @see com.windowtester.event.model.IEventRecorder#trace(com.windowtester.swt.event.model.RecorderTraceEvent)
*/
public void trace(RecorderTraceEvent event) {
for (Iterator iter = getListeners().iterator(); iter.hasNext(); )
((ISemanticEventListener)iter.next()).notifyTrace(event);
}
/* (non-Javadoc)
* @see com.windowtester.event.model.IEventRecorder#addListener(com.windowtester.swt.event.model.ISemanticEventListener)
*/
public void addListener(ISemanticEventListener listener) {
List listeners = getListeners();
if (listener == null)
return;
if (!listeners.contains(listener)) //multiple adds simply ignored
listeners.add(listener);
}
/* (non-Javadoc)
* @see com.windowtester.event.model.IEventRecorder#removeListener(com.windowtester.swt.event.model.ISemanticEventListener)
*/
public void removeListener(ISemanticEventListener listener) {
List listeners = getListeners();
if (listeners.contains(listener))
debug("listener removed that was not registered: " + listener);
else
listeners.remove(listener);
}
/**
* Get the registered event listeners.
* @return a list of resgistered listeners
*/
private List getListeners() {
if (_listeners == null)
_listeners = new ArrayList/*<ISemanticEventListener>*/();
return _listeners;
}
/**
* @see com.windowtester.recorder.IEventRecorder#addEventFilter(com.windowtester.recorder.IEventFilter)
*/
public void addEventFilter(IEventFilter filter) {
List filters = getEventFilters();
if (filters.contains(filter))
debug("multiple adds of filter: : " + filter);
else
filters.add(filter);
}
/**
* @see com.windowtester.recorder.IEventRecorder#removeEventFilter(com.windowtester.recorder.IEventFilter)
*/
public void removeEventFilter(IEventFilter filter) {
List filters = getEventFilters();
if (filters.contains(filter))
debug("filter removed that was not registered: " + filter);
else
filters.remove(filter);
}
/**
* @return the list of event filters
*/
protected List getEventFilters() {
if (_filters == null)
_filters = new ArrayList/*<IEventFilter>*/();
return _filters;
}
/**
* @return true if this recorder is currently recording
*/
public boolean isRecording() {
return _isRecording;
}
/**
* Send this debug message to the tracer
*/
private static void debug(String msg) {
DebugHandler.trace(TRACE_OPTION, msg);
}
class EventRecorderCallBack extends DropTargetAdapter implements IEventRecorderCallBack {
public void listenForDropEvents(DropTarget dropTarget) {
dropTarget.addDropListener(this);
}
/**
* @see org.eclipse.swt.dnd.DropTargetListener#drop(org.eclipse.swt.dnd.DropTargetEvent)
*/
public void drop(DropTargetEvent event) {
logEvent(event);
}
}
}