Skip to content

myth-MC/callbacks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

6 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

callbacks

Latest release Pull requests Issues License
A Java library for the management of event flows.

🧲 Quick navigation
  1. πŸ“š Information
  2. πŸ“₯ Project Setup
  3. πŸ–ŠοΈ Generating Callbacks
  4. ⛓️‍πŸ’₯ Using Handlers and Listeners
  5. ❗️ Invoking a Callback
  6. πŸ“• References

πŸ“š Information

Callbacks is an annotation-based Java library for managing event flows. It works by generating callback classes (source files) at build time which can be intercepted with custom handlers and listeners.

  • Handlers provide access to the callback's object so that it can be handled, hence the name.
  • Listeners give access to the object's parameters, which are mirrored from the callback's constructor and passed to the listener when a callback is handled. Listeners cannot modify the object at all, they just listen to the result.

πŸ“₯ Project Setup

Maven

<repositories>
  <repository>
    <id>myth-mc-releases</id>
    <name>myth-MC Repository</name>
    <url>https://repo.mythmc.ovh/releases</url>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupId>ovh.mythmc</groupId>
    <artifactId>callbacks-lib</artifactId>
    <version>$latest$</version>
    <scope>compile/<scope>
  </dependency>
</dependencies>

Gradle Kotlin

repositories {
    maven {
        name = "mythmc"
        url = uri("https://repo.mythmc.ovh/releases")
    }
}

dependencies {
    implementation("ovh.mythmc:callbacks-lib:$latest$")
}

Gradle Groovy

repositories {
    maven {
        name = 'mythmc'
        url = 'https://repo.mythmc.ovh/releases'
    }
}

dependencies {
    compileOnly 'ovh.mythmc:callbacks-lib:$latest$'
}

πŸ–ŠοΈ Generating Callbacks

Simple Callback Class

Generating a callback class can be as simple as putting a single @Callback annotation above your class' definition. For example:

@Callback
public class Example {

  public final String exampleField;

  public Example(String exampleField) {
    this.exampleField = exampleField;
  }

}

This will generate a class named ExampleCallback in the same package as the source class.

Private fields

Classes with private fields and public getters require some additional configuration by using the @CallbackFieldGetter annotation:

@Callback
@CallbackFieldGetter(field = "exampleField", getter = "getExampleField()")
public class Example {

  private final String exampleField;

  public Example(String exampleField) {
    this.exampleField = exampleField;
  }

  public String getExampleField() {
    return exampleField;
  }

}

Tip

The @CallbackFieldGetter annotation is marked as repeteable, which means that you can use it as many times as you need. You can also group these annotations with @CallbackFieldGetters:

@CallbackFieldGetters({
 @CallbackFieldGetter(field = "exampleField", getter = "getExampleField()"),
 @CallbackFieldGetter(field = "exampleField2", getter = "getExampleField2()")
})

Inheritance

When extending a @Callback annotated class, all of its callback field getters will be inherited from the super class. This means that we can easily extend our Example class with even more fields:

@Callback
@CallbackFieldGetter(field = "newField", getter = "getNewField()")
public class ExtendExample extends Example {

  private final String newField;

  public ExtendExample(String exampleField, String newField) {
    super(exampleField);
    this.newField = newField;
  }

  public String getNewField() {
    return newField;
  }

}

Warning

The @Callback annotation is not inherited.

Constructors

You may also find cases where you might want to use a specific constructor from your class. This can be done too by using the constructor property within the @Callback annotation:

@Callback(constructor = 2)
public class ConstructorExample {

  public String field;

  ConstructorExample() { // 1
  }

  public ConstructorExample(String field) { // 2
    this.field = field;
  }

}

Record Classes

Record classes are also supported:

@Callback
public record ExampleRecord(String field) {

}

⛓️‍πŸ’₯ Using Handlers and Listeners

Handlers and listeners allow us to intercept callbacks and handle them accordingly.

Handlers

Handlers provide access to the callback's object so that it can be handled, hence the name. Let's register one in our ConstructorExampleCallback and modify the value of field:

var callbackInstance = ConstructorExampleCallback.INSTANCE; // Singleton instance of our callback
var handlerIdentifier = IdentifierKey.of("group", "identifier"); // Unique identifier of our handler. You can also use a namespaced string ("group:identifier")

callbackInstance.registerHandler(handlerIdentifier, constructorExample -> {
  constructorExample.field = "Hello world!";
});

Listeners

Listeners give access to the object's parameters, which are mirrored from the callback's constructor and passed to the listener when a callback has been handled. Listeners cannot modify the object at all, they just listen to the result. Let's register one in our 'ConstructorExampleCallback' and listen to the result:

var callbackInstance = ConstructorExampleCallback.INSTANCE; // Singleton instance of our callback
var listenerIdentifier = IdentifierKey.of("group", "identifier"); // Unique identifier of our listener. You can also use a namespaced string ("group:identifier")

callbackInstance.registerListener(listenerIdentifier, field -> {
  System.out.println(field);
});

Generic Types

If the callback object uses generic types, we'll need to specify the types we're expecting while registering handlers or listeners. For example:

callbackInstance.registerListener(listenerIdentifier, field -> {
  System.out.println(field);
}, String.class);

❗️ Invoking a Callback

Once we have our handlers and listeners registered, we can invoke the callback:

var callbackInstance = ConstructorExampleCallback.INSTANCE; // Singleton instance of our callback

callbackInstance.invoke(new ConstructorExample(""), result -> { // We can get the result of the callback (which will be a ConstructorExample as well)
  System.out.println("Callback result: " + result.toString());
});

callbackInstance.invoke(new ConstructorExample("")); // Or we can ignore it

πŸ“• References

Most of the information on this README comes from our blog post "Callbacks: a modern approach to events".

These are some projects we've used as inspiration for the library: