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
WARNING
The standard class builder is one of the most in-development aspects of Allium. Expect it to change dramatically over the course of the Beta.
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 "usage" section of each method will build out a Dog class in Lua, starting with:
local Animal = require("com.example.Animal")
local dogClassBuilder = java.extendClass(Animal)classBuilder:override(methodName, parameters, access)
Override an existing method of the parent class.
Parameters
methodName-string: The name of the method to override.parameters-table<userdata [class]>: The parameters of the method to override.access-{ static = boolean? }: Access flags for the overriding method. Setstaticif the method to override is static.func-function: The function to call for handling when the method is invoked.
Usage
In addition to overriding the method we must supply a function to the builder on the index matching methodName. If the overriding method is not static then the first parameter to the submitted function is this, followed by the rest of the parameters specified in parameters.
dogClassBuilder:override("noise", {java.boolean, java.int}, {})
function dogClassBuilder:noise(this, chasing, packSize)
if this:makesNoise() then
-- We create isRunning() later, but can use it here!
if this: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()
endclassBuilder:method(methodName, parameters, returnClass, access)
Create an entirely new method on the class.
Parameters
methodName-string: The method name. Must not exist in any parent classes.parameters-table<userdata [class]>: A table representing the parameter types in order.returnClass-userdata [class]: The class to be returned by this method.access-{ abstract = boolean?, static = boolean? }: Access flags for overriding the method.func-function: The function to call for handling when the method is invoked. If the parameteraccess.abstractistrue, this parameter is ignored.
Usage
If the method to be created is abstract, a function does not have to be submitted. If the method is not static then the first parameter to the submitted function is this, followed by the rest of the parameters specified in parameters.
dogClassBuilder:method("isRunning", {}, java.boolean, {})
function dogClassBuilder:isRunning(this)
return this.speed >= 5
endclassBuilder:build()
Builds the class.
Returns
userdata [class]: The completed class.
Usage
local Dog = dogClassBuilder:build()
local inu = Dog(0)
print(inu:noise()) -- prints "*woof!* *woof!*"Mixin Class Builder
Obtained from mixin.to(), 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:
local mixinBuilder = mixin.to("com.example.Car")
local mixinInterfaceBuilder = mixin.to("com.example.Car", nil, nil, true)The sections will also use an arbitrary "entrypoint" script representing either the static or dynamic entrypoint:
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))mixinBuilder:createInjectMethod(hookId, methodAnnotations, sugarParameters)
Create an inject method. Can not be used on mixin builders where duck is true. See mixin.to().
Parameters
hookId-string: hook ID to later apply a hook onto this mixin withmixin.get().methodAnnotations-table<userdata [instance]>: Table of annotations to apply to the injector method. Requires exactly one injector annotation. See Mixin Library - Annotations.sugarParameters-table<userdata [instance]>: Table of sugar parameters to apply after the last parameter of the injector method. See Mixin Library - Sugars.
Usage
Modify the constructor to negate the added 5 speed:
mixinBuilder:createInjectMethod("negate_init_speed", {
mixin.annotation.inject({
method = { "<init>(I)V" },
at = { "TAIL" }
})
})Then in either the static or dynamic script entrypoint:
mixin.get("negate_init_speed"):hook(function(this, speed)
-- override the speed that was previously set.
this.speed = speed
end)TIP
To inject into constructors, and static blocks, use <init>()V and <clinit>()V as target method names, respectively. Don't forget to fill in parameters!
mixinInterfaceBuilder:accessor(annotations)
Defines a setter and getter accessor using the @Accessor annotation. Can only be used on mixin builders where duck is true. See mixin.to().
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.
Usage
Add a setter and getter accessor for the speed:
mixinInterfaceBuilder:accessor({ "speed" })Mess with it later on in the static or dynamic entrypoints:
sedan:setSpeed(10)
print(sedan:getSpeed())mixinInterfaceBuilder:getAccessor(annotations)
Defines a getter accessor using the @Accessor annotation. Can only be used on mixin builders where duck is true. See mixin.to().
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.
Usage
Add a getter accessor for the number of wheels:
mixinInterfaceBuilder:getAccessor({ "wheels" })Mess with it later on in the static or dynamic entrypoints:
print("sedan has", sedan:getWheels(), "wheels")mixinInterfaceBuilder:setAccessor(annotations)
Defines a setter accessor using the @Accessor annotation. Can only be used on mixin builders where duck is true. See mixin.to().
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.
Usage
Add a setter accessor for gas:
mixinInterfaceBuilder:setAccessor({ "gas" })Mess with it later on in the static or dynamic entrypoints:
sedan:setGas(150)mixinInterfaceBuilder:invoker(annotations)
Defines an invoker using the @Invoker annotation. Can only be used on mixin builders where duck is true. See mixin.to().
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.
Usage
Add an invoker for isSpeeding():
mixinInterfaceBuilder:invoker({ "isSpeeding()Z" })Mess with it later on in the static or dynamic entrypoints:
print("sedan speeding:", sedan:invokeIsSpeeding())mixinBuilder:build(mixinId)
Builds the mixin.
Parameters
mixinId?-string: A unique ID representing the mixin class. Used to obtain the duck interface for accessing fields and invoking methods. Only required if the mixin builder was created withduckset totrue.
Usage
mixinBuilder:build()
mixinInterfaceBuilder:build("accessible_car")