Bean Editor

We use plugin setting to explain how to use the bean editor to generate plugin UI. As introduced in [QBTEAM:Write a Simple Plugin], user can provide a plugin setting class by overriding getPluginSettingClass() method in the plugin class. If provided, this class will be used by QuickBuild to generate plugin setting user interface. The setting will be saved as an instance of this class and can be retrieved later through getPluginSetting() method when this plugin runs. Let's start with a very simple plugin setting class:

A simple editor

public class PluginSetting {
    public enum FaithType {CHRISTIANISM, CATHOLICISM, BUDDHISM, ISLAMISM, JUDAISM};

    private String name;

    private FaithType faith;

    private boolean married;

    private Date birthday;

    private int workingYears;

    @Editable(order=100, description="Specify the name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Editable(order=200, description="Choose your faith")
    public FaithType getFaith() {
        return faith;
    }

    public void setFaith(FaithType faith) {
        this.faith = faith;
    }

    @Editable(order=300, name="Are You Married?")
    public boolean isMarried() {
        return married;
    }

    public void setMarried(boolean married) {
        this.married = married;
    }

    @Editable(order=400)
    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Editable(order=500)
    public int getWorkingYears() {
        return workingYears;
    }

    public void setWorkingYears(int workingYears) {
        this.workingYears = workingYears;
    }

}

By specifying the @Editable annotation for getter method, that property will be editable with user interface. The generated interface to edit this plugin setting looks as following:

Please note:

  1. The bean for which the editor is generated must have a default constructor.
  2. order attribute need to be specified for @Editable annotation to make sure that properties are displayed in the correct order.
  3. The name and description attribute of the @Editable annotation is optional. Name attribute is used to specify the display name of the property and description is used to specify the description of this property which will be displayed when a user hovers the mouse over the question mark icon next to the the property name in the user interface.
  4. Validation annotations can be applied to the properties in order to put constraint on the values of the certain properties. Refer to [Bean Validator] for details.
  5. Some helper annotation can be added to specify how the property should be edited. For example:
    @Editabel(order=100, description="Specify the name")
    @ChoiceProvider("getAvailableNames")
    @Multiline(lines=8)
    public String getName() {
        Return name;
    }
    
    private List<String> getAvailableNames() {
        return Arrays.asList(new String[]{"Alvin", "robin" });
    }
    

    By specifying @Multiline annotation for the name property, the editor for the name property will be displayed as textarea with specified number of rows.
    By specifying @ChoiceProvider for the name property, the editor for the name property will have a dropdown icon next to the input box, to help user to choose from the set of predefined values of this property. The predefined values are provided by calling a method specified as value attribute of the @ChoiceProvider annotation.
    Some other helper annotations are:

    • @Password: by specifying this annotation, the property will be displayed as a password field.
    • @Color: by specifying this annotation, a color picker will be displayed next to the input box, and the input value will be validated against color formats, in the form of rrggbb.

List editor

QuickBuild editor is able to generate user interface to edit list if the list element is of the same type. For example in the above plugin, we add a new property of type List to this class as follows:

public class PluginSetting {
    ...
    private List<Dog> dogs;
    ...
    @Editable(order=600)
    public List getDogs() {
        return dogs;
    }

    public void setDogs(List<Dog> dogs) {
        this.dogs = dogs;
    }
}

And the Dog bean is defined as:

public class Dog {
    public enum DogType {HUNTAWAY, MASTIFF, CHOWCHOW, MALAMUTE};

    private String name;

    private int age;

    private DogType type;

    @Editable(order=100)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Editable(order=200)
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Editable(order=300)
    public DogType getType() {
        return type;
    }

    public void setType(DogType type) {
        this.type = type;
    }
}

The resulting edit page will look like:

Please note that:

  1. Element type of the list needs to be specified. There are two ways to specify the element type: by using Java generics or by specifying elementType attribute of the @Editable annotation. For Dogs bean above, we use the Java generics declaration, that is, the return type of getDogs() method is List<Dog>. This tells QuickBuild that the element type of the list is Dog. In cases you cannot specify Java generics return type (for example if you are overriding a method whose return type is List), you will need to specify the element type as follows:
    Editable(order=600, elementType=Dog.class)
    
  2. All elements in the list should be of the same concrete type.
  3. You may also attach Hibernate validator annotations to the list, for example you may attach @Size annotation to the getDogs() method to limit the list size.

Polymorphic editor

QuickBuild editor is able to generate user interface to edit polymorphic properties. For example, for the plugin setting class above:

public class PluginSetting {
    ...
    Private Vehicle vehicle;
    ...
    @Editable(order=700)
    public Vehicle getVehicle() {
        return vehicle;
    }

