diff options
2 files changed, 1336 insertions, 1234 deletions
diff --git a/bundles/org.eclipse.equinox.preferences/.settings/org.eclipse.pde.api.tools.prefs b/bundles/org.eclipse.equinox.preferences/.settings/org.eclipse.pde.api.tools.prefs new file mode 100644 index 000000000..7e99fe9e5 --- /dev/null +++ b/bundles/org.eclipse.equinox.preferences/.settings/org.eclipse.pde.api.tools.prefs @@ -0,0 +1,98 @@ +#Tue Aug 23 08:47:58 EDT 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error +ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_METHOD=Error +CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error +CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error +CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error +CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error +ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error +ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error +ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +FIELD_ELEMENT_TYPE_ADDED_VALUE=Error +FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error +FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error +FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error +FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error +ILLEGAL_EXTEND=Warning +ILLEGAL_IMPLEMENT=Warning +ILLEGAL_INSTANTIATE=Warning +ILLEGAL_OVERRIDE=Warning +ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error +INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error +INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_JAVADOC_TAG=Ignore +INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error +LEAK_EXTEND=Warning +LEAK_FIELD_DECL=Warning +LEAK_IMPLEMENT=Warning +LEAK_METHOD_PARAM=Warning +LEAK_METHOD_RETURN_TYPE=Warning +METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error +METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error +UNUSED_PROBLEM_FILTERS=Warning +automatically_removed_unused_problem_filters=false +eclipse.preferences.version=1 +incompatible_api_component_version=Error +incompatible_api_component_version_include_major_without_breaking_change=Disabled +incompatible_api_component_version_include_minor_without_api_change=Disabled +invalid_since_tag_version=Error +malformed_since_tag=Error +missing_since_tag=Error +report_api_breakage_when_major_version_incremented=Disabled +report_resolution_errors_api_component=Warning diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java index 59deb0b9f..f419200b2 100644 --- a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java +++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java @@ -1,1234 +1,1238 @@ -/******************************************************************************* - * Copyright (c) 2004, 2011 IBM Corporation and others. - * 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: - * IBM Corporation - initial API and implementation - * Julian Chen - fix for bug #92572, jclRM - *******************************************************************************/ -package org.eclipse.core.internal.preferences; - -import java.io.*; -import java.util.*; -import org.eclipse.core.internal.runtime.RuntimeLog; -import org.eclipse.core.runtime.*; -import org.eclipse.core.runtime.preferences.*; -import org.eclipse.osgi.util.NLS; -import org.osgi.service.prefs.BackingStoreException; -import org.osgi.service.prefs.Preferences; - -/** - * Represents a node in the Eclipse preference node hierarchy. This class - * is used as a default implementation/super class for those nodes which - * belong to scopes which are contributed by the Platform. - * - * Implementation notes: - * - * - For thread safety, we always synchronize on the node object when writing - * the children or properties fields. Must ensure we don't synchronize when calling - * client code such as listeners. - * - * @since 3.0 - */ -public class EclipsePreferences implements IEclipsePreferences, IScope { - - public static final String DEFAULT_PREFERENCES_DIRNAME = ".settings"; //$NON-NLS-1$ - public static final String PREFS_FILE_EXTENSION = "prefs"; //$NON-NLS-1$ - protected static final IEclipsePreferences[] EMPTY_NODE_ARRAY = new IEclipsePreferences[0]; - protected static final String[] EMPTY_STRING_ARRAY = new String[0]; - private static final String FALSE = "false"; //$NON-NLS-1$ - private static final String TRUE = "true"; //$NON-NLS-1$ - protected static final String VERSION_KEY = "eclipse.preferences.version"; //$NON-NLS-1$ - protected static final String VERSION_VALUE = "1"; //$NON-NLS-1$ - protected static final String PATH_SEPARATOR = String.valueOf(IPath.SEPARATOR); - protected static final String DOUBLE_SLASH = "//"; //$NON-NLS-1$ - protected static final String EMPTY_STRING = ""; //$NON-NLS-1$ - - private String cachedPath; - protected Map children; - protected boolean dirty = false; - protected boolean loading = false; - protected final String name; - // the parent of an EclipsePreference node is always an EclipsePreference node. (or null) - protected final EclipsePreferences parent; - protected ImmutableMap properties = ImmutableMap.EMPTY; - protected boolean removed = false; - private ListenerList nodeChangeListeners; - private ListenerList preferenceChangeListeners; - - public static boolean DEBUG_PREFERENCE_GENERAL = false; - public static boolean DEBUG_PREFERENCE_SET = false; - public static boolean DEBUG_PREFERENCE_GET = false; - - protected final static String debugPluginName = "org.eclipse.equinox.preferences"; //$NON-NLS-1$ - - static { - DEBUG_PREFERENCE_GENERAL = PreferencesOSGiUtils.getDefault().getBooleanDebugOption(debugPluginName + "/general", false); //$NON-NLS-1$ - DEBUG_PREFERENCE_SET = PreferencesOSGiUtils.getDefault().getBooleanDebugOption(debugPluginName + "/set", false); //$NON-NLS-1$ - DEBUG_PREFERENCE_GET = PreferencesOSGiUtils.getDefault().getBooleanDebugOption(debugPluginName + "/get", false); //$NON-NLS-1$ - } - - protected class SortedProperties extends Properties { - - private static final long serialVersionUID = 1L; - - public SortedProperties() { - super(); - } - - /* (non-Javadoc) - * @see java.util.Hashtable#keys() - */ - public synchronized Enumeration keys() { - TreeSet set = new TreeSet(); - for (Enumeration e = super.keys(); e.hasMoreElements();) - set.add(e.nextElement()); - return Collections.enumeration(set); - } - - /* (non-Javadoc) - * @see java.util.Hashtable#entrySet() - */ - public Set entrySet() { - TreeSet set = new TreeSet(new Comparator() { - public int compare(Object e1, Object e2) { - String s1 = (String) ((Map.Entry) e1).getKey(); - String s2 = (String) ((Map.Entry) e2).getKey(); - return s1.compareTo(s2); - } - }); - for (Iterator i = super.entrySet().iterator(); i.hasNext();) - set.add(i.next()); - return set; - } - } - - public EclipsePreferences() { - this(null, null); - } - - protected EclipsePreferences(EclipsePreferences parent, String name) { - super(); - this.parent = parent; - this.name = name; - this.cachedPath = null; // make sure the cached path is cleared after setting the parent - } - - /* - * @see org.osgi.service.prefs.Preferences#absolutePath() - */ - public String absolutePath() { - if (cachedPath == null) { - if (parent == null) - cachedPath = PATH_SEPARATOR; - else { - String parentPath = parent.absolutePath(); - // if the parent is the root then we don't have to add a separator - // between the parent path and our path - if (parentPath.length() == 1) - cachedPath = parentPath + name(); - else - cachedPath = parentPath + PATH_SEPARATOR + name(); - } - } - return cachedPath; - } - - public void accept(IPreferenceNodeVisitor visitor) throws BackingStoreException { - if (!visitor.visit(this)) - return; - IEclipsePreferences[] toVisit = getChildren(true); - for (int i = 0; i < toVisit.length; i++) - toVisit[i].accept(visitor); - } - - protected synchronized IEclipsePreferences addChild(String childName, IEclipsePreferences child) { - //Thread safety: synchronize method to protect modification of children field - if (children == null) - children = Collections.synchronizedMap(new HashMap()); - children.put(childName, child == null ? (Object) childName : child); - return child; - } - - /* - * @see org.eclipse.core.runtime.IEclipsePreferences#addNodeChangeListener(org.eclipse.core.runtime.IEclipsePreferences.INodeChangeListener) - */ - public void addNodeChangeListener(INodeChangeListener listener) { - checkRemoved(); - if (nodeChangeListeners == null) - nodeChangeListeners = new ListenerList(); - nodeChangeListeners.add(listener); - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Added preference node change listener: " + listener + " to: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ - } - - /* - * @see org.eclipse.core.runtime.IEclipsePreferences#addPreferenceChangeListener(org.eclipse.core.runtime.IEclipsePreferences.IPreferenceChangeListener) - */ - public void addPreferenceChangeListener(IPreferenceChangeListener listener) { - checkRemoved(); - if (preferenceChangeListeners == null) - preferenceChangeListeners = new ListenerList(); - preferenceChangeListeners.add(listener); - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Added preference property change listener: " + listener + " to: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ - } - - private IEclipsePreferences calculateRoot() { - IEclipsePreferences result = this; - while (result.parent() != null) - result = (IEclipsePreferences) result.parent(); - return result; - } - - /* - * Convenience method for throwing an exception when methods - * are called on a removed node. - */ - protected void checkRemoved() { - if (removed) - throw new IllegalStateException(NLS.bind(PrefsMessages.preferences_removedNode, name)); - } - - /* - * @see org.osgi.service.prefs.Preferences#childrenNames() - */ - public String[] childrenNames() { - // illegal state if this node has been removed - checkRemoved(); - return internalChildNames(); - } - - protected String[] internalChildNames() { - Map temp = children; - if (temp == null || temp.size() == 0) - return EMPTY_STRING_ARRAY; - return (String[]) temp.keySet().toArray(EMPTY_STRING_ARRAY); - } - - /* - * @see org.osgi.service.prefs.Preferences#clear() - */ - public void clear() { - // illegal state if this node has been removed - checkRemoved(); - // call each one separately (instead of Properties.clear) so - // clients get change notification - String[] keys = properties.keys(); - for (int i = 0; i < keys.length; i++) - remove(keys[i]); - makeDirty(); - } - - protected String[] computeChildren(IPath root) { - if (root == null) - return EMPTY_STRING_ARRAY; - IPath dir = root.append(DEFAULT_PREFERENCES_DIRNAME); - final ArrayList result = new ArrayList(); - final String extension = '.' + PREFS_FILE_EXTENSION; - File file = dir.toFile(); - File[] totalFiles = file.listFiles(); - if (totalFiles != null) { - for (int i = 0; i < totalFiles.length; i++) { - if (totalFiles[i].isFile()) { - String filename = totalFiles[i].getName(); - if (filename.endsWith(extension)) { - String shortName = filename.substring(0, filename.length() - extension.length()); - result.add(shortName); - } - } - } - } - return (String[]) result.toArray(EMPTY_STRING_ARRAY); - } - - protected IPath computeLocation(IPath root, String qualifier) { - return root == null ? null : root.append(DEFAULT_PREFERENCES_DIRNAME).append(qualifier).addFileExtension(PREFS_FILE_EXTENSION); - } - - /* - * Version 1 (current version) - * path/key=value - */ - protected static void convertFromProperties(EclipsePreferences node, Properties table, boolean notify) { - String version = table.getProperty(VERSION_KEY); - if (version == null || !VERSION_VALUE.equals(version)) { - // ignore for now - } - table.remove(VERSION_KEY); - for (Iterator i = table.keySet().iterator(); i.hasNext();) { - String fullKey = (String) i.next(); - String value = table.getProperty(fullKey); - if (value != null) { - String[] splitPath = decodePath(fullKey); - String path = splitPath[0]; - path = makeRelative(path); - String key = splitPath[1]; - if (DEBUG_PREFERENCE_SET) - PrefsMessages.message("Setting preference: " + path + '/' + key + '=' + value); //$NON-NLS-1$ - //use internal methods to avoid notifying listeners - EclipsePreferences childNode = (EclipsePreferences) node.internalNode(path, false, null); - String oldValue = childNode.internalPut(key, value); - // notify listeners if applicable - if (notify && !value.equals(oldValue)) - childNode.firePreferenceEvent(key, oldValue, value); - } - } - PreferencesService.getDefault().shareStrings(); - } - - /* - * Helper method to persist a Properties object to the filesystem. We use this - * helper so we can remove the date/timestamp that Properties#store always - * puts in the file. - */ - protected static void write(Properties properties, IPath location) throws BackingStoreException { - // create the parent dirs if they don't exist - File parentFile = location.toFile().getParentFile(); - if (parentFile == null) - return; - parentFile.mkdirs(); - - OutputStream output = null; - try { - output = new BufferedOutputStream(new FileOutputStream(new File(location.toOSString()))); - output.write(removeTimestampFromTable(properties).getBytes("UTF-8")); //$NON-NLS-1$ - output.flush(); - } catch (IOException e) { - String message = NLS.bind(PrefsMessages.preferences_saveException, location); - log(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e)); - throw new BackingStoreException(message); - } finally { - if (output != null) - try { - output.close(); - } catch (IOException e) { - // ignore - } - } - } - - protected static String removeTimestampFromTable(Properties properties) throws IOException { - // store the properties in a string and then skip the first line (date/timestamp) - Writer writer = new StringWriter(); - properties.store(writer, null); - String string = writer.toString(); - String separator = System.getProperty("line.separator"); //$NON-NLS-1$ - return string.substring(string.indexOf(separator) + separator.length()); - } - - /* - * Helper method to convert this node to a Properties file suitable - * for persistence. - */ - protected Properties convertToProperties(Properties result, String prefix) throws BackingStoreException { - // add the key/value pairs from this node - boolean addSeparator = prefix.length() != 0; - //thread safety: copy reference in case of concurrent change - ImmutableMap temp = properties; - String[] keys = temp.keys(); - for (int i = 0, imax = keys.length; i < imax; i++) { - String value = temp.get(keys[i]); - if (value != null) - result.put(encodePath(prefix, keys[i]), value); - } - // recursively add the child information - IEclipsePreferences[] childNodes = getChildren(true); - for (int i = 0; i < childNodes.length; i++) { - EclipsePreferences child = (EclipsePreferences) childNodes[i]; - String fullPath = addSeparator ? prefix + PATH_SEPARATOR + child.name() : child.name(); - child.convertToProperties(result, fullPath); - } - PreferencesService.getDefault().shareStrings(); - return result; - } - - /* - * @see org.eclipse.core.runtime.preferences.IScope#create(org.eclipse.core.runtime.preferences.IEclipsePreferences) - */ - public IEclipsePreferences create(IEclipsePreferences nodeParent, String nodeName) { - return create((EclipsePreferences) nodeParent, nodeName, null); - } - - protected boolean isLoading() { - return loading; - } - - protected void setLoading(boolean isLoading) { - loading = isLoading; - } - - public IEclipsePreferences create(EclipsePreferences nodeParent, String nodeName, Object context) { - EclipsePreferences result = internalCreate(nodeParent, nodeName, context); - nodeParent.addChild(nodeName, result); - IEclipsePreferences loadLevel = result.getLoadLevel(); - - // if this node or a parent node is not the load level then return - if (loadLevel == null) - return result; - - // if the result node is not a load level, then a child must be - if (result != loadLevel) - return result; - - // the result node is a load level - if (isAlreadyLoaded(result) || result.isLoading()) - return result; - try { - result.setLoading(true); - result.loadLegacy(); - result.load(); - result.loaded(); - result.flush(); - } catch (BackingStoreException e) { - IPath location = result.getLocation(); - String message = NLS.bind(PrefsMessages.preferences_loadException, location == null ? EMPTY_STRING : location.toString()); - IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e); - RuntimeLog.log(status); - } finally { - result.setLoading(false); - } - return result; - } - - /* - * @see org.osgi.service.prefs.Preferences#flush() - */ - synchronized public void flush() throws BackingStoreException { - // illegal state if this node has been removed - checkRemoved(); - - IEclipsePreferences loadLevel = getLoadLevel(); - - // if this node or a parent is not the load level, then flush the children - if (loadLevel == null) { - String[] childrenNames = childrenNames(); - for (int i = 0; i < childrenNames.length; i++) - node(childrenNames[i]).flush(); - return; - } - - // a parent is the load level for this node - if (this != loadLevel) { - loadLevel.flush(); - return; - } - - // this node is a load level - // any work to do? - if (!dirty) - return; - //remove dirty bit before saving, to ensure that concurrent - //changes during save mark the store as dirty - dirty = false; - try { - save(); - } catch (BackingStoreException e) { - //mark it dirty again because the save failed - dirty = true; - throw e; - } - } - - /* - * @see org.osgi.service.prefs.Preferences#get(java.lang.String, java.lang.String) - */ - public String get(String key, String defaultValue) { - String value = internalGet(key); - return value == null ? defaultValue : value; - } - - /* - * @see org.osgi.service.prefs.Preferences#getBoolean(java.lang.String, boolean) - */ - public boolean getBoolean(String key, boolean defaultValue) { - String value = internalGet(key); - return value == null ? defaultValue : TRUE.equalsIgnoreCase(value); - } - - /* - * @see org.osgi.service.prefs.Preferences#getByteArray(java.lang.String, byte[]) - */ - public byte[] getByteArray(String key, byte[] defaultValue) { - String value = internalGet(key); - return value == null ? defaultValue : Base64.decode(value.getBytes()); - } - - /* - * Return a boolean value indicating whether or not a child with the given - * name is known to this node. - */ - protected synchronized boolean childExists(String childName) { - if (children == null) - return false; - return children.get(childName) != null; - } - - /** - * Thread safe way to obtain a child for a given key. Returns the child - * that matches the given key, or null if there is no matching child. - */ - protected IEclipsePreferences getChild(String key, Object context, boolean create) { - synchronized (this) { - if (children == null) - return null; - Object value = children.get(key); - if (value == null) - return null; - if (value instanceof IEclipsePreferences) - return (IEclipsePreferences) value; - // if we aren't supposed to create this node, then - // just return null - if (!create) - return null; - } - return addChild(key, create(this, key, context)); - } - - /** - * Thread safe way to obtain all children of this node. Never returns null. - */ - protected IEclipsePreferences[] getChildren(boolean create) { - ArrayList result = new ArrayList(); - String[] names = internalChildNames(); - for (int i = 0; i < names.length; i++) { - IEclipsePreferences child = getChild(names[i], null, create); - if (child != null) - result.add(child); - } - return (IEclipsePreferences[]) result.toArray(EMPTY_NODE_ARRAY); - } - - /* - * @see org.osgi.service.prefs.Preferences#getDouble(java.lang.String, double) - */ - public double getDouble(String key, double defaultValue) { - String value = internalGet(key); - double result = defaultValue; - if (value != null) - try { - result = Double.parseDouble(value); - } catch (NumberFormatException e) { - // use default - } - return result; - } - - /* - * @see org.osgi.service.prefs.Preferences#getFloat(java.lang.String, float) - */ - public float getFloat(String key, float defaultValue) { - String value = internalGet(key); - float result = defaultValue; - if (value != null) - try { - result = Float.parseFloat(value); - } catch (NumberFormatException e) { - // use default - } - return result; - } - - /* - * @see org.osgi.service.prefs.Preferences#getInt(java.lang.String, int) - */ - public int getInt(String key, int defaultValue) { - String value = internalGet(key); - int result = defaultValue; - if (value != null) - try { - result = Integer.parseInt(value); - } catch (NumberFormatException e) { - // use default - } - return result; - } - - protected IEclipsePreferences getLoadLevel() { - return null; - } - - /* - * Subclasses to over-ride - */ - protected IPath getLocation() { - return null; - } - - /* - * @see org.osgi.service.prefs.Preferences#getLong(java.lang.String, long) - */ - public long getLong(String key, long defaultValue) { - String value = internalGet(key); - long result = defaultValue; - if (value != null) - try { - result = Long.parseLong(value); - } catch (NumberFormatException e) { - // use default - } - return result; - } - - protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) { - return new EclipsePreferences(nodeParent, nodeName); - } - - /** - * Returns the existing value at the given key, or null if - * no such value exists. - */ - protected String internalGet(String key) { - // throw NPE if key is null - if (key == null) - throw new NullPointerException(); - // illegal state if this node has been removed - checkRemoved(); - String result = properties.get(key); - if (DEBUG_PREFERENCE_GET) - PrefsMessages.message("Getting preference value: " + absolutePath() + '/' + key + "->" + result); //$NON-NLS-1$ //$NON-NLS-2$ - return result; - } - - /** - * Implements the node(String) method, and optionally notifies listeners. - */ - protected IEclipsePreferences internalNode(String path, boolean notify, Object context) { - - // illegal state if this node has been removed - checkRemoved(); - - // short circuit this node - if (path.length() == 0) - return this; - - // if we have an absolute path use the root relative to - // this node instead of the global root - // in case we have a different hierarchy. (e.g. export) - if (path.charAt(0) == IPath.SEPARATOR) - return (IEclipsePreferences) calculateRoot().node(path.substring(1)); - - int index = path.indexOf(IPath.SEPARATOR); - String key = index == -1 ? path : path.substring(0, index); - boolean added = false; - IEclipsePreferences child = getChild(key, context, true); - if (child == null) { - child = create(this, key, context); - added = true; - } - // notify listeners if a child was added - if (added && notify) - fireNodeEvent(new NodeChangeEvent(this, child), true); - return (IEclipsePreferences) child.node(index == -1 ? EMPTY_STRING : path.substring(index + 1)); - } - - /** - * Stores the given (key,value) pair, performing lazy initialization of the - * properties field if necessary. Returns the old value for the given key, - * or null if no value existed. - */ - protected String internalPut(String key, String newValue) { - // illegal state if this node has been removed - checkRemoved(); - String oldValue = properties.get(key); - if (oldValue != null && oldValue.equals(newValue)) - return oldValue; - if (DEBUG_PREFERENCE_SET) - PrefsMessages.message("Setting preference: " + absolutePath() + '/' + key + '=' + newValue); //$NON-NLS-1$ - properties = properties.put(key, newValue); - return oldValue; - } - - /* - * Subclasses to over-ride. - */ - protected boolean isAlreadyLoaded(IEclipsePreferences node) { - return true; - } - - /* - * @see org.osgi.service.prefs.Preferences#keys() - */ - public String[] keys() { - // illegal state if this node has been removed - checkRemoved(); - return properties.keys(); - } - - /** - * Loads the preference node. This method returns silently if the node does not exist - * in the backing store (for example non-existent project). - * - * @throws BackingStoreException if the node exists in the backing store but it - * could not be loaded - */ - protected void load() throws BackingStoreException { - load(getLocation()); - } - - protected static Properties loadProperties(IPath location) throws BackingStoreException { - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Loading preferences from file: " + location); //$NON-NLS-1$ - InputStream input = null; - Properties result = new Properties(); - try { - input = new BufferedInputStream(new FileInputStream(location.toFile())); - result.load(input); - } catch (FileNotFoundException e) { - // file doesn't exist but that's ok. - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Preference file does not exist: " + location); //$NON-NLS-1$ - return result; - } catch (IOException e) { - String message = NLS.bind(PrefsMessages.preferences_loadException, location); - log(new Status(IStatus.INFO, PrefsMessages.OWNER_NAME, IStatus.INFO, message, e)); - throw new BackingStoreException(message); - } finally { - if (input != null) - try { - input.close(); - } catch (IOException e) { - // ignore - } - } - return result; - } - - protected void load(IPath location) throws BackingStoreException { - if (location == null) { - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Unable to determine location of preference file for node: " + absolutePath()); //$NON-NLS-1$ - return; - } - Properties fromDisk = loadProperties(location); - convertFromProperties(this, fromDisk, false); - } - - protected void loaded() { - // do nothing - } - - protected void loadLegacy() { - // sub-classes to over-ride if necessary - } - - public static void log(IStatus status) { - RuntimeLog.log(status); - } - - protected void makeDirty() { - EclipsePreferences node = this; - while (node != null && !node.removed) { - node.dirty = true; - node = (EclipsePreferences) node.parent(); - } - } - - public boolean isDirty() { - return dirty; - } - - /* - * @see org.osgi.service.prefs.Preferences#name() - */ - public String name() { - return name; - } - - /* - * @see org.osgi.service.prefs.Preferences#node(java.lang.String) - */ - public Preferences node(String pathName) { - return internalNode(pathName, true, null); - } - - protected void fireNodeEvent(final NodeChangeEvent event, final boolean added) { - if (nodeChangeListeners == null) - return; - Object[] listeners = nodeChangeListeners.getListeners(); - for (int i = 0; i < listeners.length; i++) { - final INodeChangeListener listener = (INodeChangeListener) listeners[i]; - ISafeRunnable job = new ISafeRunnable() { - public void handleException(Throwable exception) { - // already logged in Platform#run() - } - - public void run() throws Exception { - if (added) - listener.added(event); - else - listener.removed(event); - } - }; - SafeRunner.run(job); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#nodeExists(java.lang.String) - */ - public boolean nodeExists(String path) throws BackingStoreException { - // short circuit for checking this node - if (path.length() == 0) - return !removed; - - // illegal state if this node has been removed. - // do this AFTER checking for the empty string. - checkRemoved(); - - // use the root relative to this node instead of the global root - // in case we have a different hierarchy. (e.g. export) - if (path.charAt(0) == IPath.SEPARATOR) - return calculateRoot().nodeExists(path.substring(1)); - - int index = path.indexOf(IPath.SEPARATOR); - boolean noSlash = index == -1; - - // if we are looking for a simple child then just look in the table and return - if (noSlash) - return childExists(path); - - // otherwise load the parent of the child and then recursively ask - String childName = path.substring(0, index); - if (!childExists(childName)) - return false; - IEclipsePreferences child = getChild(childName, null, true); - if (child == null) - return false; - return child.nodeExists(path.substring(index + 1)); - } - - /* - * @see org.osgi.service.prefs.Preferences#parent() - */ - public Preferences parent() { - // illegal state if this node has been removed - checkRemoved(); - return parent; - } - - /* - * Convenience method for notifying preference change listeners. - */ - protected void firePreferenceEvent(String key, Object oldValue, Object newValue) { - if (preferenceChangeListeners == null) - return; - Object[] listeners = preferenceChangeListeners.getListeners(); - final PreferenceChangeEvent event = new PreferenceChangeEvent(this, key, oldValue, newValue); - for (int i = 0; i < listeners.length; i++) { - final IPreferenceChangeListener listener = (IPreferenceChangeListener) listeners[i]; - ISafeRunnable job = new ISafeRunnable() { - public void handleException(Throwable exception) { - // already logged in Platform#run() - } - - public void run() throws Exception { - listener.preferenceChange(event); - } - }; - SafeRunner.run(job); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#put(java.lang.String, java.lang.String) - */ - public void put(String key, String newValue) { - if (key == null || newValue == null) - throw new NullPointerException(); - String oldValue = internalPut(key, newValue); - if (!newValue.equals(oldValue)) { - makeDirty(); - firePreferenceEvent(key, oldValue, newValue); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#putBoolean(java.lang.String, boolean) - */ - public void putBoolean(String key, boolean value) { - if (key == null) - throw new NullPointerException(); - String newValue = value ? TRUE : FALSE; - String oldValue = internalPut(key, newValue); - if (!newValue.equals(oldValue)) { - makeDirty(); - firePreferenceEvent(key, oldValue, newValue); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#putByteArray(java.lang.String, byte[]) - */ - public void putByteArray(String key, byte[] value) { - if (key == null || value == null) - throw new NullPointerException(); - String newValue = new String(Base64.encode(value)); - String oldValue = internalPut(key, newValue); - if (!newValue.equals(oldValue)) { - makeDirty(); - firePreferenceEvent(key, oldValue, newValue); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#putDouble(java.lang.String, double) - */ - public void putDouble(String key, double value) { - if (key == null) - throw new NullPointerException(); - String newValue = Double.toString(value); - String oldValue = internalPut(key, newValue); - if (!newValue.equals(oldValue)) { - makeDirty(); - firePreferenceEvent(key, oldValue, newValue); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#putFloat(java.lang.String, float) - */ - public void putFloat(String key, float value) { - if (key == null) - throw new NullPointerException(); - String newValue = Float.toString(value); - String oldValue = internalPut(key, newValue); - if (!newValue.equals(oldValue)) { - makeDirty(); - firePreferenceEvent(key, oldValue, newValue); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#putInt(java.lang.String, int) - */ - public void putInt(String key, int value) { - if (key == null) - throw new NullPointerException(); - String newValue = Integer.toString(value); - String oldValue = internalPut(key, newValue); - if (!newValue.equals(oldValue)) { - makeDirty(); - firePreferenceEvent(key, oldValue, newValue); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#putLong(java.lang.String, long) - */ - public void putLong(String key, long value) { - if (key == null) - throw new NullPointerException(); - String newValue = Long.toString(value); - String oldValue = internalPut(key, newValue); - if (!newValue.equals(oldValue)) { - makeDirty(); - firePreferenceEvent(key, oldValue, newValue); - } - } - - /* - * @see org.osgi.service.prefs.Preferences#remove(java.lang.String) - */ - public void remove(String key) { - // illegal state if this node has been removed - checkRemoved(); - String oldValue = properties.get(key); - if (oldValue == null) - return; - properties = properties.removeKey(key); - makeDirty(); - firePreferenceEvent(key, oldValue, null); - } - - /* - * @see org.osgi.service.prefs.Preferences#removeNode() - */ - public void removeNode() throws BackingStoreException { - // illegal state if this node has been removed - checkRemoved(); - // clear all the property values. do it "the long way" so - // everyone gets notification - String[] keys = keys(); - for (int i = 0; i < keys.length; i++) - remove(keys[i]); - // don't remove the global root or the scope root from the - // parent but remove all its children - if (parent != null && !(parent instanceof RootPreferences)) { - // remove the node from the parent's collection and notify listeners - removed = true; - parent.removeNode(this); - } - IEclipsePreferences[] childNodes = getChildren(false); - for (int i = 0; i < childNodes.length; i++) - try { - childNodes[i].removeNode(); - } catch (IllegalStateException e) { - // ignore since we only get this exception if we have already - // been removed. no work to do. - } - } - - /* - * Remove the child from the collection and notify the listeners if something - * was actually removed. - */ - protected void removeNode(IEclipsePreferences child) { - boolean wasRemoved = false; - synchronized (this) { - if (children != null) { - wasRemoved = children.remove(child.name()) != null; - if (wasRemoved) - makeDirty(); - if (children.isEmpty()) - children = null; - } - } - if (wasRemoved) - fireNodeEvent(new NodeChangeEvent(this, child), false); - } - - /* - * Remove non-initialized node from the collection. - */ - protected void removeNode(String key) { - synchronized (this) { - if (children != null) { - boolean wasRemoved = children.remove(key) != null; - if (wasRemoved) - makeDirty(); - if (children.isEmpty()) - children = null; - } - } - } - - /* - * @see org.eclipse.core.runtime.IEclipsePreferences#removeNodeChangeListener(org.eclipse.core.runtime.IEclipsePreferences.removeNodeChangeListener) - */ - public void removeNodeChangeListener(INodeChangeListener listener) { - checkRemoved(); - if (nodeChangeListeners == null) - return; - nodeChangeListeners.remove(listener); - if (nodeChangeListeners.size() == 0) - nodeChangeListeners = null; - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Removed preference node change listener: " + listener + " from: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ - } - - /* - * @see org.eclipse.core.runtime.IEclipsePreferences#removePreferenceChangeListener(org.eclipse.core.runtime.IEclipsePreferences.IPreferenceChangeListener) - */ - public void removePreferenceChangeListener(IPreferenceChangeListener listener) { - checkRemoved(); - if (preferenceChangeListeners == null) - return; - preferenceChangeListeners.remove(listener); - if (preferenceChangeListeners.size() == 0) - preferenceChangeListeners = null; - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Removed preference property change listener: " + listener + " from: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ - } - - /** - * Saves the preference node. This method returns silently if the node does not exist - * in the backing store (for example non-existent project) - * - * @throws BackingStoreException if the node exists in the backing store but it - * could not be saved - */ - protected void save() throws BackingStoreException { - save(getLocation()); - } - - protected void save(IPath location) throws BackingStoreException { - if (location == null) { - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Unable to determine location of preference file for node: " + absolutePath()); //$NON-NLS-1$ - return; - } - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Saving preferences to file: " + location); //$NON-NLS-1$ - Properties table = convertToProperties(new SortedProperties(), EMPTY_STRING); - if (table.isEmpty()) { - // nothing to save. delete existing file if one exists. - if (location.toFile().exists() && !location.toFile().delete()) { - String message = NLS.bind(PrefsMessages.preferences_failedDelete, location); - log(new Status(IStatus.WARNING, PrefsMessages.OWNER_NAME, IStatus.WARNING, message, null)); - } - return; - } - table.put(VERSION_KEY, VERSION_VALUE); - write(table, location); - } - - /** - * Traverses the preference hierarchy rooted at this node, and adds - * all preference key and value strings to the provided pool. If an added - * string was already in the pool, all references will be replaced with the - * canonical copy of the string. - * - * @param pool The pool to share strings in - */ - public void shareStrings(StringPool pool) { - properties.shareStrings(pool); - IEclipsePreferences[] myChildren = getChildren(false); - for (int i = 0; i < myChildren.length; i++) - if (myChildren[i] instanceof EclipsePreferences) - ((EclipsePreferences) myChildren[i]).shareStrings(pool); - } - - /* - * Encode the given path and key combo to a form which is suitable for - * persisting or using when searching. If the key contains a slash character - * then we must use a double-slash to indicate the end of the - * path/the beginning of the key. - */ - public static String encodePath(String path, String key) { - String result; - int pathLength = path == null ? 0 : path.length(); - if (key.indexOf(IPath.SEPARATOR) == -1) { - if (pathLength == 0) - result = key; - else - result = path + IPath.SEPARATOR + key; - } else { - if (pathLength == 0) - result = DOUBLE_SLASH + key; - else - result = path + DOUBLE_SLASH + key; - } - return result; - } - - /* - * Return the segment from the given path or null. - * "segment" parameter is 0-based. - */ - public static String getSegment(String path, int segment) { - int start = path.indexOf(IPath.SEPARATOR) == 0 ? 1 : 0; - int end = path.indexOf(IPath.SEPARATOR, start); - if (end == path.length() - 1) - end = -1; - for (int i = 0; i < segment; i++) { - if (end == -1) - return null; - start = end + 1; - end = path.indexOf(IPath.SEPARATOR, start); - } - if (end == -1) - end = path.length(); - return path.substring(start, end); - } - - public static int getSegmentCount(String path) { - StringTokenizer tokenizer = new StringTokenizer(path, String.valueOf(IPath.SEPARATOR)); - return tokenizer.countTokens(); - } - - /* - * Return a relative path - */ - public static String makeRelative(String path) { - String result = path; - if (path == null) - return EMPTY_STRING; - if (path.length() > 0 && path.charAt(0) == IPath.SEPARATOR) - result = path.length() == 0 ? EMPTY_STRING : path.substring(1); - return result; - } - - /* - * Return a 2 element String array. - * element 0 - the path - * element 1 - the key - * The path may be null. - * The key is never null. - */ - public static String[] decodePath(String fullPath) { - String key = null; - String path = null; - - // check to see if we have an indicator which tells us where the path ends - int index = fullPath.indexOf(DOUBLE_SLASH); - if (index == -1) { - // we don't have a double-slash telling us where the path ends - // so the path is up to the last slash character - int lastIndex = fullPath.lastIndexOf(IPath.SEPARATOR); - if (lastIndex == -1) { - key = fullPath; - } else { - path = fullPath.substring(0, lastIndex); - key = fullPath.substring(lastIndex + 1); - } - } else { - // the child path is up to the double-slash and the key - // is the string after it - path = fullPath.substring(0, index); - key = fullPath.substring(index + 2); - } - - // adjust if we have an absolute path - if (path != null) - if (path.length() == 0) - path = null; - else if (path.charAt(0) == IPath.SEPARATOR) - path = path.substring(1); - - return new String[] {path, key}; - } - - /* - * @see org.osgi.service.prefs.Preferences#sync() - */ - - public void sync() throws BackingStoreException { - // illegal state if this node has been removed - checkRemoved(); - IEclipsePreferences node = getLoadLevel(); - if (node == null) { - if (DEBUG_PREFERENCE_GENERAL) - PrefsMessages.message("Preference node is not a load root: " + absolutePath()); //$NON-NLS-1$ - return; - } - if (node instanceof EclipsePreferences) { - ((EclipsePreferences) node).load(); - node.flush(); - } - } - - public String toDeepDebugString() { - final StringBuffer buffer = new StringBuffer(); - IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() { - public boolean visit(IEclipsePreferences node) throws BackingStoreException { - buffer.append(node); - buffer.append('\n'); - String[] keys = node.keys(); - for (int i = 0; i < keys.length; i++) { - buffer.append(node.absolutePath()); - buffer.append(PATH_SEPARATOR); - buffer.append(keys[i]); - buffer.append('='); - buffer.append(node.get(keys[i], "*default*")); //$NON-NLS-1$ - buffer.append('\n'); - } - return true; - } - }; - try { - accept(visitor); - } catch (BackingStoreException e) { - System.out.println("Exception while calling #toDeepDebugString()"); //$NON-NLS-1$ - e.printStackTrace(); - } - return buffer.toString(); - } - - public String toString() { - return absolutePath(); - } -} +/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * 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:
+ * IBM Corporation - initial API and implementation
+ * Julian Chen - fix for bug #92572, jclRM
+ *******************************************************************************/
+package org.eclipse.core.internal.preferences;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.preferences.*;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * Represents a node in the Eclipse preference node hierarchy. This class
+ * is used as a default implementation/super class for those nodes which
+ * belong to scopes which are contributed by the Platform.
+ *
+ * Implementation notes:
+ *
+ * - For thread safety, we always synchronize on the node object when writing
+ * the children or properties fields. Must ensure we don't synchronize when calling
+ * client code such as listeners.
+ *
+ * @since 3.0
+ */
+public class EclipsePreferences implements IEclipsePreferences, IScope {
+
+ public static final String DEFAULT_PREFERENCES_DIRNAME = ".settings"; //$NON-NLS-1$
+ public static final String PREFS_FILE_EXTENSION = "prefs"; //$NON-NLS-1$
+ protected static final IEclipsePreferences[] EMPTY_NODE_ARRAY = new IEclipsePreferences[0];
+ protected static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final String FALSE = "false"; //$NON-NLS-1$
+ private static final String TRUE = "true"; //$NON-NLS-1$
+ protected static final String VERSION_KEY = "eclipse.preferences.version"; //$NON-NLS-1$
+ protected static final String VERSION_VALUE = "1"; //$NON-NLS-1$
+ protected static final String PATH_SEPARATOR = String.valueOf(IPath.SEPARATOR);
+ protected static final String DOUBLE_SLASH = "//"; //$NON-NLS-1$
+ protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+ private String cachedPath;
+ protected Map children;
+ protected boolean dirty = false;
+ protected boolean loading = false;
+ protected final String name;
+ // the parent of an EclipsePreference node is always an EclipsePreference node. (or null)
+ protected final EclipsePreferences parent;
+ protected ImmutableMap properties = ImmutableMap.EMPTY;
+ protected boolean removed = false;
+ private ListenerList nodeChangeListeners;
+ private ListenerList preferenceChangeListeners;
+
+ public static boolean DEBUG_PREFERENCE_GENERAL = false;
+ public static boolean DEBUG_PREFERENCE_SET = false;
+ public static boolean DEBUG_PREFERENCE_GET = false;
+
+ protected final static String debugPluginName = "org.eclipse.equinox.preferences"; //$NON-NLS-1$
+
+ static {
+ DEBUG_PREFERENCE_GENERAL = PreferencesOSGiUtils.getDefault().getBooleanDebugOption(debugPluginName + "/general", false); //$NON-NLS-1$
+ DEBUG_PREFERENCE_SET = PreferencesOSGiUtils.getDefault().getBooleanDebugOption(debugPluginName + "/set", false); //$NON-NLS-1$
+ DEBUG_PREFERENCE_GET = PreferencesOSGiUtils.getDefault().getBooleanDebugOption(debugPluginName + "/get", false); //$NON-NLS-1$
+ }
+
+ protected class SortedProperties extends Properties {
+
+ private static final long serialVersionUID = 1L;
+
+ public SortedProperties() {
+ super();
+ }
+
+ /* (non-Javadoc)
+ * @see java.util.Hashtable#keys()
+ */
+ public synchronized Enumeration keys() {
+ TreeSet set = new TreeSet();
+ for (Enumeration e = super.keys(); e.hasMoreElements();)
+ set.add(e.nextElement());
+ return Collections.enumeration(set);
+ }
+
+ /* (non-Javadoc)
+ * @see java.util.Hashtable#entrySet()
+ */
+ public Set entrySet() {
+ TreeSet set = new TreeSet(new Comparator() {
+ public int compare(Object e1, Object e2) {
+ String s1 = (String) ((Map.Entry) e1).getKey();
+ String s2 = (String) ((Map.Entry) e2).getKey();
+ return s1.compareTo(s2);
+ }
+ });
+ for (Iterator i = super.entrySet().iterator(); i.hasNext();)
+ set.add(i.next());
+ return set;
+ }
+ }
+
+ public EclipsePreferences() {
+ this(null, null);
+ }
+
+ protected EclipsePreferences(EclipsePreferences parent, String name) {
+ super();
+ this.parent = parent;
+ this.name = name;
+ this.cachedPath = null; // make sure the cached path is cleared after setting the parent
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#absolutePath()
+ */
+ public String absolutePath() {
+ if (cachedPath == null) {
+ if (parent == null)
+ cachedPath = PATH_SEPARATOR;
+ else {
+ String parentPath = parent.absolutePath();
+ // if the parent is the root then we don't have to add a separator
+ // between the parent path and our path
+ if (parentPath.length() == 1)
+ cachedPath = parentPath + name();
+ else
+ cachedPath = parentPath + PATH_SEPARATOR + name();
+ }
+ }
+ return cachedPath;
+ }
+
+ public void accept(IPreferenceNodeVisitor visitor) throws BackingStoreException {
+ if (!visitor.visit(this))
+ return;
+ IEclipsePreferences[] toVisit = getChildren(true);
+ for (int i = 0; i < toVisit.length; i++)
+ toVisit[i].accept(visitor);
+ }
+
+ protected synchronized IEclipsePreferences addChild(String childName, IEclipsePreferences child) {
+ //Thread safety: synchronize method to protect modification of children field
+ if (children == null)
+ children = Collections.synchronizedMap(new HashMap());
+ children.put(childName, child == null ? (Object) childName : child);
+ return child;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.IEclipsePreferences#addNodeChangeListener(org.eclipse.core.runtime.IEclipsePreferences.INodeChangeListener)
+ */
+ public void addNodeChangeListener(INodeChangeListener listener) {
+ checkRemoved();
+ if (nodeChangeListeners == null)
+ nodeChangeListeners = new ListenerList();
+ nodeChangeListeners.add(listener);
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Added preference node change listener: " + listener + " to: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.IEclipsePreferences#addPreferenceChangeListener(org.eclipse.core.runtime.IEclipsePreferences.IPreferenceChangeListener)
+ */
+ public void addPreferenceChangeListener(IPreferenceChangeListener listener) {
+ checkRemoved();
+ if (preferenceChangeListeners == null)
+ preferenceChangeListeners = new ListenerList();
+ preferenceChangeListeners.add(listener);
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Added preference property change listener: " + listener + " to: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private IEclipsePreferences calculateRoot() {
+ IEclipsePreferences result = this;
+ while (result.parent() != null)
+ result = (IEclipsePreferences) result.parent();
+ return result;
+ }
+
+ /*
+ * Convenience method for throwing an exception when methods
+ * are called on a removed node.
+ */
+ protected void checkRemoved() {
+ if (removed)
+ throw new IllegalStateException(NLS.bind(PrefsMessages.preferences_removedNode, name));
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#childrenNames()
+ */
+ public String[] childrenNames() {
+ // illegal state if this node has been removed
+ checkRemoved();
+ return internalChildNames();
+ }
+
+ protected String[] internalChildNames() {
+ Map temp = children;
+ if (temp == null || temp.size() == 0)
+ return EMPTY_STRING_ARRAY;
+ return (String[]) temp.keySet().toArray(EMPTY_STRING_ARRAY);
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#clear()
+ */
+ public void clear() {
+ // illegal state if this node has been removed
+ checkRemoved();
+ // call each one separately (instead of Properties.clear) so
+ // clients get change notification
+ String[] keys = properties.keys();
+ for (int i = 0; i < keys.length; i++)
+ remove(keys[i]);
+ makeDirty();
+ }
+
+ protected String[] computeChildren(IPath root) {
+ if (root == null)
+ return EMPTY_STRING_ARRAY;
+ IPath dir = root.append(DEFAULT_PREFERENCES_DIRNAME);
+ final ArrayList result = new ArrayList();
+ final String extension = '.' + PREFS_FILE_EXTENSION;
+ File file = dir.toFile();
+ File[] totalFiles = file.listFiles();
+ if (totalFiles != null) {
+ for (int i = 0; i < totalFiles.length; i++) {
+ if (totalFiles[i].isFile()) {
+ String filename = totalFiles[i].getName();
+ if (filename.endsWith(extension)) {
+ String shortName = filename.substring(0, filename.length() - extension.length());
+ result.add(shortName);
+ }
+ }
+ }
+ }
+ return (String[]) result.toArray(EMPTY_STRING_ARRAY);
+ }
+
+ protected IPath computeLocation(IPath root, String qualifier) {
+ return root == null ? null : root.append(DEFAULT_PREFERENCES_DIRNAME).append(qualifier).addFileExtension(PREFS_FILE_EXTENSION);
+ }
+
+ /*
+ * Version 1 (current version)
+ * path/key=value
+ */
+ protected static void convertFromProperties(EclipsePreferences node, Properties table, boolean notify) {
+ String version = table.getProperty(VERSION_KEY);
+ if (version == null || !VERSION_VALUE.equals(version)) {
+ // ignore for now
+ }
+ table.remove(VERSION_KEY);
+ for (Iterator i = table.keySet().iterator(); i.hasNext();) {
+ String fullKey = (String) i.next();
+ String value = table.getProperty(fullKey);
+ if (value != null) {
+ String[] splitPath = decodePath(fullKey);
+ String path = splitPath[0];
+ path = makeRelative(path);
+ String key = splitPath[1];
+ if (DEBUG_PREFERENCE_SET)
+ PrefsMessages.message("Setting preference: " + path + '/' + key + '=' + value); //$NON-NLS-1$
+ //use internal methods to avoid notifying listeners
+ EclipsePreferences childNode = (EclipsePreferences) node.internalNode(path, false, null);
+ String oldValue = childNode.internalPut(key, value);
+ // notify listeners if applicable
+ if (notify && !value.equals(oldValue))
+ childNode.firePreferenceEvent(key, oldValue, value);
+ }
+ }
+ PreferencesService.getDefault().shareStrings();
+ }
+
+ /*
+ * Helper method to persist a Properties object to the filesystem. We use this
+ * helper so we can remove the date/timestamp that Properties#store always
+ * puts in the file.
+ */
+ protected static void write(Properties properties, IPath location) throws BackingStoreException {
+ // create the parent dirs if they don't exist
+ File parentFile = location.toFile().getParentFile();
+ if (parentFile == null)
+ return;
+ parentFile.mkdirs();
+
+ OutputStream output = null;
+ try {
+ output = new BufferedOutputStream(new FileOutputStream(new File(location.toOSString())));
+ output.write(removeTimestampFromTable(properties).getBytes("UTF-8")); //$NON-NLS-1$
+ output.flush();
+ } catch (IOException e) {
+ String message = NLS.bind(PrefsMessages.preferences_saveException, location);
+ log(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e));
+ throw new BackingStoreException(message);
+ } finally {
+ if (output != null)
+ try {
+ output.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ protected static String removeTimestampFromTable(Properties properties) throws IOException {
+ // store the properties in a string and then skip the first line (date/timestamp)
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ try {
+ properties.store(output, null);
+ } finally {
+ output.close();
+ }
+ String string = output.toString("UTF-8"); //$NON-NLS-1$
+ String separator = System.getProperty("line.separator"); //$NON-NLS-1$
+ return string.substring(string.indexOf(separator) + separator.length());
+ }
+
+ /*
+ * Helper method to convert this node to a Properties file suitable
+ * for persistence.
+ */
+ protected Properties convertToProperties(Properties result, String prefix) throws BackingStoreException {
+ // add the key/value pairs from this node
+ boolean addSeparator = prefix.length() != 0;
+ //thread safety: copy reference in case of concurrent change
+ ImmutableMap temp = properties;
+ String[] keys = temp.keys();
+ for (int i = 0, imax = keys.length; i < imax; i++) {
+ String value = temp.get(keys[i]);
+ if (value != null)
+ result.put(encodePath(prefix, keys[i]), value);
+ }
+ // recursively add the child information
+ IEclipsePreferences[] childNodes = getChildren(true);
+ for (int i = 0; i < childNodes.length; i++) {
+ EclipsePreferences child = (EclipsePreferences) childNodes[i];
+ String fullPath = addSeparator ? prefix + PATH_SEPARATOR + child.name() : child.name();
+ child.convertToProperties(result, fullPath);
+ }
+ PreferencesService.getDefault().shareStrings();
+ return result;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScope#create(org.eclipse.core.runtime.preferences.IEclipsePreferences)
+ */
+ public IEclipsePreferences create(IEclipsePreferences nodeParent, String nodeName) {
+ return create((EclipsePreferences) nodeParent, nodeName, null);
+ }
+
+ protected boolean isLoading() {
+ return loading;
+ }
+
+ protected void setLoading(boolean isLoading) {
+ loading = isLoading;
+ }
+
+ public IEclipsePreferences create(EclipsePreferences nodeParent, String nodeName, Object context) {
+ EclipsePreferences result = internalCreate(nodeParent, nodeName, context);
+ nodeParent.addChild(nodeName, result);
+ IEclipsePreferences loadLevel = result.getLoadLevel();
+
+ // if this node or a parent node is not the load level then return
+ if (loadLevel == null)
+ return result;
+
+ // if the result node is not a load level, then a child must be
+ if (result != loadLevel)
+ return result;
+
+ // the result node is a load level
+ if (isAlreadyLoaded(result) || result.isLoading())
+ return result;
+ try {
+ result.setLoading(true);
+ result.loadLegacy();
+ result.load();
+ result.loaded();
+ result.flush();
+ } catch (BackingStoreException e) {
+ IPath location = result.getLocation();
+ String message = NLS.bind(PrefsMessages.preferences_loadException, location == null ? EMPTY_STRING : location.toString());
+ IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
+ RuntimeLog.log(status);
+ } finally {
+ result.setLoading(false);
+ }
+ return result;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#flush()
+ */
+ synchronized public void flush() throws BackingStoreException {
+ // illegal state if this node has been removed
+ checkRemoved();
+
+ IEclipsePreferences loadLevel = getLoadLevel();
+
+ // if this node or a parent is not the load level, then flush the children
+ if (loadLevel == null) {
+ String[] childrenNames = childrenNames();
+ for (int i = 0; i < childrenNames.length; i++)
+ node(childrenNames[i]).flush();
+ return;
+ }
+
+ // a parent is the load level for this node
+ if (this != loadLevel) {
+ loadLevel.flush();
+ return;
+ }
+
+ // this node is a load level
+ // any work to do?
+ if (!dirty)
+ return;
+ //remove dirty bit before saving, to ensure that concurrent
+ //changes during save mark the store as dirty
+ dirty = false;
+ try {
+ save();
+ } catch (BackingStoreException e) {
+ //mark it dirty again because the save failed
+ dirty = true;
+ throw e;
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#get(java.lang.String, java.lang.String)
+ */
+ public String get(String key, String defaultValue) {
+ String value = internalGet(key);
+ return value == null ? defaultValue : value;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#getBoolean(java.lang.String, boolean)
+ */
+ public boolean getBoolean(String key, boolean defaultValue) {
+ String value = internalGet(key);
+ return value == null ? defaultValue : TRUE.equalsIgnoreCase(value);
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#getByteArray(java.lang.String, byte[])
+ */
+ public byte[] getByteArray(String key, byte[] defaultValue) {
+ String value = internalGet(key);
+ return value == null ? defaultValue : Base64.decode(value.getBytes());
+ }
+
+ /*
+ * Return a boolean value indicating whether or not a child with the given
+ * name is known to this node.
+ */
+ protected synchronized boolean childExists(String childName) {
+ if (children == null)
+ return false;
+ return children.get(childName) != null;
+ }
+
+ /**
+ * Thread safe way to obtain a child for a given key. Returns the child
+ * that matches the given key, or null if there is no matching child.
+ */
+ protected IEclipsePreferences getChild(String key, Object context, boolean create) {
+ synchronized (this) {
+ if (children == null)
+ return null;
+ Object value = children.get(key);
+ if (value == null)
+ return null;
+ if (value instanceof IEclipsePreferences)
+ return (IEclipsePreferences) value;
+ // if we aren't supposed to create this node, then
+ // just return null
+ if (!create)
+ return null;
+ }
+ return addChild(key, create(this, key, context));
+ }
+
+ /**
+ * Thread safe way to obtain all children of this node. Never returns null.
+ */
+ protected IEclipsePreferences[] getChildren(boolean create) {
+ ArrayList result = new ArrayList();
+ String[] names = internalChildNames();
+ for (int i = 0; i < names.length; i++) {
+ IEclipsePreferences child = getChild(names[i], null, create);
+ if (child != null)
+ result.add(child);
+ }
+ return (IEclipsePreferences[]) result.toArray(EMPTY_NODE_ARRAY);
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#getDouble(java.lang.String, double)
+ */
+ public double getDouble(String key, double defaultValue) {
+ String value = internalGet(key);
+ double result = defaultValue;
+ if (value != null)
+ try {
+ result = Double.parseDouble(value);
+ } catch (NumberFormatException e) {
+ // use default
+ }
+ return result;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#getFloat(java.lang.String, float)
+ */
+ public float getFloat(String key, float defaultValue) {
+ String value = internalGet(key);
+ float result = defaultValue;
+ if (value != null)
+ try {
+ result = Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ // use default
+ }
+ return result;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#getInt(java.lang.String, int)
+ */
+ public int getInt(String key, int defaultValue) {
+ String value = internalGet(key);
+ int result = defaultValue;
+ if (value != null)
+ try {
+ result = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ // use default
+ }
+ return result;
+ }
+
+ protected IEclipsePreferences getLoadLevel() {
+ return null;
+ }
+
+ /*
+ * Subclasses to over-ride
+ */
+ protected IPath getLocation() {
+ return null;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#getLong(java.lang.String, long)
+ */
+ public long getLong(String key, long defaultValue) {
+ String value = internalGet(key);
+ long result = defaultValue;
+ if (value != null)
+ try {
+ result = Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ // use default
+ }
+ return result;
+ }
+
+ protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
+ return new EclipsePreferences(nodeParent, nodeName);
+ }
+
+ /**
+ * Returns the existing value at the given key, or null if
+ * no such value exists.
+ */
+ protected String internalGet(String key) {
+ // throw NPE if key is null
+ if (key == null)
+ throw new NullPointerException();
+ // illegal state if this node has been removed
+ checkRemoved();
+ String result = properties.get(key);
+ if (DEBUG_PREFERENCE_GET)
+ PrefsMessages.message("Getting preference value: " + absolutePath() + '/' + key + "->" + result); //$NON-NLS-1$ //$NON-NLS-2$
+ return result;
+ }
+
+ /**
+ * Implements the node(String) method, and optionally notifies listeners.
+ */
+ protected IEclipsePreferences internalNode(String path, boolean notify, Object context) {
+
+ // illegal state if this node has been removed
+ checkRemoved();
+
+ // short circuit this node
+ if (path.length() == 0)
+ return this;
+
+ // if we have an absolute path use the root relative to
+ // this node instead of the global root
+ // in case we have a different hierarchy. (e.g. export)
+ if (path.charAt(0) == IPath.SEPARATOR)
+ return (IEclipsePreferences) calculateRoot().node(path.substring(1));
+
+ int index = path.indexOf(IPath.SEPARATOR);
+ String key = index == -1 ? path : path.substring(0, index);
+ boolean added = false;
+ IEclipsePreferences child = getChild(key, context, true);
+ if (child == null) {
+ child = create(this, key, context);
+ added = true;
+ }
+ // notify listeners if a child was added
+ if (added && notify)
+ fireNodeEvent(new NodeChangeEvent(this, child), true);
+ return (IEclipsePreferences) child.node(index == -1 ? EMPTY_STRING : path.substring(index + 1));
+ }
+
+ /**
+ * Stores the given (key,value) pair, performing lazy initialization of the
+ * properties field if necessary. Returns the old value for the given key,
+ * or null if no value existed.
+ */
+ protected String internalPut(String key, String newValue) {
+ // illegal state if this node has been removed
+ checkRemoved();
+ String oldValue = properties.get(key);
+ if (oldValue != null && oldValue.equals(newValue))
+ return oldValue;
+ if (DEBUG_PREFERENCE_SET)
+ PrefsMessages.message("Setting preference: " + absolutePath() + '/' + key + '=' + newValue); //$NON-NLS-1$
+ properties = properties.put(key, newValue);
+ return oldValue;
+ }
+
+ /*
+ * Subclasses to over-ride.
+ */
+ protected boolean isAlreadyLoaded(IEclipsePreferences node) {
+ return true;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#keys()
+ */
+ public String[] keys() {
+ // illegal state if this node has been removed
+ checkRemoved();
+ return properties.keys();
+ }
+
+ /**
+ * Loads the preference node. This method returns silently if the node does not exist
+ * in the backing store (for example non-existent project).
+ *
+ * @throws BackingStoreException if the node exists in the backing store but it
+ * could not be loaded
+ */
+ protected void load() throws BackingStoreException {
+ load(getLocation());
+ }
+
+ protected static Properties loadProperties(IPath location) throws BackingStoreException {
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Loading preferences from file: " + location); //$NON-NLS-1$
+ InputStream input = null;
+ Properties result = new Properties();
+ try {
+ input = new BufferedInputStream(new FileInputStream(location.toFile()));
+ result.load(input);
+ } catch (FileNotFoundException e) {
+ // file doesn't exist but that's ok.
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Preference file does not exist: " + location); //$NON-NLS-1$
+ return result;
+ } catch (IOException e) {
+ String message = NLS.bind(PrefsMessages.preferences_loadException, location);
+ log(new Status(IStatus.INFO, PrefsMessages.OWNER_NAME, IStatus.INFO, message, e));
+ throw new BackingStoreException(message);
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ return result;
+ }
+
+ protected void load(IPath location) throws BackingStoreException {
+ if (location == null) {
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Unable to determine location of preference file for node: " + absolutePath()); //$NON-NLS-1$
+ return;
+ }
+ Properties fromDisk = loadProperties(location);
+ convertFromProperties(this, fromDisk, false);
+ }
+
+ protected void loaded() {
+ // do nothing
+ }
+
+ protected void loadLegacy() {
+ // sub-classes to over-ride if necessary
+ }
+
+ public static void log(IStatus status) {
+ RuntimeLog.log(status);
+ }
+
+ protected void makeDirty() {
+ EclipsePreferences node = this;
+ while (node != null && !node.removed) {
+ node.dirty = true;
+ node = (EclipsePreferences) node.parent();
+ }
+ }
+
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#name()
+ */
+ public String name() {
+ return name;
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#node(java.lang.String)
+ */
+ public Preferences node(String pathName) {
+ return internalNode(pathName, true, null);
+ }
+
+ protected void fireNodeEvent(final NodeChangeEvent event, final boolean added) {
+ if (nodeChangeListeners == null)
+ return;
+ Object[] listeners = nodeChangeListeners.getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ final INodeChangeListener listener = (INodeChangeListener) listeners[i];
+ ISafeRunnable job = new ISafeRunnable() {
+ public void handleException(Throwable exception) {
+ // already logged in Platform#run()
+ }
+
+ public void run() throws Exception {
+ if (added)
+ listener.added(event);
+ else
+ listener.removed(event);
+ }
+ };
+ SafeRunner.run(job);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#nodeExists(java.lang.String)
+ */
+ public boolean nodeExists(String path) throws BackingStoreException {
+ // short circuit for checking this node
+ if (path.length() == 0)
+ return !removed;
+
+ // illegal state if this node has been removed.
+ // do this AFTER checking for the empty string.
+ checkRemoved();
+
+ // use the root relative to this node instead of the global root
+ // in case we have a different hierarchy. (e.g. export)
+ if (path.charAt(0) == IPath.SEPARATOR)
+ return calculateRoot().nodeExists(path.substring(1));
+
+ int index = path.indexOf(IPath.SEPARATOR);
+ boolean noSlash = index == -1;
+
+ // if we are looking for a simple child then just look in the table and return
+ if (noSlash)
+ return childExists(path);
+
+ // otherwise load the parent of the child and then recursively ask
+ String childName = path.substring(0, index);
+ if (!childExists(childName))
+ return false;
+ IEclipsePreferences child = getChild(childName, null, true);
+ if (child == null)
+ return false;
+ return child.nodeExists(path.substring(index + 1));
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#parent()
+ */
+ public Preferences parent() {
+ // illegal state if this node has been removed
+ checkRemoved();
+ return parent;
+ }
+
+ /*
+ * Convenience method for notifying preference change listeners.
+ */
+ protected void firePreferenceEvent(String key, Object oldValue, Object newValue) {
+ if (preferenceChangeListeners == null)
+ return;
+ Object[] listeners = preferenceChangeListeners.getListeners();
+ final PreferenceChangeEvent event = new PreferenceChangeEvent(this, key, oldValue, newValue);
+ for (int i = 0; i < listeners.length; i++) {
+ final IPreferenceChangeListener listener = (IPreferenceChangeListener) listeners[i];
+ ISafeRunnable job = new ISafeRunnable() {
+ public void handleException(Throwable exception) {
+ // already logged in Platform#run()
+ }
+
+ public void run() throws Exception {
+ listener.preferenceChange(event);
+ }
+ };
+ SafeRunner.run(job);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#put(java.lang.String, java.lang.String)
+ */
+ public void put(String key, String newValue) {
+ if (key == null || newValue == null)
+ throw new NullPointerException();
+ String oldValue = internalPut(key, newValue);
+ if (!newValue.equals(oldValue)) {
+ makeDirty();
+ firePreferenceEvent(key, oldValue, newValue);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#putBoolean(java.lang.String, boolean)
+ */
+ public void putBoolean(String key, boolean value) {
+ if (key == null)
+ throw new NullPointerException();
+ String newValue = value ? TRUE : FALSE;
+ String oldValue = internalPut(key, newValue);
+ if (!newValue.equals(oldValue)) {
+ makeDirty();
+ firePreferenceEvent(key, oldValue, newValue);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#putByteArray(java.lang.String, byte[])
+ */
+ public void putByteArray(String key, byte[] value) {
+ if (key == null || value == null)
+ throw new NullPointerException();
+ String newValue = new String(Base64.encode(value));
+ String oldValue = internalPut(key, newValue);
+ if (!newValue.equals(oldValue)) {
+ makeDirty();
+ firePreferenceEvent(key, oldValue, newValue);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#putDouble(java.lang.String, double)
+ */
+ public void putDouble(String key, double value) {
+ if (key == null)
+ throw new NullPointerException();
+ String newValue = Double.toString(value);
+ String oldValue = internalPut(key, newValue);
+ if (!newValue.equals(oldValue)) {
+ makeDirty();
+ firePreferenceEvent(key, oldValue, newValue);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#putFloat(java.lang.String, float)
+ */
+ public void putFloat(String key, float value) {
+ if (key == null)
+ throw new NullPointerException();
+ String newValue = Float.toString(value);
+ String oldValue = internalPut(key, newValue);
+ if (!newValue.equals(oldValue)) {
+ makeDirty();
+ firePreferenceEvent(key, oldValue, newValue);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#putInt(java.lang.String, int)
+ */
+ public void putInt(String key, int value) {
+ if (key == null)
+ throw new NullPointerException();
+ String newValue = Integer.toString(value);
+ String oldValue = internalPut(key, newValue);
+ if (!newValue.equals(oldValue)) {
+ makeDirty();
+ firePreferenceEvent(key, oldValue, newValue);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#putLong(java.lang.String, long)
+ */
+ public void putLong(String key, long value) {
+ if (key == null)
+ throw new NullPointerException();
+ String newValue = Long.toString(value);
+ String oldValue = internalPut(key, newValue);
+ if (!newValue.equals(oldValue)) {
+ makeDirty();
+ firePreferenceEvent(key, oldValue, newValue);
+ }
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#remove(java.lang.String)
+ */
+ public void remove(String key) {
+ // illegal state if this node has been removed
+ checkRemoved();
+ String oldValue = properties.get(key);
+ if (oldValue == null)
+ return;
+ properties = properties.removeKey(key);
+ makeDirty();
+ firePreferenceEvent(key, oldValue, null);
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#removeNode()
+ */
+ public void removeNode() throws BackingStoreException {
+ // illegal state if this node has been removed
+ checkRemoved();
+ // clear all the property values. do it "the long way" so
+ // everyone gets notification
+ String[] keys = keys();
+ for (int i = 0; i < keys.length; i++)
+ remove(keys[i]);
+ // don't remove the global root or the scope root from the
+ // parent but remove all its children
+ if (parent != null && !(parent instanceof RootPreferences)) {
+ // remove the node from the parent's collection and notify listeners
+ removed = true;
+ parent.removeNode(this);
+ }
+ IEclipsePreferences[] childNodes = getChildren(false);
+ for (int i = 0; i < childNodes.length; i++)
+ try {
+ childNodes[i].removeNode();
+ } catch (IllegalStateException e) {
+ // ignore since we only get this exception if we have already
+ // been removed. no work to do.
+ }
+ }
+
+ /*
+ * Remove the child from the collection and notify the listeners if something
+ * was actually removed.
+ */
+ protected void removeNode(IEclipsePreferences child) {
+ boolean wasRemoved = false;
+ synchronized (this) {
+ if (children != null) {
+ wasRemoved = children.remove(child.name()) != null;
+ if (wasRemoved)
+ makeDirty();
+ if (children.isEmpty())
+ children = null;
+ }
+ }
+ if (wasRemoved)
+ fireNodeEvent(new NodeChangeEvent(this, child), false);
+ }
+
+ /*
+ * Remove non-initialized node from the collection.
+ */
+ protected void removeNode(String key) {
+ synchronized (this) {
+ if (children != null) {
+ boolean wasRemoved = children.remove(key) != null;
+ if (wasRemoved)
+ makeDirty();
+ if (children.isEmpty())
+ children = null;
+ }
+ }
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.IEclipsePreferences#removeNodeChangeListener(org.eclipse.core.runtime.IEclipsePreferences.removeNodeChangeListener)
+ */
+ public void removeNodeChangeListener(INodeChangeListener listener) {
+ checkRemoved();
+ if (nodeChangeListeners == null)
+ return;
+ nodeChangeListeners.remove(listener);
+ if (nodeChangeListeners.size() == 0)
+ nodeChangeListeners = null;
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Removed preference node change listener: " + listener + " from: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.IEclipsePreferences#removePreferenceChangeListener(org.eclipse.core.runtime.IEclipsePreferences.IPreferenceChangeListener)
+ */
+ public void removePreferenceChangeListener(IPreferenceChangeListener listener) {
+ checkRemoved();
+ if (preferenceChangeListeners == null)
+ return;
+ preferenceChangeListeners.remove(listener);
+ if (preferenceChangeListeners.size() == 0)
+ preferenceChangeListeners = null;
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Removed preference property change listener: " + listener + " from: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Saves the preference node. This method returns silently if the node does not exist
+ * in the backing store (for example non-existent project)
+ *
+ * @throws BackingStoreException if the node exists in the backing store but it
+ * could not be saved
+ */
+ protected void save() throws BackingStoreException {
+ save(getLocation());
+ }
+
+ protected void save(IPath location) throws BackingStoreException {
+ if (location == null) {
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Unable to determine location of preference file for node: " + absolutePath()); //$NON-NLS-1$
+ return;
+ }
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Saving preferences to file: " + location); //$NON-NLS-1$
+ Properties table = convertToProperties(new SortedProperties(), EMPTY_STRING);
+ if (table.isEmpty()) {
+ // nothing to save. delete existing file if one exists.
+ if (location.toFile().exists() && !location.toFile().delete()) {
+ String message = NLS.bind(PrefsMessages.preferences_failedDelete, location);
+ log(new Status(IStatus.WARNING, PrefsMessages.OWNER_NAME, IStatus.WARNING, message, null));
+ }
+ return;
+ }
+ table.put(VERSION_KEY, VERSION_VALUE);
+ write(table, location);
+ }
+
+ /**
+ * Traverses the preference hierarchy rooted at this node, and adds
+ * all preference key and value strings to the provided pool. If an added
+ * string was already in the pool, all references will be replaced with the
+ * canonical copy of the string.
+ *
+ * @param pool The pool to share strings in
+ */
+ public void shareStrings(StringPool pool) {
+ properties.shareStrings(pool);
+ IEclipsePreferences[] myChildren = getChildren(false);
+ for (int i = 0; i < myChildren.length; i++)
+ if (myChildren[i] instanceof EclipsePreferences)
+ ((EclipsePreferences) myChildren[i]).shareStrings(pool);
+ }
+
+ /*
+ * Encode the given path and key combo to a form which is suitable for
+ * persisting or using when searching. If the key contains a slash character
+ * then we must use a double-slash to indicate the end of the
+ * path/the beginning of the key.
+ */
+ public static String encodePath(String path, String key) {
+ String result;
+ int pathLength = path == null ? 0 : path.length();
+ if (key.indexOf(IPath.SEPARATOR) == -1) {
+ if (pathLength == 0)
+ result = key;
+ else
+ result = path + IPath.SEPARATOR + key;
+ } else {
+ if (pathLength == 0)
+ result = DOUBLE_SLASH + key;
+ else
+ result = path + DOUBLE_SLASH + key;
+ }
+ return result;
+ }
+
+ /*
+ * Return the segment from the given path or null.
+ * "segment" parameter is 0-based.
+ */
+ public static String getSegment(String path, int segment) {
+ int start = path.indexOf(IPath.SEPARATOR) == 0 ? 1 : 0;
+ int end = path.indexOf(IPath.SEPARATOR, start);
+ if (end == path.length() - 1)
+ end = -1;
+ for (int i = 0; i < segment; i++) {
+ if (end == -1)
+ return null;
+ start = end + 1;
+ end = path.indexOf(IPath.SEPARATOR, start);
+ }
+ if (end == -1)
+ end = path.length();
+ return path.substring(start, end);
+ }
+
+ public static int getSegmentCount(String path) {
+ StringTokenizer tokenizer = new StringTokenizer(path, String.valueOf(IPath.SEPARATOR));
+ return tokenizer.countTokens();
+ }
+
+ /*
+ * Return a relative path
+ */
+ public static String makeRelative(String path) {
+ String result = path;
+ if (path == null)
+ return EMPTY_STRING;
+ if (path.length() > 0 && path.charAt(0) == IPath.SEPARATOR)
+ result = path.length() == 0 ? EMPTY_STRING : path.substring(1);
+ return result;
+ }
+
+ /*
+ * Return a 2 element String array.
+ * element 0 - the path
+ * element 1 - the key
+ * The path may be null.
+ * The key is never null.
+ */
+ public static String[] decodePath(String fullPath) {
+ String key = null;
+ String path = null;
+
+ // check to see if we have an indicator which tells us where the path ends
+ int index = fullPath.indexOf(DOUBLE_SLASH);
+ if (index == -1) {
+ // we don't have a double-slash telling us where the path ends
+ // so the path is up to the last slash character
+ int lastIndex = fullPath.lastIndexOf(IPath.SEPARATOR);
+ if (lastIndex == -1) {
+ key = fullPath;
+ } else {
+ path = fullPath.substring(0, lastIndex);
+ key = fullPath.substring(lastIndex + 1);
+ }
+ } else {
+ // the child path is up to the double-slash and the key
+ // is the string after it
+ path = fullPath.substring(0, index);
+ key = fullPath.substring(index + 2);
+ }
+
+ // adjust if we have an absolute path
+ if (path != null)
+ if (path.length() == 0)
+ path = null;
+ else if (path.charAt(0) == IPath.SEPARATOR)
+ path = path.substring(1);
+
+ return new String[] {path, key};
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#sync()
+ */
+
+ public void sync() throws BackingStoreException {
+ // illegal state if this node has been removed
+ checkRemoved();
+ IEclipsePreferences node = getLoadLevel();
+ if (node == null) {
+ if (DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Preference node is not a load root: " + absolutePath()); //$NON-NLS-1$
+ return;
+ }
+ if (node instanceof EclipsePreferences) {
+ ((EclipsePreferences) node).load();
+ node.flush();
+ }
+ }
+
+ public String toDeepDebugString() {
+ final StringBuffer buffer = new StringBuffer();
+ IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() {
+ public boolean visit(IEclipsePreferences node) throws BackingStoreException {
+ buffer.append(node);
+ buffer.append('\n');
+ String[] keys = node.keys();
+ for (int i = 0; i < keys.length; i++) {
+ buffer.append(node.absolutePath());
+ buffer.append(PATH_SEPARATOR);
+ buffer.append(keys[i]);
+ buffer.append('=');
+ buffer.append(node.get(keys[i], "*default*")); //$NON-NLS-1$
+ buffer.append('\n');
+ }
+ return true;
+ }
+ };
+ try {
+ accept(visitor);
+ } catch (BackingStoreException e) {
+ System.out.println("Exception while calling #toDeepDebugString()"); //$NON-NLS-1$
+ e.printStackTrace();
+ }
+ return buffer.toString();
+ }
+
+ public String toString() {
+ return absolutePath();
+ }
+}
|