ProM开发指北3——ProM插件开发入门

以下内容参考: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的属性也即插件的属性:
插件属性作用例子
@Pluginname       插件名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此插件的存在,其属性有:
插件属性作用例子
@UITopiaViantaffiliation作者的组织
author作者名
email作者邮箱
  • 插件运行效果:①运行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

执行效果如下: 

 

一些注意点

  1. 返回类型以及参数类型不应使用泛型(也就是说,List<String>不能用作参数)。原因是框架无法在运行时读取这些泛型,因此不可能知道哪些插件可以作为其他插件的输入。

  2. 数组既可以用作参数类型,也可以用作返回类型。插件可以指定返回String[].class类型的对象。此外,还可以请求此类型的参数。

  3. 插件应尽可能在其参数和返回类型定义中使用接口。例如,返回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));
        }
}

注意点

  1. 如果在特定上下文中不能执行任何一个插件变体,则框架将忽略该插件;
  2. 使用@PluginVariant注解的插件变体的方法必须返回在@Plugin注解中指定类型的对象。在执行插件之前,框架不会对此进行检查,在这种情况下,如果返回类型不匹配,将引发异常;
  3. 插件变体既可以是静态的也可以是非静态的,并且它们的任何组合都可以存在于一个插件中;
  4. 使用过多的插件变体可能会令人困惑(但有时是必要的)。通常,如果存在许多变体,那么最好的编程实践是将实际逻辑的实现推迟到(一个或多个)私有方法,并尽可能清晰明确地对方法用@PluginVariant注解;
  5. 插件变体可以在超类中定义,也就是说,可以定义一个包含许多变体的抽象类,这些变体都调用一个abstract protected方法,该方法在子类中实现。然而,@Plugin注解应该只在子类级别上使用,否则超类和子类都被视为插件。对于该构造的示例,我们指的是以下组合:
    • org.processmining.plugins.abstractplugins.AbstractImportPlugin,它定义了用于打开由不同对象指定的文件的变体,但本身不是插件;
    • org.processmining.plugins.petrinet.tpn.TpnImport,它实现了AbstractImportPlugin的抽象方法,并定义了@Plugin注解;
  6. 用@Plugin的类的方法也可以用@Plugin。在这种情况下,框架将这些方法视为单独的插件。但是,不鼓励这种构造,因为它会导致复杂的代码;
  7. @PluginVariant由子类继承。因此,任何带有@Plugin的类都可以被另一个带有@Plugin的类扩展。超类的变体也存在于子类中。这要求继承关系中的任意两个插件的结果类型相同。同样,只有在插件执行之后,才会检查这一点。

总结

这篇文章从官方教程的GettingStarted出发,入门ProM中插件的基本概念和开发,在使用ProM开发时,我们通常会对算法去开发一个插件,然后在ProM平台上去使用插件。


版权声明:本文为hyhy12580原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。