| /******************************************************************************* |
| * Copyright (c) 2004, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Yves YANG <yves.yang@soyatec.com> - |
| * Initial Fix for Bug 138078 [Preferences] Preferences Store for i18n support |
| *******************************************************************************/ |
| package org.eclipse.ui.preferences; |
| |
| import java.io.IOException; |
| import java.util.Objects; |
| 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.IEclipsePreferences.INodeChangeListener; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent; |
| import org.eclipse.core.runtime.preferences.IScopeContext; |
| 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.eclipse.ui.internal.WorkbenchMessages; |
| import org.osgi.service.prefs.BackingStoreException; |
| |
| /** |
| * The ScopedPreferenceStore is an IPreferenceStore that uses the scopes |
| * provided in org.eclipse.core.runtime.preferences. |
| * <p> |
| * 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. |
| * </p> |
| * <p> |
| * The default scope is always included in the search scopes when searching for |
| * preference values. |
| * </p> |
| * |
| * @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 = DefaultScope.INSTANCE; |
| |
| /** |
| * 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. <strong>NOTE:</strong> 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() { |
| @Override |
| public void added(NodeChangeEvent event) { |
| if (nodeQualifier.equals(event.getChild().name()) && isListenerAttached()) { |
| getStorePreferences().addPreferenceChangeListener(preferencesListener); |
| } |
| } |
| |
| @Override |
| 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 = 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 <code>null</code> |
| */ |
| 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 Integer.valueOf(defaults.getInt(key, INT_DEFAULT_DEFAULT)); |
| } else if (obj instanceof Double) { |
| return Double.valueOf(defaults.getDouble(key, DOUBLE_DEFAULT_DEFAULT)); |
| } else if (obj instanceof Float) { |
| return Float.valueOf(defaults.getFloat(key, FLOAT_DEFAULT_DEFAULT)); |
| } else if (obj instanceof Long) { |
| return Long.valueOf(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); |
| } |
| |
| @Override |
| 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. |
| * <p> |
| * Whether or not the default context should be included in the resulting list |
| * is specified by the <code>includeDefault</code> parameter. |
| * </p> |
| * |
| * @param includeDefault <code>true</code> if the default context should be |
| * included and <code>false</code> 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. |
| * <p> |
| * If the given list is <code>null</code>, 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. |
| * </p> |
| * <p> |
| * The defaultContext will be added to the end of this list automatically and |
| * <em>MUST NOT</em> be included by the user. |
| * </p> |
| * |
| * @param scopes a list of scope contexts to use when searching, or |
| * <code>null</code> |
| */ |
| 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 (IScopeContext scope : scopes) { |
| if (scope.equals(defaultContext)) { |
| Assert.isTrue(false, WorkbenchMessages.ScopedPreferenceStore_DefaultAddedError); |
| } |
| } |
| } |
| |
| @Override |
| public boolean contains(String name) { |
| if (name == null) { |
| return false; |
| } |
| return (Platform.getPreferencesService().get(name, null, getPreferenceNodes(true))) != null; |
| } |
| |
| @Override |
| 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[] listeners = getListeners(); |
| if (listeners.length == 0) { |
| return; |
| } |
| final PropertyChangeEvent event = new PropertyChangeEvent(this, name, oldValue, newValue); |
| for (Object listener : listeners) { |
| final IPropertyChangeListener propertyChangeListener = (IPropertyChangeListener) listener; |
| SafeRunner.run(new SafeRunnable(JFaceResources.getString("PreferenceStore.changeError")) { //$NON-NLS-1$ |
| @Override |
| public void run() { |
| propertyChangeListener.propertyChange(event); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public boolean getBoolean(String name) { |
| String value = internalGet(name); |
| return value == null ? BOOLEAN_DEFAULT_DEFAULT : Boolean.parseBoolean(value); |
| } |
| |
| @Override |
| public boolean getDefaultBoolean(String name) { |
| return getDefaultPreferences().getBoolean(name, BOOLEAN_DEFAULT_DEFAULT); |
| } |
| |
| @Override |
| public double getDefaultDouble(String name) { |
| return getDefaultPreferences().getDouble(name, DOUBLE_DEFAULT_DEFAULT); |
| } |
| |
| @Override |
| public float getDefaultFloat(String name) { |
| return getDefaultPreferences().getFloat(name, FLOAT_DEFAULT_DEFAULT); |
| } |
| |
| @Override |
| public int getDefaultInt(String name) { |
| return getDefaultPreferences().getInt(name, INT_DEFAULT_DEFAULT); |
| } |
| |
| @Override |
| public long getDefaultLong(String name) { |
| return getDefaultPreferences().getLong(name, LONG_DEFAULT_DEFAULT); |
| } |
| |
| @Override |
| public String getDefaultString(String name) { |
| return getDefaultPreferences().get(name, STRING_DEFAULT_DEFAULT); |
| } |
| |
| @Override |
| 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 <code>null</code>. |
| * |
| * @param key the key to search with |
| * @return String or <code>null</code> if the value does not exist. |
| */ |
| private String internalGet(String key) { |
| return Platform.getPreferencesService().get(key, null, getPreferenceNodes(true)); |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| public String getString(String name) { |
| String value = internalGet(name); |
| return value == null ? STRING_DEFAULT_DEFAULT : value; |
| } |
| |
| @Override |
| public boolean isDefault(String name) { |
| if (name == null) { |
| return false; |
| } |
| return (Platform.getPreferencesService().get(name, null, getPreferenceNodes(false))) == null; |
| } |
| |
| @Override |
| public boolean needsSaving() { |
| return dirty; |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| public void removePropertyChangeListener(IPropertyChangeListener listener) { |
| removeListenerObject(listener); |
| if (!isListenerAttached()) { |
| disposePreferenceStoreListener(); |
| } |
| } |
| |
| @Override |
| public void setDefault(String name, double value) { |
| getDefaultPreferences().putDouble(name, value); |
| } |
| |
| @Override |
| public void setDefault(String name, float value) { |
| getDefaultPreferences().putFloat(name, value); |
| } |
| |
| @Override |
| public void setDefault(String name, int value) { |
| getDefaultPreferences().putInt(name, value); |
| } |
| |
| @Override |
| public void setDefault(String name, long value) { |
| getDefaultPreferences().putLong(name, value); |
| } |
| |
| @Override |
| public void setDefault(String name, String defaultObject) { |
| getDefaultPreferences().put(name, defaultObject); |
| } |
| |
| @Override |
| public void setDefault(String name, boolean value) { |
| getDefaultPreferences().putBoolean(name, value); |
| } |
| |
| @Override |
| 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 (!Objects.equals(oldValue, defaultValue)) { |
| dirty = true; |
| firePropertyChangeEvent(name, oldValue, defaultValue); |
| } |
| |
| } finally { |
| silentRunning = false;// Restart listening to preferences |
| } |
| |
| } |
| |
| @Override |
| 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, Double.valueOf(oldValue), Double.valueOf(value)); |
| } finally { |
| silentRunning = false;// Restart listening to preferences |
| } |
| } |
| |
| @Override |
| 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, Float.valueOf(oldValue), Float.valueOf(value)); |
| } finally { |
| silentRunning = false;// Restart listening to preferences |
| } |
| } |
| |
| @Override |
| 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, Integer.valueOf(oldValue), Integer.valueOf(value)); |
| } finally { |
| silentRunning = false;// Restart listening to preferences |
| } |
| } |
| |
| @Override |
| 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, Long.valueOf(oldValue), Long.valueOf(value)); |
| } finally { |
| silentRunning = false;// Restart listening to preferences |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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 |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| } |