Preferences API in NetBeans

NetBeans adopts Java Preferences API standard to be used in NetBeans to store preference and configuration data to be able to adapt to the needs of different users. NetBeans keeps this standard and enhances it slightly by providing its own preference tree in addition to default system and user roots. The purpose for this enhancement is to unify the way how persistent preference and configuration data are stored in NetBeans whereas all changes go into userdir which is the central place where also other configuration data are kept.This concept is convenient for developers or power users for running more instances of NetBeans, for debugging, for testing, for analysing structure or content of such files whenever any problems occures whereas running NetBeans with various userdirs means actually running NetBeans with different preferences. Running NetBeans with fresh userdir means actually running NetBeans with factory preset defaults.

The decision whether to use standard or enhanced NetBeans preference tree is left up to the developer.

Usage Notes

How to obtain NetBeans Preferences?

To get preference node there are two factory methods in Utilities API (org.openide.util):

How to verify that preferences work properly?

Layout of files on disk:

    config/Preferences/
    `-- org
        |-- apache
        |   `-- tools
        |       `-- ant
        |           `-- module.properties
        `-- netbeans
            `-- modules
                `-- derby.properties
    

prefs.properties is regular properties file. See:

#Thu Sep 28 18:15:22 CEST 2006
location=/tmp/location
systemHome=/tmp/systemHome

How to migrate from SystemOptions into Preferences API?

  1. Remove registration from layer:
    -     <folder name="Services"/>
    -         <file name="org-netbeans-modules-derby-DerbyOptions.settings" url="DerbyOptions.settings"/>
    -     </folder>
    
  2. Add module dependency on Utilities API (org.openide.util with specification version >= 7.4) if necessary
  3. Remove module dependency on Settings Options API (org.openide.options)
  4. Remove registration of Node which was used to visualize original SystemOptions in Tools/Options
    -     <folder name="Services">
    -         <folder name="IDEConfiguration">
    -             <folder name="ServerAndExternalToolSettings">
    -                    <file name="org-netbeans-modules-derby-DerbyOptions.shadow">
    -                        <attr name="originalFile" stringvalue="Services/org-netbeans-modules-derby-DerbyOptions.settings"/>
    -                    </file>
    -             </folder>
    -         </folder>
    -     </folder>
    
          
  5. Rewrite the code not to extend SystemOption anymore
  6. Ensure visualization in Tools/Options if still needed.
  7. Ensure import from previous version into preferences storage
  8. Delete associated BeanInfo if it isn't necessary anymore
  9. Document usage of Preferences API like any other API in arch document
  10. Run tests to verify that code after migration is working

Hints how to replace SystemOption

It is recommended to delete the whole settings class extending SystemOption and call directly Preferences API. This approach isn't convenient when except reading, writing from storage is necessary additional logic. Then following steps can be used as a hint:
  1. Change your setting class not to extend SystemOption anymore
  2. Either use static methods for getters and setters or replace factory method SharedClassObject.findObject by common singleton pattern:
    -public class DerbyOptions extends SystemOption {
    -
    -    private static final long serialVersionUID = 1101894610105398924L;
    +public class DerbyOptions  {
    +    private static DerbyOptions INSTANCE = new DerbyOptions();
    
         public static DerbyOptions getDefault() {
    -        return (DerbyOptions)SharedClassObject.findObject(DerbyOptions.class, true);
    +        return INSTANCE;
         }
          
  3. Replace all other SystemOption and SharedClassObject calls. E.g.:
    +    protected final String putProperty(String key, String value, boolean notify) {
    +        String retval = getPreferences().get(key, null);
    +        if (value != null) {
    +            NbPreferences.forModule(DerbyOptions.class).put(key, value);
    +        } else {
    +            NbPreferences.forModule(DerbyOptions.class).remove(key);
    +        }
    +        return retval;
    +    }
    
    +    protected final String getProperty(String key) {
    +        return NbPreferences.forModule(DerbyOptions.class).get(key, null);
    +    }
          

How to ensure visualization in Tools/Options?

If you need keep UI backward compatibility then the way is very easy: provide a node representing your settings class. See:

+    public static BeanNode createViewNode() throws IntrospectionException {
+        return new BeanNode(DerbyOptions.getDefault());
+    }

-    <file name="org-netbeans-modules-derby-DerbyOptions.shadow">
-        <attr name="originalFile" stringvalue="Services/org-netbeans-modules-derby-DerbyOptions.settings"/>
+    <file name="DerbyOptionsNode.instance">
+        <attr name="instanceCreate" methodvalue="org.netbeans.modules.derby.DerbyOptions.createViewNode"/>

    
The other way means to go a little more farther and provide and install custom options panels/categories to Options Dialog according to the Options Dialog API. The module development support in NetBeans can help by generating boilerplate code and registering the option into your layer.

How to document usage of Preferences API?

Answer properly arch questions like any other APIs. There was added new arch question related to preferences (resources-preferences). See example:
<answer id="resources-preferences">
  <api group="preferences" name="org.netbeans.modules.derby" type="export" category="private" url="">
      <table>
          <tbody>
              <tr>
                  <th>key</th>
                  <th>description</th>
                  <th>read</th>
                  <th>write</th>
              </tr>
              <tr>
                  <td>location</td>
                  <td>Derby location or an empty string if the Derby location is not set</td>
                  <td>x</td>
                  <td>x</td>
              </tr>
              <tr>
                  <td>systemHome</td>
                  <td>Derby system home or an empty string if the system home is not set</td>
                  <td>x</td>
                  <td>x</td>
              </tr>
          </tbody>
      </table>
  </api>
 </answer>
    

How to ensure import from previous version?

There is support for importing old serialized instances of SystemOptions into preferences storage in ide/launcher/upgrade module in package org.netbeans.upgrade.systemoptions. This infrastructure reads configuration file containing enumeration of settings files that should be imported. In simple cases there is actually no neeed to code anything, but this is rare case which may happen just when all the properties are primitive data types or String and if method writeExternal isn't overridden.

How to start? The best is to start with tests. But first you must have settings file that will be parsed and tested. Put this file among tests:

test/unit/src/org/netbeans/upgrade/systemoptions
|-- BasicTestForImport.java
|-- DerbyOptionsTest.java
|-- org-netbeans-modules-derby-DerbyOptions.settings
Then write simple test that should contain assertions what actually will be parsed and imported. See:
public class DerbyOptionsTest extends BasicTestForImport {
    public DerbyOptionsTest(String testName) {
        super(testName, "org-netbeans-modules-derby-DerbyOptions.settings");
    }    
    public void testPreferencesNodePath() throws Exception {
        assertPreferencesNodePath("/org/netbeans/modules/derby");
    }    
    public void testPropertyNames() throws Exception {
        assertPropertyNames(new String[] {
            "systemHome",
            "location"
        });
    }    
    public void testSystemHome() throws Exception {
        assertPropertyType("systemHome","java.lang.String");
        assertProperty("systemHome","/tmp/systemHome");
    }
    public void testLocation() throws Exception {
        assertPropertyType("location","java.lang.String");
        assertProperty("location","/tmp/location");
    }    
}
If the tests didn't pass then probably there is more complicated object graph serialized then you must subclass PropertyProcessor and put your own code in. (See as an example: TaskTagsProcessor and here is a test).

How to write tests that needs Preferences API?

Not persistent implementation of java.util.prefs.Preferences is installed in place of the platform-specific default implementation for running tests which is in spirit of unit testing because individual tests shouldn't interact. But if this behaviour isn't suitable then there is possible to reinstall platform-specific default implementation again:
+         public void run(final TestResult result) {
+             //just initialize Preferences before code NbTestCase
+             Preferences.userRoot();                        
+             super.run(result);
+         }