/*******************************************************************************
 *  Copyright (c) 2004, 2007 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
 *******************************************************************************/
package org.eclipse.core.internal.preferences.legacy;

import java.io.*;
import java.util.Iterator;
import java.util.Properties;
import org.eclipse.core.internal.preferences.*;
import org.eclipse.core.internal.runtime.RuntimeLog;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.osgi.service.prefs.BackingStoreException;

/**
 * This class represents a convenience layer between the Eclipse 3.0
 * preferences and pre-3.0 preferences. It acts as a bridge between the 
 * org.eclipse.core.runtime.Preferences object associated with a particular plug-in
 * object, and its corresponding preference node in the 3.0 preference node
 * hierarchy.
 * 
 * @since 3.0
 */
public class PreferenceForwarder extends Preferences implements IEclipsePreferences.IPreferenceChangeListener, IEclipsePreferences.INodeChangeListener {

	private static final byte[] BYTE_ARRAY_DEFAULT_DEFAULT = new byte[0];

	private IEclipsePreferences pluginRoot = (IEclipsePreferences) PreferencesService.getDefault().getRootNode().node(InstanceScope.SCOPE);
	private DefaultPreferences defaultsRoot = (DefaultPreferences) PreferencesService.getDefault().getRootNode().node(DefaultScope.SCOPE);
	private String pluginID;
	private Object plugin;
	// boolean to check to see if we should re-wrap and forward change
	// events coming from the new runtime APIs.
	private boolean notify = true;

	/*
	 * Used for test suites only.
	 */
	public PreferenceForwarder(String pluginID) {
		this(null, pluginID);
	}

	public PreferenceForwarder(Object plugin, String pluginID) {
		super();
		this.plugin = plugin;
		this.pluginID = pluginID;
	}

	/*
	 * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener#added(org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent)
	 */
	public synchronized void added(IEclipsePreferences.NodeChangeEvent event) {
		if (listeners.size() > 0 && pluginID.equals(event.getChild().name())) {
			try {
				EclipsePreferences prefs = (EclipsePreferences) event.getChild();
				prefs.addPreferenceChangeListener(this);
			} catch (ClassCastException e) {
				throw new RuntimeException("Plug-in preferences must be instances of EclipsePreferences: " + e.getMessage()); //$NON-NLS-1$
			}
		}
	}

	/*
	 * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener#removed(org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent)
	 */
	public synchronized void removed(IEclipsePreferences.NodeChangeEvent event) {
		// Do nothing. We can't remove ourselves from the node's list of preference change
		// listeners because the node has already been removed.
	}

	/**
	 * Adds a property change listener to this preference object.
	 * Has no effect if the identical listener is already registered.
	 *
	 * @param listener a property change listener
	 */
	public synchronized void addPropertyChangeListener(IPropertyChangeListener listener) {
		if (listeners.size() == 0) {
			EclipsePreferences prefs = getPluginPreferences(false);
			if (prefs != null) {
				prefs.addPreferenceChangeListener(this);
			}
			pluginRoot.addNodeChangeListener(this);
		}
		listeners.add(listener);
	}


	/*
	 * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener#preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent)
	 */
	public void preferenceChange(IEclipsePreferences.PreferenceChangeEvent event) {
		// if we are the ones making this change, then don't broadcast
		if (!notify)
			return;
		Object oldValue = event.getOldValue();
		Object newValue = event.getNewValue();
		String key = event.getKey();
		if (newValue == null)
			newValue = getDefault(key, oldValue);
		else if (oldValue == null)
			oldValue = getDefault(key, newValue);
		firePropertyChangeEvent(key, oldValue, newValue);
	}

	private EclipsePreferences getPluginPreferences(boolean create) {
		try {
			if (!create && !pluginRoot.nodeExists(pluginID))
				return null;
		} catch (BackingStoreException e) {
			return null;
		}
		try {
			return (EclipsePreferences) pluginRoot.node(pluginID);
		} catch (ClassCastException e) {
			throw new RuntimeException("Plug-in preferences must be instances of EclipsePreferences: " + e.getMessage()); //$NON-NLS-1$
		}
	}

	private IEclipsePreferences getDefaultPreferences() {
		return defaultsRoot.node(pluginID, plugin);
	}

