1. Introduction

Preferences are represented by a tree of nodes. Each node has a name (may not be unique) and a path (always unique). The root node has "/" as name and path. No other node may have the character '/' in its name. Nodes may contain key-value entries.

Preferences trigger events whenever a node changes value or nodes are added/removed. You may register a griffon.plugins.preferences.PreferenceChangeListener to handle the first type of event, and a griffon.plugins.preferences.NodeChangeListener to handle the second one.

Preferences may be resolved at any time given a PreferencesManager; this plugin will automatically instantiate a manager given the default configuration. Preference values may also be injected following a naming convention. Classes that participate in preferences injection have their properties annotated with @Preference. Only classes annotated with @PreferencesAware will be notified of updates whenever preferences change value. The @Preference annotation defines additional parameters such as key, args, defaultValue and format; these parameters work exactly as shown by @InjectedResource.

Here’s an example of a Model class defining a preference for a title and a custom format for a Date property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package sample

import griffon.core.artifact.GriffonModel
import griffon.metadata.ArtifactProviderFor
import griffon.plugins.preferences.Preference
import griffon.plugins.preferences.PreferencesAware

@griffon.transform.Observable
@PreferencesAware
@ArtifactProviderFor(GriffonModel)
class SampleModel {
    @Preference(defaultValue='Sample')
    String title

    @Preference(defaultValue='08.04.2013 2:30 PM', format='dd.MM.yyyy h:mm a')
    Date date

    private String isbn

    @Preference(defaultValue='9781935182238')
    void setIsbn(String isbn) { this.isbn = isbn }
    String getIsbn() { this.isbn }
}

When the application is run for the first the title property will have "Sample" as its value. Preferences will be written to disk when the application is shutdown. Here are the contents of the default.json file (JSON support is enabled if the griffon-preferences-json dependency is added to the classpath)

1
2
3
4
5
6
7
8
9
{
    "sample": {
        "SampleModel": {
           "isbn": "9781935182238",
            "title": "Sample",
            "date": "08.04.2013 2:30 PM"
        }
    }
}

If that file is edited so that the title property has a different value then the new value will be shown the next time the application is launched.

It’s worth noting that if a preference cannot be resolved a griffon.plugins.preferences.NoSuchPreferenceException is thrown.

The @Preference annotation may be applied to methods too, as long as the method represents a property getter or setter. Both read and write methods must exist. The annotation may be applied to any of the property accessors. The setter has precedence in the event of both accessors being annotated.

Values of properties annotated with @Preference are not automatically synchronized with their PreferenceNode counterpart. This is to ensure only valid preference values are stored. Thus developers must manually invoke the save() method on an instance of PreferencesManager, such as

1
preferencesManager.save(model)

Version 1.4.0 and earlier computed the value of a preference key based on the declaring class of the matching annotated field or method. You can change this behavior since version 1.5.0 by defining a configuration flag in Config.groovy and/or Config.properties.

Config.properties
preferences.key.resolution.strategy = instance_class

Valid values are: declaring_class, instance_class. Although instance_class is more aligned with the inheritance model of members in Java, declaring_class remains the default value to keep backward compatibility with existing applications.

Griffon version: 2.14.0

2. Build Configuration

2.1. Gradle

You have two options for configuring this plugin: automatic and manual.

2.1.1. Automatic

As long as the project has the org.codehaus.griffon.griffon plugin applied to it you may include the following snippet in build.gradle

dependencies {
    griffon 'org.codehaus.griffon.plugins:griffon-preferences-plugin:2.1.0'
}

The griffon plugin will take care of the rest given its configuration.

2.1.2. Manual

You will need to configure any of the following blocks depending on your setup

Core Support
dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-preferences-core:2.1.0'
}
Jackson Support
dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-preferences-jackson:2.1.0'
}
JSON Support
dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-preferences-json:2.1.0'
}
YAML Support
dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-preferences-yaml:2.1.0'
}

2.2. Maven

First configure the griffon-preferences-plugin BOM in your POM file, by placing the following snippet before the <build> element

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.griffon.plugins</groupId>
            <artifactId>griffon-preferences-plugin</artifactId>
            <version>2.1.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Next configure dependencies as required by your particular setup

Core Support
<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-preferences-core</artifactId>
</dependency>
Jackson Support
<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-preferences-jackson</artifactId>
</dependency>
JSON Support
<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-preferences-json</artifactId>
</dependency>
YAML Support
<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-preferences-yaml</artifactId>
</dependency>

3. Modules

The following sections display all bindings per module. Use this information to successfully override a binding on your own modules or to troubleshoot a module binding if the wrong type has been applied by the Griffon runtime.

3.1. Preferences

Module name: preferences

bind(PreferencesManager.class)
    .to(DefaultPreferencesManager.class)
    .asSingleton();

bind(PreferencesPersistor.class)
    .to(SerializingPreferencesPersistor.class)
    .asSingleton();

bind(GriffonAddon.class)
    .to(PreferencesAddon.class)
    .asSingleton();

3.2. Preferences Jackson

Module name: preferences-jackson

Depends on: preferences

bind(PreferencesPersistor.class)
    .to(JacksonPreferencesPersistor.class)
    .asSingleton();
You must define a binding for Jackson’s com.fasterxml.jackson.databind.ObjectMapper type.

3.3. Preferences JSON

Module name: preferences-json

Depends on: preferences

bind(PreferencesPersistor.class)
    .to(JsonPreferencesPersistor.class)
    .asSingleton();

3.4. Preferences YAML

Module name: preferences-yaml

Depends on: preferences

bind(PreferencesPersistor.class)
    .to(YamlPreferencesPersistor.class)
    .asSingleton();