Showing posts with label EASE. Show all posts
Showing posts with label EASE. Show all posts

Monday, July 8, 2019

Building UIs with EASE

You probably used EASE before to automate daily tasks in your IDE or to augment toolbars and menus with custom functionality. But so far scripts could not be used to build UIs. This changed with the recent contribution of the UI Builder module.

What it is all about
The UI Builder module allows to create views and dialogs by pure script code in your IDE. It is great for custom views that developers do not want to put into their products, for rapid prototyping and even for mocking.

The aim of EASE is to hide layout complexity as much as possible and provide a simple, yet flexible way to implement typical UI tasks.

Example 1: Input Form
We will start by creating a simple input form for address data.

loadModule("/System/UI Builder");
createView("Create Contact");

setColumnCount(2);
createLabel("First Name:");
var txtFirstName = createText();
createLabel("Last Name:");
var txtLastName = createText();
This snippet will create a dynamic view as shown below:
The renderer used will apply a GridLayout. By setting the columnCount to 2 we may simply add our elements without providing any additional layout information - a simple way to create basic layouts.

If needed EASE provides more control by providing layout information when creating components:

createView("Create Contact");
createLabel("First Name:", "1/1 >x");
var txtFirstName = createText("2-4/1 o!");
createLabel("Last Name:", "1/2 >x");
var txtLastName = createText("2-4/2 o!");
Creates the same view as above, but now with detailed layout information.
As an example "1/2 >x" means: first column, second row, horizontal align right, vertical align middle. A full documentation on the syntax is provided in the module documentation (Hover over the UI Builder module in the Modules Explorer view).

Now lets create a combo viewer to select a country for the address:
cmbCountry = createComboViewer(["Austria", "Germany", "India", "USA"])
Simple, isn't it?

So far we did not need to react on any of our UI elements. Next step is to create a button which needs some kind of callback action:
createButton("Save 1", 'print("you hit the save button")')
createButton("Save 2", saveAddress)

function saveAddress() {
 print("This is the save method");
}
The first version of a button adds the callback code as string argument. When the button gets pressed, the callback code will be executed. You may call any script code that the engine is capable of interpreting.

The second version looks a bit cleaner, as it defines a function saveAddress() which is called on a button click. Note that we provide a function reference to createButton().

View the full example of this script on our script repository. In addition to some more layouting it also contains a working implementation of the save action to store addresses as JSON data files.

Interacting with SWT controls

The saveAddress() method needs to read data from the input fields of our form. This could be done using
var firstName = txtFirstName.getText();
Unfortunately SWT Controls can only be queried in the UI thread, while the script engine is executed in its own thread. To move code execution to the UI thread, the UI module provides a function executeUI(). By default this functionality is disabled as a bad script executed in the UI thread might stall your Eclipse IDE. To enable it you need to set a checkbox in Preferences/Scripting. The full call then looks like this:
loadModule("/System/UI")
var firstName = executeUI('txtFirstName.getText();');

Example 2: A viewer for our phone numbers

Now that we are able to create some sample data, we also need a viewer for our phone numbers. Say we are able to load all our addresses into an array, the only thing we need is a table viewer to visualize our entries. Following 2 lines will do the job:
var addresses = readAddresses();
var tableViewer = createTableViewer(addresses)
Where readAddresses() collects our *.address files and stores them into an array.

So the viewer works, however we need to define how our columns shall be rendered.
createViewerColumn(tableViewer, "Name", createLabelProvider("getProviderElement().firstName + ' ' + getProviderElement().lastName"))
createViewerColumn(tableViewer, "Phone", createLabelProvider("getProviderElement().phone"))
Whenever a callback needs a viewer element, getProviderElement() holds the actual element.
We are done! 3 lines of code for a TableViewer does not sound too bad, right? Again a full example is available on our script repository. It automatically loads *.address files from your workspace and displays them in the view.

Example 3: A workspace viewer

We had a TableViewer before, now lets try a TreeViewer. As a tree needs structure, we need to provide a callback to calculate child elements from a given parent:
var viewer = createTreeViewer(getWorkspace().getProjects(), getChildren);

function getChildren() {
 if (getProviderElement() instanceof org.eclipse.core.resources.IContainer)
  return getProviderElement().members();
 
 return null;
}
So simple! The full example looks like this:
Example 4: Math function viewer

The last example demonstrates how to add a custom Control to a view.
For plotting we use the Charting module that is shipped with EASE. The source code should be pretty much self explanatory.

Some Tips & Tricks

  • Layouting is dynamic.
    Unlike the Java GridLayout you do not need to fill all cells of your layout. The EASE renderer takes care to automatically fill empty cells with placeholders
  • Elements can be replaced.
    If you use coordinates when creating controls, you may easily replace a given control by another one. This simplifies the process of layouting (eg if you experience with alignments) and even allows a view to dynamically change its components depending on some external data/events
  • Full control.
    While some methods from SWT do not have a corresponding script function, still all SWT calls may be used as the create* methods expose the underlying SWT instances.
  • Layout help.
    To simplify layouting use the showGrid() function. It displays cell borders that help you to see row/column borders.


Thursday, May 12, 2016

EASE release, Community award & EclipseCon France

EASE v0.3.0 release

Today we shipped the brand new v0.3.0 release of EASE, your beloved scripting framework. Most important we improved the installation process for Jython as you do not need to know about external update sites anymore.

Code completion got introduced to help you browse your script objects and to improve coding speed. Completion proposals are provided for module functions, Java classes and even for some parameters (like file paths). In case you run eclipse from a JDK we also provide help popups for java methods.



Then we added a whole bunch of new commands to our modules to simplify your scripts. The Scripting module now provides means to exchange data between multiple script instances, the UI module got new methods to manipulate views and the brand new P2 module allows to install or update packages.

Of course we also had lots of bugfixes, if you are interested you may browse the complete list of changes.

