Writing an Attribute Plugin

Preliminaries

An attribute is additional information which can be added to the graph, the nodes, or the edges, see Assigning Data to Graph Elements.
This document explains how new attributes for graph elements can be realized using the plugin mechanism of Gravisto. For the following example(s) it is necessary to have Graffiti_Core and Graffiti_Editor in your classpath.

Getting Started

Like for every plugin it is assumed that you provide a valid plugin description file first. The next step is creating a new plugin adapter, e.g. AttributeTestPlugin. For an attribute plugin which is member of the package de.chris.plugins.attributes.test it looks similar to the following:

package de.chris.plugins.attributes.test;

import org.graffiti.plugin.GenericPluginAdapter;

public class AttributeTestPlugin
    extends GenericPluginAdapter
{
    public AttributeTestPlugin()
    {
        this.attributes = new Class[1];
        this.attributes[0] = TestAttribute.class;
    }
}
      

Because a single attribute plugin can contain more than attributes, the inherited member array attributes can be filled with an arbitrary number of attribute classes. The above example adds only one. For this every attribute class must implement the interfaces org.graffiti.attributes.Attribute.

Writing an Attribute

The following class TestAttribute is an implementation of an attribute. Please note that TestAttribute indirectly implements org.graffiti.attributes.Attribute because it extends for convenience reasons org.graffiti.attributes.AbstractAttribute which implements itself some standard methods of the interface Attribute.

package de.chris.plugins.attributes.test;

import org.graffiti.attributes.AbstractAttribute;
import org.graffiti.event.AttributeEvent;

public class TestAttribute
    extends AbstractAttribute
{
    private String value;

    public TestAttribute(String id)
    {
        super(id);
        setDescription("This is an example attribute"); // tooltip
    }

    public TestAttribute(String id, String value)
    {
        super(id);
        this.value = value;
        setDescription("This is an example attribute"); // tooltip
    }

    public void setDefaultValue()
    {
        value = "";
    }

    public void setString(String value)
    {
        assert value != null;

        AttributeEvent ae = new AttributeEvent(this);
        callPreAttributeChanged(ae);
        this.value = value;
        callPostAttributeChanged(ae);
    }

    public String getString()
    {
        return value;
    }

    public Object getValue()
    {
        return value;
    }

    public Object copy()
    {
        return new TestAttribute(this.getId(), this.value);
    }

    public String toString(int n)
    {
        return getSpaces(n) + getId() + " = \"" + value + "\"";
    }

    public String toXMLString()
    {
        return getStandardXML(value);
    }

    protected void doSetValue(Object o)
        throws IllegalArgumentException
    {
        assert o != null;

        try
        {
            value = (String) o;
        }
        catch(ClassCastException cce)
        {
            throw new IllegalArgumentException("Invalid value type.");
        }
    }
}
      

This example is a new attribute type which simply wraps a string value. It provides two constructors. Each calls the super constructor AbstractAttribute() which sets the id of this attribute since every attribute has a unique id. Further, in every of the two constructors a tooltip text is set which is displayed in the inspector when moving the mouse over this attribute. The second constructor sets a initial value.
The method setDefaultValue() initializes the data member value with a default value. It is called for example by AbstractAttribute().
The methods setString and getString are optional, i.e., not demanded by org.graffiti.attributes.Attribute. This job is done by the two methods setValue and getValue. But setString is a good example to see how setValue works: It generates an AttributeEvent and calls callPreAttributeChanged and callPostAttributeChanged prior and post to assigning a new value, respectively, to inform the ListenerManager. The method getValue must be implemented while setValue is inherited from AbstractAttribute().
The method copy is necessary because every attribute should implement the interface DeepCopy to preserve the possibility that every graph can be copied with all its attributes. toString fulfills the Attribute interface and toXMLString is necessary to save a graph having this attribute in a XML file format.
doSetValue is required by AbstractAttribute because AbstractAttribute.setValue calls this method which does the actual work.

Collection Attributes

A collection attribute implementing the interface CollectionAttribute or extending AbstractCollectionAttribute for convenience is an attribute which contains a collection of other attributes. It is displayed as a directory in the attribute tree view of the inspector.

Using Attributes

For adding the above attribute to the graph or an element of the graph select the element and press the attribute add button of the inspector. In the appearing attribute selection dialog enter a name for the attribute, e.g., "annotation" and if your attribute is not listed in the drop down box, press the search for more button. If your attribute is still not listed in the drop down box check that your attribute package is in the execution classpath of Gravisto and that it is activated in the plugin manager. Afterwards select your attribute, or the example from above de.chris.plugins.attributes.test.TestAttribute. After pressing ok the attribute appears in the inspector. It is not editable yet because we have to add code for that, see Writing a New Component for Editing Attribute Values. For visualizing our attribute graphically with the graph see Writing an AttributeComponent Plugin.

Automatically Generated Attributes

An AttributeConsumer is a class which demands that certain attributes are always created for an arbitrary new attributable object. This consumer must be added to every graph with the method addAttributeConsumer. Therefore one must write a listener plugin which does this automatically every time a graph is created. There is no direct plugin mechanism, e.g., an array to fill with consumers, for doing that, yet. Writing directly a listener plugin is also not possible at the moment, only GUI elements are automatically listeners.