1 How to create and use plugins in ProM.

estrapadesherbetSoftware and s/w Development

Nov 18, 2013 (3 years and 4 months ago)

76 views

1

How to create
and use plugins

in ProM.


In this document, it is assumed that ProM has been checked out from prom.win.tue.nl and that
the Eclipse environment is set up as explained in “How to become a ProM developer”.

Furthermore, basic knowledge of Eclipse

is assumed.


After starting ProM for the first time, it is wise to get it to look like the figure below.




The panels on the right
-
hand side give an overview of executed plugins, available provided
objects and registered connections. The panel on t
he bottom shows messages produced by
plugins.

In this document, we use these panels to indicate what a plugin does. For example,
after executing a plugin, provided objects or connections are created, which can be seen in the
respective panels.


Provided Objects

Plugins

Connect
ions

Messages

VM state


2

Simple me
thod
-
based
Hello World


We start the tutorial with the simplest possible plugin, following the “hello world” tradition.
All examples in this tutorial use the “test” package, but of course any package can be used.


2.1

Defining the plugin.


To create the plugi
n create the following class:




This simple piece of code defines a ProM plugin. The actual plugin logic is contained in the
method body, lines 14 to 16, where a String is produced containing the text “Hello World”.
The paramet
er of this method is a PluginContext object, which is a requirement for all ProM
plugins.


The fact that this method constitutes a plugin is defined by the @Plugin annotation. This
annotation gives the plugin a name in line 8.
Furthermore, it defines the
following:


-

The parameter labels in line 9, which is an empty list, as out plugin does not require
parameters other than the context in which it should be executed,


-

The return labels in line 10. This is an array of Strings, labeling the returned objects.
In this case, only one object is returned, namely the “Hello world string”,


-

The return types in line 11. This is an array of Class objects, defining the types of the
returned objects. In this case, the type is “String.class”, i.e. the object returned by t
his
plugin is of type String,


-

Whether or not the plugin is user accessible in line 12. This should normally be set to
true,


1.

package test;

2.


3.

import org.processmining.framework.plugin.PluginContext;

4.

import org.processmining.framework.plugin.annotations.Plugin;

5.


6.

public class HelloWorldPlugin {

7.


8.


@Plugin(name = "Hello World Plugin",

9.



parameterLabels = {},

10.



returnLabels = {

"Hello world string" },

11.



returnTypes
= { String.class },

12.



userAccessible = true,

13.



help = "Produces the string: 'Hello world'")

14.


public static String helloWorld(PluginContext context) {

15.



return "Hello World";

16.


}

17.

}

-

Some explanatory text about the plugin in line 13 (optional).


By using the @Plugin annotation, the framework knows that this meth
od constitutes a plugin.
When ProM is started, all class files are scanned for this annotation and plugins are registered.

If
a
method
annotated with the @Plugin annotation is static,
as in our example
,

then the
framework, when asked to execute this plugi
n, will invoke the method as if somewhere in the
framework
the following
code was called:


HelloWorldPlugin.helloWorld(context);


Before we extend the example with parameters, we first explain how this plugin can be
executed.


2.2

Executing the plugin


After a
dding this class to the ProM project , run “ProM with GUI”.

When ProM is running go
to “Plugins”, “Chaining Panel”. In the panel that appears now, the left
-
most drop
-
down box
should contain “Hello World”, as shown below.




This “Hello World

Plugin
” is
a
plugin known to the framework under
the name defined in line
8

of the code
.


Select “Hello World

Plugin
” and click on the “Add” button next to the list. The panel should
now look like this:




This grey box indicates that the there is a “Hello World

Plug
in
”, which has no input and
produces an “Hello world string” as output of type “String”
, which is indicated between the
brackets
. The fact that the box is grey indicates that the plugin requires no input. Now click on
“Execute…” to run the plugin.


After r
unning the plugin, the result is provided to the framework as a provided object, which
can be seen in the provided objects panel. When right
-
clicking on this object, a dialog
appears, on which the “Show” option can be selected.







After
clicking on “
Show”, ProM should look like this:





Provided Object:

“Hello world string”

䕸ec畴敤⁰汵u楮i

“Hello World Plugin”

噩獵s汩za瑩潮o⁴桥
灲潶楤p搠潢橥dt
W

“Hello World”

2.3

Multiple return objects


In the previous example, we created a plugin that returns a single string, However, plugins
can return multiple objects. Consider the following plugin definition, which is a new method
i
n the HelloWorldPlugin class:




As plugins are identified through their names, we gave this plugin a new name, namely “Hello
Worlds Plugin”.


Again, the logic of the plugin is defined in the method body. Note that the return ty
pe of the
method is actually

Object[]
, indicating that this plugin returns multiple objects. The
framework does not care about the return type of the method. Instead the types of the objects
returned by a plugin have to be specified by the returnTypes para
meter of the @Plugin
annotation, as is shown in line 4.


In line 3, the labels of the returned objects are provided. The framework requires the list of
return labels of be of the same size as the list of return types. Furthermore, the order of the
returne
d objects is determined here as well.


It is important to realize that the framework cannot verify whether the returned objects are
indeed of the correct types, until the plugin has been executed. If, for example, this method
would not return an array con
taining two strings, but two integers, then the framework throws
an exception. This can be seen in the message panel, as well as the plugin panel, where a red
mark appears next to the plugin.