Community Award

While the release might be the most important news for you, the development team is really proud of the Eclipse Community Award for the Most Innovative Project 2016.


It is a real honor that you, the community, rate this little project so high. While there seems to be quite some userbase using EASE, I have little knowledge of the adopters out there. I would love to read who you are and what you are using EASE for. If you got a minute left, please leave a comment to this blog entry.

EclipseCon France

In case you have no idea why this project might be innovative, meet us at EclipseCon France, where there will be several talks on EASE. I will be involved in 2 of them:

On Tuesday afternoon there will be a tutorial session EASE-ily Make the Most of Eclipse with Python, where we will do a lot of scripting (of course in Python). Besides scripts we will also look on how to integrate such scripts into the IDE and how to distribute them successfully.

On Thursday morning I will be giving a talk on the EASE framework itself, showing how to Elevate your IDE with scripts. We will see what scripting is capable of and I will give a sneak preview of the upcoming features for the 0.4 release. I will just say so much: your scripts never integrated so well and easily with your IDE.

While those talks are already worth going to EclipseCon, there is also tons of other interesting topics to investigate, so be there!

Monday, April 25, 2016

A new interpreter for EASE (5): Support for script keywords

EASE scripts registered in the preferences support  a very cool feature: keyword support in script headers. While this does not sound extremely awesome, it allows to bind scripts to the UI and will allow for more fancy stuff in the near future. Today we will add support for keyword detection in registered BeanShell scripts.

Read all tutorials from this series.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 

Step 1: Provide a code parser

Code parser is a big word. Currently all we need to detect in given script code are comments. As there already exists a corresponding base class, all we need to do is to provide a derived class indicating comment tokens:
package org.eclipse.ease.lang.beanshell;

import org.eclipse.ease.AbstractCodeParser;

public class BeanShellCodeParser extends AbstractCodeParser {

 @Override
 protected boolean hasBlockComment() {
  return true;
 }

 @Override
 protected String getBlockCommentEndToken() {
  return "*/";
 }

 @Override
 protected String getBlockCommentStartToken() {
  return "/*";
 }

 @Override
 protected String getLineCommentToken() {
  return "//";
 }
}

Step 2: Register the code parser

Similar to registering the code factory, we also need to register the code parser. Open the plugin.xml and select the scriptType extension for BeanShell. There register the code parser from above. Now EASE is able to parse script headers for keywords and interprets them accordingly.

Wednesday, December 16, 2015

A new interpreter for EASE (4): Provide modules support

EASE is shipped with modules: libraries written in Java, made available for script interpreters. The intention of such modules is to write them once and use them in all available script languages.

Read all tutorials from this series.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 


Introduction

Typically modules are plain java classes. We could easily create an instance in BeanShell and then call instance.method(). While this is perfectly ok for the average programmer, some of our user might expect a more simple, functional interface. What module loading does is:
  • create an instance of the module
  • inject the instance into the interpreter
  • create dynamic script code to wrap method calls
  • execute the created code
For BeanShell we would create code like
method(param1, param2) { 

result = __MOD_module_instance.method(param1, param2);

return result;

}
After loading this fragment we could access method() without the knowledge of modules, instances and object orientation at all.

Step 1: The code factory

To dynamically create code EASE needs an ICodeFactory implementation. To start you may look at existing implementations, basically we just need to build strings containing script code.
package org.eclipse.ease.lang.beanshell;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import org.eclipse.ease.Logger;
import org.eclipse.ease.modules.AbstractCodeFactory;
import org.eclipse.ease.modules.IEnvironment;
import org.eclipse.ease.modules.IScriptFunctionModifier;
import org.eclipse.ease.modules.ModuleHelper;

public class BeanShellCodeFactory extends AbstractCodeFactory {

 public static final List<String> RESERVED_KEYWORDS = Arrays.asList("abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package",
   "synchronized", "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", "else", "import", "public",
   "throws", "case", "enum", "instanceof", "return", "transient", "catch", "extends", "int", "short", "try", "char", "final", "interface", "static",
   "void", "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while");

 private static boolean isValidMethodName(final String methodName) {
  return BeanShellHelper.isSaveName(methodName) && !RESERVED_KEYWORDS.contains(methodName);
 }

 @Override
 public String getSaveVariableName(final String variableName) {
  return BeanShellHelper.getSaveName(variableName);
 }

 @Override
 public String createFunctionWrapper(final IEnvironment environment, final String moduleVariable, final Method method) {

  final StringBuilder scriptCode = new StringBuilder();

  // parse parameters
  final List<Parameter> parameters = ModuleHelper.getParameters(method);

  // build parameter string
  final StringBuilder parameterList = new StringBuilder();
  for (final Parameter parameter : parameters)
   parameterList.append(", ").append(parameter.getName());

  if (parameterList.length() > 2)
   parameterList.delete(0, 2);

  final StringBuilder body = new StringBuilder();

  // insert hooked pre execution code
  body.append(getPreExecutionCode(environment, method));

  // insert deprecation warnings
  if (ModuleHelper.isDeprecated(method))
   body.append("\tprintError('" + method.getName() + "() is deprecated. Consider updating your code.');\n");

  // insert method call
  body.append("\t ");
  if (!method.getReturnType().equals(Void.TYPE))
   body.append(IScriptFunctionModifier.RESULT_NAME).append(" = ");

  body.append(moduleVariable).append('.').append(method.getName()).append('(');
  body.append(parameterList);
  body.append(");\n");

  // insert hooked post execution code
  body.append(getPostExecutionCode(environment, method));

  // insert return statement
  if (!method.getReturnType().equals(Void.TYPE))
   body.append("\treturn ").append(IScriptFunctionModifier.RESULT_NAME).append(";\n");

  // build function declarations
  for (final String name : getMethodNames(method)) {
   if (!isValidMethodName(name)) {
    Logger.error(IPluginConstants.PLUGIN_ID,
      "The method name \"" + name + "\" from the module \"" + moduleVariable + "\" can not be wrapped because it's name is reserved");

   } else if (!name.isEmpty()) {
    scriptCode.append(name).append("(").append(parameterList).append(") {\n");
    scriptCode.append(body);
    scriptCode.append("}\n");
   }
  }

  return scriptCode.toString();
 }

