Fix for PR#118484 - added support for the OSGi Preferences Service
diff --git a/bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF
index ac1f9ed..8a4c2a2 100644
--- a/bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF
@@ -11,6 +11,6 @@
 Export-Package: org.eclipse.core.internal.preferences;x-friends:="org.eclipse.core.resources,org.eclipse.core.runtime",
  org.eclipse.core.runtime,
  org.eclipse.core.runtime.preferences,
- org.osgi.service.prefs;version="1.0"
+ org.osgi.service.prefs;version="1.1"
 Eclipse-LazyStart: true
 Import-Package: org.eclipse.equinox.registry
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Activator.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Activator.java
index 1735ddd..f69e53d 100644
--- a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Activator.java
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Activator.java
@@ -21,6 +21,8 @@
  */
 public class Activator implements BundleActivator {
 
+	private static final String OSGI_PREFERENCES_SERVICE = "org.osgi.service.prefs.PreferencesService"; //$NON-NLS-1$	
+
 	/**
 	 * The bundle associated this plug-in
 	 */
@@ -32,6 +34,11 @@
 	private ServiceRegistration preferencesService = null;
 
 	/**
+	 * This plugin provides the OSGi Preferences service.
+	 */
+	private ServiceRegistration osgiPreferencesService;
+	
+	/**
 	 * This method is called upon plug-in activation
 	 */
 	public void start(BundleContext context) throws Exception {
@@ -55,10 +62,13 @@
 
 	private void registerServices() {
 		preferencesService = bundleContext.registerService(IPreferencesService.class.getName(), PreferencesService.getDefault(), new Hashtable());
+		osgiPreferencesService = bundleContext.registerService(OSGI_PREFERENCES_SERVICE, new OSGiPreferencesServiceManager(bundleContext), null);
 	}
 
 	private void unregisterServices() {
 		preferencesService.unregister();
+		osgiPreferencesService.unregister();
+		
 	}
 
 	/**
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/OSGiPreferencesServiceImpl.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/OSGiPreferencesServiceImpl.java
new file mode 100644
index 0000000..8a13cc0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/OSGiPreferencesServiceImpl.java
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * Copyright (c) 2004 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;
+
+import java.io.File;
+import java.util.*;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.osgi.service.prefs.*;
+import org.osgi.service.prefs.Preferences;
+import org.osgi.service.prefs.PreferencesService;
+
+/**
+ * <p>
+ * Implements OSGi PreferencesService using the Eclipse preference system.
+ * </p>
+ * 
+ * <p>
+ * Note: Eclipse preferences are not accessible through the OSGi Preferences API and vice
+ *  versa.
+ * </p>
+ */
+public class OSGiPreferencesServiceImpl implements PreferencesService {
+
+	/**
+	 * Adaptor that implements OSGi Preferences interface on top of EclipsePreferences.
+	 *
+	 */
+	private static final class OSGiPreferences extends EclipsePreferences implements Preferences {
+
+		private IPath location;
+		private IEclipsePreferences loadLevel;
+		private OSGiPreferencesServiceImpl prefsServiceImpl;
+
+		private OSGiPreferences(File prefsDir, OSGiPreferencesServiceImpl prefsServiceImpl) {
+			super(null, ""); //$NON-NLS-1$
+			this.prefsServiceImpl = prefsServiceImpl;
+			this.location = new Path(prefsDir.getPath());
+			this.loadLevel = this;
+		}
+
+		private OSGiPreferences(EclipsePreferences nodeParent, String nodeName, OSGiPreferencesServiceImpl prefsServiceImpl) {
+			super(nodeParent, nodeName);
+			this.loadLevel = nodeParent.getLoadLevel();
+			this.prefsServiceImpl = prefsServiceImpl;
+		}
+
+		protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
+			return new OSGiPreferences(nodeParent, nodeName, prefsServiceImpl);
+		}
+
+		protected IPath getLocation() {
+			return location;
+		}
+
+		protected IEclipsePreferences getLoadLevel() {
+			return loadLevel;
+		}
+
+		/**
+		 * Override node(String pathName) to be more strict about forbidden names - 
+		 * EclipsePreferences implementation does a best-effort instead of throwing 
+		 * {@link IllegalArgumentException}.
+		 */
+		public Preferences node(String pathName) {
+			if ((pathName.length() > 1 && pathName.endsWith("/")) //$NON-NLS-1$
+					|| pathName.indexOf("//") != -1) { //$NON-NLS-1$				
+				throw new IllegalArgumentException();
+			}
+			return super.node(pathName);
+		}
+
+		/**
+		 * Override removeNode() to allow removal of root nodes.  EclipsePreferences ignores
+		 * attempts to remove the root node, but in OSGi Preferences there are many root nodes
+		 * and removal is permitted.
+		 */
+		public void removeNode() throws BackingStoreException {
+			if (parent() == null) {
+				flush();
+				if (this == prefsServiceImpl.systemPreferences) {
+					prefsServiceImpl.systemPreferences = null;
+				} else {
+					prefsServiceImpl.userPreferences.values().remove(this);
+				}
+			}
+
+			super.removeNode();
+			removed = true;
+		}
+
+		/**
+		 * <p>
+		 * Override getByteArray(String key, byte [] defaultValue) to be more strict when
+		 * decoding byte values.  EclipsePreferences implementation pads bytes if they are not 4
+		 * bytes long, but the OSGi TCK expects this function to return null if the length of 
+		 * the byte array is not an even multiple of 4. 
+		 * </p>
+		 * <p>
+		 * Also catches any decoding exceptions and returns the default value instead of 
+		 * propagating the exception.
+		 * </p>
+		 */
+		public byte[] getByteArray(String key, byte[] defaultValue) {
+			String value = internalGet(key);
+			byte[] byteArray = null;
+			if (value != null) {
+				byte[] encodedBytes = value.getBytes();
+				if (encodedBytes.length % 4 == 0) {
+					try {
+						byteArray = Base64.decode(encodedBytes);
+					} catch (Exception e) {
+						//do not raise exception - return defaultValue
+					}
+				}
+			}
+			return byteArray == null ? defaultValue : byteArray;
+		}
+
+	}
+
+	private File systemPrefsDir;
+	private File userPrefsDir;
+
+	Preferences systemPreferences;
+
+	//Map of String user name -> Preferences 
+	Map userPreferences;
+
+	OSGiPreferencesServiceImpl(File prefsLocation) {
+		systemPrefsDir = new File(prefsLocation, "system"); //$NON-NLS-1$
+		userPrefsDir = new File(prefsLocation, "user"); //$NON-NLS-1$
+		userPreferences = new TreeMap(); //use TreeMap since keys are strings
+	}
+
+	public Preferences getSystemPreferences() {
+		if (systemPreferences == null) {
+			systemPreferences = new OSGiPreferences(systemPrefsDir, this);
+			try {
+				systemPreferences.sync();
+			} catch (BackingStoreException e) {
+				//nothing
+			}
+		}
+		return systemPreferences;
+	}
+
+	public Preferences getUserPreferences(String name) {
+		Preferences userPref = (Preferences) userPreferences.get(name);
+		if (userPref == null) {
+			userPref = new OSGiPreferences(new File(userPrefsDir, name), this);
+			try {
+				userPref.sync();
+			} catch (BackingStoreException e) {
+				//nothing
+			}
+			userPreferences.put(name, userPref);
+		}
+		return userPref;
+	}
+
+	public String[] getUsers() {
+		return userPrefsDir.list();
+	}
+
+	/**
+	 * Called when Bundle ungets Preferences Service - flushes all preferences to disk.
+	 */
+	void destroy() {
+		try {
+			if (systemPreferences != null && systemPreferences.nodeExists("")) { //$NON-NLS-1$
+				systemPreferences.flush();
+			}
+		} catch (BackingStoreException e) {
+			//nothing
+		}
+		Iterator it = userPreferences.values().iterator();
+		while (it.hasNext()) {
+			Preferences userPreference = (Preferences) it.next();
+			try {
+				if (userPreference.nodeExists("")) { //$NON-NLS-1$
+					userPreference.flush();
+				}
+			} catch (BackingStoreException e) {
+				//nothing
+			}
+		}
+
+	}
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/OSGiPreferencesServiceManager.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/OSGiPreferencesServiceManager.java
new file mode 100644
index 0000000..770e86c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/OSGiPreferencesServiceManager.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2004 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;
+
+import java.io.File;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.osgi.framework.*;
+
+/**
+ * <p>
+ * Class used to manage OSGi Preferences Service.  Creates a new OSGiPreferencesServiceImpl
+ * object for every bundle that gets the Preferences Service.  When a bundle ungets the 
+ * Preference Service, it's preferences are flushed to disk.
+ * </p>
+ * <p>
+ * Also deletes saved preferences for bundles which are uninstalled.
+ * </p>
+ * <p>
+ * Preferences are saved in the Bundle Data area under the directory "OSGiPreferences".
+ * </p>
+ */
+public class OSGiPreferencesServiceManager implements ServiceFactory, BundleListener {
+
+	private static final String OSGI_PREFS_DIR = "OSGiPreferences"; //$NON-NLS-1$
+
+	private File prefsDir;
+
+	public OSGiPreferencesServiceManager(BundleContext context) {
+
+		prefsDir = context.getDataFile(OSGI_PREFS_DIR);
+
+		context.addBundleListener(this);
+
+		//clean up prefs for bundles that have been uninstalled
+		Bundle[] allBundles = context.getBundles();
+		Set bundleDirNames = new TreeSet();
+		for (int i = 0; i < allBundles.length; i++) {
+			bundleDirNames.add(getBundleDirName(allBundles[i]));
+		}
+		File[] prefsNodeDirs = prefsDir.listFiles();
+		prefsNodeDirs = prefsNodeDirs == null ? new File[0] : prefsNodeDirs;
+
+		for (int i = 0; i < prefsNodeDirs.length; i++) {
+			if (!bundleDirNames.contains(prefsNodeDirs[i].getName())) {
+				rmdir(prefsNodeDirs[i]);
+			}
+
+		}
+	}
+
+	/**
+	 * Recursively remove a file or a directory and all of it's children.
+	 */
+	private void rmdir(File file) {
+		if (!file.exists()) {
+			return;
+		}
+		if (file.isDirectory()) {
+			File[] children = file.listFiles();
+
+			for (int i = 0; i < children.length; i++) {
+				rmdir(children[i]);
+			}
+		}
+		file.delete();
+	}
+
+	/**
+	 * Bundle Preferences are saves in a directory with the same name as the bundle's 
+	 * symbolic id.  For backwards compatibility, preferences for bundles that do not 
+	 * have a symbolic id are saved in a directory named 
+	 * 'org.eclipse.core.internal.preferences.OSGiPreferences.bundleid.&lt;bundle id&gt;'.
+	 */
+	private String getBundleDirName(Bundle bundle) {
+		String bundleDirName = bundle.getSymbolicName();
+
+		//backwards compatibility - if bundle does not have symbolic name
+		if (bundleDirName == null) {
+			bundleDirName = "org.eclipse.core.internal.preferences.OSGiPreferences.bundleid." + bundle.getBundleId(); //$NON-NLS-1$
+		}
+		return bundleDirName;
+	}
+	
+	/**
+	 * Creates a new OSGiPreferencesServiceImpl for each bundle.
+	 */
+	public Object getService(Bundle bundle, ServiceRegistration registration) {
+		return new OSGiPreferencesServiceImpl(new File(prefsDir, getBundleDirName(bundle)));
+	}
+
+	/**
+	 * Flush the bundle's preferences to disk.
+	 */
+	public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
+		((OSGiPreferencesServiceImpl) service).destroy();
+	}
+
+	/**
+	 * If a bundle is uninstalled, delete all of it's preferences from the disk.
+	 */
+	public void bundleChanged(BundleEvent event) {
+		if (event.getType() == BundleEvent.UNINSTALLED) {
+			File bundlePrefs = new File(prefsDir, getBundleDirName(event.getBundle()));
+			rmdir(bundlePrefs);
+		}
+
+	}
+
+}