Settings API

Overview

# Javadoc

The primary class involved is `Convertor`,

# Contents

- What are Convertors?
- Make Own Convertor

- Subclass Convertor
- Register Convertor
- Runtime Instances
- Composed Content

- Finding Settings
- Versioning Settings
- Properties Format

# Settings API

## What are Convertors?

Convertors are intended, as the name indicates, to convert objects from and to its persistent state. They are supposed to facilitate module writers to persist objects in a human readable format and to allow to separate code related information like class names from data to prevent to later compatibility issues.

## Make Own Convertor

### Subclass Convertor

Creating a convertor means creating a new class which subclasses the abstract `Convertor` class.

Methods `read` and `write` should contain an converting algorithm.

Methods `registerSaver` and `unregisterSaver` allow to define own logic to detect changes in a setting object and notify the framework about these changes via Saver interface. Notifications are interpreted as requests to store the object or to mark it as changed (e.g the framework provides SaveCookie).

E.g. you can incorporate the property change support in your setting object as the source of notifications. The `Convertor.registerSaver` will register own listener and filter fired events you want the framework to be notified.

### Register Convertor

If you have already written own convertor class it is necessary to register it.

Each `.settings` file containing values in the format supported by your convertor has to be headed by DOCTYPE containing a public identifier defining grammar used for entity registrations.

First you should register an entity beneath `xml/entities` according to public identifier in the module layer. That registration has to be in the shape recognizable by a system entity resolver.

    <folder name="xml">
      <folder name="entities">
        <!--Entity registration-->
        <folder name="Vendor_org_netbeans_modules_foo">
          <file name="DTD_FooSetting_1_0" url="nbres:/org/netbeans/modules/foo/entity-1_0.dtd">
            <attr name="hint.originalPublicID"
              stringvalue="-//Vendor org.netbeans.modules.foo//DTD FooSetting 1.0//EN"/>
          </file>
        </folder>
      </folder>
    </folder>

Next register the Environment Provider associated with the entity under `xml/lookups`. The registration has to contain following attributes

    <folder name="xml">
      <folder name="lookups">
        <folder name="Vendor_org_netbeans_modules_foo">
          <file name="DTD_FooSetting_1_0.instance">
            <!--Environment Provider provided by the settings module-->
            <attr name="instanceCreate" methodvalue="org.netbeans.api.settings.Factory.create"/>
            <!--Custom convertor object-->
            <attr name="settings.convertor" methodvalue="org.netbeans.modules.foo.FooConvertor.create"/>
            <!--class name of the setting object-->
            <attr name="settings.instanceClass" stringvalue="org.netbeans.modules.foo.FooSetting"/>
            <!--the setting object; optional attribute; here you can specify
              a factory producing object into which stored data are read. The produced object
              should conform the settings.instanceClass attribute-->
            <attr name="settings.instanceCreate" methodvalue="org.netbeans.modules.foo.FooSetting.create"/>
            <!--class name and subclass names of the setting object separated by comma;
              used for performance reasons; be careful what you include or exclude here to ensure
              your setting is accessible via the Lookup API -->
            <attr name="settings.instanceOf" stringvalue="org.netbeans.modules.foo.FooSetting"/>
            <!--plus attributes specific to your convertor-->
          </file>
        </folder>
      </folder>
    </folder>

### Runtime Instances

Now if you need to create new persistent instances of your setting object in the runtime you have to register its class with attribute `settings.providerPath` under `xml/memory` folder. The attribute has to contain path to the environment provider associated with a proper entity registration.

    <folder name="xml">
      <folder name="memory">
        <folder name="org">
          <folder name="netbeans">
            <folder name="modules">
              <folder name="foo">
                <!--allows to create .settings file from memory via InstanceDataObject.create-->
                <file name="FooSetting">
                  <attr name="settings.providerPath"
                     stringvalue="xml/lookups/Vendor_org_netbeans_modules_foo/DTD_FooSetting_1_0.instance"/>
                </file>
              </folder>
            </folder>
          </folder>
        </folder>
      </folder>
    </folder>

To create a persistent instance use method org.openide.loaders.InstanceDataObject.create. The framework will look up the provider registration for the exact class of the object passed into the method.

