Implementing Modules in Java

Initialization scripts of modules can be implemented as loadable classes. So it is legal to implement initialization scripts as pnuts.lang.Executable implementation classes, but it is more common to implement them as pnuts.ext.ModuleBase subclass.

For example, let's define "my_module" module in Java. we define my_module.init class that inherits pnuts.ext.ModuleBase. public Object execute(Context) method defines the function hello() in the "my_module" package

package my_module;

import pnuts.ext.ModuleBase;
import pnuts.lang.Context;
import pnuts.lang.Package;

public class init extends ModuleBase {
    public Object execute(Context context){
      Package pkg = Package.getPackage("my_module", context);
	pkg.set("hello".intern(), new hello(), context);
	return null;
    }
}

Note that the symbol "hello" is an interned String, since pnuts.lang.Package manages symbols as interned String objects.

Dependency on other modules

The easiest way to define dependency on other modules is to override ModuleBase.getRequredModules(). The following example define my_module module that depends on "pnuts.lib" and "pnuts.io" module.

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    protected String[] getRequiredModules(){
        return new String[]{"pnuts.lib", "pnuts.io"};
    }
    ...
}

Defining Sub-modules

Module may define subsets of its functionality as sub-modules, which can be defined by overriding ModuleBase.getSubModules() method.

For example, the following initialization script defines a module that has "functional" module as a sub-module.

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    protected String[] getSubModules(){
        return new String[]{"functional"};
    }
    ...
}

Defining Dependency/Submodules at Run Time

Dependency or submodule can be defined at run time by using appropriate modules in ModuleBase.execute(Context) method, as the following steps.

  1. Call Context.usePackage() with sub-modules
  2. Call Context.clearPackage() method
  3. Call Context.usePackage() with requred modules to implement the module

For example, the following code defines "my_module" module that depends on "pnuts.lib" module and "pnuts.io" module and has a sub-module "functional" module.

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    public Object execute(Context context){
        context.usePackage("functional");
        context.clearPackages();  // reset the module list
        context.usePackage("pnuts.lib");
        context.usePackage("pnuts.io");
	...
    }
}

Specifying the intialization script

The initialization script of a module can be specified in META-INF/pnuts/module/moduleName. For example, java.net module's initialization script is defined at the 1st line of META-INF/pnuts/module/java.net, as follows.

org/pnuts/java_net/init

Autoloading in Java

Autoloading is used in order to minimize start-up time of modules.

Package.autoload() method can be used to autoload Pnuts scripts and loadable classes. pnuts.ext.ModuleBase class provides more convenient methods for autoloading.

(1) Autoloading Pnuts Scripts

To autoload Pnuts scripts (and loadable classes), ModuleBase.autoload() can be used. For example, the following code defines the name 'hello', which is expected to be defined in the script "my_module/hello", but the script is not loaded until the name is first used.

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    static String[] files  = { "my_module/hello" };
    static String[][] functions = {
        { "hello" }
    };
    
    public Object execute(Context context){
	for (int i = 0; i < files.length; i++){
	    autoload(functions[i], files[i], context);
	}
	return null;
    }
}

(2) Autoloading Pnuts Functions

To autoload functions implemented in Java, ModuleBase.autoloadFunction() method can be used. For example, the following class defines the initialization of my_module module. When the symbol 'hello' is first dereferenced, org.pnuts.my_module.hello class is loaded and instantiated by the default constructor and returned to Pnuts interpreter.

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    static String[] javaFunctions = { "hello" };

    protected String getPrefix(){
        return "org.pnuts";
    }
    
    public Object execute(Context context){
	for (int i = 0; i < javaFunctions.length; i++){
	    autoloadFunction(javaFunctions[i], context);
	}
	return null;
    }
}

getPrefix() method defines the prefix of the class names (the default prefix is null). In this example, the class name is constructed from the prefix "org.pnuts", module name "my_module", and the function name "hello". If getPrefix() method is not defined, the prefix is an empty string.

(3) Autoloading Java Classes

To autoload Java classes, ModuleBase.autoloadClass() can be used. For example, the following code defines two names 'Vector' and 'Hashtable', which will be the corresponding Class objects, but the classes are not loaded until the names are first used.

package my_module;

import pnuts.lang.Context;
import pnuts.ext.ModuleBase;

public class init extends ModuleBase {
    public Object execute(Context context){
	for (int i = 0; i < javaFunctions.length; i++){
	    autoloadClass("java.util", "Vector", context);
	    autoloadClass("java.util", "Hashtable", context);
	}
	return null;
    }
}

Localization of Error Messages

When pnuts.lib module is being used, the formatMessage() function could be used to show localized message. But how can be do that in a module which does not depend on pnuts.lib module?

If the initialization code is a subclass of pnuts.ext.ModuleBase, ERROR() function can be used in the functions of the module.

ERROR ( String errorType {, String param_1 ... } )

This function finds a message resource from "errors.properties", format the error message in the same way as formatMessage(), and then throw pnuts.lang.PnutsException with the formatted message.

if (!isFunction(obj)){
  ERROR("must_be_a_function", obj)
}
my_module/errors.properties
must_be_a_function=A function is expected: {0}