API State: Unfinished
Todo
This part of the API is only planned.Do not use anything of this part of the API yet. It will change in the future.
Unfinished
This part of the API is brand-new. It will be changed if there are any bugs, missing features or usability improvements.It is not recommended relying on this part of the API, it will most likely change.
Draft
Our own usage and testing has shown that this part of the API is complete and seems bug free. However, other plugins may have other use cases which are not covered by our testing. Therefore, please go ahead and use this API part. Let us know if there are missing features or bugs. This API part will be changed if there are more bugs, missing features or usability improvements.Please use this part of the API and give us feedback!
Stable
Both our own and third party testing showed that this part of the API is complete. Only bugs and major conceptual problems would lead to more changes.This part of the API should be safe to use. We try to keep it compatible with previous versions if changes are needed.
Instruction Overviewπ
The Instruction refers to the user-defined string that specifies conditions, events, items, and similar elements.
Info
The org.betonquest.betonquest.api.instruction package contains the Instruction interface and related objects.
Reading the Instruction Objectπ
The Instruction object is responsible for parsing the instruction string provided by the user and splitting it into
arguments. You can retrieve required arguments or optional key-value arguments one at a time through a parser chain.
Required arguments are those specified at the beginning of an instruction string,
such as add someTag in the tag event.
If the instruction string contains an argument formatted as arg:something and you request the optional arg,
it will return something. If there is no optional argument by that name,
it will return an empty Optional for that argument.
If an error occurs the Instruction object will automatically throw a QuestException.
This may happen whenever there are no more arguments in the user's instruction or if the argument cannot
be parsed into the requested type.
Instruction Chainπ
The instruction chain offers java stream-like methods to read, parse, convert and ultimately retrieve a argument from an instruction. It minimally consists of two steps: the argument parsing step and the argument retrieval step.
To offer an overview before explaing all details, the following examples show excerpts from factories in BetonQuest:
Compare experience condition.
public PlayerCondition parsePlayer(final Instruction instruction) throws QuestException {
final Argument<Number> amount = instruction.number().get();
//creating the condition object and returning it...
}
Compare itemdurability event.
public PlayerEvent parsePlayer(final Instruction instruction) throws QuestException {
final Argument<EquipmentSlot> slot = instruction.enumeration(EquipmentSlot.class).get();
final Argument<PointType> operation = instruction.enumeration(PointType.class).get();
final Argument<Number> amount = instruction.number().get();
final boolean ignoreUnbreakable = instruction.hasArgument("ignoreUnbreakable");
final boolean ignoreEvents = instruction.hasArgument("ignoreEvents");
//creating event object and returning it...
}
Compare delay objective.
public Objective parseInstruction(final Instruction instruction) throws QuestException {
final Argument<Number> delay = instruction.number().atLeast(0).get();
final Argument<Number> interval = instruction.number().atLeast(1).get("interval", 20 * 10);
//creating objective object and returning it...
}
Argument Parsingπ
In the argument parsing step the method of converting a string into a java type is decided.
The instruction chain may be accessed conveniently, starting directly from any Instruction instance.
| Call | Description |
|---|---|
.number() |
Default parser for java.lang.Number covering both integer and floating point values |
.string() |
Default parser for java.lang.String |
.bool() |
Default parser for java.lang.Boolean |
.location() |
Default parser for org.bukkit.Location |
.world() |
Default parser for org.bukkit.World |
.item() |
Default parser for org.betonquest.betonquest.api.instruction.type.ItemWrapper representing items defined in BetonQuest |
.blockSelector() |
Default parser for org.betonquest.betonquest.api.instruction.type.BlockSelector representing a matcher for a group of bukkit materials |
.vector() |
Default parser for org.bukkit.util.Vector |
.uuid() |
Default parser for java.util.UUID |
.component() |
Default parser for net.kyori.adventure.text.Component |
.packageIdentifier() |
Default parser for package identifiers producing a java.lang.String. This parser simply expands the existing string value to a full package identifier using the instruction's package if necessary. |
.enumeration(Enum<E>) |
Default parser for an enum of the given type |
.parse(Parser<P>) |
Using a custom parser matching the functional interfaces InstructionArgumentParser or SimpleArgumentParser |
Argument Retrievalπ
The argument retrieval step is required after the argument parsing and represents the wrapping into a Argument<T>
instance to be carried on into events, conditions and objectives.
To have valid calls the Number parser is used as an example, but naturally any parser will do.
| Call | Type | Description |
|---|---|---|
.get() |
Argument<Number> |
Retrieves an argument of the next value in order from the instruction |
.get("amount") |
Optional<Argument<Number>> |
Retrieves an optional argument of the value with the key amount from the instruction |
.get("amount", 10) |
Argument<Number> |
Retrieves an optional argument of the value with the key amount from the instruction or gets an argument with default value |
.getList() |
Argument<List<Number>> |
Retrieves an argument of the next value in order from the instruction parsed as list |
.getList("amounts") |
Optional<Argument<List<Number>>> |
Retrieves an optional argument of the value with the key amounts from the instruction parsed as list |
.getList("amounts",List.of(1,5,10)) |
Argument<List<Number>> |
Retrieves an optional argument of the value with the key amounts from the instruction parsed as list or gets an argument with default list as value |
Advanced Argument Parsingπ
Parsers via the chain offer more functionality than just parsing a string into a specific type. By chaining different kinds of operations, the outcome can be modified in certain ways.
Validationsπ
You can validate an argument using the validate(ValueValidator<T>) or validate(ValueValidator<T>, String) method.
This method will throw a QuestException if the predicate does not match the argument (aka returns false).
It may be used to check if a value is within a certain range or if a value satisfies a certain condition
outside matching just the type.
In the error message a single %s maybe used to inline the wrong value.
Examples:
Even Number
The example below checks if the parsed number is even and throws an error if it is not.
instruction.number().validate(i -> i.intValue() % 2)
instruction.number().validate(i -> i.intValue() % 2, "Number must be even, but was %s.")
Non-Empty-String
The example below checks if the parsed string is not empty and throws an error if it is empty.
instruction.string().validate(s -> !s.isEmpty())
instruction.string().validate(s -> !s.isEmpty(), "Empty strings are not permitted.")
Prefilterπ
You can use prefilters to modify the argument's parsing result without actually parsing it.
This can be useful to parse additional cases that are not covered by the default parsers.
Use the prefilter(String expected, T fixedValue) method to decorate the parser with a prefilter.
The matcher to find the expected value uses String#equalsIgnoreCase(String).
Examples:
infinity Number Value
The example below parses the string "infinity" into the number Double.POSITIVE_INFINITY while still allowing other
number values.
instruction.number().prefilter("infinity", Double.POSITIVE_INFINITY)
any Value
The example below parses the string "any" into the enum EntityType without EntityType allowing any as value.
instruction.enumeration(EntityType.class).prefilterOptional("any", null)
Warning
The usage of prefilter(String expected, @Nullable T fixedValue) is required here,
since the chain has a strict non-null policy to prevent exceptions.
This method will capsule the result in an Optional. To retrieve the any value you are safe to assume
that an Optional#isEmpty() corresponds to any while a present value represents a parsed known enum constant.
Another unknown value will cause it to throw an exception.
Mapπ
You can also modify the value or map it to a different type after parsing without reading it from the argument.
Use the map(QuestionFunction<T,U>) method to decorate the parser with such a mapping function.
Example:
Explicit Integer Value
The examples below show two ways of parsing an explicit integer value. Both come with advantages and disadvantages. While the first one parses all kinds of numbers and fits them into an integer, the second one fails when trying to parse anything other than a valid integer value.
instruction.number().map(Number::intValue)
instruction.parse(Integer::parseInt)
Special Case: Numberπ
The instruction.number() parser offers a more convenient way to create a number argument with range limits.
Additional methods are available to set the minimum and maximum values of the number.
While the methods atLeast(int) and atMost(int) are using inclusive bounds, inRange(int,int) is using an exclusive
upper bound as commonly used in other Java classes.
instruction.number().atLeast(0); // value has to be 0 or greater
instruction.number().atMost(10); // value has to be 10 or less
instruction.number().atLeast(1).atMost(10); // value has to be in the interval [1,10]
instruction.number().inRange(0, 100); // value has to be in the interval [0,99]