From the very beginning Kendal was designed to be open for new extensions. Here we present a short guide how to make your own custom functionality.
First thing that has to be done is obviously project creation. Although Kendal works in any kind of Java project, and using any build system, for simplicity reasons we are going to create a Maven project. This project will contain handler implementation and annotation definition. It will be also responsible for handler registration to Kendal.
After project is created we have to add dependencies to it. In this manual we assume that we work with
projects managed by Maven. In such case, it is very easy to add required dependencies. Just add this snipped to your pom.xml file:
<dependencies>
<dependency>
<groupId>com.github.projectkendal</groupId>
<artifactId>processor</artifactId>
<version>LATEST_KENDAL_VERSION</version>
</dependency>
</dependencies>
Remember to replace LATEST_KENDAL_VERSION with the latest Kendal version that is available.
You can check it for example here:
https://mvnrepository.com/artifact/com.github.projectkendal/Kendal
To make SPI mechanism work correctly we have to disable annotation processing in our handler's project.
It is usually done by adding -proc:none parameter when executing the compiler. It Maven it can be
achieved by adding the following code to the pom.xml file:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- Disable annotation processing for ourselves. -->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
Whatever logic you may want to implement, you must attach it to the Kendal processing process.
First step to do it is to create a class implementing kendal.api.KendalHandler<T extends Annotation> interface.
This interface defines one method: void handle(Collection<Node> handledNodes, AstHelper helper).
This method is called by processor for each registered handler. First parameter collection of nodes
representing annotations. In runtime, that collection will contain all occurrences of annotations (or inheriting
annotations) of type with which handler is connected via type parameter <T extends Annotation> declaration.
Let's assume we want to create a handler for our awesome feature. This feature will rely on annotation @Awesome,
that will be allowed to be put on classes declarations. This way, programmer will claim that he/she wants this class to be
automatically refactored to be awesome. First step to achieve this is to create annotation and its corresponding handler:
@Target(ElementType.TYPE)
public @interface Awesome { }
public class AwesomeHandler implements KendalHandler<Awesome> {
@Override
public void handle(Collection<Node> handledNodes, AstHelper helper) {
// some logic
}
}
Next step is to let Kendal know about your handler. You can do it using SPI mechanism. That is quite simple.
It is enough to create in your project's resources directory a file: META-INF/services/kendal.api.KendalHandler.
This file should contain a list of handlers implemented in the project, each in the new line.
In the case of our example project, this file's content will be very simple:
kendal.apiExperiments.handler.AwesomeHandler
After this is done, our project is fully functional Kendal handler.
Now we can start implementing our awesome feature. As you already learned, method handle is the core of a handler.
We know that first parameter is a collection of annotation nodes. Now it is time to learn about the second parameter - AstHelper helper.
This is a very useful class, it aims at facilitating whatever you may want to do to AST tree.
It delivers methods that allow AST nodes modification. It also shares some more specific helpers:
AstNodeBuilder, AstValidator and AstUtils. They contain methods which are
often needed in projects of this kind. Using them saves your time and lines of code.
After we finish our handler, we can start using it in other projects. In order to do this, we have to add some
dependencies to the target project. These are com.github.projectkendal.processor and your own project.
If we want to add our custom handler to some other application we have to add the following code to the pom.xml file of that application:
<dependencies>
<dependency>
<groupId>com.github.projectkendal</groupId>
<artifactId>processor</artifactId>
<version>LATEST_KENDAL_VERSION</version>
</dependency>
<dependency>
<groupId>your.package</groupId>
<artifactId>awesome</artifactId>
<version>AWESOME_HANDLER_VERSION</version>
</dependency>
</dependencies>
Then we can start using our annotation:
@Awesome
public class SomeClass {
// logic
}
Our AwesomeHandler will be run whenever there is at least one usage of @Awesome annotation
in the target project. Each such usage will be included in handledNodes collection that is passed
to handle method of our handler.