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 / | __/ \__ \| (__ | | | || |_) || |_ | || | | || (_| |
|___| |_| \___/ \_/ \___| |___/ \___||_| |_|| .__/ \__||_||_| |_| \__, |
|_| |___/