FeaturesPluginsDocs & SupportCommunityPartners
Author: Jiri Skrivanek
Last update: July 15, 2002

Writing Jelly Operators Guide - NetBeans 36

Introduction

This document describes how to write module-specific Jelly classes which will cover non core IDE parts.

Jellytools provide objects in architecture which is easy to understand and maps to IDE object model. It helps people familiar with IDE to understand Jellytools well. Jellytools offer APIs for most of the operations required by tests. In the same time it allows tests to be extended beyond Jellytools API capabilities without "hacking".  Users can see ways how to go beyond Jellytools as really supported and clear.

Jellytools are based on Jemmy that means majority of Jelly classes extends Jemmy operator what brings advantage that Jelly operators can use all methods already implemented in Jemmy. That's why we will call classes which extends Jemmy operator "Jelly operators".

The document will guide you through several areas of operators creation. It describes where to store operators, how to name them, inner structure of Jelly operator and how to handle internationalization. An extra attention is paid to nodes, actions, properties and wizards.

CVS structure

The root of package hierarchy is org.netbeans.jellytools. It corresponds to module name and it satisfies no conflicts with previous jelly package structure. Previous and current versions of jellytools will live next to each other. Operators and components serving functionality belonging to IDE's core and openide are under root package org.netbeans.jellytools. Also utilities and other stuff go there.

There exist special packages dedicated to extra functionality - actions, nodes and properties. Package actions holds implementation of possible node actions like CopyAction. Package nodes  includes classes to manipulate tree nodes like FolderNode. Package properties contains stuff related to IDE's property sheet and sub package editors for custom editor operators.
Module specific operators are stored in jellytools.modules.<moduleName> package. Under that package can be repeated structure of root package - actions, nodes, properties.

See following schema where packages are in bold.

org.netbeans.jellytools

  • actions
    • Action
    • ActionNoBlock
    • CopyAction
    • DeleteAction
  • modules
    • form
      • actions
        • FormEditorViewAction
      • nodes
      • properties
        • editors
          • FormCustomEditorAdvancedOperator
      • ComponentInspectorOperator
    • javacvs
    • xml
  • nodes
    • FolderNode
    • Node
  • properties
    • editors
      • PointCustomEditorOperator
    • ComboBoxProperty
    • Property
    • PropertySheetTabOperator
    • TextFieldProperty
  • Bundle
  • ExplorerOperator
  • MainWindowOperator
  • NbDialogOperator
  • NbFrameOperator
  • TopComponentOperator
  • WizardOperator

Class naming conventions

1) All classes which extend a Jemmy or Jelly operator should have suffix "Operator" (e.g. ExplorerOperator).

2) "Nb" prefix should be used, if there is a need to distinguish IDE specific operators (e.g. NbDialogOperator). Additionally it can be used, if IDE's peer component has such prefix (e.g. NbDialog <-> NbDialogOperator).

3) Classes for actions have "Action" suffix (e.g. CopyAction).

4) Classes for nodes have "Node" suffix (e.g. JavaNode).

5) Classes for properties have "Property" suffix (e.g. TextFieldProperty).

6) Classes for set of properties have "Properties" in the middle (e.g. XMLPropertiesOperator)
 

Jelly operator

Every Jelly operator represents an existing IDE component which can include some sub components. Typical example of such component is a dialog with buttons, text fields, etc. Jelly operator can be inherited from a Jemmy operator:
  • ComponentOperator
  • ContainerOperator
  • DialogOperator
  • FrameOperator
  • JComponentOperator
  • JDialogOperator
  • JFrameOperator
  • JInternalFrameOperator
  • WindowOperator
It is highly recommended that module-specific operator extends one of following Jelly operators:
  • NbDialogOperator
  • TopComponentOperator
  • WizardOperator
TopComponentOperator represents any dockable view which can reside inside a host frame. NbDialogOperator is generic ancestor for majority of IDE dialogs. NbDialogOperator is extended by WizardOperator which helps to implement operators for wizards.