 @Override
 public String classInstantiation(final Class<?> clazz, final String[] parameters) {
  final StringBuilder code = new StringBuilder();
  code.append("new ").append(clazz.getName()).append("(");

  if (parameters != null) {
   for (final String parameter : parameters)
    code.append(parameter).append(", ");

   if (parameters.length > 0)
    code.delete(code.length() - 2, code.length());
  }

  code.append(")");

  return code.toString();
 }

 @Override
 public String createFinalFieldWrapper(final IEnvironment environment, final String moduleVariable, final Field field) {
  return getSaveVariableName(field.getName()) + " = " + moduleVariable + "." + field.getName() + ";\n";
 }

 @Override
 protected String getNullString() {
  return "null";
 }
}
createFunctionWrapper() is called for each exposed method, createFinalFieldWrapper() is called for each exposed final field. The function wrapper injects preExecutionCode and postExecutionCode. This is interesting for modules implementing IScriptFunctionModifier. If such a module is loaded, it may influence code generation for all other modules. Use cases are creation of log files or unit testing.

Step 2: Register code factory

Now we need to register our code factory, so EASE is able to find it. Open the Extensions tab of org.eclipse.ease.lang.beanshell/plugin.xml. Navigate to the scriptType extension and add the code factory class to the BeanShell type.
Step 3: Bootstrapping

There exists a dedicated Environment module that provides basic commands like loadModule() to load further modules. Lets try to load it in a BeanShell:

run EASE and open a bean shell in the Script Shell View. We will create an instance of EnvironmentModule and advise it to load itself:
new org.eclipse.ease.modules.EnvironmentModule().loadModule("/System/Environment");
If your code factory works as expected, commands like print() and loadModule() should be available.

To automate this task we will add a launch extension to our BeanShell language definition.
Switch to the plugin.xml and add a launchExtension to the org.eclipse.ease.language node.We bind it to the engineID of BeanShell and need to provide an implementation:
public class BootStrapper implements IScriptEngineLaunchExtension {

 @Override
 public void createEngine(final IScriptEngine engine) {
  ICodeFactory codeFactory = ScriptService.getCodeFactory(engine);
  if (codeFactory != null) {
   StringBuilder stringBuilder = new StringBuilder();
   stringBuilder.append(codeFactory.classInstantiation(EnvironmentModule.class, new String[0]));
   stringBuilder.append(".loadModule(\"");
   stringBuilder.append(EnvironmentModule.MODULE_NAME);
   stringBuilder.append("\");\n");

   engine.executeAsync(new Script("Bootloader", stringBuilder.toString()));
  }
 }
}
Of course we could directly use the string we tried manually before, but using the code factory is somewhat nicer. The implementation given above is already available from the BootStrapper class from EASE. If you do want to add more stuff to the bootloader you need to provide your own implementation.

With that in place you should be able to load all available modules into BeanShell and use its functions.

Optional: Loading external jars

We added support to register URLs to the classloader in our previous tutorial. Now as we have modules working we may try this out by calling
loadJar("http://central.maven.org/maven2/com/github/lalyos/jfiglet/0.0.7/jfiglet-0.0.7.jar")
 true
