How to handle preferences – consistently

Eclipse offers a great support to handle preferences.

Eclipse PreferencePage

Eclipse PreferencePage

Moreover, Eclipse 4 offers dependency injection for an even better handling, see http://www.vogella.com/tutorials/EclipsePreferences/article.html.

But could this functionality be improved? I think yes!

Imagine, you’d like to access the preferences from a model plug-in. I’m used to separate the model and user interface plug-ins. Hence, how could it be achieved to not mix up the Model and UI plug-in? Let me show you a simple solution how this issue can be solved.

net.openchrom.calculator (Model plug-in)
net.openchrom.calculator.ui (UI plug-in)

Everybody should be familiar with the decalaration of a preference page and its initializer in the UI plug-in. Once more, here are the extension points for the plugin.xml:

...
<extension
	point="org.eclipse.ui.preferencePages">
	<page
		class="net.openchrom.calculator.ui.preferences.PreferencePage"
		id="net.openchrom.calculator.ui.preferences.preferencePage"
		name="My Preference Test">
	</page>
</extension>
<extension
	point="org.eclipse.core.runtime.preferences">
	<initializer
		class="net.openchrom.calculator.ui.preferences.PreferenceInitializer">
	</initializer>
</extension>
...

The preference initializer is responsible to set the default values. Now we have to decide where to define the pereference constants and its default values. We don’t want to define the constants twice. This could be done in the model plug-in. It’s a big advantage to declare it in the model plug-in, cause it can be referenced by the UI plug-in without violating the separation of concerns. We use for example the class net.openchrom.calculator.preferences.PreferenceSupplier:

public class PreferenceSupplier {
	...
	public static final String P_STRING = "string";
	public static final String DEF_STRING = "Hello World";
	public static final String P_BOOLEAN = "boolean";
	public static final boolean DEF_BOOLEAN = true;
	public static final String P_DOUBLE = "double";
	public static final double DEF_DOUBLE = 3.14159d;
	public static final String P_FLOAT = "float";
	public static final float DEF_FLOAT = 1.618f;
	public static final String P_INT = "int";
	public static final int DEF_INT = 42;
	public static final String P_LONG = "long";
	public static final long DEF_LONG = 23;
	...
}

The string “P_*” is the preference constant and the “DEF_*” member is the default value for the aforementioned preference. As noted earlier, there is a preference initializer. Anyhow, we can’t move it to the model plug-in, cause it contains a reference to the class org.eclipse.jface.preference.IPreferenceStore which should be not part of the model (org.eclipse.jface is a UI plug-in). Hence, we have to find a way to satisfy the needs of the preference initializer dynamically. That’s why we add a static method to our PreferenceSupplier that returns a Map with the initialization entries.

public class PreferenceSupplier {
	...
	public static Map<String, String> getInitializationEntries() {

		Map<String, String> entries = new HashMap<String, String>();
		//
		entries.put(P_STRING, DEF_STRING);
		entries.put(P_BOOLEAN, Boolean.toString(DEF_BOOLEAN));
		entries.put(P_DOUBLE, Double.toString(DEF_DOUBLE));
		entries.put(P_FLOAT, Float.toString(DEF_FLOAT));
		entries.put(P_INT, Integer.toString(DEF_INT));
		entries.put(P_LONG, Long.toString(DEF_LONG));
		//
		return entries;
	}
	...
}

This is an advantage, cause we are now able to control the entries to be initialized. We can go back the UI plug-in and have a look at the class net.openchrom.calculator.ui.preferences.PreferenceInitializer. The following code adds the values for us automatically:

public class PreferenceInitializer extends AbstractPreferenceInitializer {
	...
	public void initializeDefaultPreferences() {

		IPreferenceStore store = Activator.getDefault().getPreferenceStore();
		Map<String, String> initializationEntries = PreferenceSupplier.getInitializationEntries();
		for(Map.Entry<String, String> entry : initializationEntries.entrySet()) {
			store.setDefault(entry.getKey(), entry.getValue());
		}
	}
	...
}

As you can see, the IPreferenceStore instance is retrieved by calling the Activator. Normally, the Activator extends the AbstractUIPlugin class. In such case, everything is fine. Do we have a problem if the Activator only implements the interface BundleActivator? No, we don’t. We only have to add a method to return the preference store, which would result in the following call “Activator.getPreferenceStore();” to get the store. A new ScopedPreferenceStore will be created. But why do we import the scope context and the preference node from the PreferenceSupplier of our model class? It’s simple, because we have therewith control over the scope and can fetch preferences dynamically, as it will be shown later on.

public class PreferenceSupplier {
	...
	public static final IScopeContext SCOPE_CONTEXT = InstanceScope.INSTANCE;
	public static final String PREFERENCE_NODE = "net.openchrom.calculator.ui";
	...
}
public class Activator implements BundleActivator {

	private static BundleContext context;
	private static IPreferenceStore preferenceStore;
	...
	public static IPreferenceStore getPreferenceStore() {

		if(preferenceStore == null) {
			preferenceStore = new ScopedPreferenceStore(PreferenceSupplier.SCOPE_CONTEXT, PreferenceSupplier.PREFERENCE_NODE);
		}
		return preferenceStore;
	}
}

Anyhow, we’re using the AbstractUIPlugin approach in this case, hence we don’t need the BundleActivator approach. The next class is the preference page of UI plug-in which needs to be edited. The field editors can be added as we think it’s the choice for the user:

public class PreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {

	...
	public void createFieldEditors() {

		addField(new StringFieldEditor(PreferenceSupplier.P_STRING, "String", getFieldEditorParent()));
		addField(new BooleanFieldEditor(PreferenceSupplier.P_BOOLEAN, "Boolean", getFieldEditorParent()));
		addField(new DoubleFieldEditor(PreferenceSupplier.P_DOUBLE, "Double", getFieldEditorParent()));
		addField(new FloatFieldEditor(PreferenceSupplier.P_FLOAT, "Float", getFieldEditorParent()));
		addField(new IntegerFieldEditor(PreferenceSupplier.P_INT, "Integer", getFieldEditorParent()));
		addField(new LongFieldEditor(PreferenceSupplier.P_LONG, "Long", getFieldEditorParent()));
	}

	public void init(IWorkbench workbench) {

		setPreferenceStore(Activator.getDefault().getPreferenceStore());
		setDescription("");
	}
}

Last but not least, we’d like to access the preference values from our model plug-in. Therefore, we add a convenient method to the PreferenceSupplier class to get the IEclipsePreferences instance:

public class PreferenceSupplier {
	...
	public static IEclipsePreferences getPreferences() {

		return SCOPE_CONTEXT.getNode(PREFERENCE_NODE);
	}
	...
}

That’s it. We can now access each preference in our model and UI plug-in by make a simple call:

...
IEclipsePreferences preferences = PreferenceSupplier.getPreferences();
int theAnswerToTheQuestionOfAllQuestions = preferences.getInt(PreferenceSupplier.P_INT, PreferenceSupplier.DEF_INT);
...

Easy, isn’t it?

About these ads

About eselmeister

Founder of OpenChrom
This entry was posted in Uncategorized. Bookmark the permalink.

2 Responses to How to handle preferences – consistently

  1. Nice pattern.

    Do you prefer using enums as opposed to string constants for preference keys? I started using enums for most of my constants after reading Effective Java. Of course one has to convert the type of the default value before using it.

    Do you have plans to adopt the E4 Application Model also in OpenChrom?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s