The Aussom Java Interface

Purpose

The Aussom Java Interface (AJI) is an API for working with Java objects natively from within Aussom. This interface does NOT require you to compile any Java code and allows you to use classes on the classpath right from within Aussom. This guide provides high level and in depth information about using Java objects in Aussom. This guide is for anyone wanting to execute Java code from within Aussom or for API developers to use AJI along with an external Java library to implement an API.

About

AJI is only comprised of two Aussom classes (aji and AussomJavaObject) which both belong to the aji.aus module. They provide the functionality required to work with Java objects natively within Aussom.

Loading JARs

With AJI you can use just about any class already on the classpath. That's great, but what if you want to use a class from a library that isn't on the classpath already? No worries, we have a way to do that. There's a function in the app.aus module that will load a JAR for you so that it's classes are then on the classpath. See the app.aus reference here for details. I go over this in the example below too.

aji class

This static class provides a number of functions for tasks such as creating new Java objects, invoking static class methods, getting static values, and some helper functions for tasks such as working with Java streams and closures.

AussomJavaObject

This class is a wrapper around the native Java object that provides a variety of functions for working with the object from within Aussom. When you instantiate a new object or when an object is passed back into Aussom, it will exist as an instance of this class.

Loading External JARs

As mentioned above, if you're going to work with a Java library that doesn't already exist on the classpath, you'll want to include it using the app. app.loadJar() function.

include app;
...
if (!app.loadJar("pdfbox-app-3.0.3.jar")) {
    // Do some error handling here as load failed.
}

Instantiating Java Objects

To instantiate a new Java object, we use the aji.newObj() method which takes the full canonical name of the class as the first argument and any number of arguments that should be passed to the Java object constructor. In the example below we create a new JavaFX HBox object and we pass no arguments to the constructor. The result of aji.newObj() is an instance of AussomJavaObject which in this case is saved to this.obj. It can be used later as we will see to invoke member functions or to get member variable values.

class HBox {
    public obj;
    public HBox() {
        this.obj = aji.newObj("javafx.scene.layout.HBox");
    }
}

Accessing Member Variables and Methods

Once you have an instance of AussomJavaObject, you can call functions to either invoke member functions or set/get member variables. Say you want to expose the Java function setId. In this case that Java function takes a String and returns nothing. We can write a function in our HBox class to do this using the AussomJavaObject.invoke() function. The function takes the function name as the first argument and then any number of other arguments to pass to the Java function.

If the Java definition looks like this:

public void setId(String Id) {
    this.id = Id;
}

We can invoke it from Aussom like this:

public setId(string Id) {
    this.obj.invoke("setId", Id);
    return this;
}

Notice in the example above, we make the argument in HBox.setId(string Id) a string. This is really useful because the interpreter will enforce type safety for you here. You can be sure that what is passed will be a string value.

We could also implement the above function like this instead as long as id is a public member variable. Here we are directly setting the member variable and bypassing the Java setId() function all together.

public setId(string Id) {
    this.obj.setMember("id", Id);
    return this;
}

Similarly, we can write a getter to directly get the value from that member variable id with the following function.

public getId() {
    return this.obj.getMember("id");
}

Putting it all Together

If we wanted to create a JavaFX HBox object with some of the functions we just described, here's an example of what that could look like.

class HBox {
    public obj;

    public HBox() {
        this.obj = aji.newObj("javafx.scene.layout.HBox");
    }

    public setId(string Id) {
        this.obj.invoke("setId", Id);
        return this;
    }

    public getId() {
        return this.obj.getMember("id");
    }
}

In order to use this class we just created, we could do this.

hb = new HBox();
hb.setId("myHboxId");
c.log("hb id: " + hb.getId());

The code above would print out myHboxId when ran. This is a simple example, but it illustrates how to create a new Java object, invoke its functions, and set/get member variables.

Invoking Static Functions

We can also invoke public static functions for any Java class. We just have to call the aji.invokeStatic() function to accomplish this. Say we want to invoke the Java Arrays.asList() static function, we can do that by calling invokeStatic with the canonical object name, the function, and then any number of arguments to pass to the Java function. In this case we are passing a variable called valArr.

vals = aji.invokeStatic("java.util.Arrays", "asList", valArr);

Get/Set Static Variables

Much like invoking static functions, we can get or set static member variables using the aji.getStaticMember() and aji.setStaticMember() functions. If Arrays had a public member called COUNT we could set it and get its value like this.

aji.setStaticMember("java.util.Arrays", "COUNT", 1);
val = aji.getStaticMember("java.util.Arrays", "COUNT");

Raw Functions