com.github.lalyos.jfiglet.FigletFont.convertOneLine("I      love      scripting");
  ___        _                                             _         _    _               
 |_ _|      | |  ___  __   __  ___        ___   ___  _ __ (_) _ __  | |_ (_) _ __    __ _ 
  | |       | | / _ \ \ \ / / / _ \      / __| / __|| '__|| || '_ \ | __|| || '_ \  / _` |
  | |       | || (_) | \ V / |  __/      \__ \| (__ | |   | || |_) || |_ | || | | || (_| |
 |___|      |_| \___/   \_/   \___|      |___/ \___||_|   |_|| .__/  \__||_||_| |_| \__, |
                                                             |_|                    |___/ 


Tuesday, December 15, 2015

A new interpreter for EASE (3): Loading native classes

In this tutorial we will teach our interpreter to load all kinds of classes from Eclipse and dedicated URLs. So we will have a closer look on the classloader, but don't worry - it will not get too complicated.

Our BeanShell interpreter is already capable of calling java code, so we only need to make sure that its classloader will have access to all desired resources.

Read all tutorials from this series.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 

Step 1: Access java classes from eclipse

Our BeanShell interpreter already accepts calls to JRE classes like
java.lang.System.out.println("Hello world");
However if we try to access eclipse classes like
org.eclipse.jface.resource.JFaceColors
    Sourced file: inline evaluation of: ``org.eclipse.jface.resource.JFaceColors;'' :
    Class or variable not found: org.eclipse.jface.resource.JFaceColors
We get an exception indicating that the class cannot be found. This is a classloader issue as the interpreter uses the bundle classloader from our org.eclipse.ease.lang.beanshell bundle. We could add dependencies to jface to solve the problem, but how would we introduce dependencies to all plug-ins that might be used by a customer?

Fortunately eclipse has a solution for this. We may allow plug-ins to access classes from all exported packages out there without  adding a dedicated dependency by defining
Eclipse-BuddyPolicy: global
in the MANIFEST.MF file. This comes with a penalty on class loading performance but allows to access all available classes.

Add this setting to the manifest of org.eclipse.ease.lang.beanshell. Afterwards switch to the BeanShellEngine and add following line to setupEngine():
fInterpreter.setClassLoader(getClass().getClassLoader());
This line changes the classloader of the BeanShell with the one from the org.eclipse.ease.lang.beanshell bundle.

Now go and try to load some eclipse classes:
org.eclipse.jface.resource.JFaceColors
    Class Identifier: org.eclipse.jface.resource.JFaceColors

Step 2: Load classes from external URLs

EASE allows to register external jar files and make them available to scripting. This mechanism  is provided by a module - a concept we will introduce in the next tutorial. For now we will make all necessary preparations. EASE provides a generic classloader with all necessary functionality. Go to your BeanShellEngine and modify the code as follows:
public class BeanShellEngine extends AbstractScriptEngine {

 private DelegatingJarClassLoader fClassLoader;

 @Override
 protected boolean setupEngine() {
  fInterpreter = new Interpreter();

  fClassLoader = new DelegatingJarClassLoader(getClass().getClassLoader());
  fInterpreter.setClassLoader(fClassLoader);

  [...]
 }

 @Override
 public void registerJar(final URL url) {
  if (fClassLoader != null)
   fClassLoader.registerURL(url);
 }
}
The DelegatingClassLoader will take care of registered URLs. For everything else its parent classloader will be used. We cannot test this currently, so you need to trust me on this until the next tutorial.

Friday, December 11, 2015

A new interpreter for EASE (2): Shell and script integration

Today we will focus on integrating BeanShell into the script shell and to allow to execute .bsh files.

To follow this tutorial you will either need to install EASE into your development IDE or even better create a target platform containing EASE core plugins. I expect that you are familiar with that process.

Read all tutorials from this series.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 

Step 1: A very basic EASE interpreter

Create a new Plug-in Project called org.eclipse.ease.lang.beanshell. Add dependencies to org.eclipse.ease and to our org.beanshell plug-in.

Switch to the Extensions tab and add a new extension point for org.eclipse.ease.language. Create an engine, provide an ID, a nice name and a link to the implementing class.

Instead of implementing the IScriptEngine interface directly you may inherit from AbstractScriptEngine:
package org.eclipse.ease.lang.beanshell;

import org.eclipse.ease.AbstractScriptEngine;
import org.eclipse.ease.Script;

import bsh.Interpreter;

public class BeanShellEngine extends AbstractScriptEngine {

 private Interpreter fInterpreter = null;

 public BeanShellEngine() {
  super("BeanShell");
 }

 @Override
 protected boolean setupEngine() {
  fInterpreter = new Interpreter();

  fInterpreter.setOut(getOutputStream());
  fInterpreter.setErr(getErrorStream());

  return true;
 }

 @Override
 protected boolean teardownEngine() {
  fInterpreter = null;

  return true;
 }

 @Override
 protected Object execute(final Script script, final Object reference, final String fileName, final boolean uiThread) throws Throwable {
  return fInterpreter.eval(script.getCode());
 }
}
Use the QuickFix to Add unimplemented methods.

Launch a new RCP application adding org.eclipse.ease.* and org.beanshell plugins to your run configuration.

Now open the Script Shell view and use the engine selection to switch to BeanShell. Your console name should change to BeanShellScript Shell and you should be able to enter and execute some beanshell commands.

Step 2: Adding launch target support

To launch files with an engine we need to bind our engine to dedicated content types. That means that we first need a content type for BeanShell files. Open the Plug-in Manifest Editor, switch to the Extensions tab and add a new extension for org.eclipse.contenttype.contentTypes. Add a new content-type based on org.eclipse.core.runtime.text and set file-extensions to bsh.

Now create a new extension for org.eclipse.ease.scriptType. Set name to BeanShell and the defaultExtension to bsh.  Afterwards add a binding to our previously created content type.

Finally switch to your engine definition and create a binding to the scriptType.

When launching you may now run .bsh files from your workspace using the EASE launch target (eg by using Run As/EASE Script from the context menu).

Step 3: Add variables support

Getting and setting variables improves the Script Shell experience but is also needed for modules support, which we investigate in a following tutorial.

Open your BeanShellEngine and replace your default methods for variables with these:
 @Override
 protected Object internalGetVariable(final String name) {
  try {
   return fInterpreter.get(name);
  } catch (EvalError e) {
   Logger.error("org.eclipse.ease.lang.beanshell", "Cannot retrieve variable \"" + name + "\"", e);
  }

  return null;
 }

 @Override
 protected Map<String, Object> internalGetVariables() {
  Map<String, Object> variables = new HashMap<String, Object>();

  for (Variable variable : fInterpreter.getNameSpace().getDeclaredVariables())
   variables.put(variable.getName(), internalGetVariable(variable.getName()));

  return variables;
 }

 @Override
 protected boolean internalHasVariable(final String name) {
  for (Variable variable : fInterpreter.getNameSpace().getDeclaredVariables()) {
   if (variable.getName().equals(name))
    return true;
  }

  return false;
 }

 @Override
 protected void internalSetVariable(final String name, final Object content) {
  try {
   fInterpreter.set(name, content);
  } catch (EvalError e) {
   Logger.error("org.eclipse.ease.lang.beanshell", "Cannot set variable \"" + name + "\"", e);
  }
 }

 @Override
 protected Object internalRemoveVariable(final String name) {
  Object content = internalGetVariable(name);
  fInterpreter.getNameSpace().unsetVariable(name);

  return content;
 }

 @Override
 public String getSaveVariableName(final String name) {
  return BeanShellHelper.getSaveName(name);
 }
We purely dig into the beanshell interpreter and investigate its namespace. The BeanShellHelper class is copied from the JavaScriptHelper and provides basic checks on variable names.

Once launched, the Script Shell will show a list of all available variables in your interpreter attached in the container right to the shell.

Monday, December 7, 2015

A new interpreter for EASE (1): Playing with beanshell

This new series will provide a guide how to integrate your own interpreter in EASE. As a showcase we will implement an interpreter for BeanShell.

Read all tutorials from this series.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 

Step 1: Evaluate interpreter candidates

The bare minimum requirement for an interpreter to do something useful with EASE is to support an execute() method that accepts and executes arbitrary strings of script code. This will allow to use the interpreter in the script shell and to create script files and execute them using the EASE launch target.

Now EASE typically integrates interpreters that run natively on the JRE. This allows scripts to seamlessly interact with the current application. Such interpreters have some additional requirements:
  • access native java objects (eg. java.lang.System)
  • set/get variables in the interpreter
  • allow to define functions/methods
  • redirect input/output/error streams
Interpreters like Rhino or Jython support these extended requirements and therefore allow to write scripts that fully integrate into EASE.

However much simpler interpreters may make sense in some cases. I have played around with such interpreters and built a basic git console and a bash console. Both are in a proof of concept state, but might indicate what can be done with EASE.

Step 2: Integrating BeanShell

We will concentrate on BeanShell, so download the latest stable build.

Now create a new Plug-in from Existing JAR Archives. On the next page hit Add External and add the downloaded bsh-<version>.jar. Set the project name to org.beanshell and select Unzip the JAR archives into the project.

Open the Plug-in Manifest Editor and make sure that the bsh package is correctly exported on the Runtime tab.

Step 3: Play with the interpreter

To learn how the interpreter works we create a Java Project org.beanshell.playground. Add our org.beanshell project to the Java Build Path and create a test class with a main method:
package org.beanshell.playground;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;

import bsh.EvalError;
import bsh.Interpreter;

public class RunEmbedded {

 private static final String[] CODE_FRAGMENTS = new String[] { "2+3", "foo*10", "new java.io.File(\"/\").exists()",
   "sum(a,b){\n return a+b;\n }\nsum(40,2)" };

 public static void main(String[] args) throws EvalError, FileNotFoundException, IOException {
  Interpreter interpreter = new Interpreter(); // Construct an interpreter

  // set variables
  interpreter.set("foo", 5);
  interpreter.set("date", new Date());

  // get variables
  Object date = interpreter.get("date");

  // evaluate statements
  for (String code : CODE_FRAGMENTS) {
   System.out.println(interpreter.eval(code));
  }
 }
}

A simple embedding example was quickly found using google, now our tests show how to get/set variables and how to execute various pieces of code. From what we can see so far BeanShell is a perfect candidate for scripting.

The following tutorials will show how to integrate the interpreter in EASE, stay tuned!.

Monday, November 9, 2015

EclipseCon slides on EASE

Thanks to all who attended my talk at EclipseCon Europe last week. It is great to see the community jump on the scripting topic.

Even if I do not like large slidesets, I have put them online for your reference. Remember to have javascript enabled in your browser :)

