Non-Visual Plugins (Java)

This article describes how to create a new non-visual Java plugin, what features it provides and how they can be used.

Getting Started

A Java-based Hydra plugin project consists of a three parts:

  • Plugin Descriptor - a special class that provides plugin information like its name, version and, the most important part, reference to the class that actually implements the plugin itself.
  • Plugin Interface(s) - one or more interfaces that are implemented by the plugin. Only methods defined in these interfaces can be called by the plugin host application.
  • Plugin Implementation - a class that implements the plugin interface(s).

Hydra for Java plugins can be created in any language that supports JVM as a target platform (Java, Kotlin etc). The only requirement is that this language should be able to produce .jar files. Any .jar file providing the 3 parts mentioned above can be loaded by Hydra host application.

Hydra for Java is shipped with plugin project templates for all 4 Elements family languages. The next section will describe a newly created NonVisual Plugin project based on this template.

NonVisual Plugin project review

This section contains a detailed review of Hydra for Java plugin project elements.

Plugin Descriptor

The purpose of this class is to provide the information required by a Hydra host application to properly load any use the plugin.

This class should implement the com.remobjects.hydra.PluginDescriptor interface contained in the com.remobjects.hydra.jar file shipped with Hydra for Java.

public class PluginDescriptor implements com.remobjects.hydra.PluginDescriptor {

    @Override
    public String getName() {
        return "NonVisual Plugin";
    }

    ...

    @SuppressWarnings("rawtypes")
    @Override
    public Class getPluginClass() {
        return PluginImplementation.class;
    }
}
type
  PluginDescriptor = public class(com.remobjects.hydra.PluginDescriptor)
  public
    property Name: String read 'NonVisual Plugin';
    property Description: String read 'Hydra for Java non-visual plugin';
    property UserData: String read '';
    property MajorVersion: Int32 read 1;
    property MinorVersion: Int32 read 0;
    property PluginClass: &Class read typeOf(PluginImplementation);
  end;
public class PluginDescriptor : com.remobjects.hydra.PluginDescriptor
{
    public string Name
    {
        get
        {
            return "NonVisual Plugin";
        }
    }

    ...

    public Class PluginClass
    {
        get
        {
            return typeof(PluginImplementation);
        }
    }
}
open class PluginDescriptor : com.remobjects.hydra.PluginDescriptor {
    public var Name: String! {
        get {
            return "NonVisual Plugin"
        }
    }

    ...

    public var PluginClass: Class! {
        get {
            return dynamicType(PluginImplementation)
        }
    }
}

As displayed above this class provides plugin metainformation like its name, description, version, as well as a reference to the class containing actual plugin implementation.

Plugin Interface

By its definition a plugin has to expose some implementation to its host application. This part of the plugin project defines methods that can be accessed by its host.

These class should directly or indirectly extend the com.remobjects.hydra.HYCrossPlatformNonVisualPlugin interface contained in the com.remobjects.hydra.jar file shipped with Hydra for Java.

public interface PluginInterface extends com.remobjects.hydra.HYCrossPlatformNonVisualPlugin {
    // Define plugin interface methods here
}
type
  PluginInterface = public interface(com.remobjects.hydra.HYCrossPlatformNonVisualPlugin)
    // Define plugin interface methods here
  end;
public interface PluginInterface : com.remobjects.hydra.HYCrossPlatformNonVisualPlugin
{
    // Define plugin interface methods here
}
public protocol PluginInterface : com.remobjects.hydra.HYCrossPlatformNonVisualPlugin {
    // Define plugin interface methods here
}

Methods defined in the plugin interface must follow the limitations described in the corresponding section.

Plugin Implementation

This is a class that implements the plugin interface(s). This class will be instantiated when the plugin will be requested by a host application.

public class PluginImplementation implements PluginInterface {

    public void start() {
    }

    public void stop() {
    }

    public void pause() {
    }

    public void resume() {
    }
}
type
  PluginImplementation = public class(PluginInterface)
  public
    method start(); empty;
    method stop(); empty;
    method pause(); empty;
    method resume(); empty;
  end;
public class PluginImplementation : PluginInterface
{
    public void start()
    {
    }

    public void stop()
    {
    }

    public void pause()
    {
    }

    public void resume()
    {
    }
}
open class PluginImplementation : PluginInterface {
    public func start() {
    }

    public func stop() {
    }

    public func pause() {
    }

    public func resume() {
    }
}

Note a set of 4 methods (start, stop, pause, resume) that are defined in the PluginImplementation class. These methods are defined in the com.remobjects.hydra.HYCrossPlatformNonVisualPlugin interface and are a set of methods implemented by any non-visual plugin regardless of its platform (.NET, Delphi or Java).

