/******************************************************************************* * 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.io.IOException; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.Socket; import java.util.MissingResourceException; import java.util.ResourceBundle; import org.eclipse.core.runtime.Plugin; import org.eclipse.swt.widgets.Display; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import com.windowtester.internal.debug.Logger; import com.windowtester.internal.debug.Tracer; import com.windowtester.net.ICommunicationProtocolConstants; import com.windowtester.net.SocketStreamingListener; import com.windowtester.recorder.IEventRecorder; import com.windowtester.recorder.event.ISemanticEvent; import com.windowtester.runtime.swt.internal.display.DisplayIntrospection; import com.windowtester.runtime.swt.internal.preferences.PlaybackSettingsFactory; import com.windowtester.swing.event.recorder.SwingGuiTestRecorder; import com.windowtester.swt.event.server.SessionEventController; import com.windowtester.swt.event.server.WorkbenchEventController; /** * The main access point to SWT event recording. */ public class EventRecorderPlugin extends Plugin { /** The unique identifier for this plugin */ public static final String PLUGIN_ID = "com.windowtester.swt.recorder"; /** For automatically install bundles provided by this system property */ public static final String INSTALL_BUNDLES_SYS_PROPERTY = "install.bundles"; /** Default amount of time for thread introspection */ private static final long RETRY_TIME = 10000; // TODO [Alex] move the whole Display introspection deal to utils /** The name of the launch class */ public static final String LAUNCH_CLASS_NAME_PROP = "com.windowtester.swt.launch.class.name"; /** the property that will always indicate the Swing recording session */ public static final String SWING_LAUNCH_PROP = "com.windowtester.swt.launch.swing.type"; /** The shared instance. */ private static EventRecorderPlugin _plugin; public static boolean inRecording = false; /** Resource bundle. */ private ResourceBundle _resourceBundle; /** The recorder instance */ private IEventRecorder _eventRecorder; /** Socket streaming listener that sends events to main Workbench instance */ private SocketStreamingListener _socketListener; /** Meta events controller thread reference */ private SessionEventController _metaController; /** Workbench event controller */ private WorkbenchEventController workbenchServer; /** The display reference */ private Display _display; /** * @return the current display */ public Display getDisplay() { if(_display==null){ _display = findDisplay(); } return _display; } private Display findDisplay() { DisplayIntrospection displayFinder = new DisplayIntrospection(RETRY_TIME); return displayFinder.syncIntrospect(); } /** * This method is called upon plug-in activation */ public void start(BundleContext context) throws Exception { super.start(context); _plugin = this; //_settings = new RecorderSettings(); try { _resourceBundle = ResourceBundle .getBundle("com.windowtester.event.recorder.EventRecorderPluginResources"); } catch (MissingResourceException x) { _resourceBundle = null; } if(isRecordingSession()&&!isRcpRecordingSession()){ installSpecialBundles(context); } } /** * This method is called when the plug-in is stopped */ public void stop(BundleContext context) throws Exception { super.stop(context); if(workbenchServer!=null&&workbenchServer.isAlive()){ workbenchServer.stopServer(); } } /** * Returns the shared instance. */ public static EventRecorderPlugin getDefault() { return _plugin; } /** * Get the String identifier for this plugin. * @return the plugin id */ public static String getPluginId() { return PLUGIN_ID; } /** * Returns the string from the plugin's resource bundle, or 'key' if not * found. */ public static String getResourceString(String key) { ResourceBundle bundle = EventRecorderPlugin.getDefault().getResourceBundle(); try { return (bundle != null) ? bundle.getString(key) : key; } catch (MissingResourceException e) { return key; } } /** * Returns the plugin's resource bundle, */ public ResourceBundle getResourceBundle() { return _resourceBundle; } /** * Start recording events */ public static void startRecording() { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().start(); } }).run(); } /** * Stop recording events */ public static void stopRecording() { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().stop(); } }).run(); } /** * * @return the shared event recorder instance */ public IEventRecorder getRecorder() { try { if (_eventRecorder == null) { _eventRecorder = createRecorder(); _eventRecorder.addListener(_socketListener); } } catch (RuntimeException e) { /* * This is for debugging purposes. We have no choice but to dump to * the console since this is occuring in the event recording context. */ e.printStackTrace(); throw e; } return _eventRecorder; } /** * Create a new recorder instance. */ private IEventRecorder createRecorder() { if (isSwingRecordingSession()) return new SwingGuiTestRecorder(); int apiVersion = PlaybackSettingsFactory.getPlaybackSettings().getRuntimeAPIVersion(); return new GuiTestRecorder(getDisplay(), apiVersion ); } /** * Write the recorded events. */ public static void writeRecording() { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().write(); } }).run(); } /** * Pause recording. */ public static void pauseRecording() { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().pause(); } }).run(); } /** * Terminate the recording session. */ public static void terminateRecording() { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().terminate(); } }).run(); } public static void toggleSpyMode() { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().toggleSpyMode(); } }).run(); } /** * Restart the recorder. */ public static void restartRecording() { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().restart(); } }).run(); } /** * Add an assertion hook. * @param hookName - the name of the hook to add */ public static void addAssertion(final String hookName) { (new SyncDisplayRunnable(){ void doRun() { getDefault().getRecorder().addHook(hookName); } }).run(); } /** * Find out if current application is under recording setup * @return true if it is being set for recording */ public static boolean isRecordingSession(){ return System.getProperty(ICommunicationProtocolConstants.RECORDER_PORT_SYSTEM_PROPERTY)!=null; } /** * Find out if current application is under RCP recording setup * @return true if it is being set for recording */ public static boolean isRcpRecordingSession(){ return isRecordingSession()&&(System.getProperty(EventRecorderPlugin.LAUNCH_CLASS_NAME_PROP)==null); } public static boolean isSwingRecordingSession(){ return System.getProperty(SWING_LAUNCH_PROP)!=null; } /** * Small wrapper to run synchronous operation within Display's syncExec method if session is * SWT session and not if recording session is Swing session. */ static abstract class SyncDisplayRunnable { void run(){ if(isSwingRecordingSession()){ doRun(); return; }else{ Display d = getDefault().getDisplay(); if(d!=null){ d.syncExec(new Runnable(){ public void run() { doRun(); } }); }else{ getDefault()._socketListener.notifyDisplayNotFound(); } } } abstract void doRun(); } public void startSession(){ // deal with application recording initialization if(isRecordingSession()){ // create listener that broadcasts UI events to main Workbench if(_socketListener==null) _socketListener = new SocketStreamingListener(); // start listening to meta events broadcasted from main Workbench if(_metaController==null) _metaController = new SessionEventController("Meta Event Controller Thread"); _metaController.start(); // asynchronously notify main Workbench on which port it listens new Thread(){ public void run() { // sleep for one second try { Thread.sleep(1000); } catch (InterruptedException e) { } // then notify the main Workbench _socketListener.notifyControllerStart(_metaController.getPort()); } }.start(); }else{ // not a recording session and is a debug mode if(isInDebugMode()){ // start the workbench server to be reused many times in launch configuration workbenchServer = new WorkbenchEventController(); workbenchServer.start(); } } } /** * Install special bundles provided in system property * @param context * @throws BundleException */ private void installSpecialBundles(BundleContext context) { String bundles = System.getProperty(INSTALL_BUNDLES_SYS_PROPERTY); if(bundles==null) return; String[] array = bundles.split("[,]"); for (int i = 0; i < array.length; i++) { String bundleReference = array[i]; try { Bundle bundle = context.installBundle(bundleReference); if(bundle!=null){ // workaround to force bundle to be resolved try { bundle.loadClass("does.not.matter.what.class.to.load"); } catch (ClassNotFoundException e1) { } }else{ throw new BundleException("Bundle "+bundleReference+" was not installed"); } Tracer.trace(PLUGIN_ID+"/trace", "Dynamically installed and resolved bundle: "+bundle.getSymbolicName()+"."); } catch (BundleException e) { Logger.log("Cannot install the bundle "+bundleReference, e); } } } public static boolean isInDebugMode(){ String dmode = System.getProperty("windowtester.debug.mode"); return dmode!=null && dmode.equals("true"); } public WorkbenchEventController getWorkbechController(){ return workbenchServer; } /** * Send this event over the wire * @param event - the event to send */ public static void send(ISemanticEvent event, int port) { if (event == null || !inRecording) return; //ignore null events //handleMetaEvents(event); Socket _socket = null; try { _socket = new Socket(InetAddress.getByName("localhost"), port); ObjectOutputStream _out = new ObjectOutputStream(_socket.getOutputStream()); _out.writeObject(event); _out.close(); } catch (IOException e) { Logger.log("An error occured in sending a semantic event message", e); } } }