icosilune

Metaprogramming

[Experiments,General,Toys] (01.21.09, 10:22 pm)

I am really interested in metaprogramming, writing programs which can modify themselves dynamically. More notably, users of metaprograms will be able to change their functional operation. The advantage of being able to do this is that it enables a great deal of power for development and creating things, but primarily, it’s just a lot of fun. There is something about being able to call functions using reflection that just is really pleasing and entertaining to me.

Most commonly scripting frameworks are used to create secure environments for scripts, and often these scripts will be developed ahead of time, but they also enable a possibility of dynamic scripting, where a scripting environment may be able to do interesting things during run time. Possible examples of run time uses are controlling agents within a world by issuing commands and writing code for an agent’s “brain”, then loading to that into the agent which is live in the world. It would be possible to create a dynamic music or image making program, where the artist can control what is being played or drawn using the scripted code.

With the recent release of Java 1.6, we now have standardized implementation of JSR 223, also known as the scripting framework, which enables some exciting metaprogramming possibilities. This allows one to script on top of Java. So, it would be possible to interact with Python, Ruby, Javascript, or any  other sort of scripting language through this one framework. Interestingly, it is also possible to use Java as a scripting language. Thus, you can script for a Java program… in Java. This may seem ridiculous to some, but I think this is simply delightful.

Here is an example of the scripting framework in use. Note, to actually run this, you must add the java-engine.jar, found in jsr223-engines.zip to the classpath.

package scripttest;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

/**
 *
 * @author Calvin Ashmore
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        ScriptEngineManager mgr = new ScriptEngineManager();

        ScriptEngine scriptEngine = mgr.getEngineByName("java");
        System.out.println(scriptEngine);
        Bindings bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
        for (Entry<String, Object> entry : bindings.entrySet()) {
            System.out.println("  " + entry);
        }
        System.out.println(scriptEngine.getContext());

        try {
            scriptEngine.put(ScriptEngine.FILENAME, "Toasty.java");
            String script = "public class Toasty {" +
                    "  private float myStuff;" +
                    "  public Toasty(float stuff) {" +
                    "    myStuff = stuff;" +
                    "  }" +
                    "  public String toString() {" +
                    "    return \"I have a thingy! \"+myStuff;" +
                    "  }" +
                    "  public int performWombat(String theWombat, float multiplier) {" +
                    "    int toast = Integer.valueOf(theWombat);" +
                    "    float thingy = toast * multiplier;" +
                    "    double d = Math.sqrt(1 + thingy*thingy);" +
                    "    return (int) d;" +
                    "  }" +
                    "}";

            Class toastyClass = (Class) scriptEngine.eval(script);
            Constructor c = toastyClass.getConstructor(float.class);
            Method m = toastyClass.getMethod("performWombat", String.class, float.class);

            Object toasty = c.newInstance(1.0f);
            System.out.println("My toasty: " + toasty);

            System.out.println("This will work:");
            System.out.println("result: " + m.invoke(toasty, "234", 1.23f));

            System.out.println("This will not:");
            System.out.println("result: " + m.invoke(toasty, "eek", 1.23f));

        } catch (Exception ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

(Apologies for the strange variable names) It generates the output:

com.sun.script.java.JavaScriptEngine@1abc7b9
javax.script.SimpleScriptContext@c55e36
My toasty: I have a thingy! 1.0
This will work:
result: 287
This will not:
Jan 21, 2009 9:27:15 PM scripttest.Main main
SEVERE: null
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at scripttest.Main.main(Main.java:70)
Caused by: java.lang.NumberFormatException: For input string: "eek"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
        at java.lang.Integer.parseInt(Integer.java:447)
        at java.lang.Integer.valueOf(Integer.java:553)
        at Toasty.performWombat(Unknown Source)
        ... 5 more

This example is able to instantiate and call methods on a simple object effectively. However, in order to do the more interesting things described above, we need to make use of interfaces. So, the scripted class will implement an interface defined by the main program. That interface will define the contract for operation. So, instead of using reflection to call methods, we can simply cast the object to belong to the interface and call the methods directly. I’m not going to write this yet, but maybe soon.

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

You must be logged in to post a comment.