Sub component can be an atomic Swing or AWT component or a compound component. Peers for atomic components are Jemmy operators and for other components Jelly operators, if they exist.

Implementation should follow Java code conventions. All non private methods and fields should have javadoc comments. See  NbDialogOperator  or SaveAsTemplateOperator  as examples.

Definition of Jelly operator class consist of several parts:

Sub components variables declaration

Name to identify a sub component should be the same as subcomponent text whenever it is possible (e.g. Cancel for a cancel button). Otherwise it can be text of label associated with it or other suitable name. Sub component belonging to the same logical group should have the same prefix in the variable name (see table of prefixes).

Every sub component operator should be declared as private.
Variable name: "_"+prefix+name (e.g. _btCancel)
Variable declaration example:

    private JButtonOperator _btCancel;

Also known constants should be declared here. For example, possible items in combo box:

    public static final String ITEM_FIRST_OPTION = Bundle.getString("my.Bundle", "FirstOption");

Constructors

Constructor of a component should wait for that component (not invoke it). This happens by call of super() with appropriate parameters like this:

    public NbDialogOperator() {
        super();
    }

There can exist several constructors, if there is a good reason for that, for example title of a window may change:

    public NbDialogOperator(String title) {
        super(title);
    }

Invocation

In some cases it is possible to define a way how a component can be invoked. For example, Options window can be open by main menu Tools -> Options, Save As Template dialog by popup menu on a node. Jelly operator should include invoke() method which brings component up and returns instance of appropriate operator. It is recommended to use pre-defined actions. Method invoke() can be without parameters or can accept parameters like in the following examples:

    public static SaveAsTemplateOperator invoke(Node node) {
        return invoke(new Node[] {node});
    }

    public static SaveAsTemplateOperator invoke(Node[] nodes) {
        new SaveAsTemplateAction().perform(nodes);
        return new SaveAsTemplateOperator();
    }

Sub components getters

Every sub component have to have getter to get internal instance of corresponding operator. It is highly recommended to use lazy initialization here, i.e. initialize operator right before than it is used.
Method name: prefix+name (e.g. btCancel())
Method example:

    public JButtonOperator btCancel() {
        if (_btCancel == null) {
            String cancelCaption = Bundle.getString("org.netbeans.core.Bundle",
                                                    "CANCEL_OPTION_CAPTION");
            _btCancel = new JButtonOperator(this, cancelCaption);
        }
        return _btCancel;
    }
 

Low-level methods

For some sub components can be added shortcuts to frequently used methods. See table below for required methods to a particular operator.
Method name: name with first char in lower case (e.g. cancel()) or with prefix "set"
Method examples:

    public void cancel() {
        btCancel().push();
    }

    public void setMyTextField(String text) {
        txtMyTextFiled().clearText();
        txtMyTextFiled().typeText(text);
    }
 

Verify method

Method verify() calls getters for all sub components. In fact it checks if all sub components are present in operator's container.

    public void verify() {
        lblSelectTheCategory();
        tree();
        btOK();
        btCancel();
    }

Business logic methods

Such methods perform complex actions with sub components in single call. User of Jelly doesn't need to worry about all UI manipulation necessary to perform such operations. An example of business logic method is above verify() method which does verification of all sub components. Another example can be filling a form "at once", setting all components in a wizard's panel, etc.

Method example:

    public void selectTemplate(String templatePath) {
        new Node(tree(), templatePath).select();
    }
 

I18N

Jelly operators should not contain any hard coded strings. All string values should be extracted from IDE bundles using org.netbeans.jellytools.Bundle class like this:

    // returns string "Cancel"
    Bundle.getString("org.netbeans.core.Bundle", "CANCEL_OPTION_CAPTION");

If a label is compound from optional pieces, parameters can be supplied:

   // returns string "Properties of AnObject"
   Bundle.getString("org.netbeans.core.Bundle", "CTL_FMT_LocalProperties", new Object[] {new Integer(1), "AnObject"});

Sometimes a string from Bundle contains '&' or optional parameter place holders '{0}. If you want to cut them use the following:

   // returns string "View" (originally "&View")
   Bundle.getStringTrimmed("org.netbeans.core.Bundle", "Menu/View");

