Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIsaac Arvestad2016-08-08 11:53:39 +0000
committerIsaac Arvestad2016-10-27 15:17:12 +0000
commitda175faa30356d4cf8ff1f0ee9b0fa069806482b (patch)
treecca27d344a46f8151f7d39e49ef45c941654a4c3
parent89c39c8c5bf74ab5e165dddc97e19a0faca1b486 (diff)
downloadorg.eclipse.swtbot-da175faa30356d4cf8ff1f0ee9b0fa069806482b.tar.gz
org.eclipse.swtbot-da175faa30356d4cf8ff1f0ee9b0fa069806482b.tar.xz
org.eclipse.swtbot-da175faa30356d4cf8ff1f0ee9b0fa069806482b.zip
Bug 506628: Create SWTBot recorder server/client
Adds a server for recording SWTBot code and a client for viewing generated code. The server runs in one Eclipse instance where tests are recorded and the client runs in a separate Eclipse instance where tests cases can be written and edited. This change makes it possible to simultaneously have one workspace open where a test is being recorded and another workspace where test cases are being written. Change-Id: I7b071498a143c210d4ed7692d28fe1e99562a0cf Signed-off-by: Isaac Arvestad <isaac.arvestad@ericsson.com>
-rw-r--r--org.eclipse.swtbot.generator.client/.classpath7
-rw-r--r--org.eclipse.swtbot.generator.client/.gitignore1
-rw-r--r--org.eclipse.swtbot.generator.client/.project34
-rw-r--r--org.eclipse.swtbot.generator.client/.settings/org.eclipse.jdt.core.prefs7
-rw-r--r--org.eclipse.swtbot.generator.client/META-INF/MANIFEST.MF22
-rw-r--r--org.eclipse.swtbot.generator.client/README54
-rw-r--r--org.eclipse.swtbot.generator.client/build.properties6
-rw-r--r--org.eclipse.swtbot.generator.client/icons/refresh.gifbin0 -> 327 bytes
-rw-r--r--org.eclipse.swtbot.generator.client/icons/swtbot_rec16.pngbin0 -> 824 bytes
-rw-r--r--org.eclipse.swtbot.generator.client/icons/swtbot_rec64.pngbin0 -> 4454 bytes
-rw-r--r--org.eclipse.swtbot.generator.client/plugin.xml90
-rw-r--r--org.eclipse.swtbot.generator.client/pom.xml13
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/Recorder.java442
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClient.java142
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientCodeListener.java25
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientStatusListener.java28
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/SWTBotRecorderClientPlugin.java79
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/commands/ImportClientCodeHandler.java75
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLaunchConfiguration.java129
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLauncherTabGroup.java41
-rw-r--r--org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/views/RecorderClientView.java477
-rw-r--r--org.eclipse.swtbot.generator.feature/feature.xml7
-rw-r--r--org.eclipse.swtbot.generator/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.swtbot.generator/plugin.xml3
-rw-r--r--org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/RecorderServer.java121
-rw-r--r--org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/StartupRecorderServer.java114
-rw-r--r--pom.xml1
27 files changed, 1919 insertions, 0 deletions
diff --git a/org.eclipse.swtbot.generator.client/.classpath b/org.eclipse.swtbot.generator.client/.classpath
new file mode 100644
index 00000000..0b1bcf94
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src/"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/org.eclipse.swtbot.generator.client/.gitignore b/org.eclipse.swtbot.generator.client/.gitignore
new file mode 100644
index 00000000..e4e5f6c8
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/.gitignore
@@ -0,0 +1 @@
+*~ \ No newline at end of file
diff --git a/org.eclipse.swtbot.generator.client/.project b/org.eclipse.swtbot.generator.client/.project
new file mode 100644
index 00000000..aad1e45f
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.swtbot.generator.client</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/org.eclipse.swtbot.generator.client/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.swtbot.generator.client/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000..c537b630
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/org.eclipse.swtbot.generator.client/META-INF/MANIFEST.MF b/org.eclipse.swtbot.generator.client/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..98918aff
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/META-INF/MANIFEST.MF
@@ -0,0 +1,22 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: SWTBot Generator Client
+Bundle-SymbolicName: org.eclipse.swtbot.generator.client;singleton:=true
+Bundle-Version: 2.6.0.qualifier
+Bundle-Activator: org.eclipse.swtbot.generator.client.SWTBotRecorderClientPlugin
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.ui,
+ org.eclipse.ui.ide,
+ org.eclipse.ui.console,
+ org.eclipse.jdt.core,
+ org.eclipse.jdt.ui,
+ org.eclipse.jdt.launching,
+ org.eclipse.debug.ui,
+ org.eclipse.pde.ui,
+ org.eclipse.swtbot.generator,
+ org.eclipse.jface.text,
+ org.eclipse.ui.workbench.texteditor,
+ org.eclipse.ui.editors,
+ org.eclipse.jdt
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-ActivationPolicy: lazy
diff --git a/org.eclipse.swtbot.generator.client/README b/org.eclipse.swtbot.generator.client/README
new file mode 100644
index 00000000..374f014e
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/README
@@ -0,0 +1,54 @@
+# General information about the SWTBot server/client recorder plugin
+
+This plugin makes it possible to record and edit SWTBot tests at the
+same time. By recording tests in a separate Eclipse instance the
+original editor can be used to write tests in the regular Java editing
+environment while being able to change perspective, open new views,
+etc. in the test instance.
+
+The main parts of this plugin is the client view which displays
+generated code and the recorder server which generates code. The
+server generates code using the SWTBot code generator and then sends
+it to the client view. The server code is part of the
+'org.eclipse.swtbot.generator' plugin.
+
+## First time launching
+Create a new 'SWTBot Recorder Server' launch configuration. This
+configuration is similar to a regular Eclipse Application but contains
+a VM argument for specifying that the recorder server should be
+started. By default the server will use port 8000 but this can be
+changed by changing the port number in the VM arguments. It is
+possible to use a regular Eclipse Application launch configuration but
+then one has to supply the VM argument manually and the client view
+will not start connecting automatically when launched.
+
+The default VM argument for the server is
+"-Dorg.eclipse.swtbot.generator.server.enable=8000". The integer at
+the end of the string is the selected port number and can be
+changed. When launching the edited configuration the client view will
+update the port number before starting to connect.
+
+Make sure that the Eclipse application being launched has the
+'org.eclipse.swtbot.generator.*' plugins included.
+
+## Launching
+Launch the SWTBot Recorder Server launch configuration. This will
+start a second Eclipse instance for recording tests. While launching,
+the client view will also start trying to connect to the recorder
+server. When the client view displays that it is connected you are
+ready to record tests!
+
+## Recording
+Toggle the record button to decide if code generated on the server
+side should be recorded in the view.
+
+To insert code directly in a Java method, open a Java editor, click
+"Add to method" in the client view and select the method to add the
+code to. If you are recording, the code should now be inserted last in
+the method.
+
+It is also possible to quickly move text from the client view and the
+editor by right clicking in the editor and selecting "Import SWTBot
+code" in the context menu. This moves the code from the client view to
+the selection within the editor. The shortcut for this action is
+"Ctrl+Alt+Y". \ No newline at end of file
diff --git a/org.eclipse.swtbot.generator.client/build.properties b/org.eclipse.swtbot.generator.client/build.properties
new file mode 100644
index 00000000..0d3d3a74
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/build.properties
@@ -0,0 +1,6 @@
+source.. = src/
+output.. = bin/
+bin.includes = plugin.xml,\
+ META-INF/,\
+ .,\
+ icons/
diff --git a/org.eclipse.swtbot.generator.client/icons/refresh.gif b/org.eclipse.swtbot.generator.client/icons/refresh.gif
new file mode 100644
index 00000000..3ca04d06
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/icons/refresh.gif
Binary files differ
diff --git a/org.eclipse.swtbot.generator.client/icons/swtbot_rec16.png b/org.eclipse.swtbot.generator.client/icons/swtbot_rec16.png
new file mode 100644
index 00000000..743ce38d
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/icons/swtbot_rec16.png
Binary files differ
diff --git a/org.eclipse.swtbot.generator.client/icons/swtbot_rec64.png b/org.eclipse.swtbot.generator.client/icons/swtbot_rec64.png
new file mode 100644
index 00000000..2b4b7eb5
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/icons/swtbot_rec64.png
Binary files differ
diff --git a/org.eclipse.swtbot.generator.client/plugin.xml b/org.eclipse.swtbot.generator.client/plugin.xml
new file mode 100644
index 00000000..9b04a62d
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/plugin.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+
+ <extension
+ point="org.eclipse.debug.core.launchConfigurationTypes">
+ <launchConfigurationType
+ delegate="org.eclipse.swtbot.generator.client.launcher.RecorderServerLaunchConfiguration"
+ id="org.eclipse.swtbot.generator.client.launcher.SWTBotServerRecorderType"
+ name="SWTBot Recorder Server"
+ modes="run, debug">
+ </launchConfigurationType>
+ </extension>
+ <extension point="org.eclipse.debug.ui.launchConfigurationTabGroups">
+ <launchConfigurationTabGroup
+ type="org.eclipse.swtbot.generator.client.launcher.SWTBotServerRecorderType"
+ class="org.eclipse.swtbot.generator.client.launcher.RecorderServerLauncherTabGroup"
+ id="org.eclipse.swtbot.generator.client.launcher.SWTBotServerRecorderTabGroup">
+ <launchMode
+ description="Launch a SWTBot recorder server"
+ mode="run">
+ </launchMode>
+ <launchMode
+ description="Launch a SWTBot recorder server"
+ mode="debug">
+ </launchMode>
+ </launchConfigurationTabGroup>
+ </extension>
+ <extension
+ point="org.eclipse.debug.ui.launchConfigurationTypeImages">
+ <launchConfigurationTypeImage
+ configTypeID="org.eclipse.swtbot.generator.client.launcher.SWTBotServerRecorderType"
+ icon="icons/swtbot_rec16.png"
+ id="org.eclipse.swtbot.generator.client.launcher.SWTBotServerRecorderTypeImage">
+ </launchConfigurationTypeImage>
+ </extension>
+ <extension
+ point="org.eclipse.ui.views">
+ <view
+ class="org.eclipse.swtbot.generator.client.views.RecorderClientView"
+ icon="icons/swtbot_rec16.png"
+ id="org.eclipse.swtbot.generator.client.view.recorder.client"
+ name="SWTBot Recorder "
+ restorable="true">
+ </view>
+ </extension>
+ <extension
+ point="org.eclipse.ui.commands">
+ <command
+ description="Imports code to editor and removes it from the client recorder view"
+ id="org.eclipse.swtbot.generator.client.commands.import.code"
+ name="Import SWTBot code">
+ </command>
+ </extension>
+ <extension
+ point="org.eclipse.ui.menus">
+ <menuContribution
+ allPopups="false"
+ locationURI="popup:#AbstractTextEditorContext?after=additions">
+ <command
+ commandId="org.eclipse.swtbot.generator.client.commands.import.code"
+ label="Import SWTBot code"
+ style="push">
+ </command>
+ </menuContribution>
+ </extension>
+ <extension
+ point="org.eclipse.ui.commandImages">
+ <image
+ commandId="org.eclipse.swtbot.generator.client.commands.import.code"
+ icon="icons/swtbot_rec64.png">
+ </image>
+ </extension>
+ <extension
+ point="org.eclipse.ui.handlers">
+ <handler
+ class="org.eclipse.swtbot.generator.client.commands.ImportClientCodeHandler"
+ commandId="org.eclipse.swtbot.generator.client.commands.import.code">
+ </handler>
+ </extension>
+ <extension
+ point="org.eclipse.ui.bindings">
+ <key
+ commandId="org.eclipse.swtbot.generator.client.commands.import.code"
+ contextId="org.eclipse.ui.contexts.window"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="Ctrl+Alt+Y">
+ </key>
+ </extension>
+</plugin>
diff --git a/org.eclipse.swtbot.generator.client/pom.xml b/org.eclipse.swtbot.generator.client/pom.xml
new file mode 100644
index 00000000..bf6b0187
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/pom.xml
@@ -0,0 +1,13 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.eclipse.swtbot.generator.client</groupId>
+ <artifactId>org.eclipse.swtbot.generator.client</artifactId>
+ <packaging>eclipse-plugin</packaging>
+ <parent>
+ <groupId>org.eclipse.swtbot</groupId>
+ <artifactId>parent</artifactId>
+ <version>2.6.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+</project> \ No newline at end of file
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/Recorder.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/Recorder.java
new file mode 100644
index 00000000..92ce73db
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/Recorder.java
@@ -0,0 +1,442 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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: Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jdt.core.ElementChangedEvent;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElementDelta;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.Statement;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.text.edits.MalformedTreeException;
+import org.eclipse.text.edits.TextEdit;
+
+/**
+ * Recorder is a singleton which keeps track of recorder state information and
+ * the generated code received from the server.
+ */
+public enum Recorder implements RecorderClientCodeListener, RecorderClientStatusListener {
+ INSTANCE;
+
+ private RecorderClient recorderClient;
+ private List<RecorderClientCodeListener> codeListeners;
+ private List<RecorderClientStatusListener> statusListeners;
+
+ private boolean isInitialized = false;
+ private boolean isRecording;
+ private boolean isInsertingDirectlyInEditor;
+ private ConnectionState connectionState;
+ private IDocument document;
+ private IMethod selectedMethod;
+ private IDocument selectedMethodDocument;
+
+ /**
+ * Initialize recorder.
+ */
+ public void initialize() {
+ isInitialized = true;
+
+ codeListeners = new ArrayList<RecorderClientCodeListener>();
+ statusListeners = new ArrayList<RecorderClientStatusListener>();
+ codeListeners.add(this);
+ statusListeners.add(this);
+
+ document = new Document();
+
+ reset();
+ }
+
+ /**
+ * Reset recording state.
+ */
+ public void reset() {
+ isRecording = false;
+ isInsertingDirectlyInEditor = false;
+ selectedMethod = null;
+ selectedMethodDocument = null;
+ connectionState = ConnectionState.DISCONNECTED;
+ }
+
+ /**
+ * Start a recorder client session.
+ */
+ public void startRecorderClient(int port) {
+ interruptRecorderClient();
+
+ connectionState = ConnectionState.CONNECTING;
+ recorderClient = new RecorderClient(codeListeners, statusListeners, port);
+ recorderClient.start();
+ }
+
+ /**
+ * Interrupt a recorder client session and reset recording state.
+ */
+ public void interruptRecorderClient() {
+ if (recorderClient != null) {
+ recorderClient.interrupt();
+ recorderClient.closeSocket();
+ recorderClient = null;
+ }
+
+ reset();
+ }
+
+ /**
+ * Takes care of new code. If a method is selected and
+ * <code>isInsertingDirectlyInEditor<code> is true, add code directly to
+ * editor. Otherwise add it to the recorder view document.
+ */
+ @Override
+ public void codeGenerated(String code) {
+ if (isRecording == false) {
+ return;
+ }
+
+ if (isInsertingDirectlyInEditor && selectedMethod != null && selectedMethodDocument != null) {
+ insertInEditor(code);
+ } else {
+ insertInView(code);
+ }
+ }
+
+ @Override
+ public void connectionStarted() {
+ connectionState = ConnectionState.CONNECTED;
+ }
+
+ @Override
+ public void connectionEnded() {
+ connectionState = ConnectionState.DISCONNECTED;
+
+ reset();
+ }
+
+ /**
+ * Appends a row of code to the document contained in the recorder view.
+ *
+ * @param code
+ * The code to append.
+ */
+ private void insertInView(final String code) {
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (document.get().length() == 0) {
+ document.set(code + ";");
+ } else {
+ document.set(document.get() + "\n" + code + ";");
+ }
+ }
+ });
+ }
+
+ /**
+ * Adds a new line of code to the currently selected method and the editor
+ * which contains this method.
+ *
+ * @param code
+ * The code to append.
+ */
+ private void insertInEditor(String code) {
+ ICompilationUnit methodCompilationUnit = selectedMethod.getCompilationUnit();
+ ASTParser parser = ASTParser.newParser(AST.JLS8);
+ parser.setSource(methodCompilationUnit);
+ parser.setResolveBindings(true);
+ parser.setBindingsRecovery(true);
+ ASTNode rootNode = parser.createAST(null);
+ CompilationUnit compilationUnit = (CompilationUnit) rootNode;
+
+ // Create a visitor which finds all method declarations
+ MethodDeclarationVisitor methodDeclarationVisitor = new MethodDeclarationVisitor();
+ compilationUnit.accept(methodDeclarationVisitor);
+
+ // Search for the method declaration corresponding to selectedMethod
+ MethodDeclaration method = methodDeclarationVisitor.findMethodDeclaration(selectedMethod);
+
+ final ASTRewrite rewrite = ASTRewrite.create(rootNode.getAST());
+ ListRewrite listRewrite = rewrite.getListRewrite(method.getBody(), Block.STATEMENTS_PROPERTY);
+ Statement statement = (Statement) rewrite.createStringPlaceholder(code + ";", ASTNode.EMPTY_STATEMENT);
+ listRewrite.insertLast(statement, null);
+
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ TextEdit edits = rewrite.rewriteAST();
+ try {
+ edits.apply(selectedMethodDocument);
+ } catch (MalformedTreeException e) {
+ e.printStackTrace();
+ } catch (BadLocationException e) {
+ e.printStackTrace();
+ }
+ } catch (JavaModelException e1) {
+ e1.printStackTrace();
+ } catch (IllegalArgumentException e1) {
+ e1.printStackTrace();
+ }
+ };
+ });
+ }
+
+ /**
+ * MethodDeclarationVisitor store all MethodDeclarations it can find and
+ * provides functionality for finding a specific MethodDeclaration with a
+ * corresponding IMethod.
+ */
+ private class MethodDeclarationVisitor extends ASTVisitor {
+ private List<MethodDeclaration> methodDeclarations = new ArrayList<MethodDeclaration>();
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ methodDeclarations.add(node);
+ return super.visit(node);
+ }
+
+ /**
+ * Returns the MethodDeclaration from a corresponding IMethod.
+ *
+ * @param method
+ * The IMethod to search with.
+ * @return The first corresponding MethodDeclaration found, or null if
+ * no MethodDeclaration can be found.
+ */
+ public MethodDeclaration findMethodDeclaration(IMethod method) {
+ for (MethodDeclaration methodDeclaration : methodDeclarations) {
+ if (methodDeclaration.resolveBinding().getJavaElement().equals(method)) {
+ return methodDeclaration;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Determines if UI which is related to code in the open editor needs to be
+ * updated. The method selection viewer should be updated if the document is
+ * closed or if a method is removed/added.
+ *
+ * @param event
+ * The received ElementChangedEvent.
+ * @return True if method selection viewer should be updated and false if
+ * not.
+ */
+ public boolean shouldUpdateMethodSelectionViewer(ElementChangedEvent event) {
+ IJavaElementDelta delta = event.getDelta();
+
+ List<IJavaElementDelta> children = getAffectedLeafDeltas(delta);
+ for (IJavaElementDelta child : children) {
+ if (child.getElement() instanceof IMethod) {
+ return true;
+ } else if (child.getElement() instanceof ICompilationUnit) {
+ if (child.getKind() == IJavaElementDelta.CHANGED) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds and returns the leaves of the change tree.
+ *
+ * @param delta
+ * The root to be iterated through.
+ * @return The leaf nodes.
+ */
+ private List<IJavaElementDelta> getAffectedLeafDeltas(IJavaElementDelta delta) {
+ List<IJavaElementDelta> leaves = new ArrayList<IJavaElementDelta>();
+
+ return getAffectedLeafDeltasRecursively(delta, leaves);
+ }
+
+ /**
+ * Helper method for <code>getAffectedLeafDeltas</code> which recursively
+ * iterates over a IJavaElementDelta and returns the leaf nodes of the
+ * change tree.
+ *
+ * @param delta
+ * The root to be iterated through.
+ * @param leaves
+ * The leaf nodes already found.
+ * @return The leaf nodes found.
+ */
+ private List<IJavaElementDelta> getAffectedLeafDeltasRecursively(IJavaElementDelta delta,
+ List<IJavaElementDelta> leaves) {
+ for (IJavaElementDelta childDelta : delta.getAffectedChildren()) {
+ if (childDelta.getAffectedChildren().length == 0) {
+ // Found a leaf!
+ leaves.add(childDelta);
+ } else {
+ getAffectedLeafDeltasRecursively(childDelta, leaves);
+ }
+ }
+
+ return leaves;
+ }
+
+ /**
+ * ConnectionState describes the various possible connection states between
+ * the recorder client and server.
+ */
+ public enum ConnectionState {
+ CONNECTED, CONNECTING, DISCONNECTED;
+ }
+
+ public boolean isInsertingDirectlyInEditor() {
+ return isInsertingDirectlyInEditor;
+ }
+
+ public void setInsertingDirectlyInEditor(boolean isInsertingDirectlyInEditor) {
+ this.isInsertingDirectlyInEditor = isInsertingDirectlyInEditor;
+ }
+
+ public IMethod getSelectedMethod() {
+ return selectedMethod;
+ }
+
+ /**
+ * Sets the selected method. It is important that this method is part of the
+ * selected method document.
+ *
+ * @param selectedMethod
+ * The selected method.
+ */
+ public void setSelectedMethod(IMethod selectedMethod) {
+ this.selectedMethod = selectedMethod;
+ }
+
+ public IDocument getSelectedMethodDocument() {
+ return selectedMethodDocument;
+ }
+
+ /**
+ * Sets the seletctedMethodDocument. It is important that this document
+ * contains the selected method.
+ *
+ * @param selectedMethodDocument
+ * The document that the selected method resides in.
+ */
+ public void setSelectedMethodDocument(IDocument selectedMethodDocument) {
+ this.selectedMethodDocument = selectedMethodDocument;
+ }
+
+ /**
+ * Returns the text contained in the document. Use from UI thread.
+ *
+ * @return The text contained in the document.
+ */
+ public String getDocumentText() {
+ return document.get();
+ }
+
+ /**
+ * Clears the document by setting the contents to an empty String. Use from
+ * UI thread.
+ */
+ public void clearDocument() {
+ document.set("");
+ }
+
+ /**
+ * Returns the recorder document which contains recorded code. Use from UI
+ * thread.
+ *
+ * @return The document.
+ */
+ public IDocument getDocument() {
+ return document;
+ }
+
+ public boolean isRecording() {
+ return isRecording;
+ }
+
+ public void setRecording(boolean isRecording) {
+ this.isRecording = isRecording;
+ }
+
+ public boolean isInitialized() {
+ return isInitialized;
+ }
+
+ public ConnectionState getConnectionState() {
+ return connectionState;
+ }
+
+ public int getPort() {
+ return recorderClient.getPort();
+ }
+
+ /**
+ * Add a code listener to the list of code listeners.
+ *
+ * @param listener
+ * The listener to add.
+ * @return Specified by {@link Collection#add}
+ */
+ public boolean addCodeListener(RecorderClientCodeListener listener) {
+ return codeListeners.add(listener);
+ }
+
+ /**
+ * Removes the first occurrence of the specified listener from the list of
+ * code listeners.
+ *
+ * @param listener
+ * The listener to remove.
+ * @return Specified by {@link Collection#remove(Object)}
+ */
+ public boolean removeCodeListener(RecorderClientCodeListener listener) {
+ return codeListeners.remove(listener);
+ }
+
+ /**
+ * Add a status listener to the list of status listeners.
+ *
+ * @param listener
+ * The listener to add.
+ * @return Specified by {@link Collection#add}
+ */
+ public boolean addStatusListener(RecorderClientStatusListener listener) {
+ return statusListeners.add(listener);
+ }
+
+ /**
+ * Removes the first occurrence of the specified listener from the list of
+ * code listeners.
+ *
+ * @param listener
+ * The listener to remove.
+ * @return Specified by {@link Collection#remove(Object)}
+ */
+ public boolean removeStatusListener(RecorderClientStatusListener listener) {
+ return statusListeners.remove(listener);
+ }
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClient.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClient.java
new file mode 100644
index 00000000..32de406f
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClient.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.util.List;
+
+/**
+ * RecorderClient is a client for RecorderServer and collects incoming generated
+ * code from a RecorderServer running on localhost. RecorderClient runs on a new
+ * thread.
+ */
+public class RecorderClient extends Thread {
+
+ /**
+ * Refresh time in milliseconds when attempting to connect to host.
+ */
+ private static final int REFRESH_TIME = 1000;
+
+ private List<RecorderClientCodeListener> codeListeners;
+ private List<RecorderClientStatusListener> statusListeners;
+ private int port;
+
+ private Socket socket;
+
+ /**
+ * Creates a new RecorderClient.
+ *
+ * @param port
+ * The port to connect on.
+ */
+ public RecorderClient(List<RecorderClientCodeListener> codeListeners,
+ List<RecorderClientStatusListener> statusListeners, int port) {
+ this.codeListeners = codeListeners;
+ this.statusListeners = statusListeners;
+ this.port = port;
+ }
+
+ /**
+ * Attempts to close the the socket of the recorder client. This is
+ * necessary to interrupt the thread while the thread is stuck in reading
+ * the next line of the input stream.
+ *
+ * If the socket is null or already closed, do nothing and return.
+ */
+ public void closeSocket() {
+ if (socket == null || socket.isClosed()) {
+ return;
+ }
+
+ try {
+ socket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void run() {
+ socket = connect("localhost", port);
+
+ if (socket == null) {
+ for (RecorderClientStatusListener listener : statusListeners) {
+ listener.connectionEnded();
+ }
+ throw new RuntimeException("Could not create socket for client");
+ }
+
+ for (RecorderClientStatusListener listener : statusListeners) {
+ listener.connectionStarted();
+ }
+
+ BufferedReader in;
+ try {
+ in = new BufferedReader(
+ new InputStreamReader(socket.getInputStream()));
+ String line;
+
+ while ((line = in.readLine()) != null && !interrupted()) {
+ for (RecorderClientCodeListener listener : codeListeners) {
+ listener.codeGenerated(line);
+ }
+ }
+
+ for (RecorderClientStatusListener listener : statusListeners) {
+ listener.connectionEnded();
+ }
+
+ socket.close();
+ } catch (IOException e) {
+ // Client was shut down through closeSocket() and readLine threw
+ // an exception.
+ return;
+ }
+ }
+
+ /**
+ * Attempts to connect to host. If the host is not found, wait
+ * <code>REFRESH_TIME</code> milliseconds and try again. Once connected,
+ * return.
+ *
+ * @param host
+ * The host name to connect to.
+ * @param port
+ * The port to connect to.
+ * @return The host socket.
+ */
+ private Socket connect(String host, int port) {
+ Socket socket;
+
+ while (true) {
+ try {
+ socket = new Socket(host, port);
+ return socket;
+ } catch (ConnectException e) {
+ try {
+ Thread.sleep(REFRESH_TIME);
+ } catch (InterruptedException e1) {
+ return null;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public int getPort() {
+ return port;
+ }
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientCodeListener.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientCodeListener.java
new file mode 100644
index 00000000..c52e1395
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientCodeListener.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client;
+
+/**
+ * An interface for objects interested in receiving generated code as it is
+ * received.
+ */
+public interface RecorderClientCodeListener {
+ /**
+ * Called when new code has been received.
+ *
+ * @param code
+ * The generated code.
+ */
+ public void codeGenerated(String code);
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientStatusListener.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientStatusListener.java
new file mode 100644
index 00000000..91f8a6f5
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/RecorderClientStatusListener.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client;
+
+/**
+ * An interface for objects interested in connection status between recorder
+ * server and client.
+ */
+public interface RecorderClientStatusListener {
+
+ /**
+ * Called when a connection between server and client has started.
+ */
+ public void connectionStarted();
+
+ /**
+ * Called when a connection between server and client has ended.
+ */
+ public void connectionEnded();
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/SWTBotRecorderClientPlugin.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/SWTBotRecorderClientPlugin.java
new file mode 100644
index 00000000..76391d58
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/SWTBotRecorderClientPlugin.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class SWTBotRecorderClientPlugin extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.swtbot.generator.client"; //$NON-NLS-1$
+
+ // The shared instance
+ private static SWTBotRecorderClientPlugin plugin;
+
+ /**
+ * The constructor
+ */
+ public SWTBotRecorderClientPlugin() {
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.
+ * BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.
+ * BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ Recorder.INSTANCE.interruptRecorderClient();
+
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static SWTBotRecorderClientPlugin getDefault() {
+ return plugin;
+ }
+
+ /**
+ * Returns an image descriptor for the image file at the given plug-in
+ * relative path
+ *
+ * @param path
+ * the path
+ * @return the image descriptor
+ */
+ public static ImageDescriptor getImageDescriptor(String path) {
+ return imageDescriptorFromPlugin(PLUGIN_ID, path);
+ }
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/commands/ImportClientCodeHandler.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/commands/ImportClientCodeHandler.java
new file mode 100644
index 00000000..8787b028
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/commands/ImportClientCodeHandler.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client.commands;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swtbot.generator.client.Recorder;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.texteditor.AbstractTextEditor;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.ITextEditor;
+
+/**
+ * ImportClientViewCodeHandler moves generated code to the cursor position of
+ * the active editor and then clears the generated code buffer.
+ */
+public class ImportClientCodeHandler extends AbstractHandler {
+
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ addTextToEditorAtCursor(Recorder.INSTANCE.getDocumentText());
+
+ Recorder.INSTANCE.clearDocument();
+
+ return null;
+ }
+
+ /**
+ * Adds a string to the currently selected location within a text editor.
+ *
+ * @param text
+ * The text to insert.
+ */
+ private void addTextToEditorAtCursor(String text) throws ExecutionException {
+ IWorkbenchWindow activeWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ IWorkbenchPage page = activeWindow.getActivePage();
+ IEditorPart editorPart = page.getActiveEditor();
+
+ if (editorPart instanceof AbstractTextEditor == false) {
+ throw new ExecutionException("Code can only be inserted within a text editor");
+ }
+
+ ITextEditor editor = (ITextEditor) editorPart;
+ IDocumentProvider documentProvider = editor.getDocumentProvider();
+ IDocument document = documentProvider.getDocument(editor.getEditorInput());
+
+ ISelection selection = editor.getSelectionProvider().getSelection();
+ if (selection instanceof TextSelection == false) {
+ throw new ExecutionException("Could not find text selection");
+ }
+
+ int offset = ((TextSelection) selection).getOffset();
+ try {
+ document.replace(offset, 0, text);
+ } catch (BadLocationException e) {
+ throw new ExecutionException("Could not insert code: " + e.getMessage());
+ }
+ }
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLaunchConfiguration.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLaunchConfiguration.java
new file mode 100644
index 00000000..05ded2e7
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLaunchConfiguration.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client.launcher;
+
+import javax.print.attribute.standard.Severity;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.pde.launching.EclipseApplicationLaunchConfiguration;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swtbot.generator.client.Recorder;
+import org.eclipse.swtbot.generator.client.SWTBotRecorderClientPlugin;
+import org.eclipse.swtbot.generator.client.views.RecorderClientView;
+import org.eclipse.swtbot.generator.server.StartupRecorderServer;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * Launch configuration for starting a new Eclipse application with the SWTBot
+ * recorder running in the background as a server.
+ */
+public class RecorderServerLaunchConfiguration extends EclipseApplicationLaunchConfiguration {
+
+ /**
+ * Launches an Eclipse application and then starts connecting with the
+ * client recorder.
+ */
+ @Override
+ public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)
+ throws CoreException {
+
+ super.launch(configuration, mode, launch, monitor);
+
+ int port = getServerPort(configuration);
+
+ if (port == -1) {
+ Status status = new Status(Severity.ERROR.getValue(), SWTBotRecorderClientPlugin.PLUGIN_ID,
+ "Could not find a port number in the launch arguments");
+ throw new CoreException(status);
+ }
+
+ startClientRecorder(port);
+ }
+
+ /**
+ * Returns the port that the server was launched with.
+ *
+ * @param configuration
+ * The launch configuration used when launching the Eclipse
+ * application.
+ * @return The port number or '-1' if port could not be parsed correctly.
+ * @throws CoreException
+ * If attribute from configuration cannot be parsed.
+ */
+ private int getServerPort(ILaunchConfiguration configuration) throws CoreException {
+ String launchArguments = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, "");
+ String[] vmArguments = launchArguments.split(" ");
+ for (int i = 0; i < vmArguments.length; i++) {
+ String vmArgument = vmArguments[i];
+ if (vmArgument.contains(StartupRecorderServer.ENABLED_WITH_PORT)) {
+ String[] portArgumentKeyValue = vmArgument.split("=");
+ if (portArgumentKeyValue.length == 2) {
+ return Integer.parseInt(portArgumentKeyValue[1]);
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Starts the recorder client on a certain port asynchronously on UI thread.
+ *
+ * @param port
+ * The port to start the recorder client on.
+ */
+ private void startClientRecorder(final int port) {
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ RecorderClientView view = getRecorderClientView();
+ if (view == null || !(view instanceof RecorderClientView)) {
+ // Cannot find RecorderClientView, try to open it.
+ try {
+ openRecorderClientView();
+ view = getRecorderClientView();
+ } catch (PartInitException e) {
+ throw new RuntimeException("Could not open RecorderClientView: " + e.getMessage());
+ }
+ }
+
+ Recorder.INSTANCE.startRecorderClient(port);
+ view.updateUI();
+ }
+ });
+ }
+
+ /**
+ * Finds the RecorderClientView. Should be called on the UI thread.
+ *
+ * @return The RecorderClientView
+ */
+ private RecorderClientView getRecorderClientView() {
+ return (RecorderClientView) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
+ .findView(RecorderClientView.ID);
+ }
+
+ /**
+ * Opens the RecorderClientView. Should be called on the UI thread.
+ *
+ * @throws PartInitException
+ * If the view could not be opened.
+ */
+ private void openRecorderClientView() throws PartInitException {
+ PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(RecorderClientView.ID);
+ }
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLauncherTabGroup.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLauncherTabGroup.java
new file mode 100644
index 00000000..e68feaa1
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/launcher/RecorderServerLauncherTabGroup.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client.launcher;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.pde.ui.launcher.EclipseLauncherTabGroup;
+import org.eclipse.swtbot.generator.server.StartupRecorderServer;
+
+/**
+ * Appends <code>StartupRecorderServer.ENABLED_WITH_PORT</code> to the VM
+ * arguments.
+ */
+public class RecorderServerLauncherTabGroup extends EclipseLauncherTabGroup {
+
+ @Override
+ public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
+ super.setDefaults(configuration);
+
+ try {
+ String launchArguments = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS,
+ "");
+ String launchArgument = "\n-D" + StartupRecorderServer.ENABLED_WITH_PORT + "=" + 8000;
+ configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS,
+ launchArguments + " " + launchArgument);
+
+ configuration.doSave();
+ } catch (CoreException e1) {
+ e1.printStackTrace();
+ }
+ }
+}
diff --git a/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/views/RecorderClientView.java b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/views/RecorderClientView.java
new file mode 100644
index 00000000..30284fbf
--- /dev/null
+++ b/org.eclipse.swtbot.generator.client/src/org/eclipse/swtbot/generator/client/views/RecorderClientView.java
@@ -0,0 +1,477 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.client.views;
+
+import org.eclipse.jdt.core.ElementChangedEvent;
+import org.eclipse.jdt.core.IElementChangedListener;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
+import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.jface.text.source.VerticalRuler;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.VerifyEvent;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swtbot.generator.client.Recorder;
+import org.eclipse.swtbot.generator.client.Recorder.ConnectionState;
+import org.eclipse.swtbot.generator.client.RecorderClientStatusListener;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.ViewPart;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+
+/**
+ * RecorderClientView shows SWTBot recorder output as it is received from a
+ * RecorderServer.
+ */
+public class RecorderClientView extends ViewPart implements RecorderClientStatusListener, IElementChangedListener {
+
+ /**
+ * The ID of the view as specified by the extension.
+ */
+ public static final String ID = "org.eclipse.swtbot.generator.client.view.recorder.client";
+
+ private static int DEFAULT_PORT_NUMBER = 8000;
+
+ private static String IS_CONNECTED_LABEL = "Connected";
+ private static String TRYING_CONNECT_LABEL = "Connecting...";
+ private static String NOT_CONNECTED_LABEL = "No connection";
+
+ private static String CONNECT_LABEL = " Connect ";
+ private static String CANCEL_CONNECT_LABEL = "Cancel";
+ private static String DISCONNECT_LABEL = "Disconnect";
+
+ private static String ADD_TO_METHOD_TOGGLE_LABEL = "Add to method: ";
+
+ private static String START_RECORDING_LABEL = "Record";
+ private static String STOP_RECORDING_LABEL = "Stop recording";
+
+ private Button connectButton;
+ private Text portText;
+ private Label statusLabel;
+
+ private ComboViewer availableMethodsDropDown;
+ private Button addToMethodToggle;
+ private Button refreshAvailableMethodsButton;
+
+ private Button recordingButton;
+ private SourceViewer viewer;
+
+ /**
+ * Initialize the Recorder singleton. If it already is initialized, reset
+ * it.
+ */
+ public RecorderClientView() {
+ if (Recorder.INSTANCE.isInitialized() == false) {
+ Recorder.INSTANCE.initialize();
+ } else {
+ Recorder.INSTANCE.reset();
+ }
+
+ Recorder.INSTANCE.addStatusListener(this);
+ }
+
+ @Override
+ public void dispose() {
+ Recorder.INSTANCE.removeStatusListener(this);
+ super.dispose();
+ }
+
+ /**
+ * Updates the UI state for any connection state change.
+ */
+ public void updateUI() {
+ updateUIForConnectionState(Recorder.INSTANCE.getConnectionState());
+ portText.setText(String.valueOf(Recorder.INSTANCE.getPort()));
+ }
+
+ /**
+ * This is a callback that will allow us to create the viewer and initialize
+ * it.
+ */
+ public void createPartControl(Composite parent) {
+ Group group = new Group(parent, SWT.NONE);
+ group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ group.setLayout(new GridLayout(1, false));
+
+ Composite launchContainer = new Composite(group, SWT.NONE);
+ launchContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ launchContainer.setLayout(new GridLayout(2, false));
+
+ connectButton = new Button(launchContainer, SWT.PUSH);
+ connectButton.setText(CONNECT_LABEL);
+ connectButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ connectButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (Recorder.INSTANCE.getConnectionState() == ConnectionState.CONNECTED) {
+ Recorder.INSTANCE.interruptRecorderClient();
+ } else if (Recorder.INSTANCE.getConnectionState() == ConnectionState.CONNECTING) {
+ Recorder.INSTANCE.interruptRecorderClient();
+ } else {
+ Recorder.INSTANCE.startRecorderClient(getPort());
+ }
+ updateUIForConnectionState(Recorder.INSTANCE.getConnectionState());
+ }
+ });
+
+ Composite portLaunchContainer = new Composite(launchContainer, SWT.NONE);
+ portLaunchContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ portLaunchContainer.setLayout(new GridLayout(2, false));
+
+ Label portLabel = new Label(portLaunchContainer, SWT.NONE);
+ portLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ portLabel.setText("port:");
+
+ portText = new Text(portLaunchContainer, SWT.RIGHT);
+ portText.setText(String.valueOf(DEFAULT_PORT_NUMBER));
+ portText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ portText.addVerifyListener(new VerifyListener() {
+ @Override
+ public void verifyText(VerifyEvent e) {
+ String port = portText.getText() + e.text;
+
+ try {
+ Integer.parseInt(port);
+ } catch (NumberFormatException e2) {
+ if (port.length() > 0) {
+ e.doit = false;
+ }
+ }
+ }
+ });
+
+ statusLabel = new Label(group, SWT.SHADOW_IN);
+ statusLabel.setText("No connection");
+
+ Label horizontalSeparator = new Label(group, SWT.SEPARATOR | SWT.HORIZONTAL);
+ horizontalSeparator.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+
+ Composite methodSelectionContainer = new Composite(group, SWT.NONE);
+ methodSelectionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ methodSelectionContainer.setLayout(new GridLayout(3, false));
+
+ addToMethodToggle = new Button(methodSelectionContainer, SWT.CHECK);
+ addToMethodToggle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ addToMethodToggle.setText(ADD_TO_METHOD_TOGGLE_LABEL);
+ addToMethodToggle.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (Recorder.INSTANCE.isInsertingDirectlyInEditor()) {
+ Recorder.INSTANCE.setInsertingDirectlyInEditor(false);
+ availableMethodsDropDown.getControl().setEnabled(false);
+ refreshAvailableMethodsButton.setEnabled(false);
+ } else {
+ Recorder.INSTANCE.setInsertingDirectlyInEditor(true);
+ availableMethodsDropDown.getControl().setEnabled(true);
+ refreshAvailableMethodsButton.setEnabled(true);
+
+ // If the list is empty we refresh.
+ if (availableMethodsDropDown.getInput() == null
+ || ((IMethod[]) availableMethodsDropDown.getInput()).length == 0) {
+ updateMethodSelectionDropDown();
+ }
+ }
+ }
+ });
+ addToMethodToggle.setEnabled(false);
+
+ availableMethodsDropDown = new ComboViewer(methodSelectionContainer);
+ availableMethodsDropDown.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ availableMethodsDropDown.getControl().setEnabled(false);
+ availableMethodsDropDown.setContentProvider(ArrayContentProvider.getInstance());
+ availableMethodsDropDown.setLabelProvider(new LabelProvider() {
+ @Override
+ public String getText(Object element) {
+ IMethod method = (IMethod) element;
+
+ String parameterNamesString = "";
+ try {
+ String[] parameterNames = method.getParameterNames();
+ for (int i = 0; i < parameterNames.length; i++) {
+ if (i == 0) {
+ parameterNamesString += parameterNames[i];
+ } else {
+ parameterNamesString += ", " + parameterNames[i];
+ }
+ }
+
+ } catch (JavaModelException e) {
+ e.printStackTrace();
+ }
+
+ return method.getElementName() + "(" + parameterNamesString + ")";
+ }
+ });
+ availableMethodsDropDown.addSelectionChangedListener(new ISelectionChangedListener() {
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) event.getSelection();
+ Recorder.INSTANCE.setSelectedMethod((IMethod) selection.getFirstElement());
+ }
+ });
+
+ refreshAvailableMethodsButton = new Button(methodSelectionContainer, SWT.PUSH);
+ refreshAvailableMethodsButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ refreshAvailableMethodsButton.setEnabled(false);
+ refreshAvailableMethodsButton.setImage(AbstractUIPlugin
+ .imageDescriptorFromPlugin("org.eclipse.swtbot.generator.client", "icons/refresh.gif").createImage());
+ refreshAvailableMethodsButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ updateMethodSelectionDropDown();
+ }
+ });
+
+ recordingButton = new Button(group, SWT.PUSH);
+ recordingButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ recordingButton.setText(START_RECORDING_LABEL);
+ recordingButton.setEnabled(false);
+ recordingButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (Recorder.INSTANCE.isRecording()) {
+ recordingButton.setText(START_RECORDING_LABEL);
+ Recorder.INSTANCE.setRecording(false);
+ } else {
+ recordingButton.setText(STOP_RECORDING_LABEL);
+ Recorder.INSTANCE.setRecording(true);
+ }
+ }
+ });
+
+ viewer = new SourceViewer(group, new VerticalRuler(0), SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
+ viewer.setDocument(Recorder.INSTANCE.getDocument());
+ viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ getSite().setSelectionProvider(viewer);
+
+ JavaCore.addElementChangedListener(this);
+ updateUIForConnectionState(Recorder.INSTANCE.getConnectionState());
+ updateMethodSelectionDropDown();
+ }
+
+ /**
+ * Passing the focus request to the viewer's control.
+ */
+ public void setFocus() {
+ viewer.getControl().setFocus();
+ }
+
+ /**
+ * @return The currently chosen port.
+ */
+ private int getPort() {
+ return Integer.parseInt(portText.getText());
+ }
+
+ @Override
+ public void connectionStarted() {
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ updateMethodSelectionDropDown();
+ updateUIForConnectionState(ConnectionState.CONNECTED);
+ }
+ });
+ }
+
+ @Override
+ public void connectionEnded() {
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ updateUIForConnectionState(ConnectionState.DISCONNECTED);
+ }
+ });
+ }
+
+ /**
+ * Updates all UI elements to match a new connection state.
+ *
+ * @param state
+ * The new connection state.
+ */
+ private void updateUIForConnectionState(ConnectionState state) {
+ switch (state) {
+ case CONNECTED:
+ statusLabel.setText(IS_CONNECTED_LABEL);
+ recordingButton.setText(START_RECORDING_LABEL);
+ connectButton.setText(DISCONNECT_LABEL);
+ addToMethodToggle.setSelection(false);
+
+ recordingButton.setEnabled(true);
+ connectButton.setEnabled(true);
+ portText.setEnabled(false);
+ addToMethodToggle.setEnabled(true);
+ availableMethodsDropDown.getControl().setEnabled(false);
+ refreshAvailableMethodsButton.setEnabled(false);
+
+ break;
+ case CONNECTING:
+ statusLabel.setText(TRYING_CONNECT_LABEL);
+ recordingButton.setText(START_RECORDING_LABEL);
+ connectButton.setText(CANCEL_CONNECT_LABEL);
+ addToMethodToggle.setSelection(false);
+
+ recordingButton.setEnabled(false);
+ connectButton.setEnabled(true);
+ portText.setEnabled(false);
+ addToMethodToggle.setEnabled(false);
+ availableMethodsDropDown.getControl().setEnabled(false);
+ refreshAvailableMethodsButton.setEnabled(false);
+
+ break;
+ case DISCONNECTED:
+ statusLabel.setText(NOT_CONNECTED_LABEL);
+ recordingButton.setText(START_RECORDING_LABEL);
+ connectButton.setText(CONNECT_LABEL);
+ addToMethodToggle.setSelection(false);
+
+ recordingButton.setEnabled(false);
+ connectButton.setEnabled(true);
+ portText.setEnabled(true);
+ addToMethodToggle.setEnabled(false);
+ availableMethodsDropDown.getControl().setEnabled(false);
+ refreshAvailableMethodsButton.setEnabled(false);
+
+ break;
+ }
+ }
+
+ /**
+ * Updates the method selection drop down if the change event is likely to
+ * affect the currently available methods in the method selection drop down.
+ */
+ @Override
+ public void elementChanged(ElementChangedEvent event) {
+ if (Recorder.INSTANCE.shouldUpdateMethodSelectionViewer(event)) {
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ updateMethodSelectionDropDown();
+ }
+ });
+ }
+ }
+
+ /**
+ * Returns the active editor or null if it fails.
+ *
+ * @return The active editor.
+ */
+ private IEditorPart getActiveEditor() {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ if (workbench == null) {
+ return null;
+ }
+ IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
+ if (workbenchWindow == null) {
+ return null;
+ }
+ IWorkbenchPage workbenchPage = workbenchWindow.getActivePage();
+ if (workbenchPage == null) {
+ return null;
+ }
+ return workbenchPage.getActiveEditor();
+ }
+
+ /**
+ * Updates the method selection drop down.
+ */
+ @SuppressWarnings("restriction")
+ private void updateMethodSelectionDropDown() {
+ IEditorPart activeEditor = getActiveEditor();
+
+ if (activeEditor == null) {
+ Recorder.INSTANCE.setSelectedMethod(null);
+ Recorder.INSTANCE.setSelectedMethodDocument(null);
+
+ availableMethodsDropDown.setInput(null);
+ } else if (activeEditor instanceof JavaEditor) {
+ ITypeRoot root = EditorUtility.getEditorInputJavaElement(activeEditor, false);
+ IType type = root.findPrimaryType();
+ final IMethod[] methods;
+ try {
+ methods = type.getMethods();
+
+ IMethod methodToSelect = null;
+ if (Recorder.INSTANCE.getSelectedMethod() != null) {
+ methodToSelect = findSimilarMethod(Recorder.INSTANCE.getSelectedMethod(), methods);
+ }
+ IDocument currentActiveDocument = ((JavaEditor) activeEditor).getDocumentProvider()
+ .getDocument(activeEditor.getEditorInput());
+
+ Recorder.INSTANCE.setSelectedMethod(methodToSelect);
+ Recorder.INSTANCE.setSelectedMethodDocument(currentActiveDocument);
+ availableMethodsDropDown.setInput(methods);
+
+ // If methodToSelect is non-null we want to select it to
+ // maintain the user's previous selection.
+ if (methodToSelect != null) {
+ ISelection selection = new StructuredSelection(methodToSelect);
+ availableMethodsDropDown.setSelection(selection);
+ }
+ } catch (JavaModelException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Tries to find a similar method in an array of IMethod instances using
+ * <code>IMethod.isSimilar</code>
+ *
+ * @param method
+ * The method to search for.
+ * @param methods
+ * The array to search through.
+ * @return The first similar method found or null if no similar method was
+ * found.
+ */
+ private IMethod findSimilarMethod(IMethod method, IMethod[] methods) {
+ IMethod similarMethod = null;
+ for (IMethod candidate : methods) {
+ if (method.isSimilar(candidate)) {
+ similarMethod = candidate;
+ break;
+ }
+ }
+
+ return similarMethod;
+ }
+}
diff --git a/org.eclipse.swtbot.generator.feature/feature.xml b/org.eclipse.swtbot.generator.feature/feature.xml
index 9ad7569f..63ac17cb 100644
--- a/org.eclipse.swtbot.generator.feature/feature.xml
+++ b/org.eclipse.swtbot.generator.feature/feature.xml
@@ -48,4 +48,11 @@
version="0.0.0"
unpack="false"/>
+ <plugin
+ id="org.eclipse.swtbot.generator.client"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
</feature>
diff --git a/org.eclipse.swtbot.generator/META-INF/MANIFEST.MF b/org.eclipse.swtbot.generator/META-INF/MANIFEST.MF
index b5738dcc..88dda16a 100644
--- a/org.eclipse.swtbot.generator/META-INF/MANIFEST.MF
+++ b/org.eclipse.swtbot.generator/META-INF/MANIFEST.MF
@@ -9,6 +9,7 @@ Bundle-ClassPath: .
Export-Package: org.eclipse.swtbot.generator,
org.eclipse.swtbot.generator.framework,
org.eclipse.swtbot.generator.listener,
+ org.eclipse.swtbot.generator.server,
org.eclipse.swtbot.generator.ui
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
diff --git a/org.eclipse.swtbot.generator/plugin.xml b/org.eclipse.swtbot.generator/plugin.xml
index 6092b795..873206b0 100644
--- a/org.eclipse.swtbot.generator/plugin.xml
+++ b/org.eclipse.swtbot.generator/plugin.xml
@@ -9,6 +9,9 @@
<startup
class="org.eclipse.swtbot.generator.ui.StartupRecorder">
</startup>
+ <startup
+ class="org.eclipse.swtbot.generator.server.StartupRecorderServer">
+ </startup>
</extension>
<extension
point="org.eclipse.swtbot.generator.botGeneratorSupport">
diff --git a/org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/RecorderServer.java b/org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/RecorderServer.java
new file mode 100644
index 00000000..c1f9d93b
--- /dev/null
+++ b/org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/RecorderServer.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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: Isaac Arvestad (Ericsson) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.server;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.eclipse.swtbot.generator.framework.GenerationRule;
+import org.eclipse.swtbot.generator.ui.BotGeneratorEventDispatcher;
+import org.eclipse.swtbot.generator.ui.BotGeneratorEventDispatcher.CodeGenerationListener;
+
+/**
+ * RecorderServer is a server which prints out SWTBot recorder events to all
+ * connected clients.
+ */
+public class RecorderServer {
+
+ private BotGeneratorEventDispatcher recorder;
+
+ /**
+ * Creates a new RecorderServer with a SWTBot recorder.
+ *
+ * @param recorder
+ * The recorder.
+ */
+ public RecorderServer(BotGeneratorEventDispatcher recorder) {
+ this.recorder = recorder;
+ }
+
+ /**
+ * Starts the server.
+ *
+ * @param port
+ * The port the server listens for connections on.
+ */
+ public void start(int port) {
+ recorder.setRecording(true);
+
+ ConnectionListener connectionListener = new ConnectionListener(port);
+ connectionListener.start();
+ }
+
+ /**
+ * ConnectionListener listens for collections on a separate thread.
+ */
+ private class ConnectionListener extends Thread {
+
+ private int port;
+
+ /**
+ * Creates a ConnectionListener with a certain port.
+ *
+ * @param port
+ * The port to listen on.
+ */
+ public ConnectionListener(int port) {
+ this.port = port;
+ }
+
+ @Override
+ public void run() {
+ try {
+ ServerSocket serverSocket = new ServerSocket(port);
+
+ while (!interrupted()) {
+ ConnectionHandler connectionHandler = new ConnectionHandler(serverSocket.accept());
+ recorder.addListener(connectionHandler);
+ }
+
+ serverSocket.close();
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Could not start server - There was a problem starting the recorder server. Try restarting using a different port number.");
+ }
+ }
+ }
+
+ /**
+ * ConnectionHandler handles a connection which ConnectionListener has
+ * accepted.
+ */
+ private class ConnectionHandler implements CodeGenerationListener {
+
+ private PrintWriter output;
+
+ /**
+ * Creates a new ConnectionHandler.
+ *
+ * @param socket
+ * The connecting socket.
+ */
+ public ConnectionHandler(Socket socket) {
+ try {
+ output = new PrintWriter(socket.getOutputStream(), true);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Sends any generated code to the client.
+ *
+ * @param code
+ * The generated code.
+ */
+ public void handleCodeGenerated(GenerationRule code) {
+ for (String text : code.getActions()) {
+ output.println(text);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/StartupRecorderServer.java b/org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/StartupRecorderServer.java
new file mode 100644
index 00000000..601bb214
--- /dev/null
+++ b/org.eclipse.swtbot.generator/src/org/eclipse/swtbot/generator/server/StartupRecorderServer.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ * 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:
+ * Isaac Arvestad (Ericsson) - initial API and implementation (Based on: org.eclipse.swtbot.generator.ui.StartupRecorder)
+ *******************************************************************************/
+package org.eclipse.swtbot.generator.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swtbot.generator.framework.Generator;
+import org.eclipse.swtbot.generator.listener.WorkbenchListener;
+import org.eclipse.swtbot.generator.ui.BotGeneratorEventDispatcher;
+import org.eclipse.swtbot.generator.ui.GeneratorExtensionPointManager;
+import org.eclipse.ui.IStartup;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * StartupRecorderServer handles starting a SWTBot recorder and a
+ * RecorderServer.
+ */
+public class StartupRecorderServer implements IStartup {
+
+ public static final String ENABLED_WITH_PORT = "org.eclipse.swtbot.generator.server.enable";
+
+ private static final int[] monitoredEvents = new int[] { SWT.Activate, SWT.Close, SWT.Selection, SWT.Expand,
+ SWT.Modify, SWT.MouseDown, SWT.MouseDoubleClick, SWT.KeyDown, SWT.Close };
+
+ /**
+ * StartRecorderServerRunnable starts the SWTBot recorder and the
+ * RecorderServer on a new thread.
+ */
+ private static final class StartRecorderServerRunnable implements Runnable {
+ private final Display display;
+ private int port;
+
+ /**
+ * Creates a new StartRecorderServerRunnable.
+ *
+ * @param display
+ * The display used.
+ * @param port
+ * The port used for the RecorderServer.
+ */
+ public StartRecorderServerRunnable(Display display, int port) {
+ this.display = display;
+ this.port = port;
+ }
+
+ public void run() {
+ final List<Generator> availableGenerators = GeneratorExtensionPointManager.loadGenerators();
+ Generator generator = availableGenerators.get(0);
+ final BotGeneratorEventDispatcher dispatcher = new BotGeneratorEventDispatcher();
+ dispatcher.setGenerator(generator);
+
+ List<Shell> ignoreList = new ArrayList<Shell>();
+ dispatcher.ignoreShells(ignoreList);
+
+ for (int monitoredEvent : monitoredEvents) {
+ this.display.addFilter(monitoredEvent, dispatcher);
+ }
+ if (PlatformUI.isWorkbenchRunning()) {
+ IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ if (page != null) {
+ page.addPartListener(new WorkbenchListener(dispatcher));
+ }
+ }
+
+ RecorderServer recorderServer = new RecorderServer(dispatcher);
+ recorderServer.start(port);
+ }
+ }
+
+ /**
+ * Start the SWTBot recorder and the RecorderServer on a new thread.
+ *
+ * @param port
+ * The port to use for the RecorderServer.
+ */
+ public void start(int port) {
+ final Display display = Display.getDefault();
+ StartRecorderServerRunnable serverRunnable = new StartRecorderServerRunnable(display, port);
+
+ display.syncExec(serverRunnable);
+ }
+
+ /**
+ * Starts recorder server immediately once Eclipse has launched if the
+ * argument ENABLED_WITH_PORT can be found and it is equal to an integer
+ * which is used as the port number.
+ */
+ public void earlyStartup() {
+ if (System.getProperty(ENABLED_WITH_PORT) == null) {
+ return;
+ }
+ try {
+ int port = Integer.parseInt(System.getProperty(ENABLED_WITH_PORT));
+ start(port);
+ } catch (NumberFormatException e) {
+ System.out.println("SWTBot recorder server launch aborted. " + ENABLED_WITH_PORT
+ + " argument must be assigned an integer as a port number");
+ return;
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 0889cb7b..488a2641 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,6 +50,7 @@ Authors:
<module>org.eclipse.swtbot.eclipse.ui</module>
<module>org.eclipse.swtbot.forms.finder</module>
<module>org.eclipse.swtbot.generator</module>
+ <module>org.eclipse.swtbot.generator.client</module>
<module>org.eclipse.swtbot.generator.rules.workbench</module>
<module>org.eclipse.swtbot.generator.jdt</module>
<module>org.eclipse.swtbot.generator.ui</module>

Back to the top