	/**
	 * Removes the given listener from this preference object.
	 * Has no effect if the listener is not registered.
	 *
	 * @param listener a property change listener
	 */
	public synchronized void removePropertyChangeListener(IPropertyChangeListener listener) {
		listeners.remove(listener);
		if (listeners.size() == 0) {
			EclipsePreferences prefs = getPluginPreferences(false);
			if (prefs != null) {
				prefs.removePreferenceChangeListener(this);
			}
			pluginRoot.removeNodeChangeListener(this);
		}
	}


	/**
	 * Does its best at determining the default value for the given key. Checks the
	 * given object's type and then looks in the list of defaults to see if a value
	 * exists. If not or if there is a problem converting the value, the default default 
	 * value for that type is returned.
	 */
	private Object getDefault(String key, Object obj) {
		IEclipsePreferences defaults = getDefaultPreferences();
		if (obj instanceof String)
			return defaults.get(key, STRING_DEFAULT_DEFAULT);
		else if (obj instanceof Integer)
			return new Integer(defaults.getInt(key, INT_DEFAULT_DEFAULT));
		else if (obj instanceof Double)
			return new Double(defaults.getDouble(key, DOUBLE_DEFAULT_DEFAULT));
		else if (obj instanceof Float)
			return new Float(defaults.getFloat(key, FLOAT_DEFAULT_DEFAULT));
		else if (obj instanceof Long)
			return new Long(defaults.getLong(key, LONG_DEFAULT_DEFAULT));
		else if (obj instanceof byte[])
			return defaults.getByteArray(key, BYTE_ARRAY_DEFAULT_DEFAULT);
		else if (obj instanceof Boolean)
			return defaults.getBoolean(key, BOOLEAN_DEFAULT_DEFAULT) ? Boolean.TRUE : Boolean.FALSE;
		else
			return null;
	}

	/**
	 * Returns whether the given property is known to this preference object,
	 * either by having an explicit setting or by having a default
	 * setting.
	 *
	 * @param name the name of the property
	 * @return <code>true</code> if either a current value or a default
	 *  value is known for the named property, and <code>false</code>otherwise
	 */
	public boolean contains(String name) {
		if (name == null)
			return false;
		String value = getPluginPreferences(true).get(name, null);
		if (value != null)
			return true;
		return getDefaultPreferences().get(name, null) != null;
	}

	/**
	 * Returns the current value of the boolean-valued property with the
	 * given name.
	 * Returns the default-default value (<code>false</code>) if there
	 * is no property with the given name, or if the current value 
	 * cannot be treated as a boolean.
	 *
	 * @param name the name of the property
	 * @return the boolean-valued property
	 */
	public boolean getBoolean(String name) {
		return getPluginPreferences(true).getBoolean(name, getDefaultPreferences().getBoolean(name, BOOLEAN_DEFAULT_DEFAULT));
	}

	/**
	 * Sets the current value of the boolean-valued property with the
	 * given name.
	 * <p>
	 * A property change event is reported if the current value of the 
	 * property actually changes from its previous value. In the event
	 * object, the property name is the name of the property, and the
	 * old and new values are wrapped as objects.
	 * </p>
	 * <p>
	 * If the given value is the same as the corresponding default value
	 * for the given property, the explicit setting is deleted.
	 * Note that the recommended way of re-initializing a property to its
	 * default value is to call <code>setToDefault</code>.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new current value of the property
	 */
	public void setValue(String name, boolean value) {
		Boolean oldValue = getBoolean(name) ? Boolean.TRUE : Boolean.FALSE;
		Boolean newValue = value ? Boolean.TRUE : Boolean.FALSE;
		if (newValue == oldValue)
			return;
		try {
			notify = false;
			if (getDefaultBoolean(name) == value)
				getPluginPreferences(true).remove(name);
			else
				getPluginPreferences(true).putBoolean(name, value);
			firePropertyChangeEvent(name, oldValue, newValue);
		} finally {
			notify = true;
		}
	}

	/**
	 * Returns the default value for the boolean-valued property
	 * with the given name.
	 * Returns the default-default value (<code>false</code>) if there
	 * is no default property with the given name, or if the default 
	 * value cannot be treated as a boolean.
	 *
	 * @param name the name of the property
	 * @return the default value of the named property
	 */
	public boolean getDefaultBoolean(String name) {
		return getDefaultPreferences().getBoolean(name, BOOLEAN_DEFAULT_DEFAULT);
	}