Usage of bundles will satisfy usability of jellytools library on different than English locale. It is also recommended to use such approach in test cases which are intended to be executed on several locales.
 

Jelly nodes

Nodes should enable easier navigation inside a tree and easier manipulation with tree items. A node can be used as a parameter for an action and a particular implementation of node should include methods to perform all possible actions on the node. You can look at implementation of JavaNode  as an example.

Constructor - just call parent's constructor with appropriate parameters

    public JavaNode(Node parent, String treeSubPath) {
       super(parent, treeSubPath);
    }

Override all parent's constructors and optionally add your own costructor, for example when you assume a tree where to find node:

    public JavaNode(String treePath) {
       super(new RepositoryTabOperator().tree(), treePath);
    }

Action definitions - all possible actions should be declared as static final and an instance should be immediately created:

    static final OpenAction openAction = new OpenAction();
    static final CompileAction compileAction = new CompileAction();

Methods to perform action - for each declared action there should exist method performing the action on this node

    public void open() {
        openAction.perform(this);
    }
 

Jelly actions

Actions are intended to hide and re-use main menu, popup menu, shortcut and IDE API invokation. Ancestor of all actions is class org.netbeans.jellytools.actions.Action. Its descendant ActionNoBlock is used as parent of actions which produce blocking modal dialog. There exist four ways how to perform an action:
  • main menu (menu item from IDE main window is pushed)
  • popup menu (menu item on a node or on a component is pushed)
  • IDE API (IDE system action is called)
  • keyboard shortcut (keyboard shortcut combination is pressed)
If there some of them doesn't exist for a particular action, it is not defined in constructor. By default is action performed in popup mode. If a mode is not defined, next available is used.

Method performing an action can consume following parameters:

  • without parameters
  • on node (select node before)
  • on an array of nodes (select nodes before)
  • on a component (focus component before)
If some of methods is not applicable on a particular action, method throws UnsupportedOperationException.

If an action is not defined, users can use generic Action or ActionNoBlock, or they can implement their module-specific action. See examples in actions  package.

Constructor - call parent's constructor and supply parameters for all modes in which the action should be used

    public CompileAction() {
        super(compileMenu, compilePopup, "org.openide.actions.CompileAction", compileShortcut);
    }

Of course use strings from Bundle.properties to declare menu paths:

    private static final String compilePopup = Bundle.getStringTrimmed(
                                                    "org.openide.actions.Bundle",
                                                    "Compile");
    private static final String compileMenu = Bundle.getStringTrimmed(
                                                    "org.netbeans.core.Bundle",
                                                    "Menu/Build")
                                                    + "|" + compilePopup;
    private static final Shortcut compileShortcut = new Shortcut(KeyEvent.VK_F9);

Use null, if action is not usable in a mode. For example, SaveAllAction doesn't have popup and shortcut representation:

    public SaveAllAction() {
        super(Bundle.getString("org.openide.actions.Bundle", "SaveAll"), null,
              "org.openide.actions.SaveAllAction");
    }

Perform methods - override perform methods if you need a special algorithm to perform action or if you need to supress wrong usage

    // DockingAction - perform "Dock View Into" on a tab
    public void performPopup(JTabbedPaneOperator tabbedPaneOperator) {
        int index = tabbedPaneOperator.getSelectedIndex();
        Rectangle rc = tabbedPaneOperator.getBoundsAt(index);
        tabbedPaneOperator.clickForPopup(rc.x, rc.y);
        new JPopupMenuOperator().pushMenu(popupPath+"|"+hostPath, "|");
    }
 

    // SaveAction - supress popup usage on node
    public void performPopup(Node node) {
        throw new UnsupportedOperationException(
                    "SaveAction doesn't have popup representation on node.");
    }
 

Jelly properties

In the IDE properties settings happens through so called property sheets. Property sheet can be docked to any other window. Property sheet consists of JTable with properties and optional description area. The table has two columns showing property name and property value. Properties are grouped to logical categories which can be collapsed or expanded when properties are sorted by category. It is also possible to sort properties alphabetically (Sort by Name).