The sample scripts are available from our scripts git repository:
JavaScript Beginner Tutorial

If you have questions or are just interested in the topic, consider joining our mailing list.

Thursday, July 30, 2015

EASE @ EclipseCon Europe 2015



This years EclipseCon will be very exciting for me. Not only was my talk proposal I love scripting accepted as an early bird pick, there are also other parties starting to propose talks concerning EASE.

I love to see this project start to fly and like to give you some insights and an outlook of what to expect at EclipseCon.

How it all began

I remember my first EclipseCon in 2012. I had this intriguing idea in my mind about a scripting framework in Eclipse, but no idea whom to talk to about it. By chance I met Wayne Beaton who offered help and encouraged me to find some interested parties to support my idea.

It took me almost a year to provide a prototype and start advertising this idea, but soon after my first post on this topic on this blog, Polarsys got interested and things started to move very quickly. In summer 2013 we agreed to introduce scripting (called EScript at that time) to the e4 incubator. Thanks to Paul Webster we got a nice place to play and learn about the eclipse way of working on a software project.

When the call for papers openend for EclipseCon Europe 2013 I thought 'What the hell' and sent a proposal for a talk on scripting. I never expected it to get accepted, but few months later I found myself talking about scripting at EclipseCon.

Encouraged by the positive feedback Arthur Daussy and myself worked hard on the implementation. Finally we moved out of the e4 incubator in 2014 and now you may install this great scripting environment in your IDE.

The role of EclipseCon

Meeting users, committers and eclipse staff members in person helped me a  lot  to make this project happen. In the beginning it was the encouragements and good advice I got from experienced members. Now I am looking forward to hear the user side, your success stories, your fresh ideas.

If you read this far you are definitely interested in scripting, so take the chance and meet me in person @ EclipseCon. If there is enough interest, we may have a BOF on scripting topics there. please leave me a note if you would like to see this happen.


I love scripting - what we will cover

This talk will give an overview what scripting can do for you and how you can implement it in your IDE or RCP. We will start to have a short look on the architecture (1%) and then immediately switch to a live demo (99%). There we will start with simple script commands, continue with Java integration and introduce scripting libraries. Next we will learn how to provide custom libraries for your RCP.

Once the basics are clear, we will start to extend the IDE with user scripts, start to write scripted unit tests and finally show rapid prototyping by dynamically instantiating java files from your workspace.

Expect a fast roller coaster ride through the features of EASE. I am sure you want more of it after your first ride!

see you at EclipseCon

Friday, May 22, 2015

Unit Testing with scripts in EASE


In the company I work for we do lots of regression testing, mostly for interface testing of hardware devices. Therefore we needed a unit testbench similar to JUnit without the complexity of writing java code for our tests. So we implemented a simple Unit test framework for EASE.

Source code for this tutorial is available in the EASE scripts repository.

Step 1: Create a simple test script

Create a new Project in your workspace named JavaScript Unit Testing Tutorial. Create a new file Tests/Simple/01 Valid tests.js
// starts a simple test
startTest("empty", "an empty test case");
// ends a test
endTest();

