JBuilder properties system concepts

General description

The JBuilder properties system provides a general framework for storing and retrieving user settings. These settings are either associated with a particular project and therefore stored in that project file, or are associated with a particular user and saved in ther user's user.properties file.

Each user setting, which is usually referred to as a property, has several characteristics:

The user sees neither the category or name strings. These strings act as keys for filing and locating the information.

The behavior defined by the base Property class is deliberately minimal. Each subclass defines its own data type and storage mechanism, and provides appropriate getter and setter implementations. Subclasses should follow these general guidelines:

Most property subtypes can notify listeners of changes to the underlying property value. These listeners aren't notified when the property value is originally read, but only when a subsequent change to the value occurs. In practice, this facility is of interest only to subsystems that must be notified of changes to properties of another subsystem.

A subsystem usually manages its own properties using the PropertyGroup interface. A PropertyGroup implementation can

These are the Property subclasses used to provide node and global property support:


Detailed description of feature/subsystem

Node-specific properties

Properties you set for a particular Node instance will cause its project to be "dirtied". These new settings are retained only after the project is closed and if the user elected to save the project.

You typically define a project property by creating a public constant to represent the property:

public static final String FAVORITE = "favorite";
public static final NodeProperty PROPKEY_FAVORITE_COLOR =
   new NodeProperty(FAVORITE, "color", "blue");

You read or write the property value using this property instance:

System.out.println("The node favorite color is " + PROPKEY_FAVORITE_COLOR.getValue(node));
PROPKEY_FAVORITE_COLOR.setValue(node, "red");

As an alternative, you can use the property accessors built into the Node class, which produce identical results:

System.out.println("The node favorite color is " + node.getProperty("favorite", 
    "color", "blue"));
node.setProperty("favorite", "color", "red");

Global properties

Global properties are user-specific settings that aren't tied to a particular project. These settings are preserved in a file named user.properties. The IDE reads user.properties when it starts up, and it writes to this file whenever the user closes the properties dialog box without canceling. The IDE also writes to user.properties just before it shuts down.

Your subsystem that defines a property usually creates a static public constant to represent the property:

public static final String FAVORITE = "favorite";
public static final GlobalProperty FAVORITE_COLOR =
    new GlobalProperty(FAVORITE, "color", "blue");

You read or write the property value using this static property instance:

System.out.println("User favorite color is " + FAVORITE_COLOR.getValue());
Because global properties aren't read until after the command-line parsing process, your OpenTools shouldn't attempt to get or set these values in command-line handlers.

All global properties types except arrays allow an explicit default value to be specified during construction.

Managing sets of properties with PropertyManager

Implementations of the PropertyGroup interface are typically registered by OpenTools in the UI category by calling the static registerPropertyGroup() method. When all UI OpenTools are registered, the IDE loads global property settings and notifies each property group with its initializeProperties() method.
public class FavoritesPropertyGroup implements PropertyGroup {
  public static final Object FAVORITES_TOPIC = new Object();
  public static final String FAVORITE = "favorite";
  public static final GlobalProperty FAVORITE_COLOR =
    new GlobalProperty(FAVORITE, "color", "blue");

  public static void initOpenTool(byte majorVersion, byte minorVersion) {
    if (majorVersion == PrimeTime.CURRENT_MAJOR_VERSION) {
      PropertyManager.registerPropertyGroup(new FavoritesPropertyGroup());
    }
  }

  public PropertyPageFactory getPageFactory(final Object topic) {
    if (topic == FAVORITES_TOPIC) {
      return new PropertyPageFactory("Name", "Descriptive text") {
        public PropertyPage createPropertyPage() {
          return new FavoritesPropertyPage();
        }
      };
    }
    return null;
  }

  public void initializeProperties() {}
}
Before the PropertyManager displays a properties dialog using the showPropertyDialog() method, every registered PropertyGroup is queried to see if it has an appropriate page for the given topic. (A null topic indicates that the general IDE properties are being displayed.) The PropertyManager adds one page to the dialog box for each group that returns a non-null PropertyPageFactory when the PropertyGroup's getPageFactory() is invoked.
public static BrowserAction ACTION_EditFavorites =
  new BrowserAction("Favorites options...",
    'f',
    "Edit settings for favorite things",
    BrowserIcons.ICON_BLANK) {
  public void actionPerformed(Browser browser) {
    PropertyManager.showPropertyDialog(browser,
      "Favorites Options",
      FavoritesPropertyGroup.FAVORITES_TOPIC,
      PropertyDialog.getLastSelectedPage());
  }
};

The PropertyPage instance created by the factory has three primary responsibilities:

Your PropertyPage implementation should look much like the following:

public class FavoritesPropertyPage extends PropertyPage {
  private JTextField editColor = new JTextField();

  public FavoritesPropertyPage() {
    try  {
      jbInit();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  protected void jbInit() throws Exception {
    // build the UI
  }

  public boolean isPageValid() {
    boolean valid = true;
    if (editColor.equals("white") || editColor.equals("black")) {
      reportValidationError(editColor, "An invalid color has been entered");
      valid = false;
    }
    return valid;
  }

  public void writeProperties() {
    FavoritesPropertyGroup.FAVORITE_COLOR.setValue(editColor.getText());
  }


  public void readProperties() {
    editColor.setText(FavoritesPropertyGroup.FAVORITE_COLOR.getValue());
  }
}

Sometimes it's useful to know whether one or more page factories exist for a given topic. The method getPageFactories() accepts a topic and returns an array of PropertyPageFactory instances.

Locating user settings files

There are some static convenience methods associated with the PropertyManager class for finding settings files:

These methods all return an Url.

Here is an example of finding files relative to where JBuilder is installed:

Url libExt = PropertyManager.getInstallRootUrl().getRelativeUrl("../lib/ext");
Url[] urls = VFS.getChildren(libExt, Filesystem.TYPE_FILE);
for (int i = 0; i < urls.length; i++) {
  System.out.println(urls[i].getLongDisplayName());
}

Here is an example of creating a properties file in the same directory as JBuilder keeps the user.properties file:

Url url = PropertyManager.getSettingsUrl("test.properties");
if (!VFS.exists(url)) {
  try {
    OutputStream os = VFS.getOutputStream(url);
    os.close();
  }
  catch (Exception ex) {
    System.err.println("Unable to create " + url);
  }
}