Property

Property value can be changed by inline editors (text field for string values, check box for boolean values - setValue(String) and combo box for list values - setValue(int) method) or by custom editors where they are available (openEditor() method).

You should extend Property, if you want to create your module-specific property. You can look at implementation of PointProperty  as an example. Property implementation should consist of following parts:

Constructor - just calls parent's constructor with appropriate parameters

    public PointProperty(PropertySheetOperator propertySheetOper, String name) {
        super(propertySheetOper, name);
    }

Customizer invocation - opens custom editor and returns appropriate custom editor operator

    public PointCustomEditorOperator invokeCustomizer() {
        openEditor();
        return new PointCustomEditorOperator(getName());
    }

Set/get values by custom editor - sets or gets values by property custom editor (open editor, set/get values, close editor). There can exist several methods with different parameters.

    public void setPointValue(String x, String y) {
        PointCustomEditorOperator customizer = invokeCustomizer();
        customizer.setPointValue(x, y);
        customizer.ok();
    }

    public String[] getPointValue() {
        String[] value = new String[2];
        PointCustomEditorOperator customizer = invokeCustomizer();
        value[0] = customizer.getXValue();
        value[1] = customizer.getYValue();
        customizer.cancel();
        return value;
    }

Your module-specific property inherits also methods getValue(), setValue(String) and setValue(int) from Property which gets/sets value without custom editor.

Custom Editor

If there exist special custom editor for property, there appears small "..." button which invokes such custom editor dialog. Custom editor is embedded in a dialog. If it is property of a component from Form Editor, dialog contains Default, OK and Cancel buttons, combo box enabling to change editor (e.g. PointEditor/Form Connection), button "Advanced" to call "Advanced Initialization Code" dialog. Otherwise it is dialog with OK and Cancel buttons (Default and Help are optional).

To create your module-specific custom editor you should extend NbDialogOperator (see PointCustomEditorOperator for instance). Because a custom editor is regular IDE component you should follow the same rules as for creating any other Jelly operator. It should include constructor/s, getters for sub components, low-level methods to manipulate with sub components and high-level methods to set compound property value in one method. An example of such simple high-level method can be one from PointCustomEditorOperator:

    public void setPointValue(String x, String y) {
        txtFieldX().setText(x);
        txtFieldY().setText(y);
    }

Group of properties

Properties for a special object can be grouped together to a special class for easier manipulation. For example, property sheet of an XML node in the Explorer contains three categories - Properties (Name, Template), XML (Encoding, Standalone, Version), View (Node View). Class jellytools.modules.xml.properties.XMLPropertiesOperator should embed all these categories. 

Partial implementation of grouping Properties operator should look like this:

public class XMLPropertiesOperator extends PropertySheetOperator {

    public XMLPropertiesOperator(PropertySheetOperator propertySheetOper) {
        super(propertySheetOper);
    }

    public Property prtName() {
        // "Name"
        String name = Bundle.getString("org.openide.loaders.Bundle", "PROP_name");
        return new FieldProperty(this, name);
    }

    public Property prtTemplate() {
        // "Template"
        String name = Bundle.getString("org.openide.loaders.Bundle", "PROP_template");
        return new Property(this, name);
    }

    private static final bundle = "org.netbeans.modules.xml.tax.beans.beaninfo.Bundle";

    public Property prtEncoding() {
        // "Encoding"
        String name = Bundle.getString(bundle, "PROP_TreeDocumentBeanInfo_encoding");
        return new Property(this, name);
    }
}

Package hierarchy

Common properties and related operators are stored under org.netbeans.jellytools.properties package. Operators for custom editors then comes under properties.editors package. This structure can occur under every module package.

org.netbeans.jellytools

  • properties
    • editors
      • PointCustomEditorOperator
      • StringCustomEditorOperator
    • PointProperty
    • Property
    • PropertySheetOperator
  • modules
    • <moduleName>
      • properties
        • editors
          • SpecialModuleCustomEditorOperator
        • SpecialPropertiesOperator