	/**
	 * Sets the default value for the boolean-valued property with the
	 * given name. 
	 * <p>
	 * Note that the current value of the property is affected if
	 * the property's current value was its old default value, in which
	 * case it changes to the new default value. If the property's current
	 * is different from its old default value, its current value is
	 * unaffected. No property change events are reported by changing default
	 * values.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new default value for the property
	 */
	public void setDefault(String name, boolean value) {
		getDefaultPreferences().putBoolean(name, value);
	}

	/**
	 * Returns the current value of the double-valued property with the
	 * given name.
	 * Returns the default-default value (<code>0.0</code>) if there
	 * is no property with the given name, or if the current value 
	 * cannot be treated as a double.
	 *
	 * @param name the name of the property
	 * @return the double-valued property
	 */
	public double getDouble(String name) {
		return getPluginPreferences(true).getDouble(name, getDefaultPreferences().getDouble(name, DOUBLE_DEFAULT_DEFAULT));
	}

	/**
	 * Sets the current value of the double-valued property with the
	 * given name.
	 * <p>
	 * A property change event is reported if the current value of the 
	 * property actually changes from its previous value. In the event
	 * object, the property name is the name of the property, and the
	 * old and new values are wrapped as objects.
	 * </p>
	 * <p>
	 * If the given value is the same as the corresponding default value
	 * for the given property, the explicit setting is deleted.
	 * Note that the recommended way of re-initializing a property to its
	 * default value is to call <code>setToDefault</code>.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new current value of the property; must be 
	 *   a number (not a NaN)
	 */
	public void setValue(String name, double value) {
		if (Double.isNaN(value))
			throw new IllegalArgumentException();
		final double doubleValue = getDouble(name);
		if (value == doubleValue)
			return;
		Double oldValue = new Double(doubleValue);
		Double newValue = new Double(value);
		try {
			notify = false;
			if (getDefaultDouble(name) == value)
				getPluginPreferences(true).remove(name);
			else
				getPluginPreferences(true).putDouble(name, value);
			firePropertyChangeEvent(name, oldValue, newValue);
		} finally {
			notify = true;
		}
	}

	/**
	 * Returns the default value for the double-valued property
	 * with the given name.
	 * Returns the default-default value (<code>0.0</code>) if there
	 * is no default property with the given name, or if the default 
	 * value cannot be treated as a double.
	 *
	 * @param name the name of the property
	 * @return the default value of the named property
	 */
	public double getDefaultDouble(String name) {
		return getDefaultPreferences().getDouble(name, DOUBLE_DEFAULT_DEFAULT);
	}

	/**
	 * Sets the default value for the double-valued property with the
	 * given name. 
	 * <p>
	 * Note that the current value of the property is affected if
	 * the property's current value was its old default value, in which
	 * case it changes to the new default value. If the property's current
	 * is different from its old default value, its current value is
	 * unaffected. No property change events are reported by changing default
	 * values.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new default value for the property; must be 
	 *   a number (not a NaN)
	 */
	public void setDefault(String name, double value) {
		if (Double.isNaN(value))
			throw new IllegalArgumentException();
		getDefaultPreferences().putDouble(name, value);
	}

	/**
	 * Returns the current value of the float-valued property with the
	 * given name.
	 * Returns the default-default value (<code>0.0f</code>) if there
	 * is no property with the given name, or if the current value 
	 * cannot be treated as a float.
	 *
	 * @param name the name of the property
	 * @return the float-valued property
	 */
	public float getFloat(String name) {
		return getPluginPreferences(true).getFloat(name, getDefaultPreferences().getFloat(name, FLOAT_DEFAULT_DEFAULT));
	}

	/**
	 * Sets the current value of the float-valued property with the
	 * given name.
	 * <p>
	 * A property change event is reported if the current value of the 
	 * property actually changes from its previous value. In the event
	 * object, the property name is the name of the property, and the
	 * old and new values are wrapped as objects.
	 * </p>
	 * <p>
	 * If the given value is the same as the corresponding default value
	 * for the given property, the explicit setting is deleted.
	 * Note that the recommended way of re-initializing a property to its
	 * default value is to call <code>setToDefault</code>.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new current value of the property; must be 
	 *   a number (not a NaN)
	 */
	public void setValue(String name, float value) {
		if (Float.isNaN(value))
			throw new IllegalArgumentException();
		final float floatValue = getFloat(name);
		if (value == floatValue)
			return;
		Float oldValue = new Float(floatValue);
		Float newValue = new Float(value);
		try {
			notify = false;
			if (getDefaultFloat(name) == value)
				getPluginPreferences(true).remove(name);
			else
				getPluginPreferences(true).putFloat(name, value);
			firePropertyChangeEvent(name, oldValue, newValue);
		} finally {
			notify = true;
		}
	}

