Welcome to the JPlugin Framework! This framework is designed to simplify and streamline the development of plugins, enabling developers to create, manage, and utilize classes efficiently and intuitively.
- Overview
- Installation
- Project Structure
- Annotations
- Usage Examples
- Advanced Plug-in Features
- Considerations
- Troubleshooting
- License
The JPlugin Framework provides a set of tools to facilitate modular plugin development. With annotations for automatic plugin management, this framework simplifies the plugin lifecycle, from initialization to execution.
- Plug-in Management: Annotations that allow for the definition and discovery of plugins.
- Dependency Injection: Declare necessary dependencies for your plugin's operation.
- Categorization: Organize plugins into categories for better management.
- Dynamic Initialization: Specify how a plugin should be initialized through custom implementations.
- Event Handling: Built-in support for handling events across plugins.
- Configuration Management: Easy management of configuration files for plugins.
For now, there's no public artifact at the Maven Central for this. To use the JPlugin framework. You should install it manually at your project using Maven Guide to installing 3rd party JARs
The basic structure of the project is as follows:
jplugin/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── codes/
│ │ │ └── laivy/
│ │ │ └── plugin/
│ │ │ ├── annotation/
│ │ │ ├── category/
│ │ │ ├── exception/
│ │ │ ├── factory/
│ │ │ └── handlers/
│ │ │ ├── info/
│ │ │ ├── initializer/
│ │ │ └── main/
│ │ └── resources/
│ └── test/
│ └── java/
│ └── codes/
│ └── laivy/
│ └── plugin/
└── pom.xml
The JPlugin Framework uses several key annotations to define the behavior of plugins. Below is a detailed explanation of each annotation, along with their parameters and examples.
The @Plugin annotation marks a class as a plugin component managed by the framework. It is the primary annotation that provides essential metadata about the plugin.
Usage
import java.io.Closeable;
import java.io.IOException;
@Plugin(name = "MyPlugin", description = "A sample plugin for demonstration purposes")
public final class MyPlugin implements Closeable {
public MyPlugin() {
System.out.println("MyPlugin has been enabled!");
}
@Override
public void close() {
System.out.println("MyPlugin has been disabled!");
// The Closeable interface is optional!
}
}
name
: A human-readable name for the plugin. Defaults to the class name if empty.description
: A brief description of the plugin's functionality.
The @Initializer annotation specifies the implementation responsible for initializing a plugin. This allows for custom setup logic during the plugin lifecycle.
Usage
import codes.laivy.plugin.initializer.ConstructorPluginInitializer;
import java.io.Closeable;
@Initializer(type = ConstructorPluginInitializer.class) // This is the default initializer!
@Plugin(name = "Initializer Plug-in", description = "A plugin with a custom initializer.")
public class InitializerPlugin implements Closeable {
public InitializerPlugin() {
System.out.println("Plug-in has been enabled!");
}
@Override
public void close() {
System.out.println("Plug-in has been disabled!");
}
}
type
: Specifies the PluginInitializer implementation used to initialize the plugin.
The @Dependency
annotation declares a required dependency for a plugin class. This is essential for ensuring that all necessary components are available before a plugin is loaded.
Usage
import codes.laivy.plugin.annotation.Initializer;
import codes.laivy.plugin.initializer.MethodPluginInitializer;
@Dependency(type = SomeLibrary.class) // It can have multiples dependencies!
@Initializer(type = MethodPluginInitializer.class)
@Plugin(name = "LibraryDependentPlugin", description = "A plugin that uses SomeLibrary.")
public class LibraryDependentPlugin {
public static void initialize() {
// This plugin only will be initialized when SomeLibrary has initialized.
SomeLibrary.doSomething();
}
public static void interrupt() {
// This plugin will be interrupted BEFORE the dependencies
}
}
type
: Specifies the class that represents the required dependency. The framework ensures that the dependency is loaded before the plugin.
The @Category annotation represents a category for a plugin, allowing the addition of special handlers for plugins in that category. This aids in organizing and managing plugins based on functionality.
Usage
@Category(name = "Utility") // It can have multiples categories!
@Initializer(type = MethodPluginInitializer.class)
@Plugin(name = "Utility Plug-in", description = "A plugin that falls under the utility category.")
public class UtilityPlugin {
public static void initialize() {
System.out.println("Utility Plug-in is enabled!");
}
}
import codes.laivy.plugin.factory.handlers.PluginHandler;
import codes.laivy.plugin.PluginInfo;
import codes.laivy.plugin.main.Plugins;
import org.jetbrains.annotations.NotNull;
public static void main(String[] args) {
// It will create a new category called 'Utility' and add a custom handler
Plugins.getCategory("Utility").getHandlers().add(new PluginHandler() {
@Override
public void start(@NotNull PluginInfo info) {
System.out.println("The utility plugin '" + info + "' has been started.");
// Do cool stuff here for all Utility category plugins
}
});
// Initializes all the plugins (incluidng the UtilityPlugin)
Plugins.initializeAll();
}
name
: A unique identifier for the category the plugin belongs to. This helps in filtering and managing plugins by functionality.
The @Priority annotation defines the loading priority of a plugin within the system. It allows developers to specify an integer value that determines the order in which plugins are initialized. In this context, lower integer values represent higher priority, meaning that plugins with lower numbers are loaded before those with higher numbers.
- No Annotation: If a plugin does not have the @Priority annotation, it is assumed to have a default priority of
0
. - Annotation Without a Value: If a plugin is annotated with @Priority without explicitly specifying a value, it defaults to
-1
. This effectively gives such plugins a higher priority compared to plugins that rely on the default0
.
Plugins are loaded in ascending order based on their priority values. For example:
- A plugin annotated with
@Priority(-5)
will be loaded before a plugin annotated with@Priority(2)
.
In frameworks that are dependency-aware, the dependency relationships always take precedence over numeric priority values. This means that if Plugin A is a dependency of Plugin B, Plugin A will be loaded first, regardless of the numerical priorities assigned.
// A plugin with a higher priority (loaded earlier)
@Priority(1)
@Plugin(name = "CorePlugin", description = "Handles core system functions")
public class CorePlugin {
// Plugin implementation details...
}
// A plugin with a lower priority (loaded later)
@Priority(10)
@Plugin(name = "ExtraPlugin", description = "Provides additional features")
public class ExtraPlugin {
// Plugin implementation details...
}
// A plugin with default priority (-1), which will load before plugins without @Priority annotation
@Priority
@Plugin(name = "MiddlewarePlugin", description = "Handles middleware tasks")
public class MiddlewarePlugin {
// Plugin implementation details...
}
Now, let's explore some practical examples of using the JPlugin Framework. Each example demonstrates a different aspect of the framework.
When you create a class with the @Plugin
annotation, if you simply execute your application it will not initialize at all. You need to initialize it manually, there's a lot of ways to do this.
This is the most recommended way to load plugins, because it's faster.
import codes.laivy.plugin.main.Plugins;
public static void main(String[] args) {
// Plugins.initialize("com.project.plugins", false); // Will load all the plugins inside the package 'com.project.plugins'
Plugins.initialize("com.project.plugins", true); // Will load all the plugins inside the package 'com.project.plugins' and the others packages inside it recursively
}
This is not the faster way to load plugins, but is the most precise one. You can control a lot of parameters to load plugins here.
import codes.laivy.plugin.factory.PluginFinder;
import codes.laivy.plugin.main.Plugins;
public static void main(String[] args) {
PluginFinder finder = Plugins.find();
finder.addPackage("codes.laivy"); // Will load all plug-ins into this package (recursively by default)
finder.addClassLoader(Main.class.getClassLoader()); // Using a specific class loader
finder.categories("HTTP Page"); // Only plug-ins with a specific category
// Load all and print to the console
System.out.println("Successfully loaded " + finder.load().length + " plug-ins.");
}
Here’s how to create a simple plug-in using the framework. This example will demonstrate the minimal setup required to get a plug-in running.
@Initializer(type = MethodPluginInitializer.class)
@Plugin(name = "HelloWorldPlugin", description = "A simple plug-in that greets users.")
public class HelloWorldPlugin {
public static void initialize() {
System.out.println("Hello, World!");
}
public static void interrupt() {
System.out.println("Goodbye, World!");
}
}
Explanation: The onEnable method is called when the plug-in is loaded, and it will print "Hello, World!" to the console. The onDisable method is called when the plug-in is unloaded.
Let's create a plug-in that depends on an external library, ensuring that the required dependencies are available at runtime.
/**
* First, it loads all the books (Book plug-in) before the bookshelf to control them.
*/
@Dependency(type = Book.class)
@Initializer(type = MethodPluginInitializer.class)
@Plugin(name = "Bookshelf", description = "A mastered controller of the books")
public class Bookshelf {
public static void initialize() {
// It is safe to execute the Book's plug-in methods because the Book plug-in
// is active and running with all the features.
System.out.println("Initializing bookshelf...");
System.out.println("Detected " + Book.count() + " available books.");
}
}
Explanation: This plug-in will only be enabled if SomeLibrary is available in the project. The onEnable method calls a method from the external library to demonstrate interaction.
Organizing plug-ins into categories can help in managing them effectively. Here’s how to categorize a plug-in for better organization.
package my.website.pages;
import codes.laivy.plugin.annotation.Initializer;
import codes.laivy.plugin.initializer.ConstructorPluginInitializer;
/**
* This is a plug-in that represents an HTTP page at an website environment
* As you can see, this class is private and final, but the JPlugin still can access it.
* <p>
* It uses the {@link ConstructorPluginInitializer} that initializes using the class constructor, not the {@code initialize} methods.
*/
@Category(name = "HTTP Page")
@Initializer(type = ConstructorPluginInitializer.class) // Initializes using the empty declared constructor
@Plugin(name = "Authentication Page", description = "The authentication page that allow users to access the dashboard at the website.")
final class Authentication extends Page {
// The constructor must have no parameters, and could have any visilibity.
private Authentication() {
super("/authentication"); // The page path
}
@Override
public void execute(HttpClient client, HttpData data) {
// Do stuff here
}
}
import codes.laivy.plugin.factory.handlers.PluginHandler;
import codes.laivy.plugin.exception.PluginInitializeException;
import codes.laivy.plugin.PluginInfo;
import codes.laivy.plugin.main.Plugins;
import org.jetbrains.annotations.NotNull;
public static void main(String[] args) {
// Create 'HTTP Page' category and add a plug-in handler to it
Plugins.getCategory("HTTP Page").getHandlers().add(new HTTPPageHandler());
// Initialize all plug-ins at the "my.website.pages" package, recursively.
Plugins.find().addPackage("my.website.pages", true).load();
// Plugins.find().addPackage("my.website.pages", true).load((c) -> c.getSuperclass() == Page.class); // Load only plug-ins that extends Page, to avoid issues.
}
// Classes
private static final class HTTPPageHandler implements PluginHandler {
@Override
public boolean accept(@NotNull PluginInfo.Builder builder) {
return checkReference(builder.getReference());
}
@Override // Take a read at the javadocs to know the difference between those two methods!
public boolean accept(@NotNull PluginInfo info) {
return checkReference(info.getReference());
}
public boolean checkReference(@NotNull Class<?> reference) {
// Check if the class extends Page
// If a class with the 'HTTP Page' category but that doesn't extends the Page class
// Tries to load, this method will be called. And if returned false, the loading will be interrupted.
boolean allow = info.getReference().getSuperclass() == Page.class;
if (!allow) {
System.out.println("The class " + builder.getReference().getName() + " is trying to load as HTTP Page but doesn't extends Page!");
}
return allow;
}
@Override
public void start(@NotNull PluginInfo info) throws PluginInitializeException {
// When the plug-in starts, it adds the Page instance to the HTTP Environment to start
// receiving connections as a valid http page
HTTPEnvironment.getPages().add((Page) info.getInstance());
}
}
Explanation: This plug-in easily creates an authentication page using categories, you just need to create a class that extends Page, add the @Category(name = "HTTP Page")
to it, mark as a @Plugin
and it's done.
The plug-in has advanced features that allows developers enhance the power of the plug-ins and controls a lot of things more!
You can define custom initialization logic for your plug-in using an initializer, allowing for more complex setup procedures.
import codes.laivy.plugin.PluginInfo;
import codes.laivy.plugin.category.PluginCategory;
public final class MyPluginInitializer extends PluginInitializer {
// Every plug-in initializer *MUST* have an empty declared constructor like that.
private MyPluginInitializer() {
}
@Override
public @NotNull PluginInfo.Builder create(@NotNull Class<?> reference, @Nullable String name, @Nullable String description, @NotNull PluginInfo @NotNull [] dependencies, @NotNull PluginCategory @NotNull [] categories) {
// Creates a PluginInfo.Builder with your own initialization mechanic here (custom #build method)
}
}
@Initializer(type = MyPluginInitializer.class)
@Plugin(name = "CustomInitializerPlugin", description = "A plug-in with a custom initializer.")
public class CustomInitializerPlugin {
// The initialization method should be created by you.
}
Explanation: The MyPluginInitializer class creates a custom PluginInfo object with custom #start
and #stop
methods, allowing to change completely how the plug-in should be initialized/interrupted.
- Thread Safety: Ensure that any shared resources among plug-ins are properly synchronized to avoid concurrency issues.
- Dependency Management: Clearly define and document the dependencies required for each plug-in to function properly.
- Version Compatibility: Maintain backward compatibility when updating the framework or existing plug-ins.
- Plug-in Not Loading: If you created the plug-in correctly but the initialization hasn't been performing, check if you're starting the framework using
Plugins.initializeAll()
method or similar.
We welcome contributions to the JPlugin Framework! To contribute:
- Fork the repository.
- Create a new branch for your feature or bugfix.
- Make your changes and write tests to cover new functionality.
- Submit a pull request detailing your changes.
This project is licensed under the MIT License. Feel free to use, modify, and distribute it as you wish, with proper attribution to the original authors.