When selected in the chaining panel, this plugin looks as follo
ws:


1.

@Plugin(name = "Hello Worlds Plugin",

2.


parameterLabels = {},

3.


returnLabels = { "Hello string", "N
umber", "Worlds string"},

4.


returnTypes = { String.class, Integer.class, String.class },

5.


userAccessible = true,

6.


help = "Produces three objects: 'Hello', number, 'world'")

7.

public static Object[] helloWorlds(PluginContext context) {

8.


return new Object[]
{ "Hello", new Integer(6), "Worlds" };

9.

}


After executing this plugin,
three

new provided objects are available in the framework,
namely the “Hello string”
, the “Number”,

and the “World string”.


2.4

Parameters

The next step in plugin development is the development of plugins with parameters. C
onsider
the following plugin to combine worlds
.




In contrast to the first two “hello world” plugins, this plugin requires parameters. Again, these
parameters are specified through a label and a type. However, the type is not p
rovided by an
annotation, but by the method definition. In the example above, three parameters are required:


1)

A String labeled “First string”,


2)

An Integer labeled “Number”, and


3)

A String labeled “Second string”.


Since it requires parameters, this plugin c
annot be executed as such, but this is where the
chaining panel comes in handy. When selecting the “Combine worlds” plugin and adding it to
the chaining panel, the result looks as follows:


1.

@Plugin(name = "Combine worlds",

2.


parameterLabels = {"First string", "Number", "Second string"},

3.


returnLabels = { "First string several second strings" },

4.


returnTypes = { String.class },

5.


userAccessible = tru
e,

6.


help = "Produces one string consisting of the first and a







number of times the third parameter.")

7.

public static Object helloWorlds(PluginContext context, String







first, Integer number, String second) {

8.



String s = first;

9.



for (int i = 0
; i < number; i++) {

10.




s += ","+second;

11.



}

12.



return s;

13.

}



The yellow block represents the “Combine worlds” plugin. On the

left the parameters are
shown, on the right, the returned objects are shown. Note that the plugin is yell
ow, which
indicates that it is not executable since not all parameters are set.


By right
-
clicking on the little square next to the “Number” paramete
r, the framework gives
hints as to where to get the parameters from. There are two menus: “input from plugin” and
“input from object”. The first gives an overview of all plugins of which one of the resulting
objects is of the right type. The second gives a
n overview of all available provided objects of
the right type.




When for example “Number of Hello Worlds Plugin” is selected, the result looks like this:




Here, it is indicated that the number produced by the first plugin serves as input to the sec
ond
plugin. Now, the mouse can be used to connect the other parameters to the other returned
objects, resulting in the “Combine worlds” plugin to become green, as now it is executable.




After executing the plugin, a provided object is created labeled “F
irst string several second
strings”, which equals “Hello,Worlds,Worlds,Worlds,Worlds,Worlds,Worlds”.


2.5

Summary


In this section, we presented an overview of how to create simple, method
-
based plugins. We
conclude with a list of important
points to be kept i
n mind when developing plugins:


1)

Return types, as well as parameter types should not use generics (i.e. List<String> cannot
be used as a parameter).

The reason for this is that the framework is unable to read these generics at runtime, and
hence it is not

possible to know which plugins can serve as input to others.


2)

Arrays can be used both as parameter types as wel as return types. A plugin can specify to
return an object of type
String[].class
. Also, a parameter of this type can be
requested.

When chain
ing, i.e. connecting plugins in the chaining panel, the framework allows users
to, for example, use several strings and/or arrays of strings as input for one parameter of
type String[]. However, no ordering can be specified in the chaining panel. The
frame
work in not capable of disassembling an array into its components, i.e. it is not
possible to execute a plugin that requires a string on an object that is an array of strings.


3)

The chaining panel does not allow for the introduction of loops. However, resul
ts of one
plugin can serve as input for several other plugins.


4)

Plugins should use interfaces in their parameter and return type definitions as much as
possible. For example, a plugin that returns a PetrinetImpl object (which implements the
Petrinet interf
ace) should declare that it returns an object of type Petrinet.class.


In the remainder of this document, we first discuss the notion of a context, and we show how
to develop context
-
specific plugins. Then, we show how to make more elaborate plugins by
us
ing overloading functionality built into ProM.


3

Contexts

In the example plugins of the previous section, we saw that each plugin is called with a
PluginContext parameter. The idea of the PluginContext is that it provides all the necessary
interfaces to com
municate with: 1) the framework, 2) other plugins, and 3) the user.
Plugins
can be executed in various contexts, such as a GUI, or a script context.


In this section, we first discuss the general features available in all contexts. Then, we discuss
specif
ic implementations of contexts and how plugins can be defined that require specific
contexts.



3.1

Logging

One of the most important features of a context is the logging functionality. For this purpose,
two methods are available:


void log(String message);


v
oid log(String message, MessageLevel level);


These methods allow the user to log information. The message level indicates the type of the
information, which can be “Normal”, “Warning”, “Error”, “Test” or “Debug”. If no message
level is specified, “Normal”

is assumed.


