From c4fc4e20a662388e7796b8175f3200bf2e8d4648 Mon Sep 17 00:00:00 2001 From: Eyck Jentzsch Date: Sat, 14 Nov 2015 11:24:34 +0100 Subject: [PATCH] Fixed product build --- .../scviewer.product | 1 + com.minres.scviewer.parent/.gitignore | 1 + com.minres.scviewer.parent/pom.xml | 1 + com.opcoach.e4.preferences/.classpath | 7 + com.opcoach.e4.preferences/.gitignore | 2 + com.opcoach.e4.preferences/.project | 28 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 18 + com.opcoach.e4.preferences/build.properties | 5 + com.opcoach.e4.preferences/plugin.xml | 7 + com.opcoach.e4.preferences/pom.xml | 13 + .../schema/e4PreferencePages.exsd | 163 ++++ .../schema/e4PreferenceStoreProvider.exsd | 149 +++ .../preferences/IPreferenceStoreProvider.java | 25 + .../e4/preferences/ScopedPreferenceStore.java | 861 ++++++++++++++++++ .../handlers/E4PreferencesHandler.java | 57 ++ .../internal/E4PreferenceRegistry.java | 322 +++++++ 17 files changed, 1667 insertions(+) create mode 100644 com.minres.scviewer.parent/.gitignore create mode 100644 com.opcoach.e4.preferences/.classpath create mode 100644 com.opcoach.e4.preferences/.gitignore create mode 100644 com.opcoach.e4.preferences/.project create mode 100644 com.opcoach.e4.preferences/.settings/org.eclipse.jdt.core.prefs create mode 100644 com.opcoach.e4.preferences/META-INF/MANIFEST.MF create mode 100644 com.opcoach.e4.preferences/build.properties create mode 100644 com.opcoach.e4.preferences/plugin.xml create mode 100644 com.opcoach.e4.preferences/pom.xml create mode 100644 com.opcoach.e4.preferences/schema/e4PreferencePages.exsd create mode 100644 com.opcoach.e4.preferences/schema/e4PreferenceStoreProvider.exsd create mode 100644 com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/IPreferenceStoreProvider.java create mode 100644 com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/ScopedPreferenceStore.java create mode 100644 com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/handlers/E4PreferencesHandler.java create mode 100644 com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/internal/E4PreferenceRegistry.java diff --git a/com.minres.scviewer.e4.product/scviewer.product b/com.minres.scviewer.e4.product/scviewer.product index 9f47bdd..dce1dd6 100644 --- a/com.minres.scviewer.e4.product/scviewer.product +++ b/com.minres.scviewer.e4.product/scviewer.product @@ -43,6 +43,7 @@ + diff --git a/com.minres.scviewer.parent/.gitignore b/com.minres.scviewer.parent/.gitignore new file mode 100644 index 0000000..9e440c0 --- /dev/null +++ b/com.minres.scviewer.parent/.gitignore @@ -0,0 +1 @@ +/workspace/ diff --git a/com.minres.scviewer.parent/pom.xml b/com.minres.scviewer.parent/pom.xml index 30b479e..dd1044f 100644 --- a/com.minres.scviewer.parent/pom.xml +++ b/com.minres.scviewer.parent/pom.xml @@ -13,6 +13,7 @@ ../com.minres.scviewer.database.vcd ../com.minres.scviewer.database.ui ../com.minres.scviewer.database.ui.swt + ../com.opcoach.e4.preferences ../com.minres.scviewer.e4.application ../com.minres.scviewer.ui ../com.minres.scviewer.feature diff --git a/com.opcoach.e4.preferences/.classpath b/com.opcoach.e4.preferences/.classpath new file mode 100644 index 0000000..ad32c83 --- /dev/null +++ b/com.opcoach.e4.preferences/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/com.opcoach.e4.preferences/.gitignore b/com.opcoach.e4.preferences/.gitignore new file mode 100644 index 0000000..0f63015 --- /dev/null +++ b/com.opcoach.e4.preferences/.gitignore @@ -0,0 +1,2 @@ +/target/ +/bin/ diff --git a/com.opcoach.e4.preferences/.project b/com.opcoach.e4.preferences/.project new file mode 100644 index 0000000..8a820c7 --- /dev/null +++ b/com.opcoach.e4.preferences/.project @@ -0,0 +1,28 @@ + + + com.opcoach.e4.preferences + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/com.opcoach.e4.preferences/.settings/org.eclipse.jdt.core.prefs b/com.opcoach.e4.preferences/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..c537b63 --- /dev/null +++ b/com.opcoach.e4.preferences/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/com.opcoach.e4.preferences/META-INF/MANIFEST.MF b/com.opcoach.e4.preferences/META-INF/MANIFEST.MF new file mode 100644 index 0000000..f979b7a --- /dev/null +++ b/com.opcoach.e4.preferences/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Preferences +Bundle-SymbolicName: com.opcoach.e4.preferences;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: OPCOACH +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Require-Bundle: javax.inject, + org.eclipse.core.runtime;bundle-version="3.9.0", + org.eclipse.jface;bundle-version="3.9.0", + org.eclipse.e4.core.di;bundle-version="1.3.0", + org.eclipse.e4.ui.model.workbench;bundle-version="1.0.0", + org.eclipse.e4.core.services;bundle-version="1.1.0", + org.eclipse.e4.core.contexts;bundle-version="1.3.0", + org.eclipse.e4.ui.services;bundle-version="1.0.0" +Export-Package: com.opcoach.e4.preferences, + com.opcoach.e4.preferences.handlers +Bundle-ActivationPolicy: lazy diff --git a/com.opcoach.e4.preferences/build.properties b/com.opcoach.e4.preferences/build.properties new file mode 100644 index 0000000..e9863e2 --- /dev/null +++ b/com.opcoach.e4.preferences/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml diff --git a/com.opcoach.e4.preferences/plugin.xml b/com.opcoach.e4.preferences/plugin.xml new file mode 100644 index 0000000..8026148 --- /dev/null +++ b/com.opcoach.e4.preferences/plugin.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/com.opcoach.e4.preferences/pom.xml b/com.opcoach.e4.preferences/pom.xml new file mode 100644 index 0000000..5e32d79 --- /dev/null +++ b/com.opcoach.e4.preferences/pom.xml @@ -0,0 +1,13 @@ + + 4.0.0 + com.opcoach.e4.preferences + + com.minres.scviewer + com.minres.scviewer.parent + 1.0.0-SNAPSHOT + ../com.minres.scviewer.parent + + eclipse-plugin + 1.0.0-SNAPSHOT + com.minres.scviewer + \ No newline at end of file diff --git a/com.opcoach.e4.preferences/schema/e4PreferencePages.exsd b/com.opcoach.e4.preferences/schema/e4PreferencePages.exsd new file mode 100644 index 0000000..bcae797 --- /dev/null +++ b/com.opcoach.e4.preferences/schema/e4PreferencePages.exsd @@ -0,0 +1,163 @@ + + + + + + + + + [Enter description of this extension point.] + + + + + + + + + + + + + + + + + a fully qualified identifier of the target extension point + + + + + + + an optional identifier of the extension instance + + + + + + + an optional name of the extension instance + + + + + + + + + + + + + + + + + + + + + + + a unique name that will be used to identify this page. + + + + + + + a translatable name that will be used in the UI for this page. + + + + + + + + + + a name of the fully qualified class that implements +<samp>org.eclipse.jface.preference.IPreferencePage</samp>. + +IT IS EASYER to extend FieldEditorPreferencePage + +If this class extends directly org.eclipse.jface.preference.FieldEditorPreferencePage preferenceStore is automatically set on it. + + + + + + + + + + a path indicating the location of the page in the preference tree. The path may either be a parent node ID or a sequence + of IDs separated by '/', representing the full path from the root node. + + + + + + + + + + + + + A reference by a preference page to a keyword. See the keywords extension point. + + + + + + + The id of the keyword being referred to. + + + + + + + + + + + + + + + [Enter the first release in which this extension point appears.] + + + + + + + + + [Enter extension point usage example here.] + + + + + + + + + [Enter API information here.] + + + + + + + + + [Enter information about supplied implementation of this extension point.] + + + + + diff --git a/com.opcoach.e4.preferences/schema/e4PreferenceStoreProvider.exsd b/com.opcoach.e4.preferences/schema/e4PreferenceStoreProvider.exsd new file mode 100644 index 0000000..93e4127 --- /dev/null +++ b/com.opcoach.e4.preferences/schema/e4PreferenceStoreProvider.exsd @@ -0,0 +1,149 @@ + + + + + + + + + This extension point is used to associate a preference store to a plugin. +You can choose either to implement the IPreferenceStoreProvider interface or to give the ID of the IPreferenceStore to use (stored in the workbench context of your E4 application). +If this extension point is not used, a default ScopedPreferenceStore will be used for the preference page. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Set here the plugin Id concerned by this extension. +Must be a valid plugin ID (control will be done at runtime) + + + + + + + Set a class to get the IPreferenceStore for the defined pluginID. +This parameter is optional if you use the contextId attribute. + + + + + + + + + + If no class is defined, you can set here the ID of the IPreferenceStore available in the context. +This object must be set in the workbenchContext using an Addon for instance, with the following code (in addon): + +@PostContextCreate +public void initMyAddon(IEclipseContext ctx) +{ + IPreferenceStore ps = new ... . // The code to create your pref store + ctx.set(ID set in this extension, ps); +} + + + + + + + + + + + + [Enter the first release in which this extension point appears.] + + + + + + + + + The definition could be like the following : + +pluginId="yourPluginID" +provider="a class implementing IPreferenceStoreProvider" + + +Or using the key in context (usefull to share the same preference store between plugins) : + +pluginId="yourPluginID" +keyInContext="the key of the IPreferenceStore stored in context" + + + + + + + + + + + + [Enter API information here.] + + + + + + + + + [Enter information about supplied implementation of this extension point.] + + + + + + + + + @OPCoach 2014 + + + + diff --git a/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/IPreferenceStoreProvider.java b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/IPreferenceStoreProvider.java new file mode 100644 index 0000000..b1a3307 --- /dev/null +++ b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/IPreferenceStoreProvider.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2014 OPCoach. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * OPCoach - initial API and implementation + *******************************************************************************/ +package com.opcoach.e4.preferences; + +import org.eclipse.jface.preference.IPreferenceStore; + +/** This interface can be implemented to provide a PreferenceStore for a given plugin. + * This associatino must be done in the e4PreferenceStoreProvider extension point. + * @author olivier + * + */ +public interface IPreferenceStoreProvider +{ + /** Must be implemented to return a preference store */ + public IPreferenceStore getPreferenceStore(); + +} diff --git a/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/ScopedPreferenceStore.java b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/ScopedPreferenceStore.java new file mode 100644 index 0000000..8916abb --- /dev/null +++ b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/ScopedPreferenceStore.java @@ -0,0 +1,861 @@ + +/******************************************************************************* + * Copyright (c) 2014 OPCoach. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eclipse - copy of the implementation coming from jface + *******************************************************************************/ + +package com.opcoach.e4.preferences; + +import java.io.IOException; + +import org.eclipse.core.commands.common.EventManager; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; +import org.eclipse.jface.preference.IPersistentPreferenceStore; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.util.SafeRunnable; +import org.osgi.service.prefs.BackingStoreException; + +/** + * The ScopedPreferenceStore is an IPreferenceStore that uses the scopes + * provided in org.eclipse.core.runtime.preferences. + *

+ * A ScopedPreferenceStore does the lookup of a preference based on it's search + * scopes and sets the value of the preference based on its store scope. + *

+ *

+ * The default scope is always included in the search scopes when searching for + * preference values. + *

+ * + * @see org.eclipse.core.runtime.preferences + * @since 3.1 + */ +public class ScopedPreferenceStore extends EventManager implements + IPreferenceStore, IPersistentPreferenceStore { + + /** + * The storeContext is the context where values will stored with the + * setValue methods. If there are no searchContexts this will be the search + * context. (along with the "default" context) + */ + private IScopeContext storeContext; + + /** + * The searchContext is the array of contexts that will be used by the get + * methods for searching for values. + */ + private IScopeContext[] searchContexts; + + /** + * A boolean to indicate the property changes should not be propagated. + */ + protected boolean silentRunning = false; + + /** + * The listener on the IEclipsePreferences. This is used to forward updates + * to the property change listeners on the preference store. + */ + IEclipsePreferences.IPreferenceChangeListener preferencesListener; + + /** + * The default context is the context where getDefault and setDefault + * methods will search. This context is also used in the search. + */ + private IScopeContext defaultContext = new DefaultScope(); + + /** + * The nodeQualifer is the string used to look up the node in the contexts. + */ + String nodeQualifier; + + /** + * The defaultQualifier is the string used to look up the default node. + */ + String defaultQualifier; + + /** + * Boolean value indicating whether or not this store has changes to be + * saved. + */ + private boolean dirty; + + /** + * Create a new instance of the receiver. Store the values in context in the + * node looked up by qualifier. NOTE: Any instance of + * ScopedPreferenceStore should call + * + * @param context + * the scope to store to + * @param qualifier + * the qualifier used to look up the preference node + * @param defaultQualifierPath + * the qualifier used when looking up the defaults + */ + public ScopedPreferenceStore(IScopeContext context, String qualifier, + String defaultQualifierPath) { + this(context, qualifier); + this.defaultQualifier = defaultQualifierPath; + } + + /** + * Create a new instance of the receiver. Store the values in context in the + * node looked up by qualifier. + * + * @param context + * the scope to store to + * @param qualifier + * the qualifer used to look up the preference node + */ + public ScopedPreferenceStore(IScopeContext context, String qualifier) { + storeContext = context; + this.nodeQualifier = qualifier; + this.defaultQualifier = qualifier; + + ((IEclipsePreferences) getStorePreferences().parent()) + .addNodeChangeListener(getNodeChangeListener()); + } + + /** + * Return a node change listener that adds a removes the receiver when nodes + * change. + * + * @return INodeChangeListener + */ + private INodeChangeListener getNodeChangeListener() { + return new IEclipsePreferences.INodeChangeListener() { + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener#added(org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent) + */ + public void added(NodeChangeEvent event) { + if (nodeQualifier.equals(event.getChild().name()) + && isListenerAttached()) { + getStorePreferences().addPreferenceChangeListener( + preferencesListener); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener#removed(org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent) + */ + public void removed(NodeChangeEvent event) { + // Do nothing as there are no events from removed node + } + }; + } + + /** + * Initialize the preferences listener. + */ + private void initializePreferencesListener() { + if (preferencesListener == null) { + preferencesListener = new IEclipsePreferences.IPreferenceChangeListener() { + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener#preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent) + */ + public void preferenceChange(PreferenceChangeEvent event) { + + if (silentRunning) { + return; + } + + Object oldValue = event.getOldValue(); + Object newValue = event.getNewValue(); + String key = event.getKey(); + if (newValue == null) { + newValue = getDefault(key, oldValue); + } else if (oldValue == null) { + oldValue = getDefault(key, newValue); + } + firePropertyChangeEvent(event.getKey(), oldValue, newValue); + } + }; + getStorePreferences().addPreferenceChangeListener( + preferencesListener); + } + + } + + /** + * Does its best at determining the default value for the given key. Checks + * the given object's type and then looks in the list of defaults to see if + * a value exists. If not or if there is a problem converting the value, the + * default default value for that type is returned. + * + * @param key + * the key to search + * @param obj + * the object who default we are looking for + * @return Object or null + */ + Object getDefault(String key, Object obj) { + IEclipsePreferences defaults = getDefaultPreferences(); + if (obj instanceof String) { + return defaults.get(key, STRING_DEFAULT_DEFAULT); + } else if (obj instanceof Integer) { + return new Integer(defaults.getInt(key, INT_DEFAULT_DEFAULT)); + } else if (obj instanceof Double) { + return new Double(defaults.getDouble(key, DOUBLE_DEFAULT_DEFAULT)); + } else if (obj instanceof Float) { + return new Float(defaults.getFloat(key, FLOAT_DEFAULT_DEFAULT)); + } else if (obj instanceof Long) { + return new Long(defaults.getLong(key, LONG_DEFAULT_DEFAULT)); + } else if (obj instanceof Boolean) { + return defaults.getBoolean(key, BOOLEAN_DEFAULT_DEFAULT) ? Boolean.TRUE + : Boolean.FALSE; + } else { + return null; + } + } + + /** + * Return the IEclipsePreferences node associated with this store. + * + * @return the preference node for this store + */ + IEclipsePreferences getStorePreferences() { + return storeContext.getNode(nodeQualifier); + } + + /** + * Return the default IEclipsePreferences for this store. + * + * @return this store's default preference node + */ + private IEclipsePreferences getDefaultPreferences() { + return defaultContext.getNode(defaultQualifier); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener) + */ + public void addPropertyChangeListener(IPropertyChangeListener listener) { + initializePreferencesListener();// Create the preferences listener if it + // does not exist + addListenerObject(listener); + } + + /** + * Return the preference path to search preferences on. This is the list of + * preference nodes based on the scope contexts for this store. If there are + * no search contexts set, then return this store's context. + *

+ * Whether or not the default context should be included in the resulting + * list is specified by the includeDefault parameter. + *

+ * + * @param includeDefault + * true if the default context should be included + * and false otherwise + * @return IEclipsePreferences[] + * @since 3.4 public, was added in 3.1 as private method + */ + public IEclipsePreferences[] getPreferenceNodes(boolean includeDefault) { + // if the user didn't specify a search order, then return the scope that + // this store was created on. (and optionally the default) + if (searchContexts == null) { + if (includeDefault) { + return new IEclipsePreferences[] { getStorePreferences(), + getDefaultPreferences() }; + } + return new IEclipsePreferences[] { getStorePreferences() }; + } + // otherwise the user specified a search order so return the appropriate + // nodes based on it + int length = searchContexts.length; + if (includeDefault) { + length++; + } + IEclipsePreferences[] preferences = new IEclipsePreferences[length]; + for (int i = 0; i < searchContexts.length; i++) { + preferences[i] = searchContexts[i].getNode(nodeQualifier); + } + if (includeDefault) { + preferences[length - 1] = getDefaultPreferences(); + } + return preferences; + } + + /** + * Set the search contexts to scopes. When searching for a value the seach + * will be done in the order of scope contexts and will not search the + * storeContext unless it is in this list. + *

+ * If the given list is null, then clear this store's search + * contexts. This means that only this store's scope context and default + * scope will be used during preference value searching. + *

+ *

+ * The defaultContext will be added to the end of this list automatically + * and MUST NOT be included by the user. + *

+ * + * @param scopes + * a list of scope contexts to use when searching, or + * null + */ + public void setSearchContexts(IScopeContext[] scopes) { + this.searchContexts = scopes; + if (scopes == null) { + return; + } + + // Assert that the default was not included (we automatically add it to + // the end) + for (int i = 0; i < scopes.length; i++) { + if (scopes[i].equals(defaultContext)) { + Assert + .isTrue( + false, + "Do not add the default to the search contexts"); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#contains(java.lang.String) + */ + public boolean contains(String name) { + if (name == null) { + return false; + } + return (Platform.getPreferencesService().get(name, null, + getPreferenceNodes(true))) != null; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#firePropertyChangeEvent(java.lang.String, + * java.lang.Object, java.lang.Object) + */ + public void firePropertyChangeEvent(String name, Object oldValue, + Object newValue) { + // important: create intermediate array to protect against listeners + // being added/removed during the notification + final Object[] list = getListeners(); + if (list.length == 0) { + return; + } + final PropertyChangeEvent event = new PropertyChangeEvent(this, name, + oldValue, newValue); + for (int i = 0; i < list.length; i++) { + final IPropertyChangeListener listener = (IPropertyChangeListener) list[i]; + SafeRunner.run(new SafeRunnable(JFaceResources + .getString("PreferenceStore.changeError")) { //$NON-NLS-1$ + public void run() { + listener.propertyChange(event); + } + }); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getBoolean(java.lang.String) + */ + public boolean getBoolean(String name) { + String value = internalGet(name); + return value == null ? BOOLEAN_DEFAULT_DEFAULT : Boolean.valueOf(value) + .booleanValue(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultBoolean(java.lang.String) + */ + public boolean getDefaultBoolean(String name) { + return getDefaultPreferences() + .getBoolean(name, BOOLEAN_DEFAULT_DEFAULT); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultDouble(java.lang.String) + */ + public double getDefaultDouble(String name) { + return getDefaultPreferences().getDouble(name, DOUBLE_DEFAULT_DEFAULT); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultFloat(java.lang.String) + */ + public float getDefaultFloat(String name) { + return getDefaultPreferences().getFloat(name, FLOAT_DEFAULT_DEFAULT); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultInt(java.lang.String) + */ + public int getDefaultInt(String name) { + return getDefaultPreferences().getInt(name, INT_DEFAULT_DEFAULT); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultLong(java.lang.String) + */ + public long getDefaultLong(String name) { + return getDefaultPreferences().getLong(name, LONG_DEFAULT_DEFAULT); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultString(java.lang.String) + */ + public String getDefaultString(String name) { + return getDefaultPreferences().get(name, STRING_DEFAULT_DEFAULT); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getDouble(java.lang.String) + */ + public double getDouble(String name) { + String value = internalGet(name); + if (value == null) { + return DOUBLE_DEFAULT_DEFAULT; + } + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + return DOUBLE_DEFAULT_DEFAULT; + } + } + + /** + * Return the string value for the specified key. Look in the nodes which + * are specified by this object's list of search scopes. If the value does + * not exist then return null. + * + * @param key + * the key to search with + * @return String or null if the value does not exist. + */ + private String internalGet(String key) { + return Platform.getPreferencesService().get(key, null, + getPreferenceNodes(true)); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getFloat(java.lang.String) + */ + public float getFloat(String name) { + String value = internalGet(name); + if (value == null) { + return FLOAT_DEFAULT_DEFAULT; + } + try { + return Float.parseFloat(value); + } catch (NumberFormatException e) { + return FLOAT_DEFAULT_DEFAULT; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getInt(java.lang.String) + */ + public int getInt(String name) { + String value = internalGet(name); + if (value == null) { + return INT_DEFAULT_DEFAULT; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return INT_DEFAULT_DEFAULT; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getLong(java.lang.String) + */ + public long getLong(String name) { + String value = internalGet(name); + if (value == null) { + return LONG_DEFAULT_DEFAULT; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return LONG_DEFAULT_DEFAULT; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#getString(java.lang.String) + */ + public String getString(String name) { + String value = internalGet(name); + return value == null ? STRING_DEFAULT_DEFAULT : value; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#isDefault(java.lang.String) + */ + public boolean isDefault(String name) { + if (name == null) { + return false; + } + return (Platform.getPreferencesService().get(name, null, + getPreferenceNodes(false))) == null; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#needsSaving() + */ + public boolean needsSaving() { + return dirty; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#putValue(java.lang.String, + * java.lang.String) + */ + public void putValue(String name, String value) { + try { + // Do not notify listeners + silentRunning = true; + getStorePreferences().put(name, value); + } finally { + // Be sure that an exception does not stop property updates + silentRunning = false; + dirty = true; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener) + */ + public void removePropertyChangeListener(IPropertyChangeListener listener) { + removeListenerObject(listener); + if (!isListenerAttached()) { + disposePreferenceStoreListener(); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, + * double) + */ + public void setDefault(String name, double value) { + getDefaultPreferences().putDouble(name, value); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, + * float) + */ + public void setDefault(String name, float value) { + getDefaultPreferences().putFloat(name, value); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, + * int) + */ + public void setDefault(String name, int value) { + getDefaultPreferences().putInt(name, value); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, + * long) + */ + public void setDefault(String name, long value) { + getDefaultPreferences().putLong(name, value); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, + * java.lang.String) + */ + public void setDefault(String name, String defaultObject) { + getDefaultPreferences().put(name, defaultObject); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, + * boolean) + */ + public void setDefault(String name, boolean value) { + getDefaultPreferences().putBoolean(name, value); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setToDefault(java.lang.String) + */ + public void setToDefault(String name) { + + String oldValue = getString(name); + String defaultValue = getDefaultString(name); + try { + silentRunning = true;// Turn off updates from the store + // removing a non-existing preference is a no-op so call the Core + // API directly + getStorePreferences().remove(name); + if (oldValue != defaultValue){ + dirty = true; + firePropertyChangeEvent(name, oldValue, defaultValue); + } + + } finally { + silentRunning = false;// Restart listening to preferences + } + + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, + * double) + */ + public void setValue(String name, double value) { + double oldValue = getDouble(name); + if (oldValue == value) { + return; + } + try { + silentRunning = true;// Turn off updates from the store + if (getDefaultDouble(name) == value) { + getStorePreferences().remove(name); + } else { + getStorePreferences().putDouble(name, value); + } + dirty = true; + firePropertyChangeEvent(name, new Double(oldValue), new Double( + value)); + } finally { + silentRunning = false;// Restart listening to preferences + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, + * float) + */ + public void setValue(String name, float value) { + float oldValue = getFloat(name); + if (oldValue == value) { + return; + } + try { + silentRunning = true;// Turn off updates from the store + if (getDefaultFloat(name) == value) { + getStorePreferences().remove(name); + } else { + getStorePreferences().putFloat(name, value); + } + dirty = true; + firePropertyChangeEvent(name, new Float(oldValue), new Float(value)); + } finally { + silentRunning = false;// Restart listening to preferences + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, + * int) + */ + public void setValue(String name, int value) { + int oldValue = getInt(name); + if (oldValue == value) { + return; + } + try { + silentRunning = true;// Turn off updates from the store + if (getDefaultInt(name) == value) { + getStorePreferences().remove(name); + } else { + getStorePreferences().putInt(name, value); + } + dirty = true; + firePropertyChangeEvent(name, new Integer(oldValue), new Integer( + value)); + } finally { + silentRunning = false;// Restart listening to preferences + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, + * long) + */ + public void setValue(String name, long value) { + long oldValue = getLong(name); + if (oldValue == value) { + return; + } + try { + silentRunning = true;// Turn off updates from the store + if (getDefaultLong(name) == value) { + getStorePreferences().remove(name); + } else { + getStorePreferences().putLong(name, value); + } + dirty = true; + firePropertyChangeEvent(name, new Long(oldValue), new Long(value)); + } finally { + silentRunning = false;// Restart listening to preferences + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, + * java.lang.String) + */ + public void setValue(String name, String value) { + // Do not turn on silent running here as Strings are propagated + if (getDefaultString(name).equals(value)) { + getStorePreferences().remove(name); + } else { + getStorePreferences().put(name, value); + } + dirty = true; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, + * boolean) + */ + public void setValue(String name, boolean value) { + boolean oldValue = getBoolean(name); + if (oldValue == value) { + return; + } + try { + silentRunning = true;// Turn off updates from the store + if (getDefaultBoolean(name) == value) { + getStorePreferences().remove(name); + } else { + getStorePreferences().putBoolean(name, value); + } + dirty = true; + firePropertyChangeEvent(name, oldValue ? Boolean.TRUE + : Boolean.FALSE, value ? Boolean.TRUE : Boolean.FALSE); + } finally { + silentRunning = false;// Restart listening to preferences + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.preference.IPersistentPreferenceStore#save() + */ + public void save() throws IOException { + try { + getStorePreferences().flush(); + dirty = false; + } catch (BackingStoreException e) { + throw new IOException(e.getMessage()); + } + + } + + /** + * Dispose the receiver. + */ + private void disposePreferenceStoreListener() { + + IEclipsePreferences root = (IEclipsePreferences) Platform + .getPreferencesService().getRootNode().node( + Plugin.PLUGIN_PREFERENCE_SCOPE); + try { + if (!(root.nodeExists(nodeQualifier))) { + return; + } + } catch (BackingStoreException e) { + return;// No need to report here as the node won't have the + // listener + } + + IEclipsePreferences preferences = getStorePreferences(); + if (preferences == null) { + return; + } + if (preferencesListener != null) { + preferences.removePreferenceChangeListener(preferencesListener); + preferencesListener = null; + } + } + +} diff --git a/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/handlers/E4PreferencesHandler.java b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/handlers/E4PreferencesHandler.java new file mode 100644 index 0000000..166989b --- /dev/null +++ b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/handlers/E4PreferencesHandler.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2014 OPCoach. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Manumitting Technologies : Brian de Alwis for initial API and implementation + * OPCoach : O.Prouvost fix bugs on hierarchy + *******************************************************************************//* + * Handler to open up a configured preferences dialog. + * Written by Brian de Alwis, Manumitting Technologies. + * Placed in the public domain. + * This code comes from : http://www.eclipse.org/forums/index.php/fa/4347/ + * and was referenced in the thread : http://www.eclipse.org/forums/index.php/m/750139/ + */ +package com.opcoach.e4.preferences.handlers; + +import javax.inject.Named; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.jface.preference.PreferenceDialog; +import org.eclipse.jface.preference.PreferenceManager; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.widgets.Shell; + +import com.opcoach.e4.preferences.internal.E4PreferenceRegistry; + + +public class E4PreferencesHandler +{ + + + @CanExecute + public boolean canExecute() + { + return true; + } + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell shell, E4PreferenceRegistry prefReg) + { + PreferenceManager pm = prefReg.getPreferenceManager(); + PreferenceDialog dialog = new PreferenceDialog(shell, pm); + dialog.create(); + dialog.getTreeViewer().setComparator(new ViewerComparator()); + dialog.getTreeViewer().expandAll(); + dialog.open(); + } + + + + +} diff --git a/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/internal/E4PreferenceRegistry.java b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/internal/E4PreferenceRegistry.java new file mode 100644 index 0000000..f1702fe --- /dev/null +++ b/com.opcoach.e4.preferences/src/com/opcoach/e4/preferences/internal/E4PreferenceRegistry.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * Copyright (c) 2014 OPCoach. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * OPCoach - initial API and implementation + *******************************************************************************/ +package com.opcoach.e4.preferences.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.e4.core.contexts.ContextInjectionFactory; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.core.di.annotations.Creatable; +import org.eclipse.e4.core.services.contributions.IContributionFactory; +import org.eclipse.e4.core.services.log.Logger; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IPreferenceNode; +import org.eclipse.jface.preference.IPreferencePage; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceManager; +import org.eclipse.jface.preference.PreferenceNode; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +import com.opcoach.e4.preferences.IPreferenceStoreProvider; +import com.opcoach.e4.preferences.ScopedPreferenceStore; + +@Creatable +public class E4PreferenceRegistry +{ + + public static final String PREFS_PAGE_XP = "com.opcoach.e4.preferences.e4PreferencePages"; // $NON-NLS-1$ + public static final String PREF_STORE_PROVIDER = "com.opcoach.e4.preferences.e4PreferenceStoreProvider"; // $NON-NLS-1$ + protected static final String ELMT_PAGE = "page"; // $NON-NLS-1$ + protected static final String ATTR_ID = "id"; // $NON-NLS-1$ + protected static final String ATTR_CATEGORY = "category"; // $NON-NLS-1$ + protected static final String ATTR_CLASS = "class"; // $NON-NLS-1$ + protected static final String ATTR_NAME = "name"; // $NON-NLS-1$ + + protected static final String ATTR_PLUGIN_ID = "pluginId"; // $NON-NLS-1$ + protected static final String ATTR_ID_IN_WBCONTEXT = "idInWorkbenchContext"; // $NON-NLS-1$ + + @Inject + protected Logger logger; + + @Inject + protected IEclipseContext context; + + @Inject + protected IExtensionRegistry registry; + + private PreferenceManager pm = null; + + // A map of (pluginId, { IPreferenceStoreProvider, or key in wbcontext } + private Map psProviders; + + public PreferenceManager getPreferenceManager() + { + + // Remember of the unbounded nodes to order parent pages. + // Map (all nodes except root nodes) + Map> childrenNodes = new HashMap>(); + + if (pm != null) + return pm; + + pm = new PreferenceManager(); + IContributionFactory factory = context.get(IContributionFactory.class); + + for (IConfigurationElement elmt : registry.getConfigurationElementsFor(PREFS_PAGE_XP)) + { + String bundleId = elmt.getNamespaceIdentifier(); + if (!elmt.getName().equals(ELMT_PAGE)) + { + logger.warn("unexpected element: {0}", elmt.getName()); + continue; + } else if (isEmpty(elmt.getAttribute(ATTR_ID)) || isEmpty(elmt.getAttribute(ATTR_NAME))) + { + logger.warn("missing id and/or name: {}", bundleId); + continue; + } + PreferenceNode pn = null; + if (elmt.getAttribute(ATTR_CLASS) != null) + { + PreferencePage page = null; + try + { + String prefPageURI = getClassURI(bundleId, elmt.getAttribute(ATTR_CLASS)); + Object object = factory.create(prefPageURI, context); + if (!(object instanceof PreferencePage)) + { + logger.error("Expected instance of PreferencePage: {0}", elmt.getAttribute(ATTR_CLASS)); + continue; + } + page = (PreferencePage) object; + setPreferenceStore(bundleId, page); + + } catch (ClassNotFoundException e) + { + logger.error(e); + continue; + } + ContextInjectionFactory.inject(page, context); + if ((page.getTitle() == null || page.getTitle().isEmpty()) && elmt.getAttribute(ATTR_NAME) != null) + { + page.setTitle(elmt.getAttribute(ATTR_NAME)); + } + + pn = new PreferenceNode(elmt.getAttribute(ATTR_ID), page); + } else + { + pn = new PreferenceNode(elmt.getAttribute(ATTR_ID), new EmptyPreferencePage(elmt.getAttribute(ATTR_NAME))); + } + + // Issue 2 : Fix bug on order (see : + // https://github.com/opcoach/e4Preferences/issues/2) + // Add only pages at root level and remember of child pages for + // categories + String category = elmt.getAttribute(ATTR_CATEGORY); + if (isEmpty(category)) + { + pm.addToRoot(pn); + } else + { + /* + * IPreferenceNode parent = findNode(pm, category); if (parent + * == null) { // No parent found, but may be the extension has + * not been read yet. So remember of it unboundedNodes.put(pn, + * category); } else { parent.add(pn); } + */ + // Check if this category is already registered. + Collection children = childrenNodes.get(category); + if (children == null) + { + children = new ArrayList(); + childrenNodes.put(category, children); + } + children.add(pn); + } + } + + // Must now bind pages that has not been added in nodes (depends on the + // preference page read order) + // Iterate on all possible categories + Collection categoriesDone = new ArrayList(); + + while (!childrenNodes.isEmpty()) + { + for (String cat : Collections.unmodifiableSet(childrenNodes.keySet())) + { + // Is this category already in preference manager ? If not add + // it later... + IPreferenceNode parent = findNode(pm, cat); + if (parent != null) + { + // Can add the list of children to this parent page... + for (IPreferenceNode pn : childrenNodes.get(cat)) + { + parent.add(pn); + } + // Ok This parent page is done. Can remove it from map + // outside of this loop + categoriesDone.add(cat); + } + } + + for (String keyToRemove : categoriesDone) + childrenNodes.remove(keyToRemove); + categoriesDone.clear(); + + } + + return pm; + } + + private void setPreferenceStore(String bundleId, PreferencePage page) + { + // Affect preference store to this page if this is a + // PreferencePage, else, must manage it internally + // Set the issue#1 on github : + // https://github.com/opcoach/e4Preferences/issues/1 + // And manage the extensions of IP + initialisePreferenceStoreProviders(); + + IPreferenceStore store = null; + + // Get the preference store according to policy. + Object data = psProviders.get(bundleId); + if (data != null) + { + if (data instanceof IPreferenceStore) + store = (IPreferenceStore) data; + else if (data instanceof IPreferenceStoreProvider) + store = ((IPreferenceStoreProvider) data).getPreferenceStore(); + else if (data instanceof String) + store = (IPreferenceStore) context.get((String) data); + + } else + { + // Default behavior : create a preference store for this bundle and remember of it + store = new ScopedPreferenceStore(InstanceScope.INSTANCE, bundleId); + psProviders.put(bundleId, store); + } + + + if (store != null) + page.setPreferenceStore(store); + else + { + logger.warn("Unable to set the preferenceStore for page " + page.getTitle() + " defined in bundle " + bundleId); + } + + } + + /** Read the e4PreferenceStoreProvider extension point */ + private void initialisePreferenceStoreProviders() + { + if (psProviders == null) + { + IContributionFactory factory = context.get(IContributionFactory.class); + + psProviders = new HashMap(); + + // Read extensions and fill the map... + for (IConfigurationElement elmt : registry.getConfigurationElementsFor(PREF_STORE_PROVIDER)) + { + String declaringBundle = elmt.getNamespaceIdentifier(); + String pluginId = elmt.getAttribute(ATTR_PLUGIN_ID); + if (isEmpty(pluginId)) + { + logger.warn("missing plugin Id in extension " + PREF_STORE_PROVIDER + " check the plugin " + declaringBundle); + continue; + } + + String classname = elmt.getAttribute(ATTR_CLASS); + String objectId = elmt.getAttribute(ATTR_ID_IN_WBCONTEXT); + + if ((isEmpty(classname) && isEmpty(objectId)) || (((classname != null) && classname.length() > 0) && ((objectId != null) && objectId.length() > 0))) + { + logger.warn("In extension " + PREF_STORE_PROVIDER + " only one of the two attributes (pluginId or idInWorkbenchContext) must be set. Check the plugin " + + declaringBundle); + continue; + } + + // Ok can now work with data... + Object data = objectId; + if (classname != null) + { + data = factory.create(classname, context); + if (!(data instanceof IPreferenceStoreProvider)) + { + logger.warn("In extension " + PREF_STORE_PROVIDER + " the class must implements IPreferenceStoreProvider. Check the plugin " + declaringBundle); + continue; + } + } + + psProviders.put(pluginId, data); + + } + } + } + + private IPreferenceNode findNode(PreferenceManager pm, String categoryId) + { + for (Object o : pm.getElements(PreferenceManager.POST_ORDER)) + { + if (o instanceof IPreferenceNode && ((IPreferenceNode) o).getId().equals(categoryId)) + { + return (IPreferenceNode) o; + } + } + return null; + } + + private String getClassURI(String definingBundleId, String spec) throws ClassNotFoundException + { + if (spec.startsWith("platform:")) + { + return spec; + } // $NON-NLS-1$ + return "bundleclass://" + definingBundleId + '/' + spec; + } + + private boolean isEmpty(String value) + { + return value == null || value.trim().isEmpty(); + } + + static class EmptyPreferencePage extends PreferencePage + { + + public EmptyPreferencePage(String title) + { + setTitle(title); + noDefaultAndApplyButton(); + } + + @Override + protected Control createContents(Composite parent) + { + return new Label(parent, SWT.NONE); + } + + } + +}