These methods can be called on a plugin even if its interface has not been imported into the host application project (see below).

Plugin method limitations

The features set supported by the NonVisual Java plugins is more limited that one supported by Delphi or .NET plugins.

Only a following Java types can be used as plugin method parameter or result types:

  • Boolean, Boolean[]
  • Char, Char[]
  • String, String[]
  • Byte, Byte[]
  • Short, Short[]
  • Integer, Integer[]
  • Long, Long[]
  • Float, Float[]
  • Double, Double[]

This limitation means that complex types have to be serialized or decomposed to be passed to or from plugin. Also there is no support of any form of callbacks or plugin-raised events.

Using the Plugin

Loading Plugin

The following prerequisites have to be met to allow a host application to load Java plugins:

  • JVM installed. Its bitness should match the one of the host application. This means that if the host application is built as a x86 app then to load a Java plugin it will require a x86 version of JVM.
  • com.remobjects.hydra.jar file should be placed next to the host application assembly. This .jar file contains a Java code required to load and manage Hydra for Java plugins.

Note: The com.remobjects.hydra.jar file used to load Hydra for Java plugins should match the com.remobjects.hydra.jar file used to build the plugins.

Once prerequisites are met all what is required to load a Java plugin is to call the LoadModule method of the host's ModuleManager instance:

moduleManager.LoadModule("com.hydra.sample.nonvisual.plugin.jar");
moduleManager.LoadModule('com.hydra.sample.nonvisual.plugin.jar');
moduleManager.LoadModule("com.hydra.sample.nonvisual.plugin.jar");
moduleManager.LoadModule('com.hydra.sample.nonvisual.plugin.jar')

Note: Path to the .jar file should contain only alphanumeric symbols. In case it would contain chars like # plugin loading will fail with obscure Class not found message.

Default Interface

Once the plugin is loaded it can be instantiated and used via the usual Hydra API. Note that NonVisual plugin by default supports methods Start, Stop, Pause, Resume.

var pluginInstance = ((INonVisualPlugin)(module.CreateInstance(plugin)));
pluginInstance.Start();
var pluginInstance := INonVisualPlugin(&module.CreateInstance(plugin));
pluginInstance.Start();
var pluginInstance = module.CreateInstance(plugin) as INonVisualPlugin;
pluginInstance.Start();
var pluginInstance = (module.CreateInstance(plugin) as? INonVisualPlugin)
pluginInstance.Start()

Custom Interface

This section shows how to access methods defined in the plugin's custom interface.

To access plugin's custom interface methods this plugin should be first imported into the host application. This can be done via a button in the solution explorer's header. Press it and select the plugin .jar in the dialog opened.

Plugin import procedure will create a code file containing interface definitions(s) and a wrapper class that will be used by Hydra to perform JNI calls.

public interface ICalculatorPlugin : RemObjects.Hydra.CrossPlatform.IHYCrossPlatformNonVisualPlugin
{
    double sumValues(double[] arg0);
}

[RemObjects.Hydra.CrossPlatform.JavaClass("com/hydra/sample/nonvisual/plugin/NonVisualPlugin")]
public partial class NonVisualPlugin : RemObjects.Hydra.Host.Java.JavaNonVisualPluginWrapper, ICalculatorPlugin
{
    public NonVisualPlugin(RemObjects.Hydra.Host.Java.IJavaReflection jvm, System.IntPtr classId) : base(jvm, classId)
    {
        // base(jvm, classId)
    }

    public double sumValues(double[] arg0)
    {
        System.IntPtr methodId = this.fJVM.GetMethodId(this.fClassId, "sumValues", "([D)D");
        return this.fJVM.CallMethod<double>(this.fInstanceId, methodId, (arg0 as object));
    }
}

It is not recommended to manually edit this file.

Note: After import the plugin .jar file will be cached by IDE integration code, possibly locking the .jar file and preventing importer code from accessing updated .jar file. Visual Studio restart is required to clear that cache.

Once the plugin is imported its methods can be accessed by casting the created plugin instance to the imported interface:

ICalculatorPlugin calculator = ((ICalculatorPlugin)(module.CreateInstance(plugin)));
calculator.sumValues(((double[])({1, 2, 3, 4})));
var calculator := ICalculatorPlugin(&module.CreateInstance(plugin));
calculator.sumValues([ 1.0, 2.0, 3.0, 4.0 ]);
var calculator = module.CreateInstance(plugin) as ICalculatorPlugin;
calculator.sumValues(new double[] { 1.0, 2.0, 3.0, 4.0 });
var calculator: ICalculatorPlugin! = (module.CreateInstance(plugin) as? ICalculatorPlugin)
calculator.sumValues(([1, 2, 3, 4] as? Double[]))