// start another test
startTest("no test code", "a test containing no assertions");
print("Hi from valid test");
endTest();

// third test
startTest("valid assertions", "a test containing valid assertions");
// check
assertTrue(true);
endTest();

// code outside of a testcase
print('"' + getTestFile() + '" completed');
You might ask where these test functions come from as we do not load any modules. The unit test framework comes with a module called Unittest. It is hidden by default but will be loaded automatically when we execute EASE UnitTests. To unhide go to the Preferences/Scripting/Modules (Note: this is not necessary to use the module, it will only display it in UI components).

If you are familiar with unit testing in general you also know assertions and according methods like assertTrue(). In general these methods perform a check for validity. In case an expected result does not match with a current result, an error is generated for the containing test case.

So test cases are encapsulated between a startTest() and an endTest(). It is recommended to not use assertions outside test cases, yet not mandatory.

Step 2: Create and execute a test suite

To execute test files, we need to create a Test Suite, that sets up a test environment for us. Create a new Scripting/Script Testsuite named Testsuite.suite.  The editor will allow you to select test files you want to execute. Therefore we look for script files in the same project where the suite is located.

Select our previously created test file and hit the Run Test Suite button in the top right corner.



 A new view is opened that displays unit test results.



The top tree view reflects the file structure of our test files and displays decorators indicating the test execution result. If you select a test file, detailed results are displayed in the bottom section.

Step 3: Test errors

Create a new file Tests/Simple/02 Test errors.js.
// test containing assertions that fail
startTest("invalid assertions", "a test containing invalid assertions");
assertTrue(true);
assertTrue(false);
assertFalse(true);
assertFalse(false);
endTest();

// manually create a failure
startTest("failure", "test throwing a failure");
failure("test broken, stop execution here");

// an exception would also create a failure
throw new java.lang.Exception("code exception");
endTest();

startTest("never to be reached");
// not being reached as the failure above terminates test file execution
endTest();
Save the test, add it to your suite and run the suite right away. When an assertion fails, error markers are generated on your script files on the according code location. You will also see the most critical assertion reflected in the Script Unit Test view. Double clicking on errors will open the source editor on the corresponding location.



Similar to JUnit there exist two different types of things that can go wrong: errors and failures. Errors are assertions that do not meet their criteria. They are recoverable, meaning script execution of the test file continues.

Failures on the other hand are not recoverable, so script execution of that test file is terminated immediately.

Step 4: Test Suite configuration

Typically tests need to be adapted to a certain environment. In general you do not want test engineers to modify your script code. So the test suite supports setting of Variables that are automatically available in all test files. The Setup section allows to execute arbitrary code on the Test Suite start, Test File start, startTest() call and also for the corresponding teardown sections. As you may provide multiple suite files within a project, these secions are a good place to adapt tests according to your environment.

Optional: Test automation

Often tests need to be executed automatically, eg for a nightly test run. The Unittest module allows to run a testsuite from a script. Together with the headless application from EASE you can run a suite from the commandline and create a report (we currently support the JUnit xml format that can be picked up from Hudson/Jenkins).

Optional: Additional assertions

If you want to provide your own assertion methods, simply create your own module and let your wrapped method return an IAssertion. If you call your method from within a test script it will automatically validate your assertion method.

So why do I want to write scripted tests?

In the company I work for we use these kind of tests for interface verification of hardware devices. Therefore we provide our own modules that allow us to send stimuli and validate expected responses. We also use it in combination with analog measurement devices to verify that certain hardware parameters are within valid ranges.

Currently Script Unit Testing is supported for the Rhino script engine only.

Wednesday, April 8, 2015

Live charting with EASE

Recently we added charting support to EASE using the Nebula XY chart widget. Say you have a script acquiring some data you now may plot them as your measurement advances.

Step 1: Installation

For this tutorial you need to register the nebula update site:
http://download.eclipse.org/technology/nebula/snapshot
in Preferences/Install/Update/Available Software Sites.

Afterwards install EASE and add EASE Charting Support to install all required modules. This component depends on Nebula Visualization Widgets, therefore we added the nebula update site before. Currently you have to stick to the nighty update site of EASE as charting is not part of the current release.

Step 2: Some example plots

 
Try out this little script to get these sample plots:
loadModule('/Charting');

figure("Simple Charts");
clear();

series("linear", "m+");
for (x = 0; x <= 20; x += 1)
 plotPoint(x, x / 10 - 1);

series("1/x", "ko:");
for (x = 1; x <= 20; x += 0.2)
 plotPoint(x, 4/x);

series("sine", "bx");
for (x = 0; x < 20; x += 0.05)
 plotPoint(x, Math.sin(x) * Math.sin(3 * x));

figure("Advanced");
clear();

series("drop", "gd");
for (t = 0; t < 2; t += 0.01)
 plotPoint(t, Math.pow(Math.E, t * -3) * Math.cos(t * 30));

series("upper limit", "rf--");
plot([0, 2], [0.4, 0.4]);

series("lower limit", "rf--");
plot([0, 2], [-0.4, -0.4]);
Run the script either in the Script Shell or execute it using launch targets.

Step 3: Charting Module API

The API of the charting module tries to keep close to MatLab style, so the format string for a series is almost identical. You may even omit to create figures or series and start plotting right away and we create everything necessary on the fly.

For a better user experience we extended the XYChart a bit by allowing to zoom directly using the scroll wheel either over the plot or its axis. Use a double click for an auto-zoom.

Sunday, February 22, 2015

Reformatting multiple source files

Recently I had to apply a new code formatter template to a project. Changing 50+ files by hand seemed to be too much monkey work. Writing a custom plug-in seemed to be too much work. Fortunately there is this great scripting framework to accomplish this task quite easily.

Step 1: Detecting affected source files

