Skip to content

Class Building

There are two class builder types. One is for general purpose class building, like creating a new block type by extending from Block. The other is for creating mixins to change the behavior of existing classes, like modifying a method in CactusBlock to increase the maximum height cacti can grow to.

Standard Class Builder

Given the following class:

Animal.java
java
package com.example;

public class Animal {

    protected int speed;

    public Animal(int speed) {
        this.speed = speed;
    }

    protected boolean makesNoise() {
        return true;
    }

    public String noise(boolean chasing, int packSize) {
        return makesNoise() ? "*rustle, rustle*" : "*silence*";
    }
}

The following documentation uses this Dog.lua file, and specifically pulls in snippets of the relevant sections. The entire file is provided here for reference:

Dog.lua
Lua
local Animal = require("com.example.Animal")

local dogDefinition = {}

function dogDefinition:clinit(class)
    -- set static fields here
end

function dogDefinition:constructor(speed, weight)
    self.weight = weight
end

function dogDefinition:noise(chasing, packSize)
    if self:makesNoise() then
        -- We create isRunning() later, but can use it here!
        if self.weight >= 20 then
            return "*huff*, *huff*"
        elseif self:isRunning() or chasing then 
            return "*pant*, *pant*"
        else
            if packSize > 2 then
                return "*awoo!*"
            else
                return "*woof!* *woof!*"
            end
        end
    end
    return this.super:noise()
end

function dogDefinition:isRunning()
    return self.speed >= 5
end

local Dog = java.extendClass(Animal)
    :field("weight", java.int, { final = true, private = true })
    :clinit(class)
        :index("clinit")
        :build()
    :constructor()
        :super({java.int})
        :remapper(function(speed, weight)
            -- super constructor uses the speed value, so we return that
            return speed 
        end)
        :access({ public = true })
        :parameters({java.int, java.int})
        :definesFields()
        :build()
    :method()
        :override("noise", {java.boolean, java.int})
        :access({ public = true })
        :build()
    :method()
        :name("isRunning")
        :access({ public = true })
        :parameters({})
        :returnType(java.boolean)
        :build()
    :build(dogDefinition)

local inu = Dog(0, 10)
print(inu:noise()) -- prints "*woof!* *woof!*"

classBuilder:field(name, type, access, value)

Creates a field.

Parameters

  1. name - string: The name of the field to create.
  2. type - userdata [class]: The class type of the field.
  3. access - table?: Access flags for the field. See Access Modifier Table
  4. value - number|string|boolean?: Optional primitive value for defining the field.

Returns

  • userdata [instance] The class builder for which this field is for.

Usage

Note that this field gets defined in the constructor, which was created with definesFields(). <<< @/reference/snippets/code/Dog.lua#field


classBuilder:clinit()

Creates a class initializer builder, primarily used for defining static fields.

Returns

Usage

Dog.lua
Lua
:clinit(class)
    :index("clinit")
    :build()
Dog.lua
Lua
function dogDefinition:clinit(class)
    -- set static fields here
end

classBuilder:constructor()

Creates a constructor builder for adding a constructor to this class.

Returns

Usage

Dog.lua
Lua
:constructor()
    :super({java.int})
    :remapper(function(speed, weight)
        -- super constructor uses the speed value, so we return that
        return speed 
    end)
    :access({ public = true })
    :parameters({java.int, java.int})
    :definesFields()
    :build()
Dog.lua
Lua
function dogDefinition:constructor(speed, weight)
    self.weight = weight
end

classBuilder:method()

Creates a method builder for creating an entirely new method, or overriding one

Returns

Usage

Dog.lua
Lua
:method()
    :override("noise", {java.boolean, java.int})
    :access({ public = true })
    :build()
:method()
    :name("isRunning")
    :access({ public = true })
    :parameters({})
    :returnType(java.boolean)
    :build()
Dog.lua
Lua
function dogDefinition:noise(chasing, packSize)
    if self:makesNoise() then
        -- We create isRunning() later, but can use it here!
        if self.weight >= 20 then
            return "*huff*, *huff*"
        elseif self:isRunning() or chasing then 
            return "*pant*, *pant*"
        else
            if packSize > 2 then
                return "*awoo!*"
            else
                return "*woof!* *woof!*"
            end
        end
    end
    return this.super:noise()
end

function dogDefinition:isRunning()
    return self.speed >= 5
end

classBuilder:build(hooks)

Builds the class.

Parameters

  1. hooks - table<function>: A table that defines all of the functions that will be used in the class constructors/methods/class initializers. Functions should be named based on the index, if one was provided, otherwise use the method name in the case of methods, or the default names in the case of constructors/class initializers (constructor and initializer, respectively.)

Returns

  • userdata [class]: The completed class.

Usage

Dog.lua
Lua
:build(dogDefinition)

Access Modifier Table

Access modifier tables are used in almost every class builder method. They are defined as:

Lua
{
    public = boolean?,
    protected = boolean?,
    private = boolean?,
    static = boolean?, -- only used for methods and fields
    final = boolean?, -- only used for fields and classes
    abstract = boolean?, -- only used for methods and classes
    interface = boolean? -- only used for classes
}

Mixin Class Builder

Obtained from mixin.to() when duck is false, builds out a class that gets applied as a mixin.

DANGER

