Writing JellyTools Operators Guide

Author: Jiri Skrivanek
Last update: May 13, 2011

Contents


Introduction

This document describes how to write module-specific JellyTools 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 JellyTools operators can use all methods already implemented in Jemmy. That's why we will call classes which extends Jemmy operator "JellyTools 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 JellyTools operator and how to handle internationalization. An extra attention is paid to nodes, actions, properties and wizards.

Project structure

The root of package hierarchy is org.netbeans.jellytools. 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 the following schema where packages are in bold.

org.netbeans.jellytools

  • actions
    • Action
    • ActionNoBlock
    • CopyAction
    • DeleteAction
  • modules
    • form
      • actions
        • PaletteAction
      • nodes
      • properties
        • editors
          • FormCustomEditorAdvancedOperator
      • ComponentInspectorOperator
  • nodes
    • FolderNode
    • Node
  • properties
    • editors
      • PointCustomEditorOperator
    • PointProperty
    • Property
    • PropertySheetTabOperator
  • Bundle
  • MainWindowOperator
  • NbDialogOperator
  • ProjectsTabOperator
  • TopComponentOperator
  • WizardOperator

Class naming conventions

  1. All classes which extends a Jemmy or JellyTools operator should have suffix "Operator" (e.g. MainWindowOperator).
  2. "Nb" prefix should be used, if there is a need to distinguish IDE specific operators. 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. PointProperty).
  6. Classes for set of properties have "Properties" in the middle (e.g. XMLPropertiesOperator)

JellyTools operator

Every JellyTools 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. JellyTools operator can be inherited from one of the following Jemmy operators:

  • ComponentOperator
  • ContainerOperator
  • DialogOperator
  • FrameOperator
  • JComponentOperator
  • JDialogOperator
  • JFrameOperator
  • JInternalFrameOperator
  • WindowOperator

It is highly recommended that module-specific operator extends one of the following Jelly operators:

  • NbDialogOperator
  • TopComponentOperator
  • WizardOperator

NbDialogOperator is a generic ancestor for majority of IDE dialogs. NbDialogOperator is extended by WizardOperator which helps to implement operators for wizards. TopComponentOperator represents any dockable view which can reside inside a host frame.

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 JellyTools operators, if they exist.

Implementation should follow Java code conventions. All non private methods and fields should have javadoc comments.

Definition of JellyTools operator class consist of several parts:

Sub components variables declaration

Name to identify a sub component should be the same as sub component 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's 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 opened by main menu Tools -> Options, Save As Template dialog by popup menu on a node. JellyTools 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 JellyTools 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 selectFileType(String filetype) {
       lstFileTypes().selectItem(filetype);
   }

I18N

If it is intended to use JellTools library for localized NetBeans, JellyTools 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.

JellyTools 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 constructor, for example when you assume where to find node:

   public SourcePackagesNode(String projectName) {
       super(new ProjectsTabOperator().getProjectRootNode(projectName), SOURCE_PACKAGES_LABEL);
   }

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 CopyAction copyAction = new CopyAction();

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

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

JellyTools actions

Actions are intended to hide and re-use main menu, popup menu, shortcut and IDE API invocation. 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
  • node (select node before)
  • array of nodes (select nodes before)
  • ComponentOperator (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 org.netbeans.jellytools.actions package.

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

   public CopyAction() {
       super(copyMenu, copyPopup, "org.openide.actions.CopyAction", copyShortcut);
   }

You can use strings from Bundle.properties to declare menu paths:

   private static final String copyPopup = Bundle.getStringTrimmed("org.openide.actions.Bundle", "Copy");
   private static final String copyMenu = Bundle.getStringTrimmed("org.netbeans.core.Bundle", "Menu/Edit")
                                           + "|" + copyPopup;
   private static final Shortcut copyShortcut = new Shortcut(KeyEvent.VK_C, KeyEvent.CTRL_MASK);

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

   public void performPopup(ComponentOperator compOperator) {
       if(compOperator instanceof TopComponentOperator) {
           performPopup((TopComponentOperator)compOperator);
       } else {
           throw new UnsupportedOperationException(
                   "CloneViewAction can only be called on TopComponentOperator.");
       }
   }
   // SaveAction - supress popup usage on node
   public void performPopup(Node node) {
       throw new UnsupportedOperationException(
                   "SaveAction doesn't have popup representation on node.");
   }

JellyTools properties

In the IDE settings of properties 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 into 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 org.netbeans.jellytools.properties.PointProperty.java 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 various 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 exists 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 Reset to Default, OK and Cancel buttons, combo box enabling to change editor (i.e. PointEditor/Value from existing component/Custom code). 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 org.netbeans.jellytools.properties.editors.PointCustomEditorOperator.java for instance). Because a custom editor is a regular IDE component you should follow the same rules as for creating any other JellyTools 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 Java node contains two categories - Properties (Name, ...) and Classpaths (Compile Classpath, ...). Class jellytools.modules.java.properties.JavaPropertiesOperator should embed all these categories.

Partial implementation of grouping Properties operator should look like this:

public class JavaPropertiesOperator extends PropertySheetOperator {

   public JavaPropertiesOperator(PropertySheetOperator propertySheetOper) {
       super(propertySheetOper);
   }
   public Property prtName() {
       // "Name"
       String name = Bundle.getString("org.openide.loaders.Bundle", "PROP_name");
       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

JellyTools 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 org.netbeans.jellytools.NewFileWizardOperator.java</tt> for instance). Your wizard operator will contain constructors, invoke methods and optionally business logic methods.

Constructor - calls parent's constructor with appropriate parameter

   public NewFileWizardOperator() {
       super(Bundle.getString("org.netbeans.modules.project.ui.Bundle", "LBL_NewFileWizard_Subtitle"));
   }

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

   public static NewFileWizardOperator invoke() {
       new NewFileAction().perform();
       return new NewFileWizardOperator();
   }

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 [[#JellyTools_operator|JellyTools 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 NewFileNameLocationStepOperator() {
       super();
       checkPanel(Bundle.getString("org.netbeans.modules.java.project.Bundle",
                                   "LBL_JavaTargetChooserPanelGUI_Name"));
   }

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 -
Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo