Dawid Weiss
Dynamic BeanShell binding and ANT tasks
http://www.cs.put.poznan.pl/dweiss/xml/projects/bsh-binding/index.xml
Ten artukuł jest dostępny jedynie w języku angielskim.
A full version of this article has been published by JavaWorld. Please note that BeanShell 2.0 introduces most of the features described here as part of the language (and with much lower overhead because code generation is performed using AsmLib, not BCEL). The idea: mixing the scripted with the compiled
BeanShell In spite of all BeanShell's advantages, it also has a number of limitations:
I have fiddled for quite a while with the thought of how
these limitations could be avoided and finally created this (quite tricky) project.
The solution uses BCEL
One of the gains: dynamic ANT tasks!
One could ask whether any of the above would be of any use in real life. In my opinion
there are several options for using "dynamic" BeanShell classes, starting at
rapid application development (because such classes require no compiler) and
ending at modularization (because such classes may be dynamically loaded and edited).
I came up with one particularly interesting application: dynamic tasks for ANT <!-- Define an embedded task and use it immediately --> <target name="example" depends="prepare"> <!-- define the task --> <embeddedtaskdef name="counterTask"><![CDATA[ String message; int min; int max; void setMin(int min) { // note how 'global' is used instead of 'this' // global scope refers to the task instance anyway. global.min = min; } void setMax(int max) { global.max = max; } void setMessage(String message) { global.message = message; } void execute() { if (message != null) System.out.print(message); for (int i=min;i<max;i++) { System.out.print(i); if (i+1 != max) System.out.print(","); } } ]]></embeddedtaskdef> <!-- use the defined task right away! --> <counterTask min="0" max="10" message="The numbers from 0 to 10 are: " /> <counterTask min="5" max="20" message="And the numbers from 5 to 20 are: " /> </target> A task embedded inside the ANT build file The example above defines a new task counterTask, which is used immediately after it is defined (all conventions of JavaBeans apply - you must define setters/ getters accordingly). The task definer embeddedtaskdef is part of this project and must itself be defined earlier in the build file, using standard task definition tasks: <available classname="com.dawidweiss.bsh.dynbinding.ant.DynamicBSHTask" classpathref="bsh-binding" property="bsh-binding.available" value="true" /> <fail message="Cannot find bsh-binding JAR, build the project with 'jar' target first." unless="bsh-binding.available" /> <!-- Define a new taskdef task, which is capable of creating inline scripted tasks --> <taskdef name="embeddedtaskdef" classname="com.dawidweiss.bsh.dynbinding.ant.DynamicBSHTask"> <classpath refid="bsh-binding" /> </taskdef> Dynamic task loader task definition The above example is the simplest one can think of. ANT tasks can be read (in a scripted form) from local files or even accessed just as if they were real Java classes (the scripts must be organized in a directory structure reflecting their packages and the top directory must be added to classpath just as with regular classes). One may find more examples in examples folder of the project as well as in test cases. How does it work? The BSH script is first read and parsed. All method declarations are then extracted (please note that methods must be strongly typed). A real Java class is then created (using BCEL), and it contains all signatures of the methods acquired in the previous step. This class delegates all method calls to their scripted counterparts. The example below shows a BSH script and its associated wrapper class: /*% @extends java.lang.Object %*/ /*% @implements com.dawidweiss.bsh.dynbinding.ExampleInterface %*/ // --- This is equivalent to instance-scope variables int counter = 0; // --- This is equivalent to a instance init block counter = 10; // --- This is equivalent to instance methods int getCounter() { return global.counter; } void setCounter(int newValue) { global.counter = newValue; } BSH script with some custom extensions (extends and implements keywords) /* * Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov. */ package com.dawidweiss.bsh.dynbinding.wrappers; import java.lang.reflect.InvocationTargetException; import com.dawidweiss.bsh.dynbinding.BSHScriptWrapper; import com.dawidweiss.bsh.dynbinding.ExampleInterface; public class ScriptedSuperclass implements ExampleInterface { public void setCounter(int arg0) { Object aobj[] = { new Integer(arg0) }; try { scriptWrapper.invoke("setCounter", aobj); } catch (InvocationTargetException e) { throw new RuntimeException( e.getCause().toString() ); } } public int getCounter() { Object aobj[] = new Object[0]; try { return ((Integer)scriptWrapper.invoke("getCounter", aobj)).intValue(); } catch (InvocationTargetException e) { throw new RuntimeException( e.getCause().toString() ); } } private final BSHScriptWrapper scriptWrapper = new BSHScriptWrapper(this, "com.dawidweiss.bsh.dynbinding.wrappers.ScriptedSuperclass", "ScriptedSuperclass.bsh"); } The dynamically created delegation class (decompiled to source code using JAD) Please note that the wrapper class has no embedded script code - it is loaded dynamically when the wrapper class needs it. Download and play with it! Please feel free to download source code and binaries of the project. The package contains examples and JUnit test cases, which should make it easier to understand how to use the dynamic BSH binding. In case of any questions, feel free to e-mail me.
|