ProM中的关于PluginContext的一些使用。
目录
1. 前言回顾
在上一节的示例插件中,我们看到每个插件都使用PluginContext参数进行调用。PluginContext的理念是,它提供了所有必要的接口来进行通信:框架,其他插件,以及用户在本节中,我们首先讨论所有上下文中可用的一般功能。然后,我们讨论上下文的具体实现,以及如何定义需要特定上下文的插件。。插件可以在各种上下文中执行,例如GUI或脚本上下文。
具体可参考上一讲内容:【ProM编程1】创建一个Hello World!_北冥有鱼zsp的博客-CSDN博客
2. Logging
上下文最重要的特性之一是日志记录功能。为此,有两种方法可用:
void log(String message);
void log(String message, MessageLevel level);
这些方法允许用户记录信息。消息级别指示信息的类型,可以是“正常(Normal)”、“警告(Warning)”、“错误(Error)”、“测试(Test)”或“调试(Debug)”。如果未指定消息级别,则假定为“正常(Normal)”。
每个上下文都有许多与之相关的日志侦听器。这些侦听器接收每个记录的消息,并决定如何处理它们。例如,GUI确保它被注册为框架中所有插件上下文的日志侦听器。默认情况下,调试和测试消息是关闭的,但用户可以打开这些消息。
下面的代码 显示了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;
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";
}
}
可以看出,上述的context.log()函数打印了调试信息"Started hello world plug-in"。
3.进度指示器(Progress Indicator )
尽管日志记录提供了一种向插件用户发出进度信号的方法,但更好的方法是使用进度指示器。特别是如果插件包含许多需要一些时间的步骤,这是一个好主意。下面的代码显示了一个插件,该插件指示了框架的进展:
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;
}
}
首先,设置存在进度的步骤数(在这种情况下,与参数号提供的步骤数一样多)。然后,对进展情况进行说明。最后,进度增加了,也可以使用setValue(getValue()+1)来完成。为了在GUI中观察进度,这个插件包括睡眠。
当执行此插件时,将显示一个进程指示器:
4. 未来(Futures)
对象的未来是一个概念,它允许用户在尚未可用的输入上启动插件。回想一下,插件在@Plugin注释中指定了其方法的返回类型。这使框架能够知道在开始执行插件时需要什么类型的对象,即使对象本身不可用。
当一个插件被执行时,框架首先实例化该插件的插件上下文。然后,根据所有预期结果创建期货。这些未来由一个标签和一种类型组成。类型是从@Plugin注释中指定的returnTypes读取的,标签最初是从@Plugin注释中规定的returnLabels读取的。
但是,在执行过程中,插件可以通过调用context.getFutureResult(int number)与其未来的结果进行通信。这里,作为参数给定的整数表示请求哪个future,即在多个返回类型的情况下,创建多个future。
尽管从技术上讲,插件可以取消对其未来的计算,但这通常是不可取的。相反,与未来的通信应该仅限于调用context.getFutureResult(x).setLabel(newLabel),在这种情况下,结果的标签会被更新。
考虑以下代码:
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;
}
}
有了这段代码,“My 3rd Combine Worlds Plug-in”插件会不断更新未来结果的标签,以显示添加world的频率。
请注意,方法context.getFutureResult(intx)只能从插件中调用。插件一完成,该方法就会抛出一个类强制转换异常。
除了改变表示插件的未来结果的所提供对象的标签之外,插件还可以访问所提供的对象管理系统,以对所提供的物体进行完全控制。
5. 提供对象管理
通常,只要调用插件,它们的结果就可以作为所提供的对象使用。起初,这些对象是future,但一旦插件完成执行,future就会被实际对象所取代。
框架中的所有对象都由提供的对象管理器处理,可以通过上下文访问对象管理器。然而,插件通常只需要一点可用的功能,即创建和更新所提供的对象。
尽管该框架确保插件返回的所有结果都可以作为所提供的对象使用,但插件有时需要更多。例如,考虑the genetic mining插件,此插件在完成时返回一个模型列表。然而,与此同时,构建了几个可能对用户感兴趣的模型。框架显然不知道这些中间对象。因此,插件可以创建自己提供的对象。所提供的对象由一个标签和一个对象组成。要创建提供的对象,应调用ProvidedObjectManager的createProvidedObject方法。这种方法不仅需要标签和对象,还需要上下文。此上下文是必需的,以便侦听器可以被通知所提供对象的创建。
一旦创建了所提供的对象,它就会获得一个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;
}
}
对“My 3rd Combine Worlds Plug-in”的更改显示了如何创建、更新和删除中间提供的对象。首先创建对象,并保留id以备将来参考。然后,所提供的对象被更新为新的字符串。但是,如果用户删除了对象(通过在GUI中单击鼠标右键),则会引发异常。在这种情况下,会捕获异常并创建一个新的提供对象。最后,所提供的对象被删除。
6. 连接管理
到目前为止,我们引入的插件将许多参数作为输入,并生成许多结果对象。然而,在大多数情况下,输入和输出对象是相互关联的,例如,当与另一个Petri网组合时,Petri网的标记没有意义。相反,Marking和Petri网有一个连接,表示Marking标记了Petri网的位置。
在ProM框架中,对象之间的关系存储在连接中。通过连接管理器再次添加连接或检查连接是否存在,可以通过调用context.getConnectionManager()来访问连接管理器。
我们首先解释如何添加连接,在连接管理器中可以使用一种方法。此方法需要指定上下文,因此PluginContext还提供了一个addConnection方法,该方法以自身为参数调用连接管理器的addConnection。
6.1 Connections 连接
连接是从一组标签到对象的映射。每个标签表示连接中附着对象的角色。此外,连接本身有一个标签。
一旦将连接添加到连接管理器中,它们就存在于框架中。然而,当连接中的一个对象对用户不再可用时(例如,因为提供的对象被删除),则该连接将不存在。换句话说,对连接中对象的引用被存储为弱引用,也就是说,如果框架中不再需要该对象,那么连接就不会将其保存在内存中。每次要求连接管理器获取连接时,都要检查连接是否仍然存在。
通常,连接不需要显式表示连接的方法,例如,在标记和Petri网的情况下,如果存在连接,则标记中包含的位置也包含在网中。
插件源文件夹的org.processmining.plugins.connections包中提供了几个连接。
考虑以下行代码,取自TpnImport:
Connection c = new MarkedNetConnection(petrinet, new Marking(state));
context.addConnection(c);
在第一行中,一个连接被实例化。在这种情况下,MarkedNetConnection被实例化,它假设(但不验证)给定标记中包含的位置也包含在网络中。重要的是要认识到,连接应该尽可能标准化,也就是说,如果标记和网络被连接,它们应该使用MarkedNetConnection来存储这个连接。在第二行中,通过上下文将连接添加到框架中。显示的代码是
context.getConnectionManager().addConnection(context, c);
6.2 正在查找连接Finding connections
除了注册连接之外,当然也可以向连接管理器查询现有的连接。有几种方法可用。
getConnections方法返回指定对象之间的连接,这样连接的类型就可以从给定的connectionType中赋值。如果给定的类型等于null,则返回给定对象之间的所有连接。
如果不存在正确类型的连接,那么连接管理器将搜索具有ConnectionObjectFactory注释的所有可用插件,该注释可以在给定PluginContext的子插件中执行,并接受给定对象作为输入。如果存在这样的插件,则会选择这些插件中的第一个,并对给定的对象进行调用。结果是从插件中获得的,并作为连接添加到框架中。然后返回此连接。有关ConnectionObjectFactory注释的更多信息,请参阅下一节。
要获得涉及某个对象的连接列表,只需在连接管理器中查询涉及该对象的所有连接,其中类型参数等于null。
6.3 连接工厂 Connection factory
@ConnectionFactory注释可以在插件上使用,以向框架发出此插件返回连接对象的信号。插件不必在框架中注册连接。相反,插件返回的单个对象必须是Connection is的实现,并且连接管理器将处理ProM中连接的注册。
7. Multi-threaded execution
为了方便多线程执行,ProM具有一些内置功能。每个PluginContext携带一个所谓的Executor,可通过getExecutor()方法访问。这个Executor是一个标准的java.util.concurrent.Executor。其想法是,插件可以将接口Runnable的实现提供给执行器,以便并发执行。有关更多信息,请参阅Executor的javadoc。
8.Plug-in management
有些插件需要执行其他插件。如果他们事先知道在哪些类中调用哪些方法,那么可以用标准的Java方法来完成。然而,ProM还提供了一个插件管理器,可以查询具有某些财产的插件。
9. Specific contexts
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;
}
}
10.Plug-in life-cycle
当ProM框架启动时,会自动创建一个主插件上下文。这个主插件上下文用于从派生新的上下文,以便执行插件。
当插件由框架执行时,框架首先实例化一个PluginContext对象。该上下文的实现类型可以是例如GUI上下文或命令行上下文。