Skip to content

Plugin API

Danila Rassokhin edited this page Mar 29, 2023 · 9 revisions

Plugin API provides a way for developers to extend CompaJ system with new features. There are two ways to load plugins:

  • as packaged jar libs
  • as raw source code

Packaged plugins

Install

Build system

You can add Plugin API as a dependency in your maven, gradle, e.t.c project:

<dependency>
  <artifactId>plugin-api</artifactId>
  <groupId>tech.hiddenproject.compaj</groupId>
  // Use provided scope to exclude plugin-api from packaged jar
  // It will be provided by CompaJ at runtime
  <scope>provided</scope>
  <version>0.0.3</version>
</dependency>

Manual

To use Plugin API without build system just download latest release jar at releases page and include it in your project.

Provider class

Creation

Your plugin can contain provider class which will be created during loading process. Such class must meet the following requirements:

  • have constructor with no arguments
  • implement tech.hiddenproject.compaj.plugin.api.CompaJPlugin interface

CompaJ creates instances of all provider classes, so you can run any actions in constructor.

Example

public class MyPlugin implements CompaJPlugin {

  private static final Logger LOGGER = LoggerFactory.getLogger(MyPlugin.class);
  
  public MyPlugin() {
    LOGGER.info("Plugin started: {}", this.getClass());
  }
  
}

Export

Providers can export other classes or themselves to CompaJ. It means that such classes will be auto imported in CompaJ environment and will be available for users by their names without explicit imports.

Exports

To export other classes use @Exports(Class[]). Assume we have some MyClass:

public class MyClass {

  public static double pI() {
    return Math.PI;
  }

}

To export this class into CompaJ:

@Exports(MyClass.class)
public class MyPlugin implements CompaJPlugin {

  private static final Logger LOGGER = LoggerFactory.getLogger(MyPlugin.class);

  public MyPlugin() {
    LOGGER.info("Plugin started: {}", this.getClass());
  }

}

Now MyClass will be available in CompaJ:

> myClass = :MyClass
tech.hiddenproject.plugin.example.MyClass@56de6d6b
> MyClass.pI()
3.141592653589793

ExportsSelf

Providers can export themselves with @ExportSelf:

@ExportsSelf
public class MyPlugin implements CompaJPlugin {

  private static final Logger LOGGER = LoggerFactory.getLogger(MyPlugin.class);

  public MyPlugin() {
    LOGGER.info("Plugin started: {}", this.getClass());
  }
  
  public static int myFunc() {
    return 5;
  }

}

Now MyPlugin will be available in CompaJ:

> MyPlugin.myFunc()
5

Make your plugin visible to CompaJ

CompaJ uses standard ServiceLoader from JDK. So to make your plugin visible you need to:

  1. Create resource folder if it doesn't exists
  2. Create META-INF folder
  3. Create services folder inside META-INF
  4. Create file with name tech.hiddenproject.compaj.plugin.api.CompaJPlugin and no extension
  5. Place all canonical (package + class name) provider (classes implement CompaJPlugin) class names on each new line

Result structure should look like:

├── resources
│   ├── META-INF
│   │   ├── tech.hiddenproject.compaj.plugin.api.CompaJPlugin
│

File content should look like this:

tech.hiddenproject.plugin.example.MyPlugin
tech.hiddenproject.plugin.example.AnotherPlugin

Loading

Packaged plugins can be loaded as external libraries. Read about libraries loading here.

Raw plugins

Raw plugins are raw source code files. This way is preferable, cause users can see source code and check it for danger code.

Development

Raw plugins are Groovy/Java/CompaJ source code which will be compiled by CompaJ on startup. It has equal requirements as Java code:

  • each package is a different folder
  • file name must be same as class name
  • manual class imports required

But unlike java programs CompaJ plugins can have many top-level classes in one file.

Plugins are located in ${user.home}/CompaJ/plugins/. Each plugin must be located inside it's own folder.

Example

Assume we have package tech.hiddenproject. Plugin structure looks like this:

├── ${user.home}
│   ├── CompaJ
│   │   ├── plugins
│   │   │   ├── tech
│   │   │   │   ├── hiddenproject
│   │   │   │   │   ├── Main.cjp //package tech.hiddenproject
│   │   │   │   │   │   ├── myplugin
│   │   │   │   │   │   │   ├── SomeClass.cjp // package tech.hiddenproject.myplugin

Main class

All plugins must have class called Main. It must be located in root plugin folder as Main.cjp file. Main class can have public static void main(String... args) function (as standard java program). It will be called on plugin loading. You also can import all other classes that should be imported in CompaJ, so users can use them without explicit import.

Example

package pl
import pl.TestPlugin // users can use just TestPlugin, without pl. prefix

class Main {
	static String s = "HI";
	private static String s;
	public static void main(args) { println s }
	public static int q(args) {
	  	MyPlugin.q(args)
	}
}

class MyPlugin {
	public static int q(arg) { 
		arg * arg
	}
}

Interact with other plugins

To interact with other plugins and main CompaJ components you can use EventPublisher. All communication is based on Pub-Sub pattern where components can send events with payloads to some topics and subscribe on that events.

GROOVY-3010 bug

According to https://issues.apache.org/jira/browse/GROOVY-3010, Groovy ignores private modifier on class fields. In future it will be fixed with https://github.com/stansonhealth/ast-framework, but for now you can use inner private classes to encapsulate fields.

GUI

Plugin API for Desktop app is still in development. For now CompaJ allow developers to add new menus on menu bar.

Menu

See example of new menus creation here and here.