	/**
	 * Returns the default value for the float-valued property
	 * with the given name.
	 * Returns the default-default value (<code>0.0f</code>) if there
	 * is no default property with the given name, or if the default 
	 * value cannot be treated as a float.
	 *
	 * @param name the name of the property
	 * @return the default value of the named property
	 */
	public float getDefaultFloat(String name) {
		return getDefaultPreferences().getFloat(name, FLOAT_DEFAULT_DEFAULT);
	}

	/**
	 * Sets the default value for the float-valued property with the
	 * given name. 
	 * <p>
	 * Note that the current value of the property is affected if
	 * the property's current value was its old default value, in which
	 * case it changes to the new default value. If the property's current
	 * is different from its old default value, its current value is
	 * unaffected. No property change events are reported by changing default
	 * values.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new default value for the property; must be 
	 *   a number (not a NaN)
	 */
	public void setDefault(String name, float value) {
		if (Float.isNaN(value))
			throw new IllegalArgumentException();
		getDefaultPreferences().putFloat(name, value);
	}

	/**
	 * Returns the current value of the integer-valued property with the
	 * given name.
	 * Returns the default-default value (<code>0</code>) if there
	 * is no property with the given name, or if the current value 
	 * cannot be treated as an integter.
	 *
	 * @param name the name of the property
	 * @return the int-valued property
	 */
	public int getInt(String name) {
		return getPluginPreferences(true).getInt(name, getDefaultPreferences().getInt(name, INT_DEFAULT_DEFAULT));
	}

	/**
	 * Sets the current value of the integer-valued property with the
	 * given name.
	 * <p>
	 * A property change event is reported if the current value of the 
	 * property actually changes from its previous value. In the event
	 * object, the property name is the name of the property, and the
	 * old and new values are wrapped as objects.
	 * </p>
	 * <p>
	 * If the given value is the same as the corresponding default value
	 * for the given property, the explicit setting is deleted.
	 * Note that the recommended way of re-initializing a property to its
	 * default value is to call <code>setToDefault</code>.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new current value of the property
	 */
	public void setValue(String name, int value) {
		final int intValue = getInt(name);
		if (value == intValue)
			return;
		Integer oldValue = new Integer(intValue);
		Integer newValue = new Integer(value);
		try {
			notify = false;
			if (getDefaultInt(name) == value)
				getPluginPreferences(true).remove(name);
			else
				getPluginPreferences(true).putInt(name, value);
			firePropertyChangeEvent(name, oldValue, newValue);
		} finally {
			notify = true;
		}
	}

	/**
	 * Returns the default value for the integer-valued property
	 * with the given name.
	 * Returns the default-default value (<code>0</code>) if there
	 * is no default property with the given name, or if the default 
	 * value cannot be treated as an integer.
	 *
	 * @param name the name of the property
	 * @return the default value of the named property
	 */
	public int getDefaultInt(String name) {
		return getDefaultPreferences().getInt(name, INT_DEFAULT_DEFAULT);
	}

	/**
	 * Sets the default value for the integer-valued property with the
	 * given name. 
	 * <p>
	 * Note that the current value of the property is affected if
	 * the property's current value was its old default value, in which
	 * case it changes to the new default value. If the property's current
	 * is different from its old default value, its current value is
	 * unaffected. No property change events are reported by changing default
	 * values.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new default value for the property
	 */
	public void setDefault(String name, int value) {
		getDefaultPreferences().putInt(name, value);
	}

	/**
	 * Returns the current value of the long-valued property with the
	 * given name.
	 * Returns the default-default value (<code>0L</code>) if there
	 * is no property with the given name, or if the current value 
	 * cannot be treated as a long.
	 *
	 * @param name the name of the property
	 * @return the long-valued property
	 */
	public long getLong(String name) {
		return getPluginPreferences(true).getLong(name, getDefaultPreferences().getLong(name, LONG_DEFAULT_DEFAULT));
	}