Mixins are a very complicated topic that's very hard to grasp without a decent understanding of the JVM. If you would like to learn more about them, check out:

  1. Mixin's own wiki.
  2. The Mixin Cheatsheet.
  3. MixinExtras' Wiki.

DOUBLE DANGER

Due to a limitation in how mixins are applied, if another mod chooses to apply mixins during the preLaunch phase, all Allium script mixins will NOT apply. If a script mixin appears to not be applying properly, this might be why.

Given the following class:

Car.java
java
package com.example;

public class Car {
    private int speed;
    private int gas;
    private final int wheels;

    public Car(int speed) {
        this.speed = speed+5;
        this.wheels = 4;
        this.gas = 100;
    }

    private boolean isSpeeding() {
        return gas > 0 && speed > 10;
    }

    public int getRemainingGas() {
        return this.gas;
    }

    public String drive() {
        if (gas == 0) {
            return "*sputter*";
        } else if (wheels < 4) {
            gas--;
            return "putt putt putt";
        } else if (isSpeeding()) {
            gas-=5;
            return "VROOM!!!";
        } else {
            gas--;
            return "vroom!";
        }
    }
}

The following "usage" section of each method will modify this class in Lua, starting with:

CarMixin.lua
No region #init found in path: /opt/allium-docs/docs/reference/snippets/code/CarMixin.lua

The sections will also use an arbitrary "entrypoint" script representing the main entrypoint:

entrypoint.lua
No region #init found in path: /opt/allium-docs/docs/reference/snippets/code/entrypoint.lua

mixinClassBuilder:method(index)

Create an inject method. Can not be used on mixin builders where duck is true. See mixin.to().

TIP

To inject into constructors, and class initializers, use <init>()V and <clinit>()V as target method names, respectively. Don't forget to fill in parameters!

Parameters

  1. index - string: method name to later apply a hook onto this mixin with mixin.get().

Returns


mixinClassBuilder:build(mixinId)

Builds the mixin.

Parameters

  1. mixinId - string: A unique ID representing the mixin class. Used to obtain the hook used to define methods in this mixin.

Usage

CarMixin.lua
Lua
mixin.to("com.example.Car")
    :method("negateInitSpeed")
        :inject({ 
            method = { "<init>(I)V" },
            at = { "TAIL" }
        })
        :build()
:build("car_mixin")
entrypoint.lua
Lua
local definition = {}

function definition:negateInitSpeed(speed)
    -- override the speed that was previously set.
    self.speed = speed
end

-- Same ID as used in mixinClassBuilder:build()
mixin.get("car_mixin"):define(definition)

-- Require AFTER defining hooks.
local Car = require("com.example.Car")

Mixin Interface Builder

Obtained from mixin.to() when duck is true, builds out an interface to access private fields and methods on a class.

mixinInterfaceBuilder:accessor(annotations)

Defines a setter and getter accessor using the @Accessor annotation.

This is a convenience method that simply calls both mixinInterfaceBuilder:getAccessor() and mixinInterfaceBuilder:setAccessor() with the same annotations table.

For more information see Mixin Cheatsheet - @Accessor.

Parameters

  1. annotations - table: An annotation table that matches the @Accessor annotation.

Returns

  • userdata [instance] - The mixin builder

mixinInterfaceBuilder:getAccessor(annotations)

Defines a getter accessor using the @Accessor annotation.

The method name is automatically generated from the target field name. It starts with get, then the first letter of the target field name is capitalized, and concatenated with the get. For example, given the target field fooBar, the method getFooBar() is created.

Parameters

  1. annotations - table: An annotation table that matches the @Accessor annotation.

Returns

  • userdata [instance] - The mixin builder

mixinInterfaceBuilder:setAccessor(annotations)

Defines a setter accessor using the @Accessor annotation.

The method name is automatically generated from the target field name. It starts with set, then the first letter of the target field name is capitalized, and concatenated with the set. For example, given the target field fooBar, the method setFooBar() is created.

Parameters

  1. annotations - table: An annotation table that matches the @Accessor annotation.

Returns

  • userdata [instance] - The mixin builder

mixinInterfaceBuilder:invoker(annotations)

Defines an invoker using the @Invoker annotation.

The method name is automatically generated from the target method name. It starts with invoke, then the first letter of the target method name is capitalized, and concatenated with the invoke. For example, given the target method fooBar(), the method invokeFooBar() is created.

Parameters

  1. annotations - table: An annotation table that matches the @Invoker annotation.

Returns

  • userdata [instance] - The mixin builder

mixinBuilder:build(mixinId)

Builds the mixin.

Parameters

  1. mixinId - string: A unique ID representing the duck interface. Used to obtain the interface for later use.

Usage

CarMixin.lua
Lua
mixin.to("com.example.Car", nil, nil, true)
    :accessor({ "speed" })
    :getAccessor({ "wheels" })
    :setAccessor({ "gas" })
    :invoker({ "isSpeeding()Z" })
    :build("accessible_car")
entrypoint.lua
Lua
local Car = require("com.example.Car")

-- Same ID as used in mixinInterfaceBuilder:build()
local AccessibleCar = mixin.quack("accessible_car")
local sedan = java.cast(Car(0), AccessibleCar)
sedan:setSpeed(10)
print(sedan:getSpeed())
print("sedan has", sedan:getWheels(), "wheels")
sedan:setGas(150)
print("sedan speeding:", sedan:invokeIsSpeeding())