Luaj lcdui

Luaj is a library bringing Lua to Java 2 ME platform. It does not have the bindings to javax.microedition.lcdui package. I wanted to see how easy it is to add a few classes from this package in order for them to be available to Lua programs. You can grab the source from here.

Main MIDlet

Here’s the MIDlet code:

package com.icyrock.j2me.luaj.example;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jme.JmePlatform;

import com.icyrock.j2me.luaj.LcduiLib;
import com.icyrock.j2me.luaj.Midlet;

public class LuajMidlet extends MIDlet {
  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };
    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  private String readFile(String fileName) {
    String ret = "";

    FileConnection fc = null;
    try {
      fc = (FileConnection) Connector.open(fileName);
      InputStream is = fc.openInputStream();

      StringBuffer sb = new StringBuffer();

      int cap = 2000;
      byte[] bytes = new byte[cap];

      while (true) {
        int available = is.available();
        if (available == 0) {
          break;
        }

        int len = available > cap ? cap : available;
        is.read(bytes, 0, len);

        sb.append(new String(bytes, 0, len, "UTF-8"));
      }

      ret = sb.toString();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fc != null) {
        try {
          fc.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    return ret;
  }
}

It has a readFile method, which is nothing else but a method to read files. Java 2 ME is pretty limited, so there are no e.g. BufferedReader or classes such as that to help with this task, so I added this quick method to read the file in whole.

The main code is in startApp method, the breakdown of which follows:

  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

When used in an emulator, such as MicroEmulator, just print the string to know this is starting to be loaded. Also, define the Lua script to be run.

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

Define the _G, which is the global Luaj context. Add our LcduiLib, so it’s accessible to Lua scripts we write. Initialize a variable lcdui.midlet.current that will, when accessed from within Lua script, point to the current MIDlet instance.

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };

Define the loader function. This function will be called to load all parts of Lua script, which will then be concatenated and run. The script is loaded from:

"file:///SDCard/icyrock/lua/" + script + ".lua"

so in this case that would be:

file:///SDCard/icyrock/lua/phi.lua

containing a sample Lua script we’ll see later. If you use MicroEmulator under Linux, you should put this file in:

~/.microemulator/filesystem/SDCard/icyrock/lua/phi.lua 

The last step:

    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

is to load the Lua script and run it.

Sample Lua script

You can find this one in com/icyrock/j2me/luaj/example/lua/phi.lua:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

The breakdown is:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

Get the display. This is the standard J2ME lcdui stuff.

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

Create a from and put a command listener. The listener is just going to destroy the form when a command with label "Exit" is chosen.

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

Create two commands, both of which are used for exiting and add them to the form. In MicroEmulator, this allows for both shown commands to be used to exit the script.

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

Add a sample text message with label "Message" and the string "Hi from Luaj!" as the main text. Finally, set the display to be visible (again standard J2ME).

Note two things:

  • Lua function print is going to print to MicroEmulator's console
  • You can exit from MIDlet in the emulator, change the Lua script, run the MIDlet again and the changes will be applied