	/**
	 * Sets the current value of the long-valued property with the
	 * given name.
	 * <p>
	 * A property change event is reported if the current value of the 
	 * property actually changes from its previous value. In the event
	 * object, the property name is the name of the property, and the
	 * old and new values are wrapped as objects.
	 * </p>
	 * <p>
	 * If the given value is the same as the corresponding default value
	 * for the given property, the explicit setting is deleted.
	 * Note that the recommended way of re-initializing a property to its
	 * default value is to call <code>setToDefault</code>.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new current value of the property
	 */
	public void setValue(String name, long value) {
		final long longValue = getLong(name);
		if (value == longValue)
			return;
		Long oldValue = new Long(longValue);
		Long newValue = new Long(value);
		try {
			notify = false;
			if (getDefaultLong(name) == value)
				getPluginPreferences(true).remove(name);
			else
				getPluginPreferences(true).putLong(name, value);
			firePropertyChangeEvent(name, oldValue, newValue);
		} finally {
			notify = true;
		}
	}

	/**
	 * Returns the default value for the long-valued property
	 * with the given name.
	 * Returns the default-default value (<code>0L</code>) if there
	 * is no default property with the given name, or if the default 
	 * value cannot be treated as a long.
	 *
	 * @param name the name of the property
	 * @return the default value of the named property
	 */
	public long getDefaultLong(String name) {
		return getDefaultPreferences().getLong(name, LONG_DEFAULT_DEFAULT);
	}

	/**
	 * Sets the default value for the long-valued property with the
	 * given name. 
	 * <p>
	 * Note that the current value of the property is affected if
	 * the property's current value was its old default value, in which
	 * case it changes to the new default value. If the property's current
	 * is different from its old default value, its current value is
	 * unaffected. No property change events are reported by changing default
	 * values.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new default value for the property
	 */
	public void setDefault(String name, long value) {
		getDefaultPreferences().putLong(name, value);
	}

	/**
	 * Returns the current value of the string-valued property with the
	 * given name.
	 * Returns the default-default value (the empty string <code>""</code>)
	 * if there is no property with the given name.
	 *
	 * @param name the name of the property
	 * @return the string-valued property
	 */
	public String getString(String name) {
		return getPluginPreferences(true).get(name, getDefaultPreferences().get(name, STRING_DEFAULT_DEFAULT));
	}

	/**
	 * Sets the current value of the string-valued property with the
	 * given name.
	 * <p>
	 * A property change event is reported if the current value of the 
	 * property actually changes from its previous value. In the event
	 * object, the property name is the name of the property, and the
	 * old and new values are wrapped as objects.
	 * </p>
	 * <p>
	 * If the given value is the same as the corresponding default value
	 * for the given property, the explicit setting is deleted.
	 * Note that the recommended way of re-initializing a property to its
	 * default value is to call <code>setToDefault</code>.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new current value of the property
	 */
	public void setValue(String name, String value) {
		if (value == null)
			throw new IllegalArgumentException();
		String oldValue = getString(name);
		if (value.equals(oldValue))
			return;
		try {
			notify = false;
			if (getDefaultString(name).equals(value))
				getPluginPreferences(true).remove(name);
			else
				getPluginPreferences(true).put(name, value);
			firePropertyChangeEvent(name, oldValue, value);
		} finally {
			notify = true;
		}
	}

	/**
	 * Returns the default value for the string-valued property
	 * with the given name.
	 * Returns the default-default value (the empty string <code>""</code>) 
	 * is no default property with the given name, or if the default 
	 * value cannot be treated as a string.
	 *
	 * @param name the name of the property
	 * @return the default value of the named property
	 */
	public String getDefaultString(String name) {
		return getDefaultPreferences().get(name, STRING_DEFAULT_DEFAULT);
	}

	/**
	 * Sets the default value for the string-valued property with the
	 * given name. 
	 * <p>
	 * Note that the current value of the property is affected if
	 * the property's current value was its old default value, in which
	 * case it changes to the new default value. If the property's current
	 * is different from its old default value, its current value is
	 * unaffected. No property change events are reported by changing default
	 * values.
	 * </p>
	 *
	 * @param name the name of the property
	 * @param value the new default value for the property
	 */
	public void setDefault(String name, String value) {
		if (value == null)
			throw new IllegalArgumentException();
		getDefaultPreferences().put(name, value);
	}

