====== 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 [[devel:design:plugin|structure of a typical Workcraft plugin]]. Development of more advanced features and graph models, such as [[devel:plugin:petri|Petri Nets]], will be covered in later exercises.
Before starting this exercise, make sure that you have already [[devel:plugin:setup|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 [[devel:design:plugin|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:
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 [[devel:plugin:graph#mathematical_layer_classes|mathematical layer classes section]]:
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 [[devel:plugin:graph#visual_layer_classes|visual layer classes section]]:
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 [[:devel:design:model|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 [[devel:plugin:petri|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'':
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.
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 [[:devel:design:model|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 [[devel:plugin:petri|Petri Net model]] where the user must be able to somehow generate both types of nodes and connect them.
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 [[devel:plugin:petri|next exercise about Petri Nets]]. Source code of the ''VisualVertex'' class is shown below:
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
[[https://github.com/workcraft/workcraft-plugin-example-graph|workcraft-plugin-example-graph]] repo.