    public void setVehicle(Vehicle vehicle) {
        this.vehicle = vehicle;
    }
}

Vehicle class is defined as:

public abstract class Vehicle {
    private String manufacturer;

    private int price;

    @Editable(order=100)
    @NotEmpty
    public String getManufacturer() {
         return manufacturer;
    }

    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }

    @Editable(order=200)
    @Range(min=100, max=1000000)
    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

}

and Vehicle class has two implementations subclasses in the same package as the Vehicle class: SUV and Car:

@Editable(name="SUV")
public class SUV extends Vehicle {
    private int size;

    @Editable(order=1000)
    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

}

@Editable(name="Car")
public class Car extends Vehicle {
    private int maxSpeed;

    @Editable(order=1000)
    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

}

Then the user interface for the plugin will be:

Please note that:

  1. In order to get the polymorphic editor to work, the return type of the getter method needs to be an abstract class or an interface.
  2. The implementation types can be specified by elementTypes attribute of the Editable annotation. If this attribute does not exist, QuickBuild will load all implementation classes from the package where the abstract class (or interface) resides in.
  3. The @Editable annotation applied to the implementation class provides display name of the implementation.
  4. When you specify @Edtiable annotation for the properties defined in Car and SUV classes, the order attribute is starts from 1000. This is to make sure that the properties defined in these implementation classes come after the properties defined in the abstract class.

Edit variables

Variables are widely used in QuickBuild to add flexibility. To define variables:

public class PluginSetting {
    ...
    private List<Variable> attributes;
    ...
    @Editable(order=800, description="Specify some attributes")
    public List<Variable> getAttributes {
        return attributes;
    }

    public void setAttributes(List<Variable> attributes) {
        this.attributes = attributes;
    }
}

The editor recognizes a variable definition automatically and provides following interface to define variables (the last property attributes):

Expression edit helper

Sometimes the bean defines property that expects expressions. Proving some predefined expressions will be convenient for the user. The annotation PresetExpressions serves this purpose. For example:

public class PluinSetting {
    ...
    private String runCondition;
    ...
    @Editable(order=600, name="Condition to Run")
    @Expressions({
        "always run", "true",
        "do not run", "false"
    })
    public String getRunCondition() {
        return runCondition;
    }

    public void setRunCondition(String runCondition) {
        this.runCondition = runCondition;
    }
}

The generated user interface will be:

As you can see, the preset values provided by @PresetExpressions appears as a menu next to the input box. Menu items are presented with meaningful text, and selecting these menu items will set corresponding expression in the input box.
Sometimes the expressions cannot be hard-coded inside the annotation, if we want to provide expressions generated dynamically at runtime, for example an input box for users to enter a valid node address in the build grid. In this case, the @ExpressionProvider annotation can be used as follows:

public class PluginSetting {
    ...
    private String runNode;
    ...
    @Editable(order=700, name="Node Address to Run")
    @ExpressionProvider("getAvailableNodes")
    public String getRunNode() {
        return runNode;
    }

    public void setRunNode(String runNode) {
        this.runNode = runNode;
    }

    private Map<String, String> getAvailableNodes() {
        Map<String, String> availableNodes = new LinkedHashMap<String, String>();
        Collection<BuildGridNode> allNodes = Quickbuild.getInstance(BuildGrid.class).getAllNodes();
        for (BuildGridNode node: allNodes) {
            availableNodes.put("node '" + node.getAddress() + "'", node.getAddress());
        }
        return availableNodes;
    }
}

As the code demonstrates, the @ExpressionProvider annotation specifies a method in the bean, which QuickBuild can call to get the expressions associated with the menu and the generated user interface looks as follows:

Input validation

  1. Validate single property

You may specify validation annotation(s) to the properties to put restraint on values of these properties. For example:

@Editable(order=200, description="Specify the faith")
@NotNull
public FaithType getFaith() {
    Return faith;
}

@Editable(order=500)
@Range(0, 100)
public int getWorkingYears() {
    return workingYears;
}

The validation annotations are the annotation validations defined in Hibernate validation framework.
Besides Hibernatebased validation, QuickBuild also has a facility to validate properties mutually through the Validatable interface.

  1. Validate multiple properties all together

Enable scripting support

Labels:

Enter labels to add to this page:
Wait Image 
Looking for a label? Just start typing.