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:
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:
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
name-string: The name of the field to create.type-userdata [class]: The class type of the field.access-table?: Access flags for the field. See Access Modifier Tablevalue-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
userdata [instance]- A Class Init Builder
Usage
:clinit(class)
:index("clinit")
:build()function dogDefinition:clinit(class)
-- set static fields here
endclassBuilder:constructor()
Creates a constructor builder for adding a constructor to this class.
Returns
userdata [instance]- A Constructor Builder
Usage
: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()function dogDefinition:constructor(speed, weight)
self.weight = weight
endclassBuilder:method()
Creates a method builder for creating an entirely new method, or overriding one
Returns
userdata [instance]- A Method Builder
Usage
:method()
:override("noise", {java.boolean, java.int})
:access({ public = true })
:build()
:method()
:name("isRunning")
:access({ public = true })
:parameters({})
:returnType(java.boolean)
:build()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
endclassBuilder:build(hooks)
Builds the class.
Parameters
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 (constructorandinitializer, respectively.)
Returns
userdata [class]: The completed class.
Usage
:build(dogDefinition)Access Modifier Table
Access modifier tables are used in almost every class builder method. They are defined as:
{
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:
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:
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:
No region #init found in path: /opt/allium-docs/docs/reference/snippets/code/CarMixin.luaThe sections will also use an arbitrary "entrypoint" script representing the main entrypoint:
No region #init found in path: /opt/allium-docs/docs/reference/snippets/code/entrypoint.luamixinClassBuilder: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
index-string: method name to later apply a hook onto this mixin withmixin.get().
Returns
userdata [instance]- A Mixin Method Builder
mixinClassBuilder:build(mixinId)
Builds the mixin.
Parameters
mixinId-string: A unique ID representing the mixin class. Used to obtain the hook used to define methods in this mixin.
Usage
mixin.to("com.example.Car")
:method("negateInitSpeed")
:inject({
method = { "<init>(I)V" },
at = { "TAIL" }
})
:build()
:build("car_mixin")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
annotations-table: An annotation table that matches the@Accessorannotation.
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
annotations-table: An annotation table that matches the@Accessorannotation.
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
annotations-table: An annotation table that matches the@Accessorannotation.
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
annotations-table: An annotation table that matches the@Invokerannotation.
Returns
userdata [instance]- The mixin builder
mixinBuilder:build(mixinId)
Builds the mixin.
Parameters
mixinId-string: A unique ID representing the duck interface. Used to obtain the interface for later use.
Usage
mixin.to("com.example.Car", nil, nil, true)
:accessor({ "speed" })
:getAccessor({ "wheels" })
:setAccessor({ "gas" })
:invoker({ "isSpeeding()Z" })
:build("accessible_car")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())