	/**
	 * Returns whether the property with the given name has the default value in
	 * virtue of having no explicitly set value.
	 *
	 * @param name the name of the property
	 * @return <code>true</code> if the property has no explicitly set value,
	 * and <code>false</code> otherwise (including the case where the property
	 * is unknown to this object)
	 */
	public boolean isDefault(String name) {
		if (name == null)
			return false;
		return getPluginPreferences(true).get(name, null) == null;
	}

	/**
	 * Sets the current value of the property with the given name back
	 * to its default value. Has no effect if the property does not have
	 * its own current value.
	 * <p>
	 * Note that the recommended way of re-initializing a property to the
	 * appropriate default value is to call <code>setToDefault</code>.
	 * This is implemented by removing the named value from the object, 
	 * thereby exposing the default value.
	 * </p>
	 * <p>
	 * A property change event is always reported. In the event
	 * object, the property name is the name of the property, and the
	 * old and new values are either strings, or <code>null</code> 
	 * indicating the default-default value.
	 * </p>
	 *
	 * @param name the name of the property
	 */
	public void setToDefault(String name) {
		IEclipsePreferences preferences = getPluginPreferences(true);
		Object oldValue = preferences.get(name, null);
		if (oldValue != null)
			preferences.remove(name);
	}

	/**
	 * Returns a list of all properties known to this preference object which
	 * have current values other than their default value.
	 *
	 * @return an array of property names 
	 */
	public String[] propertyNames() {
		return getPluginPreferences(true).keys();
	}

	/**
	 * Returns a list of all properties known to this preference object which
	 * have default values other than their default-default value.
	 *
	 * @return an array of property names 
	 */
	public String[] defaultPropertyNames() {
		try {
			return getDefaultPreferences().keys();
		} catch (BackingStoreException e) {
			logError(e.getMessage(), e);
			return new String[0];
		}
	}

	/**
	 * Returns whether the current values in this preference object
	 * require saving.
	 *
	 * @return <code>true</code> if at least one of the properties
	 *  known to this preference object has a current value different from its
	 *  default value, and <code>false</code> otherwise
	 */
	public boolean needsSaving() {
		return getPluginPreferences(true).isDirty();
	}

	/**
	 * Flush the values of these plug-in preferences to disk.
	 * 
	 * @throws BackingStoreException
	 */
	public void flush() throws BackingStoreException {
		IEclipsePreferences node = getPluginPreferences(false);
		if (node != null)
			node.flush();
	}

	/*
	 * Something bad happened so log it. 
	 */
	private void logError(String message, Exception e) {
		IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
		RuntimeLog.log(status);
	}

	/*
	 * @see org.eclipse.core.runtime.Preferences#load(java.io.InputStream)
	 */
	public void load(InputStream in) throws IOException {
		Properties result = new Properties();
		result.load(in);
		convertFromProperties(result);
		// We loaded the prefs from a non-default location so now
		// store them to disk. This also clears the dirty flag
		// and therefore sets the #needsSaving() state correctly.
		try {
			flush();
		} catch (BackingStoreException e) {
			throw new IOException(e.getMessage());
		}
	}

	/*
	 * @see org.eclipse.core.runtime.Preferences#store(java.io.OutputStream, java.lang.String)
	 */
	public void store(OutputStream out, String header) throws IOException {
		Properties result = convertToProperties();
		result.store(out, header);
		// We stored the prefs to a non-default location but the spec
		// says that the dirty state is cleared so we want to store
		// them to disk at the default location as well.
		try {
			flush();
		} catch (BackingStoreException e) {
			throw new IOException(e.getMessage());
		}
	}

	private void convertFromProperties(Properties props) {
		IEclipsePreferences preferences = getPluginPreferences(true);
		for (Iterator i = props.keySet().iterator(); i.hasNext();) {
			String key = (String) i.next();
			String value = props.getProperty(key);
			if (value != null)
				preferences.put(key, value);
		}
	}

	public String toString() {
		return "PreferenceForwarder(" + pluginID + ")"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	/*
	 * Convert the preferences in this node to a properties file
	 * suitable for persistence.
	 */
	private Properties convertToProperties() {
		Properties result = new Properties();
		String[] keys = propertyNames();
		for (int i = 0; i < keys.length; i++) {
			String key = keys[i];
			String value = getString(key);
			if (!Preferences.STRING_DEFAULT_DEFAULT.equals(value))
				result.put(key, value);
		}
		return result;
	}
}