Both aji.invokeStatic() and AussomJavaObject.invoke() functions have matching raw functions which are aji.invokeStaticRaw() and AussomJavaObject.invokeRaw() respectively. These functions are the exact same as the other ones except when it comes to the return object.

When using the raw functions, no type conversion is done and a AussomJavaObject with the Java object is returned. Keep reading further in this doc for details of type conversion/coercion, but for now we can just say that no type conversion/coercion is done when returning values in raw functions. This can be useful when say you want to get a Java List object, but don't want Aussom to automatically conver the Java List to an Aussom list.

Conversion and Coercion

This is where things get a little more complicated. For transparency and to provide the most detail as possible, this section outlines how datatypes are handled when they are passed through AJI to Java and the conversion back. This process includes two potential functions, conversion and coercion with conversion being faster.

Conversion is the first method that is attempted. If datatypes between Aussom and Java line up perfectly, we can directly convert them. Think aussom string to Java String. This is one situation where we can just directly convert.

If the datatypes don't match up exactly, then coercion is attempted. An exapmple of this is if you are passing an Aussom double value to a function that expects a Java float. Here we coerce the value from double to float. If coercion is attempted but still can't conver the value, an exception is then thrown with an error message saying something about not being about to find a suitable conversion.

Conversion of Input Arguments

Aussom Type Java Type Notes
null null
string String
bool boolean
int long Internally Aussom stores int values as longs.
double double
list List Will iterate over every element in the array converting it with this same conversion process.
map Map<String, Object> Will iterate over evey element in the map converting it with this same conversion process.
AussomJavaObject Object The external object that the AussomJavaObject holds is extracted and passed as the argument.
buffer byte[] When an Aussom buffer object is passed as an argument, the internal byte array is extracted and provided as an argument.
object Object This is when an Aussom object is passed that isn't an AussomJavaObject or a buffer. In this case it will attempt to provide the external Java object that the Aussom class uses. In the event that the provided Aussom object is not an extern class, then simply the Aussom object (Java type AussomObject) is provided.

Coercion of Input Arguments

Aussom Type Java Type Notes
int int or Integer
int short or Short
int byte or Byte
double float or Float
string char A string is provided but just the first character is passed on as the char value argument.
string char[] A string is converted to a char array.
AussomJavaObject Object The wrapped Java object is extracted and provided as the argument

Conversion of the Return Value

Note that if any of the raw functions are called, this mapping isn't used. In that case the result object is simply wrapped in an AussomJavaObject and returned.

Java Type Aussom Type Notes
null null An Aussom null value is returned
AussomType AussomType If the Java function returns any type of AussomType, it is returned as is.
String string
Character string
Boolean bool
Byte int
Short int
Integer int
Long int These are actually the same since Aussom uses long as the type to store an int value.
Double double
Float double
List list The items in the Java List are iterated and each one ran through this same conversion process. A native Aussom list is returned.
Map map The items in the Java Map are iterated and each one ran through this same conversion process. A native Aussom map is returned.
byte[] buffer
char[] string
* AussomJavaObject In the event that none of the other conversion options were met, the Java object is wrapped in an AussomJavaObject and returned.

Handling Closures

Some Java functions expect a closure to be provided as an argument. This is a challenge because a closure is an implementation of a single function interface. Each interface is different, so it's difficult to create a single closure that can be passed in any case. Thankfully there is a way to do this using the Java Proxy class. This has been implemented so there is a way to pass an Aussom callback to a Java function expecting a closure.

Let's have a look at an example. Below is the implementation of the JavaFX ButtonBase class in Aussom. It implements an onClick function that takes an Aussom callback and sets it as the closure to be called when the JavaFX onAction event is triggered. The function that creates the closure is aji.closure() and it takes two arguments, the canonical name of the Java Inteface of the closure, and the second is the Aussom callback.

class ButtonBase {
    public onClick(callback OnClick) {
        this.obj.invoke("setOnAction", aji.closure("javafx.event.EventHandler", OnClick));
        return this;
    }
}

The EventHandler.java Java definition looks something like this. Notice that it's expecting to call the handle function and pass a single event argument of type T.

public interface EventHandler<T extends Event> extends EventListener {
    void handle(T event);
}

With this information, we know what we need to provide to the closure. In this case, we could do something like this. We create a function calledcallOnClick with a single event variable for an argument. We can then provide it as a callback to the onClick function of the ButtonBase class.

class myTestClass {
    public main(args) {
        bb = new ButtonBase();
        bb.onClick(::callOnClick);
    }
    public callOnClick(event) {
        c.log("ran");
    }
}

Helper Functions

Along with everything already discussed, there are a few helper functions implemented in aji static class. Two of them are aji.bufferToInputStream() and aji.inputStreamToBuffer() for converting to and from Java InputStream and Aussom buffer object.