I have already blogged about EASE and how to install it. So I went directly to the script shell to fetch all java files form a certan project:
loadModule('/System/Resources');

 org.eclipse.ease.modules.platform.ResourcesModule@5f8466b4
files = findFiles("*.java", getProject("org.eclipse.some.dedicated.project"), true)
 [Ljava.lang.Object;@1b61effc
Now we have an array of IFile instances. Easy, isn't it?

Step 2: The formatter script

Ideas how to format source files can be found in the forum. Taking the script and porting it to JS is simple:
function formatUnitSourceCode(file) {
 unit = org.eclipse.jdt.core.JavaCore.create(file);

 unit.becomeWorkingCopy(null);

 formatter = org.eclipse.jdt.core.ToolFactory.createCodeFormatter(null);
 range = unit.getSourceRange();
 formatEdit = formatter
   .format(
     org.eclipse.jdt.core.formatter.CodeFormatter.K_COMPILATION_UNIT
       | org.eclipse.jdt.core.formatter.CodeFormatter.F_INCLUDE_COMMENTS,
     unit.getSource(), 0, unit.getSource().length(), 0, null);
 if (formatEdit.hasChildren()) {
  unit.applyTextEdit(formatEdit, null);
  unit.reconcile(org.eclipse.jdt.core.dom.AST.JLS4, false, null, null);
 }

 unit.commitWorkingCopy(true, null);
}

Step 3: glueing it all together

Load the function into your script shell, fetch the list of files as in step 1 and then process all files in a loop:
for each (file in files) formatUnitSourceCode(file);
That's it. This short script uses your current source formatter settings and applies it to all selected files.

This little helper is available online in the EASE script repository.

Wednesday, January 14, 2015

EASE 0.1.0 - the very first release

I am proud to announce the very first release of the Eclipse Advanced Scripting Environment (EASE).

In case you have no idea what this is about, check out the project page. Afterwards you definitely need to install it right away in your IDE and start scripting.

Some facts: 
  • executes script code in your IDE, providing access to the running JRE and thus to the whole Eclipse API
  • supports JavaScript, Jython, Groovy and JRuby (see details)
  • allows to dynamically integrate scripts into toolbars and menus
  • extensible via script libraries (write your own)

This project started as an in house company solution some years ago. When I released it as open source back in 2013 I was quite astonished about the interest from the community. Soon this project moved into the e4 incubator which gave us a great chance to evolve and build up an infrastructure.
Last summer EASE was accepted as an Eclipse incubation project. With our first release we feel to be part of the eclipse community.

Let me take this opportunity to thank all the people who helped to make this happen. When I started out almost 2 years ago I did not expect to meet such an open minded community. So if you think of starting your own project I can just encourage you to take the first step. There are helping hands everywhere, trying to push you forward. Did I already say that I love this community?

Friday, December 5, 2014

EASE scripts conquer the UI

EASE just recently got extended to inject scripts into the UI. This means you may insert scripts into your view toolbars, menus and context menus. No need to deploy anything, just provide the script and add some keywords to it.

During this tutorial we will develop our script step by step. The final version is available from the EASE scripts repository.

To follow this tutorial you need to install EASE into your IDE/RCP.

Step 1: A very simple script

Our target will be to open a file browser on the current location selected in the Project Explorer view.

Create a General/Project in your workspace and a file Explore from here.js. To launch an external program we may use the Platform module:
loadModule('/System/Platform');
// runProcess('krusader');
runProcess('explorer.exe')
Now register your script location in the preferences: Scripting/Script Locations. Afterwards test your script by running it using one of the follwing methods:
  • double click in the Script Explorer view
  • right click in Project Explorer and select Run As/EASE Script
  • D&D script file to Script Shell view
Step 2: Bind to toolbar

As a next step we want to attach our script to the toolbar. So open your script file again and add following comment at the top of the file:
// ********************************************************************************
// name                 : Explore from here
// toolbar              : Project Explorer
// script-type          : JavaScript
// description          : Start a file browser using current selection.
// ********************************************************************************
The new toolbar button appears right after saving the script file. No need to restart eclipse! If not you probably did not set up your script locations in preferences correctly.
The toolbar keyword accepts view titles and view IDs if you know them. All supported keywords are listed in the wiki.


Step 3: Using the current selection

When starting the file browser we want to use the current selection as our start folder. it can be easily consumed by using getSelection() from the UI module:
loadModule('/System/UI');

var selection = getSelection();
if (selection instanceof org.eclipse.jface.viewers.IStructuredSelection)
 selection = selection.getFirstElement();

if (selection instanceof org.eclipse.core.resources.IFile)
 selection = selection.getParent();

if (selection instanceof org.eclipse.core.resources.IContainer)
// runProcess("/usr/bin/krusader", ["--left", getSystemProperty("user.home"), "--right", selection.getRawLocation()]);
 runProcess("explorer.exe", [selection.getRawLocation()]);
First we extract the very first element of the current selection. In case it is a file, we get its parent folder. Finally check that we really have a container, then open the file browser.


Step 4: Adding a context menu entry

We might also want to bind our script to any IResource context menu in our application. This is really simple, just add following keyword to the header:
// popup                : enableFor(org.eclipse.core.resources.IResource)

Step 5: Adding an image to the toolbar

Instead of having a text button in the Project Explorer toolbar we may replace it with an image. You guessed it, its just another keyword:
// image                : platform:/plugin/org.eclipse.ease.ui/icons/eobj16/folder.png
This URI reuses an icon stored within the ease.ui plugin. You may use relative paths, file system paths or even http URIs for your images.
When you start building up your own script library, consider pushing your scripts to our script sample repository.

Tuesday, October 21, 2014

Writing modules for EASE

While EASE is shipped with some basic functionality already implemented, the real benefit comes when we provide our own script modules. Today we will see how such modules are provided.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.

Step 1: Get EASE

To write modules you either need to install EASE into your running IDE, provide a target platform containing EASE, or import the source (core repository is sufficient) directly. At the end of this tutorial we will create help pages automatically. If you want to use that feature directly go for the source code option.

If not sure which option to choose simply install EASE from the update site.

Step 2: A simple module

Modules are simple classes, that are registered using the org.eclipse.ease.modules extension point. Start with creating a new Plug-in Project and create the extension point. Create a new module extension with id, name and a class. visible sets the default module visibility, but can be changed by the user in the preferences anytime.

If you start providing more and more modules it is a good idea to cluster them by using categories. This results in a cleaner UI integration using trees instead of a flat list of modules. Categories are defined under the same extension point and are straight forward, so I will not explain in detail.


The implementation starts with a POJO class:

package com.codeandme.ease.modules;

import org.eclipse.ease.modules.WrapToScript;

/**
 * Provides basic mathematics operations.
 */
public class SimpleMathModule {

 /**
  * Provide sum of 2 variables.
  *
  * @param a
  *            summand 1
  * @param b
  *            summand 2
  * @return sum
  */
 @WrapToScript
 public double sum(double a, double b) {
  return a + b;
 }

 /**
  * Subtract b from a.
  */
 @WrapToScript
 public double sub(double a, double b) {
  return a - b;
 }

 /**
  * Multiply 2 values.
  */
 @WrapToScript
 public double mul(double a, double b) {
  return a * b;
 }

 /**
  * Divide a by b.
  */
 @WrapToScript
 public double div(double a, double b) {
  return a / b;
 }

 public void doNothing() {
  // not exposed as it lacks the @WrapToScript annotation
 }
}

The only addition to a plain class is the @WrapToScript annotation, which indicates methods to be exposed to scripting. In case a class does not contain any @WrapToScript annotation, all public methods will get exposed.
As modules are created dynamically, make sure that the default constructor exists and is visible!

Step 3: A more complex module

Our 2nd module will be a bit more complex as we introduce a dependency to the SimpleMathModule. Define the module the same way we did before, but add a dependency to com.codeandme.ease.modules.module.simpleMath.


The implementation now derives from AbstractScriptModule which allows us to retrieve the used script engine and the basic environment module, which keeps track of loaded modules. If you cannot extend your class you may also implement IScriptModule and populate the initialize() method on your own.

package com.codeandme.ease.modules;

import org.eclipse.ease.modules.AbstractScriptModule;
import org.eclipse.ease.modules.WrapToScript;

/**
 * High sophisticated mathematics operations.
 */
public class ComplexMathModule extends AbstractScriptModule {

 /** PI constant. */
 @WrapToScript
 public static final double PI = 3.1415926;

 /**
  * Get radus from circle area.
  *
  * @param area
  *            circle area
  * @return radius
  */
 @WrapToScript
 public double getRadius(double area) {
  double rSquare = getEnvironment().getModule(SimpleMathModule.class).div(area, PI);
  return Math.sqrt(rSquare);
 }
}

By querying the environment we can retrieve other module instances dynamically and make use of their functionality.

We also expose a constant PI here. Just remember that only constant fields can be wrapped.


Optional: Create documentation

Users typically will request for module documentation. EASE allows to create eclipse help pages automatically by using a special JavaDoc doclet. The doclet is available in source and binary from the EASE core repository. To use it you need to download at least the bin folder content (along with the directory structure).

Now open menu Project / Generate JavaDoc...  , provide the location of the javadoc binary, select your modules project and point to the custom doclet location.



On the 2nd page add a parameter that points to the Plug-in Project root folder:

-root /your/workspace/location/com.codeandme.ease.modules


Hit Finish to build the documentation. The doclet will alter your plugin.xml, MANIFEST.MF and adds a new help folder to your project.

The help pages are located under Scripting Guide/Loadable modules reference/<Category>/<ModuleName>.

If you are interested in building documentation automatically using maven, please ask on the ease-dev mailing list for details.

Thursday, August 21, 2014

EASE - Eclipse Advanced Scripting Environment launched

It's been a while since I last wrote about EASE. Not writing about it does not mean that we were lazy. We applied for an official eclipse project and were busy setting up the infrastructure.

So please welcome the new Eclipse citizen: EASE.


What it does for you

EASE allows to write, maintain and execute scripts right within your IDE. Executed scripts run in the context of the Eclipse JRE, allowing to access all Java classes in your Eclipse universe. Thus you can manipulate and extend your IDE without the need to write plugins, pack them into features, export them into a p2 repository, install, restart, ...

As accessing Java code from script languages is typically an annoying task ("If I could write Java code I would do it in Java, why scripting?") EASE provides extension points to encapsulate typical actions into simple script commands. Basically it allows to create wrappers in the target script language to access Java methods. We already started writing some useful modules. The Modules Explorer view gives a short overview of the available commands (hint: try DND).


You already have a nice API to use? Great, just wrap() it from your script or register it via extension point.

Scripts may include other scripts using URIs. You could even access your scripts using http.To register script locations check your preferences in Preferences/Scripting.

Current UI integration gives access to a nice interactive shell to play around with, script recording and launch support.



Right, where do I get it from?

You find the update site locations on our webpage. We are focusing on Eclipse Luna, so if you are using something older and things do not work as expected, let us know.


Contribute? It's easier than you thought

Contribution is not only about writing code. Just test EASE and let us know what you think of it. Fill the bugtracker with ideas, design icons, write help, provide sample scripts - there are so many things that need a little care.


What's next

This summer we received a great contribution via Google Summer of Code. Martin Kloesch developed a Jython debugger which will be available on the update sites soon.

UI integration to bind scripts to menus and toolbars is basically working, but needs some tweaking.

A set of core modules needs to evolve. Currently we are playing around with the functions, but we need a stable script API for those modules rather soon.

... and I know we have to write lots of help and tutorials to make this project useful for you out there.