| /******************************************************************************* |
| * Copyright (c) 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.internal.preferences; |
| |
| import java.io.*; |
| import java.util.*; |
| import org.eclipse.core.internal.runtime.InternalPlatform; |
| import org.eclipse.core.internal.runtime.Policy; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.preferences.*; |
| 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$ |
| protected static final IEclipsePreferences[] EMPTY_NODE_ARRAY = new IEclipsePreferences[0]; |
| private static final String[] EMPTY_STRING_ARRAY = new String[0]; |
| private static final String FALSE = "false"; //$NON-NLS-1$ |
| public static final String PREFS_FILE_EXTENSION = "prefs"; //$NON-NLS-1$ |
| private static final String TRUE = "true"; //$NON-NLS-1$ |
| private static final String VERSION_KEY = "eclipse.preferences.version"; //$NON-NLS-1$ |
| private static final String VERSION_VALUE = "1"; //$NON-NLS-1$ |
| private String cachedPath; |
| protected Map children; |
| protected boolean dirty = false; |
| protected boolean isLoading = false; |
| protected boolean loading = false; |
| protected final String name; |
| protected ListenerList nodeListeners; |
| protected final IEclipsePreferences parent; |
| protected ListenerList preferenceListeners; |
| protected Properties properties; |
| protected boolean removed = false; |
| |
| public EclipsePreferences() { |
| this(null, null); |
| } |
| |
| protected EclipsePreferences(IEclipsePreferences parent, String name) { |
| super(); |
| this.parent = parent; |
| this.name = name; |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#absolutePath() |
| */ |
| public String absolutePath() { |
| if (cachedPath == null) |
| cachedPath = parent == null ? Path.ROOT.toString() : new Path(parent.absolutePath()).append(name()).toString(); |
| return cachedPath; |
| } |
| |
| public void accept(IPreferenceNodeVisitor visitor) throws BackingStoreException { |
| if (!visitor.visit(this)) |
| return; |
| IEclipsePreferences[] toVisit = getChildren(); |
| for (int i = 0; i < toVisit.length; i++) |
| toVisit[i].accept(visitor); |
| } |
| |
| protected synchronized void 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); |
| } |
| |
| /* |
| * @see org.eclipse.core.runtime.IEclipsePreferences#addNodeChangeListener(org.eclipse.core.runtime.IEclipsePreferences.INodeChangeListener) |
| */ |
| public void addNodeChangeListener(INodeChangeListener listener) { |
| checkRemoved(); |
| if (nodeListeners == null) |
| nodeListeners = new ListenerList(); |
| nodeListeners.add(listener); |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("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 (preferenceListeners == null) |
| preferenceListeners = new ListenerList(); |
| preferenceListeners.add(listener); |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("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) { |
| String message = Policy.bind("preferences.removedNode", name); //$NON-NLS-1$ |
| throw new IllegalStateException(message); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#childrenNames() |
| */ |
| public String[] childrenNames() { |
| // illegal state if this node has been removed |
| checkRemoved(); |
| 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(); |
| Properties temp = properties; |
| if (temp == null) |
| return; |
| // call each one separately (instead of Properties.clear) so |
| // clients get change notification |
| String[] keys = (String[]) temp.keySet().toArray(EMPTY_STRING_ARRAY); |
| for (int i = 0; i < keys.length; i++) |
| remove(keys[i]); |
| //Thread safety: protect against concurrent modification |
| synchronized (this) { |
| properties = null; |
| } |
| makeDirty(); |
| } |
| |
| protected IPath computeLocation(IPath root, String qualifier) { |
| return root == null ? null : root.append(DEFAULT_PREFERENCES_DIRNAME).append(qualifier).addFileExtension(PREFS_FILE_EXTENSION); |
| } |
| |
| private void convertFromProperties(Properties table) { |
| if (!VERSION_VALUE.equals(table.get(VERSION_KEY))) { |
| legacyConvertFromProperties(table); |
| return; |
| } |
| table.remove(VERSION_KEY); |
| for (Iterator i = table.keySet().iterator(); i.hasNext();) { |
| String key = (String) i.next(); |
| String value = table.getProperty(key); |
| if (value != null) { |
| IPath childPath = new Path(key); |
| if (!childPath.isAbsolute() && childPath.segmentCount() > 0) { |
| key = childPath.lastSegment(); |
| IPath child = childPath.removeLastSegments(1); |
| //use internal methods to avoid notifying listeners |
| EclipsePreferences childNode = (EclipsePreferences) internalNode(child, false); |
| childNode.internalPut(key, value); |
| childNode.makeDirty(); |
| } else { |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Ignoring value: " + value + " for key: " + childPath + " for node: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| } |
| } |
| |
| /* |
| * Helper method to convert this node to a Properties file suitable |
| * for persistence. |
| */ |
| private Properties convertToProperties(Properties result, IPath prefix) throws BackingStoreException { |
| // add the key/value pairs from this node |
| Properties temp = properties; |
| if (temp != null) { |
| synchronized (temp) { |
| String[] keys = (String[]) temp.keySet().toArray(EMPTY_STRING_ARRAY); |
| for (int i = 0; i < keys.length; i++) { |
| String value = temp.getProperty(keys[i], null); |
| if (value != null) |
| result.put(prefix.append(keys[i]).toString(), value); |
| } |
| } |
| } |
| // recursively add the child information |
| IEclipsePreferences[] childNodes = getChildren(); |
| for (int i = 0; i < childNodes.length; i++) { |
| EclipsePreferences child = (EclipsePreferences) childNodes[i]; |
| child.convertToProperties(result, prefix.append(child.name())); |
| } |
| return result; |
| } |
| |
| /* |
| * @see org.eclipse.core.runtime.preferences.IScope#create(org.eclipse.core.runtime.preferences.IEclipsePreferences) |
| */ |
| public IEclipsePreferences create(IEclipsePreferences nodeParent, String nodeName) { |
| EclipsePreferences result = internalCreate(nodeParent, nodeName); |
| 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)) |
| return result; |
| if (loading) |
| return result; |
| try { |
| loading = true; |
| result.loadLegacy(); |
| result.load(result.getLocation()); |
| result.loaded(); |
| result.flush(); |
| } catch (BackingStoreException e) { |
| String message = "Exception loading preferences"; |
| IStatus status = new Status(IStatus.ERROR, Platform.PI_RUNTIME, IStatus.ERROR, message, e); |
| InternalPlatform.getDefault().log(status); |
| } finally { |
| loading = false; |
| } |
| return result; |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#flush() |
| */ |
| 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(getLocation()); |
| } 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()); |
| } |
| |
| /** |
| * 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 synchronized IEclipsePreferences getChild(String key) { |
| if (children == null) |
| return null; |
| return (IEclipsePreferences) children.get(key); |
| } |
| |
| /** |
| * Thread safe way to obtain all children of this node. Never returns null. |
| */ |
| protected synchronized IEclipsePreferences[] getChildren() { |
| if (children == null) |
| return EMPTY_NODE_ARRAY; |
| return (IEclipsePreferences[]) children.values().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(IEclipsePreferences nodeParent, String nodeName) { |
| 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(); |
| //Thread safety: copy field reference in case of concurrent modification |
| Properties temp = properties; |
| if (temp == null) |
| return null; |
| return temp.getProperty(key); |
| } |
| |
| /** |
| * Implements the node(IPath) method, and optionally notifies listeners. |
| */ |
| protected IEclipsePreferences internalNode(IPath path, boolean notify) { |
| // use the root relative to this node instead of the global root |
| // in case we have a different hierarchy. (e.g. export) |
| if (path.isAbsolute()) |
| return calculateRoot().node(path.makeRelative()); |
| |
| // TODO: handle relative paths correctly (.. refs) |
| |
| // illegal state if this node has been removed |
| checkRemoved(); |
| |
| // short circuit this node |
| if (path.isEmpty()) |
| return this; |
| |
| String key = path.segment(0); |
| boolean added = false; |
| IEclipsePreferences child; |
| synchronized (this) { |
| child = getChild(key); |
| if (child == null) { |
| child = create(this, key); |
| addChild(key, child); |
| added = true; |
| } |
| } |
| // notify listeners if a child was added |
| if (added && notify) |
| nodeAdded(child); |
| return child.node(path.removeFirstSegments(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 synchronized String internalPut(String key, String newValue) { |
| // illegal state if this node has been removed |
| checkRemoved(); |
| if (properties == null) |
| properties = new Properties(); |
| String oldValue = properties.getProperty(key); |
| properties.setProperty(key, newValue); |
| return oldValue; |
| } |
| |
| private void internalRemove(String key, Object oldValue) { |
| boolean wasRemoved = false; |
| //Thread safety: synchronize when modifying the properties field |
| synchronized (this) { |
| if (properties == null) |
| return; |
| wasRemoved = properties.remove(key) != null; |
| if (properties.size() == 0) |
| properties = null; |
| if (wasRemoved) |
| makeDirty(); |
| } |
| if (wasRemoved) |
| preferenceChanged(key, oldValue, null); |
| } |
| |
| /* |
| * Subclasses to over-ride. |
| */ |
| protected boolean isAlreadyLoaded(IEclipsePreferences node) { |
| return true; |
| } |
| |
| protected boolean isLoading() { |
| if (parent instanceof EclipsePreferences) |
| return isLoading || ((EclipsePreferences) parent).isLoading(); |
| else |
| return isLoading; |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#keys() |
| */ |
| public String[] keys() { |
| // illegal state if this node has been removed |
| checkRemoved(); |
| Properties temp = properties; |
| if (temp == null || temp.size() == 0) |
| return EMPTY_STRING_ARRAY; |
| return (String[]) temp.keySet().toArray(EMPTY_STRING_ARRAY); |
| } |
| |
| private void legacyConvertFromProperties(Properties table) { |
| IPath fullPath = new Path(absolutePath()); |
| for (Iterator i = table.keySet().iterator(); i.hasNext();) { |
| String key = (String) i.next(); |
| String value = table.getProperty(key); |
| if (value != null) { |
| IPath childPath = new Path(key); |
| if (childPath.segmentCount() > 0) { |
| key = childPath.lastSegment(); |
| IPath child = childPath.removeLastSegments(1); |
| // calculate the node relative to this node |
| if (fullPath.isPrefixOf(childPath)) { |
| child = child.removeFirstSegments(fullPath.segmentCount()); |
| //use internal methods to avoid notifying listeners |
| EclipsePreferences childNode = (EclipsePreferences) internalNode(child, false); |
| childNode.internalPut(key, value); |
| childNode.makeDirty(); |
| } else { |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Ignoring value: " + value + " for key: " + childPath + " for node: " + fullPath); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| } |
| } |
| } |
| |
| protected void load(IPath location) throws BackingStoreException { |
| if (location == null) { |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Unable to determine location of preference file for node: " + absolutePath()); //$NON-NLS-1$ |
| return; |
| } |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Loading preferences from file: " + location); //$NON-NLS-1$ |
| InputStream input = null; |
| Properties fromDisk = new Properties(); |
| try { |
| input = new BufferedInputStream(new FileInputStream(location.toFile())); |
| fromDisk.load(input); |
| } catch (FileNotFoundException e) { |
| // file doesn't exist but that's ok. |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Preference file does not exist: " + location); //$NON-NLS-1$ |
| return; |
| } catch (IOException e) { |
| String message = Policy.bind("preferences.loadException", location.toString()); //$NON-NLS-1$ |
| log(new Status(IStatus.INFO, Platform.PI_RUNTIME, IStatus.INFO, message, e)); |
| throw new BackingStoreException(message); |
| } finally { |
| if (input != null) |
| try { |
| input.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| convertFromProperties(fromDisk); |
| } |
| |
| protected void loaded() { |
| // do nothing |
| } |
| |
| protected void loadLegacy() { |
| // sub-classes to over-ride if necessary |
| } |
| |
| protected void log(IStatus status) { |
| InternalPlatform.getDefault().log(status); |
| } |
| |
| protected void makeDirty() { |
| EclipsePreferences node = this; |
| while (node != null && !node.dirty && !node.removed) { |
| node.dirty = true; |
| node = (EclipsePreferences) node.parent(); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#name() |
| */ |
| public String name() { |
| return name; |
| } |
| |
| /* |
| * @see org.eclipse.core.runtime.IEclipsePreferences#node(org.eclipse.core.runtime.IPath) |
| */ |
| public IEclipsePreferences node(IPath path) { |
| return internalNode(path, true); |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#node(java.lang.String) |
| */ |
| public Preferences node(String pathName) { |
| return internalNode(new Path(pathName), true); |
| } |
| |
| protected void nodeAdded(IEclipsePreferences child) { |
| if (nodeListeners == null) |
| return; |
| Object[] listeners = nodeListeners.getListeners(); |
| for (int i = 0; i < listeners.length; i++) { |
| final NodeChangeEvent event = new NodeChangeEvent(this, child); |
| 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 { |
| listener.added(event); |
| } |
| }; |
| Platform.run(job); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.core.runtime.IEclipsePreferences#nodeExists(org.eclipse.core.runtime.IPath) |
| */ |
| public boolean nodeExists(IPath path) throws BackingStoreException { |
| // use the root relative to this node instead of the global root |
| // in case we have a different hierarchy. (e.g. export) |
| if (path.isAbsolute()) |
| return calculateRoot().nodeExists(path.makeRelative()); |
| |
| // TODO: handle relative paths correctly (.. refs) |
| |
| // short circuit for checking this node |
| if (path.isEmpty()) |
| return !removed; |
| // illegal state if this node has been removed |
| checkRemoved(); |
| IEclipsePreferences child = getChild(path.segment(0)); |
| if (child == null) |
| return false; |
| return child.nodeExists(path.removeFirstSegments(1)); |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#nodeExists(java.lang.String) |
| */ |
| public boolean nodeExists(String pathName) throws BackingStoreException { |
| return nodeExists(new Path(pathName)); |
| } |
| |
| protected void nodeRemoved(IEclipsePreferences child) { |
| if (nodeListeners == null) |
| return; |
| final Object[] listeners = nodeListeners.getListeners(); |
| for (int i = 0; i < listeners.length; i++) { |
| final NodeChangeEvent event = new NodeChangeEvent(this, child); |
| final INodeChangeListener listener = (INodeChangeListener) listeners[i]; |
| ISafeRunnable job = new ISafeRunnable() { |
| public void handleException(Throwable exception) { |
| // already being logged in Platform#run() |
| } |
| |
| public void run() throws Exception { |
| listener.removed(event); |
| } |
| }; |
| Platform.run(job); |
| } |
| } |
| |
| /* |
| * @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 preferenceChanged(String key, Object oldValue, Object newValue) { |
| if (preferenceListeners == null) |
| return; |
| Object[] listeners = preferenceListeners.getListeners(); |
| for (int i = 0; i < listeners.length; i++) { |
| final PreferenceChangeEvent event = new PreferenceChangeEvent(this, key, oldValue, newValue); |
| 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); |
| } |
| }; |
| Platform.run(job); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#put(java.lang.String, java.lang.String) |
| */ |
| public void put(String key, String newValue) { |
| String oldValue = internalPut(key, newValue); |
| if (!newValue.equals(oldValue)) { |
| makeDirty(); |
| preferenceChanged(key, oldValue, newValue); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#putBoolean(java.lang.String, boolean) |
| */ |
| public void putBoolean(String key, boolean value) { |
| String newValue = value ? TRUE : FALSE; |
| String oldValue = internalPut(key, newValue); |
| if (!newValue.equals(oldValue)) { |
| makeDirty(); |
| preferenceChanged(key, oldValue == null ? null : new Boolean(oldValue), value ? Boolean.TRUE : Boolean.FALSE); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#putByteArray(java.lang.String, byte[]) |
| */ |
| public void putByteArray(String key, byte[] value) { |
| String newValue = new String(Base64.encode(value)); |
| String oldValue = internalPut(key, newValue); |
| if (!newValue.equals(oldValue)) { |
| makeDirty(); |
| preferenceChanged(key, oldValue == null ? null : Base64.decode(oldValue.getBytes()), value); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#putDouble(java.lang.String, double) |
| */ |
| public void putDouble(String key, double value) { |
| String newValue = Double.toString(value); |
| Object oldValue = internalPut(key, newValue); |
| if (!newValue.equals(oldValue)) { |
| makeDirty(); |
| if (oldValue != null) |
| try { |
| oldValue = new Double((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore and let oldValue be a String |
| } |
| preferenceChanged(key, oldValue, new Double(value)); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#putFloat(java.lang.String, float) |
| */ |
| public void putFloat(String key, float value) { |
| String newValue = Float.toString(value); |
| Object oldValue = internalPut(key, newValue); |
| if (!newValue.equals(oldValue)) { |
| makeDirty(); |
| if (oldValue != null) |
| try { |
| oldValue = new Float((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore and let oldValue be a String |
| } |
| preferenceChanged(key, oldValue, new Float(value)); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#putInt(java.lang.String, int) |
| */ |
| public void putInt(String key, int value) { |
| String newValue = Integer.toString(value); |
| Object oldValue = internalPut(key, newValue); |
| if (!newValue.equals(oldValue)) { |
| makeDirty(); |
| if (oldValue != null) |
| try { |
| oldValue = new Integer((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore and let oldValue be a String |
| } |
| preferenceChanged(key, oldValue, new Integer(value)); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#putLong(java.lang.String, long) |
| */ |
| public void putLong(String key, long value) { |
| String newValue = Long.toString(value); |
| Object oldValue = internalPut(key, newValue); |
| if (!newValue.equals(oldValue)) { |
| makeDirty(); |
| if (oldValue != null) |
| try { |
| oldValue = new Long((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore and let oldValue be a String |
| } |
| preferenceChanged(key, oldValue, new Long(value)); |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#remove(java.lang.String) |
| */ |
| public void remove(String key) { |
| String oldValue = internalGet(key); |
| if (oldValue != null) |
| internalRemove(key, oldValue); |
| } |
| |
| /* |
| * Added so the backwards compatibility layer (PreferenceForwarder) |
| * gets preference change events of the correct types. |
| */ |
| void removeBoolean(String key) { |
| String oldValue = internalGet(key); |
| if (oldValue != null) |
| internalRemove(key, Boolean.valueOf(oldValue)); |
| } |
| |
| /* |
| * Added so the backwards compatibility layer (PreferenceForwarder) |
| * gets preference change events of the correct types. |
| */ |
| void removeDouble(String key) { |
| Object oldValue = internalGet(key); |
| if (oldValue != null) { |
| try { |
| oldValue = Double.valueOf((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore - oldValue will be null |
| } |
| internalRemove(key, oldValue); |
| } |
| } |
| |
| /* |
| * Added so the backwards compatibility layer (PreferenceForwarder) |
| * gets preference change events of the correct types. |
| */ |
| void removeFloat(String key) { |
| Object oldValue = internalGet(key); |
| if (oldValue != null) { |
| try { |
| oldValue = Float.valueOf((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore - oldValue will be null |
| } |
| internalRemove(key, oldValue); |
| } |
| } |
| |
| /* |
| * Added so the backwards compatibility layer (PreferenceForwarder) |
| * gets preference change events of the correct types. |
| */ |
| void removeInt(String key) { |
| Object oldValue = internalGet(key); |
| if (oldValue != null) { |
| try { |
| oldValue = Integer.valueOf((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore - oldValue will be null |
| } |
| internalRemove(key, oldValue); |
| } |
| } |
| |
| /* |
| * Added so the backwards compatibility layer (PreferenceForwarder) |
| * gets preference change events of the correct types. |
| */ |
| void removeLong(String key) { |
| Object oldValue = internalGet(key); |
| if (oldValue != null) { |
| try { |
| oldValue = Long.valueOf((String) oldValue); |
| } catch (NumberFormatException e) { |
| // ignore - oldValue will be null |
| } |
| internalRemove(key, oldValue); |
| } |
| } |
| |
| /* |
| * @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 scope root from the parent but |
| // remove all its children |
| if (!(parent instanceof RootPreferences)) { |
| // remove the node from the parent's collection and notify listeners |
| if (parent instanceof EclipsePreferences) { |
| removed = true; |
| ((EclipsePreferences) parent).removeNode(this); |
| } else { |
| String message = Policy.bind("preferences.invalidParentClass", absolutePath(), parent.getClass().getName()); //$NON-NLS-1$ |
| throw new BackingStoreException(message); |
| } |
| } |
| IEclipsePreferences[] childNodes = getChildren(); |
| for (int i = 0; i < childNodes.length; i++) |
| childNodes[i].removeNode(); |
| } |
| |
| /* |
| * 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) |
| nodeRemoved(child); |
| } |
| |
| /* |
| * @see org.eclipse.core.runtime.IEclipsePreferences#removeNodeChangeListener(org.eclipse.core.runtime.IEclipsePreferences.removeNodeChangeListener) |
| */ |
| public void removeNodeChangeListener(INodeChangeListener listener) { |
| checkRemoved(); |
| if (nodeListeners == null) |
| return; |
| nodeListeners.remove(listener); |
| if (nodeListeners.size() == 0) |
| nodeListeners = null; |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("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 (preferenceListeners == null) |
| return; |
| preferenceListeners.remove(listener); |
| if (preferenceListeners.size() == 0) |
| preferenceListeners = null; |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Removed preference property change listener: " + listener + " from: " + absolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| protected void save(IPath location) throws BackingStoreException { |
| if (location == null) { |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Unable to determine location of preference file for node: " + absolutePath()); //$NON-NLS-1$ |
| return; |
| } |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Saving preferences to file: " + location); //$NON-NLS-1$ |
| Properties table = convertToProperties(new Properties(), Path.EMPTY); |
| if (table.isEmpty()) { |
| // nothing to save. delete existing file if one exists. |
| if (location.toFile().exists() && !location.toFile().delete()) { |
| String message = Policy.bind("preferences.failedDelete", location.toString()); //$NON-NLS-1$ |
| log(new Status(IStatus.WARNING, Platform.PI_RUNTIME, IStatus.WARNING, message, null)); |
| } |
| return; |
| } |
| table.put(VERSION_KEY, VERSION_VALUE); |
| OutputStream output = null; |
| try { |
| // create the parent dirs if they don't exist |
| File parentFile = location.toFile().getParentFile(); |
| if (parentFile == null) |
| return; |
| parentFile.mkdirs(); |
| // set append to be false so we overwrite current settings. |
| output = new BufferedOutputStream(new FileOutputStream(location.toFile(), false)); |
| table.store(output, null); |
| } catch (IOException e) { |
| String message = Policy.bind("preferences.saveException", location.toString()); //$NON-NLS-1$ |
| log(new Status(IStatus.ERROR, Platform.PI_RUNTIME, IStatus.ERROR, message, e)); |
| throw new BackingStoreException(message); |
| } finally { |
| if (output != null) |
| try { |
| output.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| |
| /* |
| * @see org.osgi.service.prefs.Preferences#sync() |
| */ |
| |
| public void sync() throws BackingStoreException { |
| // illegal state if this node has been removed |
| checkRemoved(); |
| IPath location = getLocation(); |
| // do nothing...subclasses to provide implementation |
| if (location == null) { |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Unable to determine location of preference file for node: " + absolutePath()); //$NON-NLS-1$ |
| return; |
| } |
| IEclipsePreferences node = getLoadLevel(); |
| if (node == null) { |
| if (InternalPlatform.DEBUG_PREFERENCES) |
| Policy.debug("Preference node is not a load root: " + absolutePath()); //$NON-NLS-1$ |
| return; |
| } |
| if (node instanceof EclipsePreferences) { |
| ((EclipsePreferences) node).load(location); |
| 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(IPath.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(); |
| } |
| } |