Sueta

WitchCraft Framework

0.7.0

Dependency injection | Commands framework | Plugin.yml generation | JDBC | Json messages

·Plugin

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 →