Each context has a number of logging listeners associated to it. These listeners receive each
logged message and decide what to do with them.
The GUI for example makes sure that it is
registered as a logging listener on all plugin contexts in

the framework. The log messages are
shown in the message panel. By default, the debug and test messages are turned off, but these
can be turned on by the user.


The code below shows the hello world plugin that logs information about its execution:



53.

package test;

54.


55.

import org.processmining.f
ramework.plugin.PluginContext;

56.

import org.processmining.framework.plugin.annotations.Plugin;

57.

import
org.processmining.framework.plugin.events.Logger.MessageLevel;

58.


59.

public class HelloWorldPlugin {

60.


61.


@Plugin(name = "Hello World Plugin",

62.



parameterLabels =
{},

63.



returnLabels = {
"Hello world string" },

64.



returnTypes = { String.class },

65.



userAccessible = true,

66.



help = "Produces the string: 'Hello world'")

67.


public static String helloWorld(PluginContext context) {

68.



context.log("Started hello world plugin
",








MessageLevel.DEBUG);

69.



return "Hello World";

70.


}

71.

}


3.2

Progress Indicator

Although logging provides a means to signal the user of a plugin about progress, a better way
is to use the progress indicator. Especially if the plugin consists of a number of steps which
take some time this is
a good idea.


The code below shows the combine worlds plugin that indicates progress to the framework:




Lines 16 and 17 set the number of steps of which the progress exists (in this case as many as
the parameter number provid
es). Then, in line 18, a caption is given to the progress. Finally,
in line 28, the progress is increased, which can also be done using setValue(getValue()+1).
Finally, lines 23 to 27 introduce a second pause after each step, to make the progress
observabl
e in the GUI.


When executing this plugin using the output of the “Hello Worlds Plugin” defined earlier as
input. The following is shown in the plugin panel:

1.

package test;

2.


3.

import org.processmining.framework.plugin.PluginContext;

4.

import org.processmining.framework.plugin.annotations.Plugin;

5.

import
org.processmining.framework.plugin.events.Logger.Mess
ageLevel;

6.


7.

public class HelloWorldPlugin {

8.


9.



@Plugin(name = "Combine worlds",

10.


parameterLabels = {"First string", "Number", "Second string"},

11.


returnLabels = { "First string several second strings" },

12.


returnTypes = { String.class },

13.


userAccessible =

true,

14.


help = "Produces one string consisting of the first and a





number of times the third parameter.")

15.


public static Object helloWorlds(PluginContext context, String







first, Integer number, String second) {

16.



context.getProgress().setMinim
um(0);

17.



context.getProgress().setMaximum(number);

18.



context.getProgress().setCaption(








"Constructing hello worlds string");

19.



context.getProgress().setIndeterminate(false);

20.



String s = first;

21.



for (int i = 0; i < number; i++) {

22.




s += "," + secon
d;

23.




try {

24.





Thread.
sleep
(1000);

25.




} catch (InterruptedException e) {

26.





// don't care

27.




}

28.





context.getProgress().inc();

29.





30.



}

31.



return s;

32.


}

33.

}




The plugin panel shows that the “Combine worlds” plugin is being executed in plugin context
wi
th identifier “pc 7”, and at the moment this picture was taken it was halfway through its
execution. Furthermore, the “Hello Worlds Plugin” that produced the input for the “Combine
worlds” plugin was also executed in a separate context with identifier “pc
8”. The execution
of this plugin has finished, as shown by the stop
-
sign.


As soon as the “Combine worlds” plugin finished, the framework collapses the tree structure
in the plugin panel, and only the top
-
level is shown to be completed (in this case plugin

context pc7). However, double clicking on that context expands the tree again.


Besides the progress seen in the plugin panel, there is another interesting phenomenon to be
observed when this combination of plugins is executed. The provided object panel s
hows the
following:



The first three provided objects shown here are the three results of the “Hello Worlds plugin”.
The stop sign in front of these objects shows that these objects are available to the framework.
The last object however shows a play
-
sig
n in front of it. This indicates that this object is still
under construction by a plugin. However, it can be used by the user as input for other plugins.
This is achieved by using futures.


3.3

Futures

A future on an object is a concept that allows users to s
tart plugins on input that is not yet
available. Recall that plugins specify the return types of their methods in the @Plugin
annotation. This allows the framework to know what types of objects to expect when the
execution of a plugin is started, even if t
he objects themselves are not available.


When a plugin is executed, the framework first instantiates a plugin context for that plugin.
Then, futures are created on all expected results. These futures consist of a label and a type.
The type is read from th
e returnTypes specified in the @Plugin annotation and the label is
initially read from the returnLabels specified in the @Plugin annotation.


However, during execution, a plugin can communicate with its future result, by calling
context.getFutureResult(int

number). Here, the integer given as parameter indicates which
future is requested, i.e. in case of multiple return types, multiple futures are created.


Although technically, it is possible that a plugin cancels the computation of its own future,
this is
typically not desirable. Instead, communication with the future should be limited to
calling context.getFutureResult(x
).setLabel(
newLabel), in which case the label of the result is
updated.



Consider the following code:




With

this code, the “Combine worlds” plugin continuously updates the label of the future
result to show how often a world was added. As a result, during execution, the provided
object panel and the plugin panel show something like this:


1.

package test;

2.


3.

import org.processmining.framework.plugin.PluginContext;

4.

import org.processmining.fra
mework.plugin.annotations.Plugin;

5.

import
org.processmining.framework.plugin.events.Logger.MessageLevel;

6.


7.

public class HelloWorldPlugin {

8.


9.



@Plugin(name = "Combine worlds",

10.


parameterLabels = {"First string", "Number", "Second string"},

11.


returnLabels = {

"First string several second strings" },

12.


returnTypes = { String.class },

13.


userAccessible = true,

14.


help = "Produces one string consisting of the first and a





number of times the third parameter.")

15.


public static Object helloWorlds(PluginContext c
ontext, String







first, Integer number, String second) {

16.



context.getProgress().setMinimum(0);

17.



context.getProgress().setMaximum(number);

18.



context.getProgress().setCaption(








"Constructing hello worlds string");

19.



context.getProgress().setInde
terminate(false);

20.



String s = first;

21.



for (int i = 0; i < number; i++) {

22.




s += "," + second;

23.





context.getFutureResult(0).setLabel(









"Hello "+i+" worlds string");

24.




try {

25.





Thread.
sleep
(1000);

26.




} catch (InterruptedException e) {

27.





// do
n't care

28.




}

29.





context.getProgress().inc();

30.





31.



}

32.



context.getFutureResult(0).setLabel(







"Hello "+
number
+" worlds string");

33.



return s;

34.


}

35.

}



Note that the meth
od context.getFutureResult(int x) can only be called from a plugin. As soon
as a plugin has finished, that method throws a class cast exception.


Besides changing the label of the provided object representing the future result of a plugin,
the plugin also
has access to the provided object management system for full control over
provided objects.


3.4

Provided Object
Management

In general, as soon as plugins are invoked, their results are available as provided objects. At
first, these objects are futures, but as

soon as the plugin finished its execution, the future is
replaced by the actual object.


All objects in the framework are handled by the provided object manager, which can be
accessed through the context. However, typically plugins only need a bit of the

available
functionality, namely to create and update provided objects.


Although the framework makes sure that all results returned by a plugin are available as
provided objects, plugins sometimes require more. Consider for example the genetic mining
plug
in. This plugin returns a list of models when it is finished. However, in the mean time,
several models are constructed that might be interesting for the user. The framework
obviously does not know about these intermediate objects. Therefore, the plugin ca
n create its
own provided objects.


A provided object consists of a label and an object.
To create a provided object, the
createProvidedObject method of ProvidedObjectManager should be called. This method
requires not only the label and the object, but als
o the context. This context is required so that
the listeners can be notified of the creation of the provided object.


Once the provided object is created, it gets an ID, which can be used to reference the object
later, for example to update, or to delete
it.


Consider the following code:


1.

package test;

2.


3.

import org.processmining.framework.plugin.PluginContext;

4.

import org.processmining.framew
ork.plugin.annotations.Plugin;

5.

import org.processmining.framework.plugin.events.Logger.











MessageLevel;

6.

import org.processmining.framework.providedobjects.
*;

7.


8.

public class HelloWorldPlugin {

9.


10.



@Plugin(name = "Combine worlds",

11.


parameterLabels = {
"First string", "Number", "Second string"},

12.


returnLabels = { "First string several second strings" },

13.


returnTypes = { String.class },

14.


userAccessible = true,

15.


help = "Produces one string consisting of the first and a





number of times the third p
arameter.")

16.


public static Object helloWorlds(PluginContext context, String







first, Integer number, String second) {

17.



context.getProgress().setMinimum(0);

18.



context.getProgress().setMaximum(number);

19.



context.getProgress().setCaption(








"Const
ructing hello worlds string");

20.



context.getProgress().setIndeterminate(false);



String s = first;

21.



ProvidedObjectID id =





context.getProvidedObjectManager().






createProvidedObject("intermediate string", s,










context);

22.



for (int i = 0; i
< number; i++) {

23.




context.getFutureResult(0).setLabel(









"Hello "+i+" worlds string");

24.




try {

25.





context.getProvidedObjectManager().








changeProvidedObjectObject(id, s);

26.





Thread.
sleep
(1000);

27.




} catch (ProvidedObjectDeletedException e1)

{

28.





// i
f the user deleted this object,

29.






//
then we create it again

30.





id = context.getProvidedObjectManager().







createProvidedObject(








"intermediate string", s, context);

31.




} catch (InterruptedException e) {

32.





// don't care

33.




}

34.




s

+= "," + second;

35.




context.getProgress().inc();

36.



}

37.



context.getFutureResult(0).setLabel(








"Hello "+number+" worlds string");

38.



// The intermediate object is no longer necessary.

39.



try {

40.




context.getProvidedObjectManager().









deleteProvide
dObject(id);

41.



} catch (ProvidedObjectDeletedException e) {

42.




// Don't care

43.



}

44.




return s;

45.


}

46.

}

This
change to the “Combine worlds” plugin show how to create, update and delete
intermediate provided objects. In line 21, the object is first created and the id kept for future
reference. Th
en, in line 25, the provided object is updated to the new string. However, if the
user deleted the object (by right clicking in the GUI)

, an exception is thrown. In this case, the
exception is caught and a new provided object is created in lines 27 to 30.

Finally, in lines 39
to 42, the provided object is deleted.


When this plugin is executed, the provided object and plugin panels show the following:




The provided object containing the future result of the plugin is still shown as not being
available.

However, the intermediate string object is available to the user and can be
visualized by clicking “show”.


When the plugin is finished, the intermediate string object is deleted and removed from the
provided object panel. Any existing visualization is a
utomatically closed.


3.5

Connection

management

The plugins we introduced so far take a number of parameters as input and produce a number
of result objects. In most of the cases however, the input and output objects are related to each
other, e.g. a Marking o
f a Petri net does not have a meaning when combined with another
Petri net. Instead, the Marking and the Petrinet have a connection expressing that the Marking
marks places of the Petrinet.


In the ProM framework relations between objects are stored in co
nnections. Adding
connections or checking for the existence thereof is again done through the
connection
manager, which can be accessed by calling
context
.getConnectionManager()
.



We first explain how to add a connection, for which one method is available

in the connection
manager. This method requires the context to be specified and therefore the PluginContext
also provides an addConnection method, which calls the addConnection of the connection
manager with itself as parameter.


3.5.1

Connections

A connection
is a mapping from a set of labels to objects. Each label represents the role of the
attached object in the connection. Furthermore, the connection itself has a label.


Connections exist in the framework as soon as they are added to the connection manager.

However, when one of the objects in the connection is no longer available to the user (for
example because a provided object was deleted) then the connection ceases to exist. In other
words, the references to the objects in the connection are stored as we
ak references, i.e. if the
object is no longer needed in the framework, then the connection does not keep it in memory.

The check whether or not a connection should still exist is done every time the connection
manager is asked to get connections.



Of
ten,

connections do not need methods to
explicitly represent the connection,
e
.
g
. in the case
of a Marking and a Petrinet, if there is a connection, then the places contained in the marking

are also contained in the net.


Several connections are available in t
he org.processmining.plugins.connections package of
the plugins source folder.


Consider the following

lines

code, taken from
org.processmining.plugins.petrinet.tpn.TpnImport.java:




In line 1,
a
connection is
instantiated. In
this case, a MarkedNetConnection is instantiated,
which assumes (but does not verify) that the places contained in the given marking are also
contained in the net.


It is important to realize that connections should be standardized as much as possible, i.e
. if a
marking and a net are connected, they should use
the MarkedNetConnection to store this
connection


In line 2, the connection is added to the framework through the context. The shown code is a
shorthand for context.getConnectionManager().addConnectio
n(context, c);


3.5.2

Finding connections

Besides registering connections, it is of course also possible to query the connection manager
for existing connections. Several methods are available.


The getConnection
s

methods r
eturn
the

connection
s

between the obje
cts specified, such that
the type of

the connection is assignable from the given connectionType
. If the given type
equals null, then all connections between the given objects are returned.


If no connections
of the right type exist
, then the
connection man
ager

searches for all
available plugins with a ConnectionObjectFactory

annotation, which can be executed in a
child of the given PluginContext

and accept the given objects as input
.


If such plugins exist, the first of these plugins is selected and invoked

on the given objects.
The result is obtained from the plugin and

added to the framework as a connection.

This
connection is then

returned.

More about the ConnectionObjectFactory annotation
can be
found
in section
3.5.3
.


To ob
tain a list of connections in which a certain object is involved, just query the connection
manager for all connections involving that object, where the type parameter equals null.


1.

Connection c =
new MarkedNetConnection(petrinet, new Marking(state))
;

2.

context.addConnection(
c
);

3.5.3

@
ConnectionObjectFactory

This annotation can be used on plugins to signal
the framework that this plugin returns a
connection object. The plugin does not have to register the connection in the framework.
Instead, the
single

object returned by the plugin
has to be an implementation of Connection
is
and the connection manager will

handle the registration of the connection in ProM


3.6

Multi
-
threaded execution


To facilitate multi
-
threading execution, ProM has some built
-
in functionality. Each
PluginContext carries a so
-
called Exector, accessible through the getExecutor() method. This
E
xecutor is a standard java.util.concurrent.Executor. The idea is that a plugin can give
implementations of the interface Runnable to the executor for concurrent execution. See the
javadoc of Executor for more information.


3.7

Plugin Management


Some plugins r
equire the execution of other plugins. If they know in advance which methods
to call in which classes, this can be done in the standard java way. However, ProM also
provides a plugin manager that can be queried for plugins with certain properties.


3.8

Specif
ic Contexts





3.9


The Plugin Life Cycle


When the ProM framework is started, a main plugin context is automatically created. This
main plugin context is used to derive new contexts from in order to execute plugins. Plugin
context
s


When a plugin is executed by the framework, the framework first instantiates a PluginContext
object. The implementing type of this context can for example be the GUI context, or a
command line context.


4

Complex
plugins


So far, this document introduce
d simple plugins, where all computations were performed by a
static method. In this section, we introduce two more complex types of plugins, namely non
-
static method based plugins and overloaded plugins.


58.

package
test;

59.


60.

import javax.swing.JOptionPane;

61.


62.

import org.processmining.contexts.gui.GUIPluginContext;

63.

import org.processmining.framework.plugin.annotations.Plugin;

64.


65.

public class HelloWorld {

66.


67.


@Plugin(name = "Combine unknown",

68.



parameterLabels = { "First strin
g", "Number" },

69.



returnLabels = { "First string several second strings" },

70.



returnTypes = { String.class },

71.



userAccessible = true,

72.



help = "Produces one string consisting of the first and a





number of times a string given as input in a dialog.
")

73.


public static Object helloWorlds(GUIPluginContext context,









String first, Integer number) {

74.



// Ask the user for his world

75.



String w = JOptionPane.
showInputDialog
(

76.





context.getMainFrame(),

77.





"What's the name of your world?",

78.





"Enter
your world",

79.





JOptionPane.
QUESTION_MESSAGE
);

80.



// change your result label

81.



context.getFutureResult(0).setLabel("Hello "+ w +" string");

82.



// return the combined string

83.



return helloWorlds(context, first, number, w);

84.


}

85.

}

4.1

Non
-
static method based plugins

Similar to plugins
based on static methods, you can define plugins based on non
-
static
methods. Consider the following code, which is very similar to the earlier hello
-
world
example, but now the method helloWorld is not static.




When playing aro
und with this plugin in ProM, you’ll find no difference in behavior with the
static version. However, an important conceptual difference exists. If a method annotated with
the @Plugin annotation is not static, as is the case here, then the framework, when

asked to
execute this plugin, will invoke the method as if somewhere in the framework the following
code was called:


HelloWorldPlugin h = new HelloWorldPlugin();

h.helloWorld(context);


Here, an instance of the class HelloWorldPlugin is created first and

for this instance, the
method helloWorld is invoked. This implies that within the method body, all private members
of the class are accessible and instantiated.


Note that the use of a non
-
static method as a plugin requires that the default constructor e
xists,
i.e. the constructor without parameters.



1.

package test;

2.


3.

import org.processmining.framework.plugin.PluginContext;

4.

import org.processmining.framework.plugin.annotations.Plugin;

5.


6.

public class HelloWorldPlugin {

7.


8.



@Plugin(name = "Hello Wo
rld Plugin",

9.




parameterLabels = {},

10.



returnLabels = { "Hello world string" },

11.



returnTypes = { String.class },

12.



userAccessible = true,

13.



help
= "Produces the string: 'Hello world'")

14.


public String helloWorld(PluginContext context) {

15.



return "Hel
lo World";

16.


}

17.

}

4.2

Overloaded plugins

The method based plugins introduced so far
allow for the definition of plugins where the input
types are known. However, many plugins in practice allow to be applied to inputs of multipl
e
types. Consider for example a plugin that constructs a reachability graph of a Petri net. Such a
plugin accepts Petri nets, but also Reset nets (Petri nets with reset arcs), Inhibitor nets (Petri
nets with inhibitor arcs) and Reset/Inhibitor nets.


Furt
hermore, sometimes plugins anly need n
-
out
-
of
-
m parameters. Consider for example a
plugin to open a file. Its input is the filename (which can be of type String, or URI, or File, or
FileInputStream). However, this plugin can also be executed without input,

in which case a
dialog is presented to the user to provide a pointer to the file to be opened. Note however that
such a dialog can only be presented if the plugin is executed in a GUI.


Overloaded plugins allow for the scenario’s mentioned above.

In this

section, we introduce
the concept of plugins with variants, which is how these scenarios can be realized. We first
introduce the idea of optional parameters, before we show parameters with multiple types.


4.2.1

Optional parameters


Consider the code below.


1.

package test;

2.


3.

import javax.swing.JOptionPane;

4.

import org.processmining.contexts.gui.GUIPluginContext;

5.

import org.processmining.framework.plugin.PluginContext;

6.

import org.processmining.framework.plugin.annotations.Plugin;

7.

import org.proces
smining.framework.plugin.annotations.PluginVariant;

8.


9.

@Plugin(name = "Overloaded Hello World Plugin",

10.


parameterLabels = { "First string", "Number", "Second string" },

11.


returnLabels = { "Hello world string" },

12.


returnTypes = { String.class },

13.


userAcces
sible = true,

14.


help = "
The plugin produces 'hello' concatenated with at least one




world. If no world is given, it is requested from the user,




provided that a GUI exists.
")

15.

public class OverloadedHelloWorld {

16.


17.


private String getWorld(GUIPluginContex
t context) {

18.



// Ask the user for his world

19.



String w = JOptionPane.
showInputDialog
(

20.





context.getMainFrame(),

21.





"What's the name of your world?",

22.





"Enter your world",

23.





JOptionPane.
QUESTION_MESSAGE
);

24.



// change your result label

25.



context.ge
tFutureResult(0).setLabel("Hello "+w+" string");

26.



return w;

27.


}

28.


29.


@PluginVariant(variantLabel = "Original hello world",

30.




requiredParameterLabels = {})

31.


public String helloWorld(PluginContext context) {

32.



return "Hello World";

33.


}

34.


35.


@PluginVariant(variant
Label = "hello unknown",

36.




requiredParameterLabels = {})

37.


public String helloUnknown(GUIPluginContext context) {

38.



return "Hello "+getWorld(context);

39.


}

40.



41.


@PluginVariant(variantLabel = "Combine worlds",

42.




requiredParameterLabels = { 0, 1, 2 })

43.


public

Object helloWorlds(PluginContext context, String first,
Integer number, String second) {

44.



String s = first;

45.



for (i
nt i = 0; i < number; i++) {

46.




s += "," + second;

47.



}

48.



return s;

49.


}

50.


51.


@PluginVariant(variantLabel = "Combine unknown
s
",

52.




requiredPara
meterLabels = { 0, 1 })

53.


public Object helloWorlds(GUIPluginContext context, String first,
Integer number) {

54.



// return the combined string, afte
r asking for the world

55.



return helloWorlds(context, first,








number, getWorld(context));

56.


}

57.

}


In
this example, the @Plugin annotation is no longer used on a method, but on a class. This
signals the framework that this is an overloaded plugin, consisting of several variants. This
implies that there should be at least one m
ethod with an @PluginVariant annotation. In this
case there are four.


The @Plugin annotation for this class is the same as for the methods for which we used it
before. Hence, the labels of the parameters have to be specified here, as well as the return
ty
pes of all variants. Therefore, all variants
must

return objects of the same type, i.e. they
should all return a String in this example. Complex return types are allowed, as long as they
are the same for all variants.


The four variants of the plugin provi
ded by this example code
are defined by a label and a list
of required parameters for that variant. Furthermore, they require different contexts to be
executed.

In the table below, a short overview of the four variants is given:


Variant

Available in
conte
xt

Required parameters

Description

Original hello world

All

None

Produces “Hello World”

䡥汬漠畮歮潷o

䝕f湬y

乯湥

Produces “Hello “ followed
by⁡⁵ er
-
獰ec楦楥搠睯w汤.

C潭扩湥⁷潲汤l

䅬A

The “First string”,

the “Number” and

the “Second string”

m牯摵
ces “Hello “, followed
by⁡畭 e爠潦⁴ 浥猠
“world”

C潭扩湥⁵湫湯睮

䝕f湬y

The “First string” and

the “Number”.

Produces “Hello “, followed
by⁡畭 e爠潦⁴ 浥猠m⁵獥爠
獰sc楦楥搠睯d汤


te⁥x灬慩渠桯眠瑨p⁦ a浥m潲欠o獥猠瑨攠景畲⁶ 物r湴猠ny⁥xa浰me⁡g
a楮⸠ff⁴桥⁣桡楮ing⁰ ne氠
is opened, the “Overloaded Hello World Plugin” can be added to the panel, which then looks
as follows:




The plugin is shown in red, since it cannot be executed as such. Recall that there are two
variants available that do not

require input and that can be executed in the GUI context.
Therefore, the framework is unable to decide which on to execute. Right clicking on the
plugin allows the user to select a variant to execute.




After selecting either one of these methods, the

plugin becomes green, as it is now executable.
The “select a method” menu always appears when multiple variants are available on the given
input. If the input is changed and a selected variant can no longer be executed on this input,
the selection is no l
onger valid.


Only when a single variant is selected (which is automatically the case if only one variant is
enabled) a plugin is enabled. In our example, there are four possibilities:


Original hello world:





Hello unknown:





Combine worlds



Com
bine unknowns



After executing any of the two variants that require a world to be specified, the following
dialog appears:



In order to produce this dialog and to change the name of the returned object, a private
method is used, defined in lines 17 to
27. Note that this requires the variants not to be static.
However, static variants are allowed and can be combined with non
-
static variants.


Finally, we like to mention that plugin variants that require a certain context are not available
if that contex
t is not used. For example, the “Hello unknown” variant is not available if the
context which is being used is not a GUIPluginContext. The framework takes care of this, by
checking for the context type when starting.


4.2.2

Parameters with multiple types

In the
previous example, the plugin variants all used different combinations of parameters of
the same type, i.e. the “Combine” unknowns variant requires the parameters labeled “First
string” and “Number”, which, in the method definition are of types “String” and

“Integer”
respectively. However, this is not a requirement imposed by the framework, as shown in the
following code:



1.

package test;

2.


3.

import javax
.swing.JOptionPane;

4.

import org.processmining.contexts.gui.GUIPluginContext;

5.

import org.processmining.framework.plugin.PluginContext;

6.

import org.processmining.framework.plugin.annotations.Plugin;

7.

import org.processmining.framework.plugin.annotations.PluginV
ariant;

8.


9.

@Plugin(name = "Overloaded Hello
Many
World
s
",

10.


parameterLabels = { "First string", "
Large
Number", "Second string"
},

11.


returnLabels = { "Hello world string" },

12.


returnTypes = { String.class },

13.


userAccessible = true,

14.


help = "
The plugin prod
uces 'hello' concatenated with at least one




world. If no world is given, it is requested from the user,




provided that a GUI exists.
")

15.

public class Overloaded
Many
World
s

{

16.


17.


private String getWorld(GUIPluginContext context) {

18.



// Ask the user for his
world

19.



String w = JOptionPane.
showInputDialog
(

20.





context.getMainFrame(),

21.





"What's the name of your world?",

22.





"Enter your world",

23.





JOptionPane.
QUESTION_MESSAGE
);

24.



// change your result label

25.



context.getFutureResult(0).setLabel("Hello "+w+"
string");

26.



return w;

27.


}

28.


29.


@PluginVariant(variantLabel = "Combine
many
worlds",

30.




requiredParameterLabels = { 0, 1, 2 })

31.


public Object helloWorlds(PluginContext context, String first,








Long number, String second) {

32.



String s = first;

33.



for (int
i = 0; i < number; i++) {

34.




s += "," + second;

35.



}

36.



return s;

37.


}

38.


39.


@PluginVariant(variantLabel = "Combine
few
unknowns",

40.



requiredParameterLabels = { 0, 1 })

41.


public Object helloWorlds(GUIPluginContext context, String first,








Integer number) {

42.



// return the combined string, after asking for the world

43.



return helloWorlds(context, first, Long.valueOf(number),








getWorld(context));

44.


}

45.


46.


@PluginVariant(variantLabel = "Combine
many
unknowns",

47.



requiredParameterLabels = { 0, 1 })

48.


public Objec
t helloWorlds(GUIPluginContext context, String first,








Long number) {

49.



// return the combined string, after asking for the world

50.



return helloWorlds(context, first, number,








getWorld(context));

51.


}


52.

}

The code shown above defines three plugin variants.


Variant

Context

Required parameters

Description

Combi
ne many
worlds

All

A String as the “First string”,

A Long as the “Number” and

A String as the “Second string”,

Produces “Hello “,
景汬潷f搠dy⁡
湵浢敲映瑩 e猠
“world”

C潭扩湥⁦ 眠
畮歮潷湳u

䝕f

A String as the “First string”, and

An Integer as the “Nu
mber”,

Produces “Hello “,
景汬潷f搠dy⁡
湵浢敲映瑩 e猠s
畳u爠獰rc楦ie搠睯d汤

C潭扩湥慮y
畮歮潷湳

䝕f

A String as the “First string”, and

A Long as the “Number” and

Produces “Hello “,
景汬潷f搠dy⁡
湵浢敲映瑩 e猠s
畳u爠獰rc楦ie搠睯d汤


ff⁡摤d搠瑯

the chaining panel, the “Overloaded Hello Many Worlds” plugin looks as follows:




It is clearly indicated by the framework that the parameter labeled “Large Number” can have
two different types, namely “Integer” and “Long”. In this case, these types are

related, and it
is generally good practice to make sure this is the case. Consider for example the plugin to
convert a marked Petri net to a transition system:


This plugin requires two parameters, namely a net and a marking. The net can have four
diffe
rent types, namely “ResetNet”, “InhibitorNet”, “Petrinet”. or “ResetInhibitorNet”.
Obviously, these types are related concepts, even though they are not in any inheritance
relation.


4.2.3

Summary


By using plugin variants, complex plugins can be defined that wo
rk on different types of
parameters. However, there are a few points to keep in mind:


1)

If no variant can be executed in a certain context, the plu
gin is ignored by the framework,


2)

The methods of a plugin variant annotated with the @PluginVariant annotation

must

all
return objects of the types specified in the @Plugin annotation of the class. This is not
checked by the framework, until a plugin is executed, in which case an exception is
thrown if the return types don’t match.


3)

Plugin variants can

both be sta
tic and non
-
static and any combination thereof can exist in
one plugin,


4)

Using too many variants of a plugin might be confusing (but sometimes necessary).
Typically, if many variants exist, it is good programming practice to defer the
implementation of the

actual logic to (a) private method(s) and keep the code of the
method annotated with the @PluginVariant annotation as clean as possible.


5)

Plugin variants can be defined in superclasses, i.e. it is possible to define an abstract class
containing many varia
nts that all call one abstract protected method, which is
implemented in the sub
-
class. However, the @Plugin annotation should only be used on
the subclass level, otherwise both the superclass and the subclass are seen as plugins. For
an example of this co
nstruct, we refer to the combination of::


-

org.processmining.plugins.abstractplugins
.AbstractImportPlugin, which defined
variants for opening a file specified by different objects, but is not a plugin by itself,
and


-

org.processmining.plugins.petrinet.tpn
.TpnImport, which implements the abstract
methods of the AbstractImportPlugin and defined the @Plugin annotation.


6)

Methods of classes annotated with the @Plugin
Variant

annotation can also be annotated
with an @Plugin annotation. In this case, these methods

are regarded as separate plugins
by the framework. However, this construction is discouraged as it results in complex code.


7)

The @PluginVariant annotation is inherited by sub
-
classes. Hence any class annotated
with an @Plugin annotation can be extended by

another class with an @Plugin
annotation. The variants of the super class also exist in the subclass. This requires the
result types to be the same of any two plugins in an inheritance relation. Again, this is not
checked until after plugins have been exe
cuted.







5

GUI Specific Plugins

In the GUI, some specific annotations can be used for plugins that give these plugins a special
status. In this section, we discuss these annotations.

5.1

@Visualizer


5.2

@GUIAction


5.3

@ProMToolView



6

Utility Classes

Some utility c
lasses are available in ProM. These can be found in the
org.processmining.framework.util package.


6.1

MultiSet
<T>


6.2

Pair
<F,S>


6.3

CompairablePair

<F extends Comparable<F>, S extends Comparable<S>>


6.4

StringUtils


6.5

ArrayUtils


6.6

FileNameExtensionFilter


6.7

IconLoader


6.8

OsU
til