以下内容参考:setup/HowToCreatePluginsInProM – prom
可以参考视频教程:流程挖掘开发,ProM Workshop 介绍_哔哩哔哩_bilibili,开发部分只需看part1-part3,字幕是自制的,若有错误欢迎评论区纠正 ^-^
Hello World插件开发初探
插件定义
package org.processmining.plugins.gettingstarted;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
public class HelloWorld {
@Plugin(
name = "My Hello World Plugin",
parameterLabels = {},
returnLabels = { "Hello world string" },
returnTypes = { String.class },
userAccessible = true,
help = "Produces the string: 'Hello world'"
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static String helloWorld(PluginContext context) {
return "Hello World";
}
}
- helloworld方法包含了插件的逻辑,输入参数PluginContext类型是必须的。
- @Plugin 插件通过该注解表示,作用于方法上,该方法是插件的逻辑。@Plugin的属性也即插件的属性:
| 插件 | 属性 | 作用 | 例子 |
| @Plugin | name | 插件名 | My Hello World Plugin |
| parameterLabels | 插件的输入的标签 | 本例中没指明 | |
| returnLabels | 给插件输出的对象的标签列表,一个输出对象对应一个标签 | 本例返回一个字符串,标签为Hello world string | |
| returnTypes | 插件输出对象的类型 | String.class | |
| userAccessible | 是否用户可以访问的 | 一般情况设为true | |
| help | 说明如何使用该插件(可选) | Produces the string: 'Hello world' |
ProM框架启动时,会扫描所有class文件中的@Plugin注解,并对应注册一个插件。
- @UITopiaViant 注解通知ProM GUI此插件的存在,其属性有:
| 插件 | 属性 | 作用 | 例子 |
| @UITopiaViant | affiliation | 作者的组织 | |
| author | 作者名 | ||
| 作者邮箱 |
- 插件运行效果:①运行ProM with UITopia (GettingStarted).launch

找到上面写的插件,点击Start运行。当有注解@UITopiaViant 时,ProM tools可以找到对应的插件,插件名称是@Plugin中的name。插件列表显示绿色表示该插件可以运行,这里是因为我们这个插件不需要input,自然是可以运行的;若为黄褐色,则表示缺乏需要的输入,因此不能运行。
运行效果:

多输出
前面说了@Plugin的returnLabels和returnTypes属性控制插件的输出,输出是一个列表,输出的标签、类型和顺序由它们确定。
package org.processmining.plugins.gettingstarted;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
public class HelloWorld2 {
@Plugin(
name = "My 2nd Hello World Plug-in",
parameterLabels = {},
returnLabels = {"Hello string", "Number", "Worlds string" },
returnTypes = {String.class, Integer.class, String.class },
userAccessible = true,
help = "Produces three objects: 'Hello', number, 'world'"
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static Object[] helloWorlds(PluginContext context) {
return new Object[] { "Hello", new Integer(6), "Worlds" };
}
}执行My 2nd Hello World Plug-in后输出三个Object(Integer没有实现可视化)

在方法中,输出一个Object[]数组类型,在这里没有对类型进行检查,但是返回类型必须在returnLabels明确,ProM在执行插件时才能检查类型是否正常,并在不正确时抛出一个异常(将上面数组第三个元素改成3,输出以下效果)。

多输入
前面说了@Plugin的parameterLabels属性说明插件的输入的标签,不同于输出,输入的类型和顺序由方法参数确定。
package org.processmining.plugins.gettingstarted;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
public class HelloWorld3 {
@Plugin(
name = "My Combine Worlds Plug-in",
parameterLabels = { "First string", "Number", "Second string" },
returnLabels = { "First string several second strings" },
returnTypes = { String.class },
userAccessible = true,
help = "Produces one string consisting of the first and a number of times the third parameter."
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static Object helloWorlds(PluginContext context, String first, Integer number, String second) {
String s = first;
for (int i = 0; i < number; i++) {
s += "," + second;
}
return s;
}
}我们将执行My 2nd Hello World Plug-in的输出作为上面代码中的插件的输入,我们到Workspace中的All找到输出的Objects,按住键盘ctrl键,选择三个Object,然后点击use resources按钮;然后找到My Combine Worlds Plug-in,点击Start运行

可以点击每一个Input修改输入Object

执行效果如下:
![]()
一些注意点
返回类型以及参数类型不应使用泛型(也就是说,List<String>不能用作参数)。原因是框架无法在运行时读取这些泛型,因此不可能知道哪些插件可以作为其他插件的输入。
数组既可以用作参数类型,也可以用作返回类型。插件可以指定返回String[].class类型的对象。此外,还可以请求此类型的参数。
插件应尽可能在其参数和返回类型定义中使用接口。例如,返回PetrinetImpl对象(实现Petrinet接口)的插件应该声明它返回Petrinet.class类型的对象。
PluginContext参数
PluginContext的思想是提供与框架、其他插件或用户通讯的所有可能的接口。下面介绍可以使用context的通用特性。
日志Logging
PluginContext接口提供三个打日志方法:
/**
* The provided String is provided to the context for information. It can
* for example signal a state change of a plugin. Note that some contexts
* can completely ignore this message.
*
* @param message
* the message to log
* @param level
* the message level(Normal,Warning,Error,Test,Debug;默认是Normal)
*/
void log(String message, MessageLevel level);
/**
* Same as calling log(message, MessageLevel.NORMAL);
*
* @param message
* The message
*/
void log(String message);
/**
* The provided Exception is provided to the context. It signals the context
* about an error in the plugin, that specifically lead to abnormal
* termination. The plugin signaling the exception is no longer executing!
*
* @param exception
* the exception thrown
*/
void log(Throwable exception);每个context都有许多与之关联的日志listeners。这些listeners 接收每个记录的消息并决定如何处理它们。例如,GUI确保它已注册为框架中所有插件context上的日志侦听器。默认情况下,Debug和Test消息处于关闭状态,但用户可以打开这些消息。
下面使用log的例子及输出:
package org.processmining.plugins.gettingstarted;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
import org.processmining.framework.plugin.events.Logger.MessageLevel;
public class HelloWorld4 {
@Plugin(
name = "My 3rd Hello World Plug-in",
parameterLabels = {},
returnLabels = { "Hello world string" },
returnTypes = { String.class },
userAccessible = true, help = "Produces the string: 'Hello world'"
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static String helloWorld(PluginContext context) {
context.log("Started hello world plug-in", MessageLevel.DEBUG);
return "Hello World";
}
}
执行进度指示器Progress Indicator
进度指示器可以让插件使用者知道执行插件时的任务的进度,PluginContext提供getProgress对进度进行控制。
/**
* Returns the progress object corresponding to this context
*
* @return the progress object corresponding to this context
*/
Progress getProgress();/**
* Interface for progress indicator
*
* @author bfvdonge
*
*/
public interface Progress {
void setMinimum(int value); //用于设置进度的起始值
void setMaximum(int value); //设置进度的最大值
void setValue(int value); //表示进度的当前值
void setCaption(String message); //进度的说明
String getCaption();
int getValue(); //获得当前进度
void inc(); // 可以用于进度加一,等价于setValue(getValue()+1)
void setIndeterminate(boolean makeIndeterminate);
boolean isIndeterminate();
int getMinimum();
int getMaximum();
boolean isCancelled();
void cancel();
}下面代码展示进度指示器的使用和效果:
package org.processmining.plugins.gettingstarted;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
public class HelloWorld5 {
@Plugin(
name = "My 2nd Combine Worlds Plug-in",
parameterLabels = { "First string", "Number", "Second string" },
returnLabels = { "First string several second strings" },
returnTypes = { String.class },
userAccessible = true,
help = "Produces one string consisting of the first and a number of times the third parameter."
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static Object helloWorlds(PluginContext context, String first, Integer number, String second) {
context.getProgress().setMinimum(0);
context.getProgress().setMaximum(number);
context.getProgress().setCaption("Constructing hello worlds string");
context.getProgress().setIndeterminate(false);
String s = first;
for (int i = 0; i < number; i++) {
s += "," + second;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// don't care
}
context.getProgress().inc();
}
return s;
}
}
使用建议:在知道任务执行的步骤数(或者milestones的数量)时,可以使用进度指示器来展示任务的执行进度。
Future
Future对象允许用户在尚未可用的输入上启动插件,这使框架能够知道在开始执行插件时预期的对象类型,即使对象本身不可用。执行插件时,框架首先实例化该插件的插件上下文,然后根据所有预期结果创建futures。每个future由一个标签和一个类型组成。类型从@Plugin注释中指定的returnTypes读取,初始标签从@Plugin注释中指定的returnLabels读取。
然而,在执行过程中,插件可以通过调用context.getFutureResult(int number)与其未来的结果进行通信。这里,作为参数给出的整数表示请求哪个future,即在多个返回类型的情况下,创建多个futures。
虽然从技术上讲,插件可能会取消其自身future的计算,但这通常是不可取的。相反,与future的通信应该局限于调用context.getFutureResult(x).setLabel(newLabel),在这种情况下,结果的标签将更新。
官方的解释实在看不懂,说人话大概是尽管output的计算结果还未确定(还未return输出),可以通过getFutureResult来改变输出的属性,比如标签(setLabel)之类的,contextt.getFutureResult(int number)中的number指示第几个输出。比如下面例子不断改变output的标签,最后输出的标签与@Plugin注解中的不同:
package org.processmining.plugins.gettingstarted;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
public class HelloWorld6 {
@Plugin(
name = "My 3rd Combine Worlds Plug-in",
parameterLabels = { "First string", "Number", "Second string" },
returnLabels = { "First string several second strings" },
returnTypes = { String.class },
userAccessible = true,
help = "Produces one string consisting of the first and a number of times the third parameter."
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static Object helloWorlds(PluginContext context, String first, Integer number, String second) {
context.getProgress().setMinimum(0);
context.getProgress().setMaximum(number);
context.getProgress().setCaption("Constructing hello worlds string");
context.getProgress().setIndeterminate(false);
String s = first;
for (int i = 0; i < number; i++) {
s += "," + second;
context.getFutureResult(0).setLabel("Hello " + i + " worlds string");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// don't care
}
context.getProgress().inc();
}
context.getFutureResult(0).setLabel("Hello " + number + " worlds string");
return s;
}
}执行完的output的标签:

Provided object management
通常,只要调用插件,它们的结果就可以作为提供的对象使用。起初,这些对象是future对象,但一旦插件完成其执行,future对象就会被实际对象替换。框架中的所有对象都由Provided Object Manager(提供的对象管理器)处理,可以通过context访问对象管理器。然而,插件通常只需要一点可用的功能,即创建和更新提供的对象。
尽管框架确保插件返回的所有结果都可以作为提供的对象使用,但插件有时需要更多的结果。例如,考虑genetic mining插件。该插件完成后返回模型列表。然而,与此同时,构建了一些用户可能感兴趣的模型。框架显然不知道这些中间对象。因此,插件可以创建自己提供的对象。
提供的对象由标签和对象组成。要创建提供的对象,应调用ProvidedObjectManager的createProvidedObject方法。此方法不仅需要标签和对象,还需要上下文context。此上下文是必需的,以便可以通知侦听器listener所提供对象的创建。创建所提供的对象后,它将获得一个ID,该ID可用于以后引用该对象,例如更新或删除该对象。提供的对象是弱引用。
说人话就是可以用作于中间对象,可以创建、更新和删除,下面给出的例子也只是这几个方法的使用,具体用处以后遇到在完善。。。
package org.processmining.plugins.gettingstarted;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
import org.processmining.framework.providedobjects.ProvidedObjectDeletedException;
import org.processmining.framework.providedobjects.ProvidedObjectID;
public class HelloWorld7 {
@Plugin(
name = "My 4th Combine Worlds Plug-in",
parameterLabels = { "First string", "Number", "Second string" },
returnLabels = { "First string several second strings" },
returnTypes = { String.class },
userAccessible = true,
help = "Produces one string consisting of the first and anumber of times the third parameter."
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static Object helloWorlds(PluginContext context, String first, Integer number, String second) {
context.getProgress().setMinimum(0);
context.getProgress().setMaximum(number);
context.getProgress().setCaption("Constructing hello worlds string");
context.getProgress().setIndeterminate(false);
String s = first;
// 创建提供的对象
ProvidedObjectID id = context.getProvidedObjectManager()
.createProvidedObject("intermediate string", s, context);
for (int i = 0; i < number; i++) {
context.getFutureResult(0).setLabel("Hello " + i + " worlds string");
try {
//更新
context.getProvidedObjectManager().changeProvidedObjectObject(id, s);
Thread.sleep(1000);
} catch (ProvidedObjectDeletedException e1) {
// if the user deleted this object,
// then we create it again
id = context.getProvidedObjectManager().createProvidedObject("intermediate string", s, context);
} catch (InterruptedException e) {
// don't care
}
s += "," + second;
context.getProgress().inc();
}
context.getFutureResult(0).setLabel("Hello " + number + " worlds string");
// The intermediate object is no longer necessary.
try {
//删除
context.getProvidedObjectManager().deleteProvidedObject(id);
} catch (ProvidedObjectDeletedException e) {
// Don't care
}
return s;
}
}Connection management
ProM使用连接(connections)来表示和存储对象之间的关系,而连接的管理和操作通过连接管理器(connection management)进行,在代码中通过调用context.getConnectionManager()得到上下文的连接管理器。
连接connections
连接表示对象之间的联系,如Petri net对象和Marking对象的联系,一个连接是一个从标签到对象的映射,连接本身也有标签。
一旦将连接加入到连接管理器中,就会存在于ProM中,当然连接也是弱引用。通常连接不需要方法去显示表示对象间的连接,如Marking和Petri net,在marking里的库所同样在Petri net中也包含。
在org.processmining.plugins.connections包可以使用一些连接。
关于connections的作用,在教程视频中讲解得很好,可以去观看理解。
// 注册连接
Connection c = new MarkedNetConnection(petrinet, new Marking(state));
context.addConnection(c);
//等价于
context.getConnectionManager().addConnection(context, c);查询连接
ConnectionManager的getConnections可以查询涉及给定对象的连接类型的所有连接。
/**
* Returns a collection of connections between the objects specified, such
* that the type of the connection is assignable from the given
* connectionType (unless the parameter equals null).
*
* If no connections satisfying these criteria exist and the required type
* is specified and no required name is specified, then the global context
* 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 a new
* connection is registered of the right type. This connection is then
* returned.
*
* @param <T>
* the type of the requested connection.
* @param connectionType
* The type of the object requested. This type can be null, in
* which case all types are considered.
* @param context
* The context which requests the connection. If a plugin is
* invoked to create a connection, a child context of this
* context is instantiated
* @param objects
* the objects which should be connected by the requested
* connection. There might be more objects involved in the
* connection
* @return A collection of connections of the requested type T. If no
* connection exists, an exception is thrown, hence the collection
* is never empty.
* @throws ConnectionCannotBeObtained
* if the requested connection does not exist and cannot be
* produced in the given context.
*/
<T extends Connection> Collection<T> getConnections(Class<T> connectionType, PluginContext context,
Object... objects) throws ConnectionCannotBeObtained;
@ConnectionFactory
@ConnectionFactory注解可作用于插件,告知框架该插件可以返回连接对象。插件不必在框架中注册连接,相反插件返回的对象必须是一个实现Connection接口的对象,并且连接管理器在ProM会处理这个连接的注册。
多线程执行
PluginContext都带有Executor,这是一个标准的java.util.concurrent.Executor。可以通过getExecutor方法,利用对应的Executor实现多线程执行
/**
* Returns an executor which can be used to execute plugins in child
* contexts.
*
* @return
*/
Executor getExecutor();插件管理
一些插件需要执行其他插件,一种方法是如果事先知道需要执行插件的所有类和方法,可以以java的方式去调用。ProM还提供了插件管理器(plugin manager)的功能,可以使用它来查询和获取指定属性的插件。
PluginContext的具体实现
UIPluginContext是具体实现例子
package org.processmining.plugins.gettingstarted;
import javax.swing.JOptionPane;
import org.processmining.contexts.uitopia.UIPluginContext;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
public class HelloWorld8 {
@Plugin(
name = "My 5th Combine Worlds Plug-in",
parameterLabels = { "First string", "Number" },
returnLabels = { "First string several second strings" },
returnTypes = { String.class },
userAccessible = true,
help = "Produces one string consisting of the first and a number of times a string given as input in a dialog."
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static Object helloWorlds(UIPluginContext context, String first, Integer number) {
// Ask the user for his world
String w = JOptionPane.showInputDialog(null, "What's the name of your world?",
"Enter your world", JOptionPane.QUESTION_MESSAGE);
// change your result label
context.getFutureResult(0).setLabel("Hello " + w + " string");
// return the combined string
return helloWorlds(context, first, number, w);
}
@Plugin(
name = "My 4th Hello World Plug-in",
parameterLabels = {},
returnLabels = { "Hello string", "Number", "Worlds string" },
returnTypes = { String.class, Integer.class, String.class },
userAccessible = true,
help = "Produces three objects: 'Hello', number, 'world'"
)
@UITopiaVariant(
affiliation = "My company",
author = "My name",
email = "My e-mail address"
)
public static Object helloWorlds(PluginContext context, String first, Integer number, String second) {
String s = first;
for (int i = 0; i < number; i++) {
s += "," + second;
}
return s;
}
}插件生命
当ProM框架启动时,会自动创建一个主插件上下文。此主插件上下文用于从中派生新上下文以执行插件。
当框架执行插件时,框架首先实例化一个PluginContext对象。例如,该上下文的实现类型可以是GUI上下文或命令行上下文。
复杂插件
非静态方法插件
同样可以在非静态方法上定义插件,只不过调用插件的方式不同,在执行插件时的差异体现就跟执行静态方法(类名.静态方法)和非静态方法一样(先new一个对象再执行)。
插件重载
插件支持重载,支持参数的不同。
可选输入(Optional inputs)
@Plugin注解可以加载类上,通知ProM这是一个重载插件,之后在方法上可以通过@PluginVariant注解表明插件的重载。
package org.processmining.plugins.gettingstarted;
import javax.swing.JOptionPane;
import org.processmining.contexts.uitopia.UIPluginContext;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
import org.processmining.framework.plugin.annotations.PluginVariant;
@Plugin(
name = "My Overloaded Hello World Plugin",
parameterLabels = { "First string", "Number", "Second string" },
returnLabels = { "Hello world string" },
returnTypes = { String.class },
userAccessible = true,
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."
)
public class HelloWorld10 {
private String getWorld(UIPluginContext context) {
// Ask the user for his world
String w = JOptionPane.showInputDialog(null, "What's the name of your world?",
"Enter your world", JOptionPane.QUESTION_MESSAGE);
// change your result label
context.getFutureResult(0).setLabel("Hello " + w + " string");
return w;
}
@PluginVariant(variantLabel = "My original hello world", requiredParameterLabels = {})
@UITopiaVariant(uiLabel = "My original hello world", affiliation = "My company", author = "My name", email = "My e-mail address")
public String helloWorld(PluginContext context) {
return "Hello World";
}
@PluginVariant(variantLabel = "My Hello unknown", requiredParameterLabels = {})
@UITopiaVariant(uiLabel = "My Hello unknown", affiliation = "My company", author = "My name", email = "My e-mail address")
public String helloUnknown(UIPluginContext context) {
return "Hello " + getWorld(context);
}
@PluginVariant(variantLabel = "My Combine worlds", requiredParameterLabels = { 0, 1, 2 })
@UITopiaVariant(uiLabel = "My Combine worlds", affiliation = "My company", author = "My name", email = "My e-mail address")
public Object helloWorlds(PluginContext context, String first, Integer number, String second) {
String s = first;
for (int i = 0; i < number; i++) {
s += "," + second;
}
return s;
}
@PluginVariant(variantLabel = "My Combine unknowns", requiredParameterLabels = { 0, 1 })
@UITopiaVariant(uiLabel = "My Combine unknowns", affiliation = "My company", author = "My name", email = "My e-mail address")
public Object helloWorlds(UIPluginContext context, String first, Integer number) {
// return the combined string, after asking for the world
return helloWorlds(context, first, number, getWorld(context));
}
}每个PluginVariant的返回需要与@Plugin中的returnTypes一致。
多类型输入
输入参数的类型在多个插件重载中可以是不一样的,只是标签的名字一样。
package org.processmining.plugins.gettingstarted;
import javax.swing.JOptionPane;
import org.processmining.contexts.uitopia.UIPluginContext;
import org.processmining.contexts.uitopia.annotations.UITopiaVariant;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.framework.plugin.annotations.Plugin;
import org.processmining.framework.plugin.annotations.PluginVariant;
@Plugin(
name = "My Overloaded Hello Many Worlds",
parameterLabels = { "First string", "Large Number", "Second string" },
returnLabels = { "Hello world string" },
returnTypes = { String.class },
userAccessible = true,
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."
)
public class HelloWorld11 {
private String getWorld(UIPluginContext context) {
// Ask the user for his world
String w = JOptionPane.showInputDialog(null, "What's the name of your world?",
"Enter your world", JOptionPane.QUESTION_MESSAGE);
// change your result label
context.getFutureResult(0).setLabel("Hello " + w + " string");
return w;
}
@PluginVariant(variantLabel = "My Combine many worlds", requiredParameterLabels = { 0, 1, 2 })
@UITopiaVariant(uiLabel = "My Combine many worlds", affiliation = "My company", author = "My name", email = "My e-mail address")
public Object helloWorlds(PluginContext context, String first, Long number, String second) {
String s = first;
for (int i = 0; i < number; i++) {
s += "," + second;
}
return s;
}
@PluginVariant(variantLabel = "My Combine few unknowns", requiredParameterLabels = { 0, 1 })
@UITopiaVariant(uiLabel = "My Combine few unknowns", affiliation = "My company", author = "My name", email = "My e-mail address")
public Object helloWorlds(UIPluginContext context, String first, Integer number) {
// return the combined string, after asking for the world
return helloWorlds(context, first, Long.valueOf(number), getWorld(context));
}
@PluginVariant(variantLabel = "My Combine many unknowns", requiredParameterLabels = { 0, 1 })
@UITopiaVariant(uiLabel = "My Combine many unknowns", affiliation = "My company", author = "My name", email = "My e-mail address")
public Object helloWorlds(UIPluginContext context, String first, Long number) {
// return the combined string, after asking for the world
return helloWorlds(context, first, number, getWorld(context));
}
}
注意点
- 如果在特定上下文中不能执行任何一个插件变体,则框架将忽略该插件;
- 使用@PluginVariant注解的插件变体的方法必须返回在@Plugin注解中指定类型的对象。在执行插件之前,框架不会对此进行检查,在这种情况下,如果返回类型不匹配,将引发异常;
- 插件变体既可以是静态的也可以是非静态的,并且它们的任何组合都可以存在于一个插件中;
- 使用过多的插件变体可能会令人困惑(但有时是必要的)。通常,如果存在许多变体,那么最好的编程实践是将实际逻辑的实现推迟到(一个或多个)私有方法,并尽可能清晰明确地对方法用@PluginVariant注解;
- 插件变体可以在超类中定义,也就是说,可以定义一个包含许多变体的抽象类,这些变体都调用一个abstract protected方法,该方法在子类中实现。然而,@Plugin注解应该只在子类级别上使用,否则超类和子类都被视为插件。对于该构造的示例,我们指的是以下组合:
- org.processmining.plugins.abstractplugins.AbstractImportPlugin,它定义了用于打开由不同对象指定的文件的变体,但本身不是插件;
- org.processmining.plugins.petrinet.tpn.TpnImport,它实现了AbstractImportPlugin的抽象方法,并定义了@Plugin注解;
- 用@Plugin的类的方法也可以用@Plugin。在这种情况下,框架将这些方法视为单独的插件。但是,不鼓励这种构造,因为它会导致复杂的代码;
- @PluginVariant由子类继承。因此,任何带有@Plugin的类都可以被另一个带有@Plugin的类扩展。超类的变体也存在于子类中。这要求继承关系中的任意两个插件的结果类型相同。同样,只有在插件执行之后,才会检查这一点。
总结
这篇文章从官方教程的GettingStarted出发,入门ProM中插件的基本概念和开发,在使用ProM开发时,我们通常会对算法去开发一个插件,然后在ProM平台上去使用插件。