Jelly wizards

IDE wizards are modal dialogs created by WizardDescriptor. They used to include list of steps on the left side, buttons Back, Next, Finish, Cancel, Help on the bottom and a panel with functional components. WizardOperator provides access to common buttons and the list. It is descendant of NbDialogOperator which is recommended ancestor for all IDE dialogs.

To create your module-specific wizard operator/s you should extend WizardOperator (see
NewWizardOperator for instance).  Your wizard operator will contain constructors, invoke methods and optionally business logic methods.

Constructor - calls parent's constructor with appropriate parameter

    public NewWizardOperator() {
        super(Bundle.getString("org.openide.loaders.Bundle", "CTL_TemplateTitle"));
    }

Invokation - define how to open wizard. Use actions preferably.

    public static NewWizardOperator invoke() {
        new NewTemplateAction().perform();
        return new NewWizardOperator();
    }

    public static NewWizardOperator invoke(Node node, String templatePath) {
        new NewTemplateAction(templatePath).perform(node);
        return new NewWizardOperator();
    }

Business logic - optionally user can implement methods to go through wizard but user should not duplicate tests of the wizard here.

Every next step/page of a wizard should be handled by single operator which extends above mentioned wizard operator. Because a wizard dialog is regular IDE component you should follow the same rules as for creating any other Jelly operator. It should include constructor/s, getters for functional sub components, low-level methods to manipulate with sub components and business logic methods to change state of several components in one method. Its name should end with StepOperator suffix.

Constructor - calls parent's constructor and checks in the list of steps whether requested panel is selected

    public NewObjectNameStepOperator() {
        super();
        checkPanel(Bundle.getString("org.openide.loaders.Bundle",
                                    "LAB_NewClassPanelName"));
    }

Sub components getters - all sub components should have getter to obtain instance of appropriate operator

Low-level methods - methods to call sub component operator's methods

Business logic methods - methods to change state of several components in a panel in obe method
 

Sub component operators prefixes and recommended low-level methods

 
Operator prefix low-level methods
AbstractButtonOperator abt push()
ButtonOperator bt  
CheckboxOperator cb  
ChoiceOperator cho  
ComponentOperator -  
ContainerOperator -  
DialogOperator -  
FrameOperator -  
JButtonOperator bt push()
JCheckBoxMenuItemOperator cbmi  
JCheckBoxOperator cb push()
JColorChooserOperator cch  
JComboBoxOperator cbo selectItem()
JComponentOperator -  
JDialogOperator -  
JEditorPaneOperator txt typeText()
JFileChooserOperator fch  
JFrameOperator -  
JInternalFrameOperator -  
JLabelOperator lbl  
JListOperator lst  
JMenuBarOperator menu  
JMenuItemOperator mi  
JMenuOperator mn  
JPasswordFieldOperator txt typeText()
JPopupMenuOperator pop  
JProgressBarOperator prb  
JRadioButtonMenuItemOperator rbmi  
JRadioButtonOperator rb push()
JScrollBarOperator scb  
JScrollPaneOperator scp  
JSliderOperator sli  
JSplitPaneOperator spp  
JTabbedPaneOperator tbp  
JTableOperator tbl  
JTextAreaOperator txt typeText()
JTextComponentOperator txt typeText()
JTextFieldOperator txt typeText()
JTextPaneOperator txt typeText()
JToggleButtonOperator tb push()
JTreeOperator tree  
LabelOperator lbl  
ListOperator lst  
Operator -  
ScrollPaneOperator scp  
ScrollbarOperator scb  
TextAreaOperator txt  
TextComponentOperator txt  
TextFieldOperator txt  
WindowOperator -  

 
 
 
 
 
Companion
Projects:
MySQL Database Server   Open JDK: an Open SourceJDK   GlassFish Community: an Open Source Application Server    Mobile & Embedded Community    Open Solaris   java.net - The Source for Java Technology Collaboration   Virtual Box - full virtualizer  Open ESB - The Open Enterprise Service Bus Powered by