WitchCraft Framework
0.7.0Dependency injection | Commands framework | Plugin.yml generation | JDBC | Json messages
WitchCraft
WitchCraft Framework is a java framework for creating Bukkit plugins. It provides annotation-based command configuration and plugin.yml file generation.
Setup
If you want to use WitchCraft Framework with building tools like maven or gradle , you will need to add Ivan1pl's repository.
Maven
<repositories>
<repository>
<id>ivan1pl</id>
<name>ivan1pl</name>
<url>http://nexus.ivan1pl.com/repository/maven-releases/</url>
</repository>
</repositories>Gradle
repositories {
maven {
url 'http://nexus.ivan1pl.com/repository/maven-releases/'
}
}Dependency injection
WitchCraft Framework will create instances of your classes for you, and it will also link them together. Your classes can depend on other classes or configuration values. The framework will create a single instance of each managed class. Dependencies are injected by constructor parameters.
Setup
You will need to add witchcraft-plugin library to your project.
Add dependency on witchcraft-plugin :
Maven
<dependencies>
<dependency>
<groupId>com.ivan1pl.witchcraft</groupId>
<artifactId>witchcraft-plugin</artifactId>
<version>0.7.0</version>
<scope>compile</scope>
</dependency>
</dependencies>Gradle
dependencies {
compile "com.ivan1pl.witchcraft:witchcraft-plugin:0.7.0"
}Then simply extend WitchCraftPlugin instead of JavaPlugin in your main plugin class. That's it.
All classes annotated with Managed or Command and placed in the same package tree as your main plugin class will be managed by WitchCraft's dependency injection feature. If you want to put your managed classes in a different package tree (i.e. neither in the same package nor in any of its subpackages) from the main plugin class, use Plugin annotation on your main plugin class to tell the framework where to search for them.
Limitations
There are several limitations to how dependency injection works. Each managed class should have exactly one constructor. It should be public and all its parameters should be instances of other managed classes or configuration values (default constructors count and are valid). Consider the following example:
@Managed
public class A {
public A ( B dep1, C dep2, @ConfigurationValue ( "some.configuration.key" ) int someConfigurationKey ) {
//constructor body...
}
}
@Managed
public class B {
}
public class C {
}
In that example dependency injection will fail. Class A depends on classes B and C and on configuration value some.configuration.key . However, class C is not managed by dependency injection feature (there's no Managed annotation), which means that the framework will not be able to create an instance of that class and pass it as parameter to the constructor of class A .
Another limitation is that there can be no dependency cycles. Consider the following example:
@Managed
public class A {
public A ( B dep ) { }
}
@Managed
public class B {
public B ( C dep ) { }
}
@Managed
public class C {
public C ( A dep ) { }
}
In this example, in order to create an instance of class A , the framework needs to create an instance of class B first. To create an instance of class B it needs to create an instance of class C first. That is impossible because it would then need to create an instance of class A which is already under construction.
Example
Take a look at a working example of dependency injection configuration containing both Managed and Command annotated classes (commands will be explained in the next section).
File config.yml :
witchcraft:
example:
counter:
initVal: 42
File Counter.java :
@Managed
public class Counter {
private final JavaPlugin javaPlugin ;
private int value ;
public Counter ( JavaPlugin javaPlugin, @ConfigurationValue ( "witchcraft.example.counter.initVal" ) int initVal ) {
this . javaPlugin = javaPlugin ;
this . value = initVal ;
}
public void increase ( ) {
value ++;
javaPlugin. getLogger ( ) . info ( "Increase counter" ) ;
}
public void decrease ( ) {
value --;
javaPlugin. getLogger ( ) . info ( "Decrease counter" ) ;
}
public int getValue ( ) {
return value ;
}
}
File WcAdd.java :
@Command ( name = "cadd" , description = "Increase counter" )
public class WcAdd {
private final Counter counter ;
public WcAdd ( Counter counter ) {
this . counter = counter ;
}
@SubCommand
@Description ( shortDescription = "Increase counter" ,
detailedDescription = "Add 1 to current value of the global counter" )
public void increaseCounter ( @Sender CommandSender commandSender ) {
counter. increase ( ) ;
commandSender. sendMessage ( "Increased counter, current value: " + counter. getValue ( ) ) ;
}
}
File WcSubtract.java :
@Command ( name = "csubtract" , description = "Decrease counter" )
public class WcSubtract {
private final Counter counter ;
public WcSubtract ( Counter counter ) {
this . counter = counter ;
}
@SubCommand
@Description ( shortDescription = "Decrease counter" ,
detailedDescription = "Subtract 1 from current value of the global counter" )
public void decreaseCounter ( @Sender CommandSender commandSender ) {
counter. decrease ( ) ;
commandSender. sendMessage ( "Decreased counter, current value: " + counter. getValue ( ) ) ;
}
}
File WitchCraftExamplePlugin.java :
public class WitchCraftExamplePlugin extends WitchCraftPlugin {
@Override
protected void preInit ( ) {
saveDefaultConfig ( ) ;
}
}
In this example a single instance of Counter class is created and shared between instances of WcAdd and WcSubtract classes. This can be seen if you execute the following command chain:
- /cadd
- /cadd
- /cadd
- /csubtract
- /csubtract
- /cadd
You will get the following messages after executing commands in that order:
- Increased counter, current value: 43
- Increased counter, current value: 44
- Increased counter, current value: 45
- Decreased counter, current value: 44
- Decreased counter, current value: 43
- Increased counter, current value: 44
Predefined managed classes
The framework comes with its own set of classes annotated with Managed annotation. All classes within packages com.ivan1pl.witchcraft.commands.adapters and com.ivan1pl.witchcraft.commands.completers will be automatically instantiated and you can use them as dependencies of your managed classes.
There are two classes that are not annotated with Managed or Command , but you can still use them as dependencies in your managed classes. One of them is your main plugin class. The other is WitchCraftContext , which is a class that is used to manage the entire dependency injection feature and contains instances of all managed classes (which you can acquire at runtime by invoking method get, e.g. witchCraftContext.get(JavaPlugin.class) will give you your plugin instance).
Modules
WitchCraft allows its users to create their own modules and hook them into the framework. This section explains how to do that. Predefined modules will be explained in separate sections.
To be able to create modules, you need the exact same dependencies as for the dependency injection feature.
Creating a module
To create a module, you will have to create an annotation with runtime retention policy. Then annotate your annotation with Module annotation and annotate your main plugin class with it. That's it, your module is now enabled. WitchCraft will scan the entire package tree of that annotation for managed classes and add them to its context. You can then depend on them in your plugin.
It may not seem very useful, and in fact in regular plugin development it is not. This is a feature designed for library/API developers who want to hook their work into the framework. Also, some of the predefined features are provided as modules, so that you can enable them if you need them, but you do not have to depend on them if you do not.
Aspects
Modules come with an interesting feature that allows you to use AOP advices in your plugin or library. If you're not familiar with aspect-oriented programming, basically it allows you to add some behaviour to already existing methods, which is used to separate distinct functionalities. For example, if you want to enable logging of method invocations, you could create a method containing your business logic and add logging as an advice so that it does not clutter your code.
Of course there are many more useful things that can be done with aspects, for example they could be used for database connection management, refreshing configuration, validating some data before method execution.
Creating an aspect is very easy. Simply create a class, mark it as Managed , implement Aspect interface and add it to your module's aspect list (aspects attribute in Module annotation).
The interface is very simple and self-explanatory. It contains the following methods:
- void beforeMethod(Object self, Method method, Method originalMethod, Object[] args) - execute advice that should happen before method execution; self , method and args give you informations about the object on which the method is being executed, the method itself and its arguments, originalMethod is the original method that would have been invoked if there were no advices, you should not use it directly but you can for example process its annotations.
- void afterMethod(Object self, Method method, Method originalMethod, Object[] args) - execute advice that should happen after method execution.
- InvocationCallback aroundMethod(InvocationCallback proceed) - execute advice that should happen around method execution.
- int getPriority() - execution priority of this aspect. The lower the number, the earlier it will be executed. Default is 0 (you do not have to implement this method).
The only method that may require some additional explaining is aroundMethod . Basically it allows you to add logic before and after the invocation at the same time. The actual invocation is carried in proceed parameter, wrapped by InvocationCallback functional interface. The interface itself contains only one method Object apply(Object self, Method method, Method originalMethod, Object[] args) which is used to invoke the method. Wherever you wish to invoke it in your code, simply use proceed.apply(self, method, originalMethod, args); .
Aspects will be applied to all methods in all managed classes, expect classes implementing Aspect or Listener interface.
Commands
WitchCraft Framework provides a very simple way of defining commands using annotated classes and functions. The framework will take care of everything: annotated classes will be automatically registered and added to command executor.
Setup
You will need to add witchcraft-plugin library to your project.
Add dependency on witchcraft-plugin :
Maven
<dependencies>
<dependency>
<groupId>com.ivan1pl.witchcraft</groupId>
<artifactId>witchcraft-plugin</artifactId>
<version>0.7.0</version>
<scope>compile</scope>
</dependency>
</dependencies>Gradle
dependencies {
compile "com.ivan1pl.witchcraft:witchcraft-plugin:0.7.0"
}Then simply extend WitchCraftPlugin instead of JavaPlugin in your main plugin class. That's it.
If you want to put your command in a different package tree (i.e. neither in the same package nor in any of its subpackages) from the main plugin class, use Plugin annotation on your main plugin class to tell the framework where to search for them.
In most cases, this is all you need:
public class WitchCraftExamplePlugin extends WitchCraftPlugin {
}
Additional logic during initialization
You may want to add some logic during plugin initialization. The method onEnable has been marked as final which means you can no longer override it. Two other methods have been provided instead: preInit and postInit . The first one is invoked before WitchCraft initializes (before the plugin is scanned for commands and before dependency injection). The second one is invoked after WitchCraft initializes.
A natural use for preInit method is saving default config:
@Override
protected void preInit ( ) {
saveDefaultConfig ( ) ;
}
Commands and subcommands
Note: the framework assumes that each command may have many subcommands. If your command does not use any, simply define a single subcommand with empty name.
To define a new command, create a class within some package (the framework will not scan the default package) and annotate it with Command annotation:
@Command ( name = "witchcraft-test" )
public class TestCommand {
//...
}
Of course, you can provide more data to the annotation: command description, required permission node and aliases:
@Command ( name = "witchcraft-test" , aliases = { "wtest" , "wctest" } ,
permission = "witchcraft-example-plugin.witchcraft-test" ,
description = "Test command for witchcraft-examples" )
public class TestCommand {
//...
}
Each command needs to have at least one subcommand (perhaps with empty name). To create a subcommand, create a public method returning void :
@SubCommand ( "subcommand" )
public void subcommand ( /* parameters */ ) {
//command body
}
You can give the method any name you want, the actual name will be taken from the SubCommand annotation. You can also provide a description and permission node required to execute the subcommand:
@SubCommand ( value = "sub" , permission = "witchcraft-example-plugin.witchcraft-test.sub" )
@Description ( shortDescription = "Short description" , detailedDescription = "Detailed description" )
public void sub ( /* parameters */ ) {
//command body
}
Command parameters
This is where the real magic starts. The framework will automatically resolve your subcommand method's parameters, and it will also generate tab completions for them! Let's use an example here:
Similar projects
Plugin →Let Me Despawn
Improves performance by tweaking mob despawn rules. Say bye to pesky unintentional persistent mobs.
25MSueta
JourneyMap
Real-time map used for mapping in-game or your browser as you explore.
14.7MSueta
Emotecraft
Create your own emotes in Minecraft.
7.5MSueta
Simple Tpa
A simple tpa plugin which sends requests
5.4MSueta
Common Network
Forge and Fabric Unified Networking Solution
3.9MSueta
Set Home
A lightweight plugin for players to set their homes.
3.8MSueta