### Composed Content

In order to allow to reuse existing convertors and to put their output together into one XML document the settings module introduces a `DOMConvertor` allowing to delegate reading and writing of setting object's properties (e.g. complex objects) to already existing convertors.

The delegating is handled by methods `DOMConvertor.delegateRead/delegateWrite` which are able to look up a proper registered `Convertor` according to a class of a setting object (see registration for runtime instances) and according to a public ID (see registration for entities) specified as an attribute (`dtd_public_id`) of a xml element.

There are two ways in which is the delegation handled:

1. in case a `DOMConvertor` is available read/write operation is delegated to `DOMConvertor.readElement` or `DOMConvertor.writeElement` which are supposed to be rewritten by subclasses.
2. in case a `DOMConvertor` is not available a plain `Convertor` is used and its output is encapsulated in a CDATA block inside the `domconvertor` element.

Follows XML fragment showing some complex property described by tag `complex_property` which was written via SubclassedDOMConvertor.writeElement and plugged by DOMConvertor.delegateWrite into document:

      ...
        <complex_property dtd_public_id="-//Vendor ...//EN">
          <foo />
        </complex_property>
      ...

Other XML fragment shows a complex property written via a SubclassedConvertor.write and plugged by DOMConvertor.delegateWrite into document:

      ...
        <domconvertor dtd_public_id="-//Vendor ...//EN"><[!CDATA[...]]></domconvertor>
      ...

The `DOMConvertor.delegateRead/delegateWrite` also solve multiple references of an object that can appear in scope of a XML document. Attributes `ID` and `IDREF` are used in accordance with the XML specification .

      ...
        <foosetting dtd_public_id="-//Vendor ...//EN" id="1">
        </foosetting
      ...
        <domconvertor idref="1"/>
      ...

Utilizing of Document Object Model (DOM) Level 2 Core Specification facilitates code formatting of the output and also provides APIs describing the hierarchy of nested elements, arbitrary complex.

## Finding Settings

To find out your setting you can use the lookup system. In such case you have to place your .settings file under `Services` folder. This is useful for settings shared among modules.

Otherwise you can place the file under own folder and use DataSystem API to get the object.

    FileSystem fs = Repository.getDefault().getDefaultFileSystem();
    FileObject fo = fs.findResource("YourFolder/YourSetting.settings");
    DataObject dobj = DataObject.find(fo);
    InstanceCookie ic = (InstanceCookie) dobj.getCookie(InstanceCookie.class);
    Object setting = ic.instanceCreate();

## Versioning Settings

### Replacing the setting object class

Update the environment provider registration mainly file attributes `settings.instanceClass` and `settings.instanceOf`. If an old class is referenced inside the format use `META-INF/netbeans/translate.names` file.

### Replacing the setting format

Register an entity for the newly introduced format. Register your setting class with proper file attribute `settings.providerPath` as was described in the section about creating settings in the runtime. Do not remove the old registration! The framework will read the file via original convertor but changes will be stored via new one.

## Properties Format

For some (and maybe many) modules it is sufficient to store its values as name, value string pairs. For them the Settings module provides a special convertor that handles content of java.util.Properties. The format definition is described in properties-1_0.dtd.

To create a setting object class compatible with the XMLProperties convertor the class has to contain the property change support (like java.beans.ProperyChangeSupport) to make possible to register `java.beans.PropertyChangeListener`, implement public default constructor (if the `settings.instanceCreate` attribute is not used). To collect data supposed to be persisted following methods have to be present:

    <ANY-ACCESS-MODIFIER> void readProperties(java.util.Properties p)

and

    <ANY-ACCESS-MODIFIER> void writeProperties(java.util.Properties p)

It is the setting object concern to collect all properties of its super classes.

The XMLProperties convertor also makes possible to prevent automatic storing of the setting object by using file attribute `xmlproperties.preventStoring` in the module layer. The attribute can contain the value whether the setting object will be stored automatically (`xmlproperties.preventStoring==false`) or `SaveCookie` will be provided. Default value is `false`. Usage

    <attr name="xmlproperties.preventStoring" boolvalue="[true|false]"/>

