Table of Contents
Basic plugin for Directed Graph model
The purpose of this exercise is to demonstrate how a new Workcraft plugin can be implemented from scratch, by using a basic graph model like Directed Graphs as an example.
Note that this exercise was intentionally made to be as simple as possible to help reduce the framework overheads, as well as to mainly focus on the structure of a typical Workcraft plugin. Development of more advanced features and graph models, such as Petri Nets, will be covered in later exercises.
Before starting this exercise, make sure that you have already set up the project for your plugin.
Plugin and descriptor classes
When creating a new Workcraft plugin, there are few necessary classes that needs to be considered. These are the plugin class (i.e. the class implementing the Plugin
interface) and the descriptor classes (i.e. the classes implementing the ModelDescriptor
and VisualModelDescriptor
interfaces), which all implement a plugin's core functionality. For further information, see how the plugin is laid out.
The plugin enables Workcraft to detect its registered model whenever the user creates a new '.work' file (i.e. the file type used to save one's models/drawings in Workcraft). The roles of this class include providing an internal description of the plugin and determining how it is initialised, as shown in the GraphPlugin
class:
- GraphPlugin.java
package org.workcraft.plugins.examples.graph; import org.workcraft.Framework; import org.workcraft.plugins.Plugin; import org.workcraft.plugins.PluginManager; @SuppressWarnings("unused") public class GraphPlugin implements Plugin { @Override public String getDescription() { return "Directed Graph plugin (example)"; } @Override public void init() { final Framework framework = Framework.getInstance(); final PluginManager pm = framework.getPluginManager(); pm.registerModelDescriptor(GraphDescriptor.class); } }
Note that in the init()
method of GraphPlugin
there are two important classes used: the Framework
and PluginManager
. While they are only shown to just retrieve the running Workcraft instance and to register the new plugin, they also hold other important functionalities that make Workcraft and its plugins work correctly. Therefore, allowing one to just use the methods above for their new plugin(s).
Note the use of @SuppressWarnings("unused")
annotation. This is to prevent warnings that the class in never used. Indeed, there is no compile-time reference to this class, this however is expected, as the plugin class is loaded at run-time.
Following from GraphPlugin
, one of the necessary plugin's descriptor classes is its ModelDescriptor
used to describe the plugin's display name (i.e. name shown when creating a new .work file) and how its math and visual models are handled (which is discussed in more detail later in the exercise). For this example, the GraphDescriptor
is created and uses the 'newly' created Graph
class found in the later mathematical layer classes section:
- GraphDescriptor.java
package org.workcraft.plugins.examples.graph; import org.workcraft.dom.ModelDescriptor; public class GraphDescriptor implements ModelDescriptor { @Override public String getDisplayName() { return "Directed Graph (example)"; } @Override public Graph createMathModel() { return new Graph(); } @Override public VisualGraphDescriptor getVisualModelDescriptor() { return new VisualGraphDescriptor(); } }
The other one of the necessary plugin's descriptor classes is its VisualModelDescriptor
class, which is similar to the ModelDescriptor
but describes and handles the visual models instead. Note that the ModelDescriptor
class actually calls its visual counterpart to handle plugin's visual models, as shown in the example above. For this example, the VisualGraphDescriptor
is created and uses the 'newly' created VisualGraph
class found in the later visual layer classes section:
- VisualGraphDescriptor.java
package org.workcraft.plugins.examples.graph; import org.workcraft.dom.VisualModelDescriptor; import org.workcraft.dom.math.MathModel; import org.workcraft.exceptions.VisualModelInstantiationException; public class VisualGraphDescriptor implements VisualModelDescriptor { @Override public VisualGraph create(MathModel mathModel) throws VisualModelInstantiationException { if (mathModel instanceof Graph) { return new VisualGraph((Graph) mathModel); } throw new VisualModelInstantiationException("Unsupported math model type."); } }
Mathematical layer classes
To describe how a plugin's math model works, its node(s) and their functionality needs to be determined using Workcraft's MathModel
interface. Fortunately, the abstract class AbstractMathModel
has already implemented most of these necessary features and functionalities. This means it can describe any type of model, even ones that are not yet created. For further details on this, see how the mathematical layer is structured.
One of the few methods worth noting in the AbstractMathModel
class is how it validates the connection between the nodes (i.e. the validateConnection
method), as this is used to determine what nodes can connect to what. This is especially important for more advanced graph models like Petri Nets, which uses two different types of nodes that can only connect to each other. Because Directed Graphs only uses a single type of node, this method is not overridden like in Graph
:
- Graph.java
package org.workcraft.plugins.examples.graph; import org.workcraft.dom.Container; import org.workcraft.dom.math.AbstractMathModel; import org.workcraft.serialisation.References; public class Graph extends AbstractMathModel { public Graph() { this(null, null); } public Graph(Container root, References refs) { super(root, refs); } }
For nodes, they must implement Workcraft's Node
interface which essentially assigns a 'parent' node and possibly some 'child' nodes to them. Again, these features are fortunately already implemented by the MathNode
abstract class and allows one to describe any type of node. The example for this class, i.e. Vertex
, is shown below.
- Vertex.java
package org.workcraft.plugins.examples.graph; import org.workcraft.annotations.VisualClass; import org.workcraft.dom.math.MathNode; @VisualClass(VisualVertex.class) public class Vertex extends MathNode { }
Note that Vertex
has actually been tagged by the @VisualClass
annotation, which essentially specifies how the node should be visualised using what visual class. For the purpose of this exercise, details about annotations have been omitted and will be covered in the later exercises.
Visual layer classes
To describe how a plugin's visual model works, its node(s) and their functionality must also be determined using Workcraft's VisualModel
interface. Like the AbstractMathModel
class, the AbstractVisualModel
class also has already implemented most of the necessary features and functionalities for this. Again, this means it can be used to represent any type of model. For further details on this, see how the visual layer is structured.
One of the few methods worth noting in the AbstractMathModel
class is what graphical tools are registered via – the registerGraphEditorTools
method. Essentially, as the name suggests, this method determines what tools become available for the user's use during the creation of their model e.g. connection between nodes and node generation. This is quite important (and even beneficial) for models that may require a different set of tools, e.g. like in Petri Net model where the user must be able to somehow generate both types of nodes and connect them.
- VisualGraph.java
package org.workcraft.plugins.examples.graph; import org.workcraft.dom.generators.DefaultNodeGenerator; import org.workcraft.dom.visual.AbstractVisualModel; import org.workcraft.dom.visual.VisualGroup; import org.workcraft.gui.tools.CommentGeneratorTool; import org.workcraft.gui.tools.ConnectionTool; import org.workcraft.gui.tools.NodeGeneratorTool; import org.workcraft.gui.tools.SelectionTool; public class VisualGraph extends AbstractVisualModel { public VisualGraph(Graph model) { this(model, null); } public VisualGraph(Graph model, VisualGroup root) { super(model, root); } @Override public void registerGraphEditorTools() { addGraphEditorTool(new SelectionTool()); addGraphEditorTool(new CommentGeneratorTool()); addGraphEditorTool(new ConnectionTool()); addGraphEditorTool(new NodeGeneratorTool(new DefaultNodeGenerator(Vertex.class))); } }
Lastly, for visual nodes, they must extend the VisualComponent
class which will allow the node to be drawn and shown on screen. Note that the necessary features for these operations have already been implemented by this class and that any newly drawn node will be set as a rectangle (along with other properties like its label, name and background colour) by default. For the purpose of this exercise, details about how these nodes are drawn and how their attributes are implemented have been omitted and will be covered in the next exercise about Petri Nets. Source code of the VisualVertex
class is shown below:
- VisualVertex.java
package org.workcraft.plugins.examples.graph; import org.workcraft.dom.visual.VisualComponent; public class VisualVertex extends VisualComponent { public VisualVertex(Vertex vertex) { super(vertex); } }
Solution
Up-to-date source code of the example classes used throughout this exercise are available in the workcraft-plugin-example-graph repo.