The second additional attribute is `xmlproperties.ignoreChanges` specifying property change events by comma separated list of property names which will be ignored. You can use special token `all` to ignore all events. Usage

    <attr name="xmlproperties.ignoreChanges" stringvalue="name[, ...]"/>

Here is an example of registrations in a module layer necessary to properly handle FooSetting object persisted in the properties format.

    <folder name="xml">

      <!--First you need to register own public identifier-->
      <folder name="entities">
        <!--the public identifier registration-->
        <folder name="NetBeans_org_netbeans_modules_foo">
          <file name="DTD_org_netbeans_modules_foo_FooSetting_1_0"
                url="nbres:/org/netbeans/modules/settings/resources/properties-1_0.dtd">
            <attr name="hint.originalPublicID"
                  stringvalue="-//NetBeans org.netbeans.modules.foo//DTD FooSetting 1.0//EN"/>
          </file>
        </folder>
      </folder>

      <!--Follows XMLPropertiesConvertor registration with proper attributes-->
      <folder name="lookups">
        <!--the environment provider registration-->
        <folder name="NetBeans_org_netbeans_modules_foo">
          <file name="DTD_org_netbeans_modules_foo_FooSetting_1_0.instance">
            <!--env. provider-->
            <attr name="instanceCreate" methodvalue="org.netbeans.api.settings.Factory.create"/>
            <!--XMLProperties convertor provided by the settings module-->
            <attr name="settings.convertor" methodvalue="org.netbeans.api.settings.Factory.properties"/>
            <attr name="settings.instanceClass" stringvalue="org.netbeans.modules.foo.FooSetting"/>
            <attr name="settings.instanceOf" stringvalue="org.netbeans.modules.foo.FooSetting"/>
            <!--changes of propertyName1, propertyName2 will be ignored-->
            <attr name="xmlproperties.ignoreChanges" stringvalue="propertyName1, propertyName2"/>
            <!--the setting object will not be stored automatically-->
            <attr name="xmlproperties.preventStoring" boolvalue="true"/>
          </file>
        </folder>
      </folder>

      <!--FooSetting class to XMLPropertiesConvertor mapping (for persistent instances
        created in the runtime)-->
      <folder name="memory">
        <folder name="org">
          <folder name="netbeans">
            <folder name="modules">
              <folder name="foo">
                <!--allows to create .settings file from memory via InstanceDataObject.create-->
                <file name="FooSetting">
                  <attr name="settings.providerPath"
                    stringvalue="xml/lookups/NetBeans_org_netbeans_modules_foo/DTD_FooSetting_1_0.instance"/>
                </file>
              </folder>
            </folder>
          </folder>
        </folder>
      </folder>
    </folder>

    <!--And here is the FooSetting data registration containing default values-->
    <folder name="Services">
      <!--the .settings data file registration-->
      <file name="org-netbeans-modules-foo-FooSetting.settings" url="FooSetting.xml">
        <!-- SystemFileSystem.localizedName and SystemFileSystem.icon as usual -->
      </file>
    </folder>

`FooSetting.xml` containing the persisted setting object:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE properties
     PUBLIC "-//NetBeans org.netbeans.modules.foo//DTD FooSetting 1.0//EN"
     "http://www.netbeans.org/dtds/properties-1_0.dtd"
    <properties>
        <property name="property1Name" value="xxx"/>
        <property name="property2Name" value="xxx"/>
    </properties>

`FooSetting` class would look like:

    public final class FooSetting {
        private final static String PROP_PROPERTY1NAME = "property1Name"; //NOI18N
        ...
        public FooSetting() {...}
        //  property change event support
        public void addPropertyChangeListener(java.beans.PropertyChangeListener l) {...}
        public void removePropertyChangeListener(java.beans.PropertyChangeListener l) {...}
        // getters/setters
        public String getPropery1() {...}
        public void setProperty1(Object property1) {
            ...
            // fire a property change event
        }
        // readProperties/writeProperties called by XMLPropertiesConvertor
        private void readProperties(java.util.Properties p) {
            property1 = p.getProperty(PROP_PROPERTY1NAME);
            ...
        }
        private void writeProperties(java.util.Properties p) {
            p.setProperty(PROP_PROPERTY1NAME, property1);
            ...
        }
        ...
    }

@FOOTER@